[C#网络编程系列]专题三:自定义Web服务器(转载:)前言:经过前面的专题中对网络层协议和HTTP协议的简单介绍相信大家对网络中的协议有了大致的了解的,本专题将针对HTTP协议定义一个Web服务器,我们平常浏览网页通过在浏览器中输入一个网址就可以看到我们想要的网页,这个过程中浏览器只是一个客户端,浏览器(应用层应用程序)通过HTTP协议把用户请求发送到服务端,服务器接受到发送来的HTTP请求,然后对请求进行处理和响应,最后把响应的内容发送给客户端(浏览器这里充当了用户代理的客户端),浏览器再对接受到的响应内容(一般是HTML文件)进行解释并且显示出来。这就是一次完整的用户请求/响应模型,本专题所讲述的是一个简单的Web服务器,其他一些大型的Web服务器(IIS,Apache)也是这样的一个原理,本专题只是简单讲述Web服务器的实现原理。一、Socket编程实现一个简单的Web服务器Socket这个概念是在Unix系统中提出来的。在Unix的时代,为了解决传输层的编程问题,Unix提供了类似于文件操作的网络操作方式——Socket,通过Socket,我们就可以像操作文件一样通过打开、写入、读取、关闭等操作完成网络编程,这样就使得网络编程可以统一到文件操作方面,这样就使我们更容易地编写网络应用程序。需要注意的是,应用层的协议需要网络程序专门处理,Socket不负责应用层协议,仅仅负责传输层的协议。现在介绍下网络端口号(port)的概念,在同一个网络地址中,为了区分使用相同协议的不同应用程序,为不同的应用程序分配一个数字编号,我们把这个编号就成为网络端口号(就是区分同一个网络地址中不同的进程)。端口号是由一个两个字节的整数,所以取值范围为0~65535,这些端口号又分为三类:第一类的范围是0~1023,称为众所周知的端口,这些端口号由特定的网络程序使用,例如,TCP协议使用80端口来完成Http协议的传输。第二类的范围是1024~49151,称为登记端口,一般情况下不应该在程序中使用。第三类的范围是49152~65535,称为私有端口,这些端口可以由普通用户程序使用。在我们用Socket开发网络应用程序中,还有一个就是端点的概念,在网络中,通过IP地址,协议和端口号可以唯一地确定网络上的一个应用程序,其中把IP地址和端口的组合叫做端点(EndPoint)。每个Socket需要绑定到一个端点上与其他端点进行通信。介绍完基本的一些概念后,下面演示通过Socket编程实现一个简单的Web服务器,此实例中就是简单向浏览器返回一个固定的静态页面,实现代码如下:usingSystem;usingSystem.Net;usingSystem.Net.Sockets;usingSystem.Text;namespaceWebServer{///summary///实现一个简单的Web服务器///该服务器向请求的浏览器返回一个静态的HTML页面////summaryclassProgram{staticvoidMain(string[]args){//获得本机的Ip地址,即127.0.0.1IPAddresslocaladdress=IPAddress.Loopback;//创建可以访问的断点,49155表示端口号,如果这里设置为0,表示使用一个由系统分配的空闲的端口号IPEndPointendpoint=newIPEndPoint(localaddress,49155);//创建Socket对象,使用IPv4地址,数据通信类型为数据流,传输控制协议TCP协议.Socketsocket=newSocket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);//将Socket绑定到断点上socket.Bind(endpoint);//设置连接队列的长度socket.Listen(10);while(true){Console.WriteLine(WaitanconnectRequest...);//开始监听,这个方法会堵塞线程的执行,直到接受到一个客户端的连接请求Socketclientsocket=socket.Accept();//输出客户端的地址Console.WriteLine(ClientAddressis:{0},clientsocket.RemoteEndPoint);//把客户端的请求数据读入保存到一个数组中byte[]buffer=newbyte[2048];intreceivelength=clientsocket.Receive(buffer,2048,SocketFlags.None);stringrequeststring=Encoding.UTF8.GetString(buffer,0,receivelength);//在服务器端输出请求的消息Console.WriteLine(requeststring);//服务器端做出相应内容//响应的状态行stringstatusLine=HTTP/1.1200OK\r\n;byte[]responseStatusLineBytes=Encoding.UTF8.GetBytes(statusLine);stringresponseBody=htmlheadtitleDefaultPage/title/headbodypstyle='font:bold;font-size:24pt'Welcomeyou/p/body/html;stringresponseHeader=string.Format(Content-Type:text/html;charset=UTf-8\r\nContent-Length:{0}\r\n,responseBody.Length);byte[]responseHeaderBytes=Encoding.UTF8.GetBytes(responseHeader);byte[]responseBodyBytes=Encoding.UTF8.GetBytes(responseBody);//向客户端发送状态行clientsocket.Send(responseStatusLineBytes);//向客户端发送回应头信息clientsocket.Send(responseHeaderBytes);//发送头部和内容的空行clientsocket.Send(newbyte[]{13,10});//想客户端发送主体部分clientsocket.Send(responseBodyBytes);//断开连接clientsocket.Close();Console.ReadKey();break;}//关闭服务器socket.Close();}}}运行结果:首先,运行服务端后的界面:然后,在浏览器中输入后,则浏览器可以看到如下的所示的结果:此时,在服务器端显示的结果为:这里只是简单实现了一个web服务器的功能,当然实际的Web服务器通过用户的发来的Http请求中获得请求文件类型,请求文件名以及请求目录等信息,然后Web服务器根据这些请求信息从服务器的物理目录中寻找请求的文件,如果在服务器中找到请求的文件,然后服务器把响应内容发送给客户端。这里只是通过这个简单的Web服务器让大家理解请求/响应模型以及Web服务器的工作原理,一些复杂的Web服务器也是在此基础进行一些其他功能的扩展。二、基于TcpListener的Web服务器在.net平台下,为了简化网络编程,.net对套接字又进行了一次封装,封装后的类是在System.Net.Sockets命名空间下的TcpListener类和TcpClient类,使用TcpListener类用来监听和接收传入的连接请求,在该类的构造函数中只需要传递一组网络端点信息就可以准备好监听参数,而不需要设置使用的网络协议等细节,调用Start方法后,监听工作就开始(间接调用了Socket.Listen方法),AcceptTcpClient方法将阻塞进程,直到一个客户端发来连接请求为止,这个方法返回一个封装了Socket的TcpClient对象,同时从传入的连接队列中删除该客户端的连接请求。此时通过这个TcpClient对象与客户端进行通信。下面是基于TcpListener和TcpClient的一个简单的Web服务器的代码:usingSystem;usingSystem.Net;usingSystem.Net.Sockets;usingSystem.Text;namespaceTcpWebserver{classProgram{staticvoidMain(string[]args){//获得本机的Ip地址,即127.0.0.1IPAddresslocaladdress=IPAddress.Loopback;//创建可以访问的断点,49155表示端口号,如果这里设置为0,表示使用一个由系统分配的空闲的端口号IPEndPointendpoint=newIPEndPoint(localaddress,49155);//创建Tcp监听器TcpListenertcpListener=newTcpListener(endpoint);//启动监听tcpListener.Start();Console.WriteLine(WaitanconnectRequest...);while(true){//等待客户连接TcpClientclient=tcpListener.AcceptTcpClient();if(client.Connected==true){//输出已经建立连接Console.WriteLine(Createdconnection);}//获得一个网络流对象//该网络流对象封装了Socket的输入和输出操作//此时通过对网络流对象进行写入来返回响应消息//通过对网络流对象进行读取来获得请求消息NetworkStreamnetstream=client.GetStream();//把客户端的请求数据读入保存到一个数组中byte[]buffer=newbyte[2048];intreceivelength=netstream.Read(buffer,0,2048);stringrequeststring=Encoding.UTF8.GetString(buffer,0,receivelength);//在服务器端输出请求的消息Console.WriteLine(requeststring);//服务器端做出相应内容//响应的状态行stringstatusLine=HTTP/1.1200OK\r\n;byte[]responseStatusLineBytes=Encoding.UTF8.GetBytes(statusLine);stringresponseBody=htmlheadtitleDefaultPage/title/headbodypstyle='font:bold;font-size:24pt'Welcomeyou/p/body/html;stringresponseHeader=string.Format(Content-Type:text/html;charset=UTf-8\r\nContent-Length:{0}\r\n,responseBody.Length);byte[]responseHeaderBytes=Encoding.UTF8.GetBytes(responseHeader);byte[]responseBodyBytes=Encoding.UTF8.GetBytes(