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

      在遥远的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请求参数,我们就不能直接通过属性访问。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章