C# | socket实现简单的web服务器

最近在搬砖做web相关的东西。 因为要解除对一些框架的依赖, 所以有机会稍微接触一些底层的东西。

传统的web服务器包括IIS, tomcat, apache,Nginx都是基于http请求与响应来实现数据对接的。

在C#和java中都可以由httpwebRequest或者servlet相关的类或者包实现封装http的一些数据, 这样我们就不需要自己手动创建http格式的数据了。

而本文要透过这些类, 去看http到底包含了哪些东西, 然后封装属于自己的http响应数据,再通过socket发送给客户端(浏览器), 实现web服务器的一些简单的响应功能。

关于http协议的一些具体细节, 推荐这篇blog:HTTP协议详细


关于http我们要了解:

1.在TCP/IP体系结构中,HTTP属于应用层协议,位于TCP/IP协议的顶层。浏览Web时,浏览器通过HTTP协议与Web服务器交换信息。这些信息(文档)类型的格式由MIME定义。


2.HTTP按客户/服务器模式工作,HTTP是无状态的.


3.HTTP使用元信息作为头标。这里的元信息就是指那些用来指明http具体要做什么的数据,包括请求行或者报头等等。


4.HTTP的请求
方 法 说 明
GET 请求读取一个Web页面
HEAD 请求读取一个Web页面的头标
PUT 请求存储一个Web页面
POST 附加到命名资源中
DELETE 删除Web页面
LINK 连接两个已有资源

UNLINK 取消两个资源之间的已有连接


5.Http常见的请求数据格式:

(1)请求数据:(最常用的两种请求是get跟post)

通常包括:请求行 + 请求报头 + 请求正文

请求行:动作(get等), URL(请求文件的位置), HTTP协议的版本号

请求报头:一些请求的具体信息。(这个可以自己看到的, 抓包)

请求正文: 注意请求正文与请求报头之间必须要有一个空行来区分。

(2)响应数据:

通常:状态行 + 响应报头 + 响应正文

状态行:HTTP协议版本号 + 状态码 + 状态描述 

这里常见状态码有:

200 (OK): 找到了该资源,并且一切正常。

304 (NOT MODIFIED): 该资源在上次请求之后没有任何修改。这通常用于浏览器的缓存机制。

401 (UNAUTHORIZED):客户端无权访问该资源。这通常会使得浏览器要求用户输入用户名和密码,以登录到服务器。

403 (FORBIDDEN):客户端未能获得授权。这通常是在401之后输入了不正确的用户名或密码。

404 (NOT FOUND):在指定的位置不存在所申请的资源。

响应报头: 响应的一些格式。通常包含传输数据的格式,数据长度等等。

响应正文:要返回给客户端的数据, 这里也注意与响应报头留有空行。


有了这些格式, 我们就可以通过C#构建自己的WEB服务器了。

需要用到socket用来传输数据, 当接受到浏览器端传来的数据时, 我们解析这个http协议, 获取一些需要知道的信息, 比如是什么类型的请求, 需要请求服务器上什么物理位置的文件等。 然后我们按照http的标准协议封装响应数据的三个部分, 发送给浏览器。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net.Sockets;
using System.Net;
using System.Threading;
using System.IO;

namespace WebServer
{
    class MyWebServer
    {
        private readonly string webServerRoot = @"E:\Pages\";
        private readonly int port = 4096;
        private readonly string host = "127.0.0.1";
        private Socket socket = null;
        public MyWebServer()
        {
            try
            {
                IPAddress ip = IPAddress.Parse(host);
                socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                socket.Bind(new IPEndPoint(ip, port));
                socket.Listen(10);
                Thread th = new Thread(new ThreadStart(Listen));
                th.Start();
                Console.WriteLine("Server Is Listening ...");
           }
            catch(Exception e)
            {
                Console.WriteLine("Fail When Listening :  " + e.ToString());
            }
        }

        public void SendHttpHead(string httpVersion, string statusCode, string mime, int codeLength, ref Socket socket)
        {
            string sendBuffer = "";
            sendBuffer += httpVersion + statusCode + "\r\n";
            sendBuffer += "Query Ok In May GMT";
            if (mime.Length == 0)
            {
                mime = "text/html";
            }
            sendBuffer += "Content-Type: " + mime + "\r\n";
            sendBuffer += "Accept-Ranges: bytes\r\n";
            sendBuffer += "Content-Length: " + codeLength + "\r\n\r\n";

            Byte[] sendDataByte = Encoding.ASCII.GetBytes(sendBuffer);

            SendToBrowser(sendDataByte, ref socket);
            // SendToBrowser(sendBuffer, socket);
        }

        public void SendToBrowserByString(string sendData, ref Socket socket)
        {
            SendToBrowser(Encoding.ASCII.GetBytes(sendData), ref socket);
        }

        public void SendToBrowser(Byte[] sendData,  ref Socket socket)
        {
            try
            {
                if(socket.Connected)
                {
                    int dataNumber = socket.Send(sendData);
                    Console.WriteLine("Send Bytes {0}", dataNumber);
                }
                else
                {
                    Console.WriteLine("Fail in Connecting ...");
                }
            }
            catch(Exception e)
            {
                Console.WriteLine("Error !");
            }
        }

        public void Listen()
        {
            string httpVersion, statusCode, mime;
            int dataLength;
           
            while(true)
            {
                Socket mySocket = socket.Accept();
                if(mySocket.Connected)
                {
                    Byte[] dataFromBrow = new Byte[100];
                    mySocket.Receive(dataFromBrow, dataFromBrow.Length, 0);
                    string receiveBuf = Encoding.ASCII.GetString(dataFromBrow);
                    Console.WriteLine(receiveBuf);

                    if(receiveBuf.Substring(0, 3) != "GET")
                    {
                        Console.WriteLine("sorry, we just can accept the \"get\" request");
                        mySocket.Close();
                        return;
                    }

                    int startPos = receiveBuf.IndexOf("HTTP", 1);
                    httpVersion = receiveBuf.Substring(startPos, 8);//get HttpVersion

                    string subString = receiveBuf.Substring(0, startPos - 1);
                    startPos = subString.LastIndexOf("/")  + 1;
                    string requestFile = subString.Substring(startPos);//get the FileName

                    string localDir = webServerRoot;

                    if(requestFile.Length == 0)
                    {
                        requestFile = "Index.html";
                    }
                    string requestFilePath = localDir + requestFile;///get the definite path of the request file

                    if(File.Exists(requestFilePath) == false)
                    {
                        string errorMessage = "404 Error! File Does Not Exists";
                        SendHttpHead(httpVersion," 404 Not Found", "", errorMessage.Length, ref mySocket);
                        SendToBrowserByString("Error", ref mySocket);
                   //  Console.WriteLine("Error : 404");
                    }
                    else
                    {
                        dataLength = 0;
                        string responseData = "";

                        FileStream fs = new FileStream(requestFilePath, FileMode.Open, FileAccess.Read, FileShare.Read);
                        BinaryReader reader = new BinaryReader(fs);
                        byte[] bytes = new byte[fs.Length];
                        int read;
                        while ((read = reader.Read(bytes, 0, bytes.Length)) != 0)
                        {
                            responseData = responseData +  Encoding.ASCII.GetString(bytes, 0, read);
                            dataLength += read;
                        }
                        reader.Close();
                        fs.Close();

                        mime = "text/html";
                        statusCode = " 200 OK";
                        SendHttpHead(httpVersion, mime, statusCode, dataLength, ref mySocket);//200 OK
                        SendToBrowserByString(responseData, ref mySocket);
                    }
                    mySocket.Close();
                }
            }
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            MyWebServer myWebServer = new MyWebServer();
            Console.ReadKey();
             
        }
    }
}

如果想要详细深入理解Web的请求与响应的流程, 推荐: 浏览器与服务器数据交换细节, 写的相当好。

这里存在一些小的Bug:应该是请求数据时候的缓冲区大小出现了问题, 等待解决。

你可能感兴趣的:(c#,Web开发,个人站点创建)