《ASP.NET本质》创建简单的Web服务器

来源:互联网 发布:linux查询mysql数据库 编辑:程序博客网 时间:2024/05/18 01:59

      在遥远的Unix时代,为了解决传输层的编程问题,从 4.2BSD Unix开始,Unix提供了类似于文件操作的网络操作方式——Socket。通过Socket,程序员就可以像操作文件一样通过打开、写入、读取、关闭等操作完成网络编程。这使得网络编程可以统一到文件操作之下。通过Socket帮助程序员解决网络传输层的问题,而系统中的网络系统负责处理网络内部的复杂操作,这样程序员就可以比较容易地编写网络应用程序。需要注意的是,应用层的协议需要针对网络程序专门处理,Socket不负责应用层的协议,仅仅负责传输层的协议。
      当然,网络毕竟不是简单的文件,所以,在使用Socket的时候,程序员还是需要设置一些网络相关的细节问题参数。
     当通过Socket开发网络应用程序的时候,首先需要考虑所使用的网络类型,注意包括以下三个方面:
   1)Socket类型,使用网络协议的类别,IPv4的类型为PF_INET。
   2)数据通信的类型,常见的数据包(SOCK_DGRAM)、数据流(SOCK_STREAM)。
   3)使用的网络协议,比如:TCP协议
    在同一个网络地址上,为了区分使用相同协议的不同应用程序,可以为不同的应用程序分配一个数字编号,这个编号成为网络端口号(port)。端口号是一个两字节的证书,取值范围从0~65535.IANA(Internet Assgned Number Authority,互联网地址分配机构)维护了一个有端口分配列表,这些端口分为三类,第一类范围是0~1023,称为总所周知的端口,有IANA进行控制和分配,有特定的网络程序使用,例如TCP协议使用80号端口来完成HTTP协议的传输。第二类的范围是1024~49151,称为登记端口,这些端口不由IANA控制,但是IANA维护了一个登记的列表,如果没有在IANA登记的话,也不应该在程序中使用。但是,大多数的系统中,在没有冲突的情况下,也可以由用户程序使用。第三轮的范围是49152~65535,称为动态或者私有端口,这些端口可以由普通用户使用。
       对于一个网络应用程序来说,通过地址、协议和端口号可以唯一地确定网络上的一个应用程序。其中地址和端口的组合成为端点(EndPoint)。每个Socket需要绑定到一个端点上与其他端点进行通信。
      在.NET中,System.Net命名空间提供了网络编程的大多数数据类型以及常用操作,其中常用的类型如下:
     IPAddress类用来表示一个IP地址
     IPEndPoint类用来表示一个IP地址和一个端口的组合,成为网络的端点
     System.Net.Sockets命名空间中提供了基于Socket编程的数据类型
     Socket类封装了Socket的操作。
     常用操作如下:
     Listen:设置基于连接通信的Socket进入监听状态,并设置等待队列的长度
     Accept:等待一个新的连接,当连接到达的时候,返回一个针对新连接的Socket对象。通过这个新的Socket对象,可以与新连接通信
     Receive:通过Socket接受字节数据,保存到一个字节数组中,返回实际接受的字节数。
     Send:通过Socket发送预先保存在字节数组中的数据。

下面的代码演示了如何通过Socket编程创建一个简单的Web服务器。这个服务器通过49152端口提供访问,向浏览器返回一个固定的静态网页。在这个示例中,请求的消息由浏览器生成,并发送到服务器,这个程序将简单地显示请求的信息。回应的消息由服务器程序生成,通过Socket传输层返回给浏览器。

static void Main(string[] args)        {            //获得本机的 loopback 网络地址            IPAddress address = IPAddress.Loopback;            //创建可以访问的端点            IPEndPoint endPoint = new IPEndPoint(address, 49152);            //创建Socket,使用IPv4地址,传输控制协议,双向、可靠、基于链接的字节流            Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);            //将socket绑定到端点上            socket.Bind(endPoint);            //设置连接队列的长度            socket.Listen(10);            Console.WriteLine("开始监听,端口号:{0}",endPoint.Port);            while (true)            {                //开始监听,这个方法会阻塞线程的执行,直到接受到一个客户端的连接请求                Socket client = socket.Accept();                //输出客户端地址                Console.WriteLine(client.RemoteEndPoint);                //准备读取客户端请求的数据读取的数据将保存在一个数组中                byte[] buffer=new byte[4096];                //接受数据并获取数据长度                int length = client.Receive(buffer, 4096, SocketFlags.None);                //将请求的数据翻译为UTF-8                System.Text.Encoding utf8 = System.Text.Encoding.UTF8;                string requestString = utf8.GetString(buffer, 0, length);                //显示请求的消息                Console.WriteLine(requestString);                //回应的状态行                string statusLine = "HTTP/1.1 200 OK \r\n";                byte[] statusLineBytes = utf8.GetBytes(statusLine);                //准备发送到客户端的网页                string responseBody = "<html><head><title>From Socket Server</title></head><body><h1>Hello,world</h1></body></html>";                byte[] responseBodyBytes = utf8.GetBytes(responseBody);                //回应头部                string responseHeader = string.Format("Content-Type:text/html;charset=UTF-8\r\nContent-Length:{0}\r\n",responseBody.Length);                byte[] responseHeaderBytes = utf8.GetBytes(responseHeader);                //向客户端发送状态信息                client.Send(statusLineBytes);                //向客户端发送回应头                client.Send(responseHeaderBytes);                //头部与内容的分隔行                client.Send(new byte[] { 13, 10 });                //向客户端发送内容部分                client.Send(responseBodyBytes);                //端口与客户端的连接                client.Close();                if (Console.KeyAvailable)                    break;            }            //关闭服务器            socket.Close();        }



开启服务,请求localhost:49152 模拟成功

-----------------------------------------------------------------
基于TcpListener的Web服务器

       为了简化基于TCP协议的监听程序,.NET在System.Net.Sockets命名空间中提供了TcpListener类,使用它,在构造函数中传递一组网络端点信息就可以准备好监听参数,而不再需要设置使用的网络协议等细节,调用Start方法之后,监听工作就开始了。AcceptTcpClient方法将阻塞进程,直到一个客户端的连接到达监听器,这个方法将返回一个代表客户端连接的代理对象,它的类型为TcpClient,我们可以通过对它与客户端进行通信。
      在输入输出部分,通过TcpClient对象可以得到一个用户输入和输出的网络流对性NetworkStream,这是一个派生自Stream对象的字节流对象,对Socket的输入和输出进行了封装,这样,我们可以通过常用的字节流操作来完成网络的输入和输出。

static void Main(string[] args)        {            //取得本机的loopback网络地址            IPAddress address = IPAddress.Loopback;            //创建可以访问的端点            IPEndPoint endPoint = new IPEndPoint(address, 49152);            //创建TCP监听器            TcpListener newserver = new TcpListener(endPoint);            //启动监听            newserver.Start();            Console.WriteLine("开始监听");            while (true)            {                //等待客户端连接                TcpClient client = newserver.AcceptTcpClient();                Console.WriteLine("已经建立连接");                //得到一个网络流                NetworkStream ns = client.GetStream();                System.Text.Encoding utf8 = System.Text.Encoding.UTF8;                byte[] request=new byte[4096];                int length = ns.Read(request, 0, 4096);                //请求信息                string requestString = utf8.GetString(request, 0, length);                Console.WriteLine(requestString);                //状态行                string statusLine = "HTTP/1.1 200 OK\r\n";                byte[] statusLineBytes = utf8.GetBytes(statusLine);                //网页内容                string responseBody = "<html><head><title></title></head><body>Hello<body></html>";                byte[] responseBodyBytes = utf8.GetBytes(responseBody);                //回应的头部信息                string responseHeader = string.Format("Content-Type:text/html;charset=UTF-8\r\nContent-Length:{0}\r\n",responseBody.Length);                byte[] responseHeaderBytes = utf8.GetBytes(responseHeader);                //输入状态行                ns.Write(statusLineBytes, 0, statusLineBytes.Length);                //输出回应头部                ns.Write(responseHeaderBytes, 0, responseHeaderBytes.Length);                //回应头部与内容之间的空行                ns.Write(new byte[]{13,10},0,2);                //输出内容                ns.Write(responseBodyBytes, 0, responseBodyBytes.Length);                //关闭客户端连接                client.Close();                if (Console.KeyAvailable)                    break;            }            //关闭服务器            newserver.Stop();        }



----------------------------------------------

基于HttpListener的Web服务器
        为了进一步简化HTTP协议的监听器,.NET在命名空间System.NET中提供了HttpListener类。伴随这个对象,.NET提供了一系列相关对象封装了HTTP的处理工作。注意,这个类使用Http.sys系统组件完成工作,所以,只有在Windows XP SP2 或者 Server 2003以上的操作系统中才能使用。
       HttpListener类进一步简化了监听工作,仅需通过字符串的方法提供监听的地址、端口号以及虚拟路径,就可以开始监听工作。
      开始监听后,GetContext方法将阻塞线程,当客户端的请求到达之后,HttpListener返回一个HttpListenerContext对性爱那个最为处理客户端请求的总代理,通过代理对象的Request属性,我们可以得到一个类型为HttpListenerRequest的代表请求参数的对象,这个对象将大多数请求参数进行了对象化,所以,我们可以通过它提供的一系列属性来获取请求参数。例如HttpListenerRequest的HttpMethod属性就提供了请求的方法类型。通过代理的Response属性,可以得到一个类型为HttpListenerResponse的回应处理对象,这个对象将回应的数据和操作进行了封装,使得我们大幅度简化了回应的编程工作了。

static void Main(string[] args)        {            //检查系统是否支持            if (!HttpListener.IsSupported)            {                throw new System.InvalidOperationException("使用HttpListener必须为Windows XP SP2 或 Server2003 以上系统");            }            //注意前缀必须以  / 正斜杠结尾            string[] prefixes = new string[] {"http://localhost:49152/" };            //创建监听器            HttpListener listener = new HttpListener();            //增加监听的前缀            foreach (string s in prefixes)            {                listener.Prefixes.Add(s);            }            //开始监听            listener.Start();            Console.WriteLine("监听中");            while (true)            {                //注意:GetContext方法将阻塞线程,知道请求到达                HttpListenerContext context = listener.GetContext();                //获取对象                HttpListenerRequest request = context.Request;                Console.WriteLine("{0}{1}HTTP/1.1",request.HttpMethod,request.RawUrl);                Console.WriteLine("Accept:{0}",string.Join(",",request.AcceptTypes));                Console.WriteLine("Accept-Language:{0}",string.Join(",",request.UserLanguages));                Console.WriteLine("User-Agent:{0}",request.UserAgent);                Console.WriteLine("Accept-Encoding:{0}",request.Headers["Accept-Encoding"]);                Console.WriteLine("Connection:{0}",request.KeepAlive?"Keep-Alive":"close");                Console.WriteLine("Host:{0}",request.UserHostName);                Console.WriteLine("Pragma:{0}",request.Headers["Pragma"]);                //取得回应对象                HttpListenerResponse response = context.Response;                //构造 回应内容                string responseString = @"<html><head><title>aaa</title></head><body>Hello</body></html>";                response.ContentLength64 = System.Text.Encoding.UTF8.GetByteCount(responseString);                response.ContentType = "text/html;charset=UTF-8";                System.Text.Encoding utf8 = System.Text.Encoding.UTF8;                System.IO.Stream output = response.OutputStream;                byte[] responseStringBytes=utf8.GetBytes(responseString);                //输出回应                output.Write(responseStringBytes, 0, responseStringBytes.Length);                if (Console.KeyAvailable)                    break;            }            listener.Stop();        }

       在使用HttpListener的时候,常用的请求和回应参数都变成了对象的属性,大幅度降低了变成的工作量。但是,大多数的参数还是需要通过Headers索引器来访问的,就像上例中的Accept-Encoding请求参数,我们就不能直接通过属性访问。