实现简单Web服务器

作者:追风剑情 发布于:2016-5-17 18:45 分类:C#

        HTTP 1.1支持持久连接,即客户端和服务器建立连接后,可以发送请求和接收应答,然后迅速发送另一个请求和接收另一个应答。同时,持久连接也使得在得到上一个请求的应答之前可以发送多个请求,这是HTTP 1.1与HTTP 1.0明显不同的地方。
        除此之外,HTTP 1.1可以发送的请求类型也比HTTP 1.0多。

HTTP 1.1提供的请求方法
(HTTP 1.0仅定义了GET、POST、HEAD)
请求的方法名 说明
GET 请求获取特定的资源,例如,请求一个Web页面
POST 请求向指定资源提交数据进行处理(例如,提交表单或者上传文件),请求的数据被包含在请求体中
PUT 向指定资源位置上传最新内容,例如,请求存储一个Web页面
HEAD 向服务器请求获取与GET请求相一致的响应,只不过响应体将不会被返回。这一方法可以在不必传输整个响应内容的情况下,就可以获取包含在响应消息头中的元信息。例如,可以使用HEAD请求来传递认证信息。可以使用HEAD请求对资源有效性进行检查。
DELETE 请求删除指定的资源
OPTIONS 返回服务器针对特定资源所支持的HTTP请求方法
TRACE 回显服务器收到的请求
CONNECT 预留给能够将连接改为管道方式的代理服务器


HTTP常用状态码
状态码 说明
200 OK 找到了该资源,并且一切正常
304 NOT MODIFIED 该资源在上次请求之后没有任何修改。这通常用于浏览器的缓存机制
401 UNAUTHORIZED 客户端无权访问该资源。这通常会使得浏览器要求用户输入用户名和密码,以登录到服务器
403 FORBIDDEN 客户端未授权,这通常是在401之后输入了不正确的用户名或密码
404 NOT FOUND 在指定的位置不存在所申请的资源
405 Method Not Allowed 不支持对应的请求方法
501 Not Implemented 服务器不能识别请求或者未实现指定的请求

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

namespace SimpleWebServer
{
    class Program
    {
        static void Main(string[] args)
        {
            WebServer webServer = new WebServer();
            webServer.StartListener(8899, "127.0.0.1");
        }
    }

    public class WebServer
    {
        //存放web内容的目录
        public static string webRoot = @"F:\web";

        public void StartListener(Int32 port, String ip)
        {
            TcpListener server = null;
            try
            {
                IPAddress localAddr = IPAddress.Parse(ip);

                server = new TcpListener(localAddr, port);
                server.Start();

                Console.WriteLine("Server started");

                HttpHeader header = new HttpHeader();
                byte[] contentBytes;

                while (true)
                {
                    Console.WriteLine("Waiting for a connection... ");

                    Socket client = server.AcceptSocket();
                    Console.WriteLine("收到HTTP请求:");

                    //读取GET请求的头信息
                    Byte[] bReceive = new Byte[1024];
                    int i = client.Receive(bReceive, bReceive.Length, 0);
                    string headInfo = Encoding.ASCII.GetString(bReceive);
                    Console.WriteLine(headInfo);
                    if (headInfo.Substring(0, 3) != "GET")
                    {
                        Console.WriteLine("无效请求,仅处理GET请求");
                        client.Close();
                        continue;
                    }

                    int iStartPos = headInfo.IndexOf("HTTP", 1);
                    //获取HTTP版本号
                    string httpVersion = headInfo.Substring(iStartPos+5, 3);
                    Console.WriteLine("版本号: " + httpVersion);

                    //获取请求的文件
                    string requestFile = headInfo.Substring(4, iStartPos - 4);
                    requestFile = requestFile.Trim();
                    Console.WriteLine("请求的文件: " + requestFile);

                    requestFile = webRoot + requestFile;

                    //如果请求的文件不存在,则向浏览器发送错误提示。
                    if (!File.Exists(requestFile))
                    {
                        Console.WriteLine("请求的文件不存在!");

                        string errorMessage = "<H2>Error!! Requested file does not exists</H2><Br>";
                        contentBytes = Encoding.ASCII.GetBytes(errorMessage);

                        header.SetStatusCode(httpVersion, 404, "Not Found");
                        header.SetContentLength(contentBytes.Length);

                        SendToBrowser(header.GetBytes(), ref client);
                        SendToBrowser(contentBytes, ref client);

                        client.Close();
                        continue;
                    }

                    //读取服务器文件
                    StreamReader sr = File.OpenText(requestFile);
                    string content = sr.ReadToEnd();
                    contentBytes = Encoding.UTF8.GetBytes(content);

                    //设置HTTP头信息
                    header.SetStatusCode(httpVersion, 200, "OK");
                    header.SetContentLength(contentBytes.Length);

                    //发送HTTP头信息及文件内容
                    SendToBrowser(header.GetBytes(), ref client);
                    SendToBrowser(contentBytes, ref client);

                    //关闭连接
                    client.Close();
                }
            }
            catch (SocketException e)
            {
                Console.WriteLine("SocketException: {0}", e);
            }
            finally
            {
                server.Stop();
            }

            Console.WriteLine("\nHit enter to continue...");
            Console.Read();
        }

        public void SendToBrowser(byte[] bSendData, ref Socket socket)
        {
            int numBytes = 0;

            try
            {
                if (socket.Connected)
                {
                    if ((numBytes = socket.Send(bSendData, bSendData.Length, 0)) == -1)
                        Console.WriteLine("Socket Error cannot Send Packet");
                    else
                    {
                        Console.WriteLine("No. of bytes send {0}", numBytes);
                    }
                }
                else
                    Console.WriteLine("连接失败....");
            }
            catch (Exception e)
            {
                Console.WriteLine("发生错误 : {0} ", e);

            }
        }
    }

    #region HTTP头信息
    public class HttpHeader
    {
        private string statusCode = "HTTP/1.1 200 OK\r\n";
        private string server = "Server: Simple-WebServer/1.0\r\n";
        private string date = "Date: Thu, 13 Jul 2016 05:46:53 GMT\r\n";
        private string contentType = "Content-Type: text/html\r\n";
        private string acceptRanges = "Accept-Ranges: bytes\r\n";
        //特别注意: 头信息的最后一行有两个空行,否则浏览器无法正确显示内容。
        private string contentLength = "Content-Length: 2291\r\n\r\n";

        public void SetStatusCode(string version, int code, string msg)
        {
            statusCode = string.Format("HTTP/{0} {1} {2}\r\n", version, code, msg);
        }

        public void SetContentType(string type)
        {
            contentType = string.Format("Content-Type: {0}\r\n", type);
        }

        public void SetContentLength(int length)
        {
            contentLength = string.Format("Content-Length: {0}\r\n\r\n", length);
        }

        public string GetString()
        {
            date = string.Format("Date: {0:R}\r\n", DateTime.Now);
            string header = statusCode + server + date + acceptRanges + contentLength;
            return header;
        }

        public byte[] GetBytes()
        {
            return Encoding.ASCII.GetBytes(GetString());
        }
    }
    #endregion
}


浏览器访问测试

访问存在的网页

111111.png

4444.png

访问不存在的文件

22222.png

33333.png


Unity版本 (可放到Unity中执行)

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using UnityEngine;

/// <summary>
/// Unity版本
/// 局域网访问需要关闭防火墙
/// </summary>
namespace SimpleWebServer
{
    public enum RequestType
    {
        GET,
        POST
    }

    public sealed class HttpContentType
    {
        public static readonly string TEXT_HTML = "text/html";
        public static readonly string JSON = "application/json";  //json
        public static readonly string VIDEO_MPEG4 = "video/mpeg4";//mp4
    }

    public interface IHttpResponse
    {
        void OnRequest(HttpHeader header);
        string ContentType { get; }
        byte[] OnResponseContent();
        void OnDispose();
    }

    [System.Serializable]
    public class ResponseMessage
    {
        public int request_code = 0; //请求码
        public int error_code = -1;  //错误码, -1代表没出错
        public string error_msg = "";//错误描述
        public string data = "";

        public static byte[] Success(int request_code=0, string data="")
        {
            ResponseMessage msg = new ResponseMessage();
            msg.request_code = request_code;
            msg.error_code = -1;
            msg.error_msg = string.Empty;
            msg.data = data;
            string json = JsonUtility.ToJson(msg);
            byte[] bytes = Encoding.UTF8.GetBytes(json);
            return bytes;
        }

        public static byte[] Failure(int request_code=0, int error_code=-1, string error_msg="")
        {
            ResponseMessage msg = new ResponseMessage();
            msg.request_code = request_code;
            msg.error_code = error_code;
            msg.error_msg = error_msg;
            string json = JsonUtility.ToJson(msg);
            byte[] bytes = Encoding.UTF8.GetBytes(json);
            return bytes;
        }
    }

    public class WorkState
    {
        public Socket client;
        public IHttpResponse response;
    }

    public class WebServer
    {
        //存放web内容的目录
        public static string WebRoot = @"D:\Dev\Temp\AVProMovieCapture\Web";
        public static int Port = 80;
        public static int ReceiveBufferSize = 1024; //接收缓冲区大小
        public static Type IHttpResponseType;
        public static bool DebugDetailLog = true; //true: 会打印更多日志,有助于调式.
        private static WebServer Instance;
        private Thread listenerThead;

        public static void Start()
        {
            if (Instance == null)
                Instance = new WebServer();
            Instance.StartServer();
        }

        public static void Stop()
        {
            if (Instance != null)
                Instance.StopServer();
        }

        private void StopServer()
        {
            if (listenerThead == null || !listenerThead.IsAlive)
                return;
            listenerThead.Abort();
            listenerThead = null;
        }

        private void StartServer()
        {
            if (listenerThead != null && listenerThead.IsAlive)
                return;
            listenerThead = new Thread(StartListener);
            listenerThead.IsBackground = true;
            listenerThead.Start();
        }

        private void StartListener()
        {
            string ip = GetLocalIP();
            TcpListener server = null;
            try
            {
                IPAddress localAddr = IPAddress.Parse(ip);

                // 在指定端口监听Http请求
                server = new TcpListener(localAddr, Port);
                server.Start();

                Debug.Log("WebServer started");
                Debug.LogFormat("Listen: ip={0}, port={1}", ip, Port);

                while (true)
                {
                    Debug.Log("Start listening to http requests...");

                    // 监听Http请求
                    Socket client = server.AcceptSocket();

                    // 创建要传递给处理线程的参数
                    IHttpResponse response = null;
                    if (IHttpResponseType != null)
                    {
                        object[] paramObject = new object[] { };
                        response = Activator.CreateInstance(IHttpResponseType, paramObject) as IHttpResponse;
                    }

                    WorkState state = new WorkState();
                    state.client = client;
                    state.response = response;

                    // 从线程池启动一条线程处理请求
                    ThreadPool.QueueUserWorkItem(new WaitCallback(DoProcessRequest), state);
                }
            }
            catch (SocketException e)
            {
                Debug.LogFormat("SocketException: {0}", e);
            }
            finally
            {
                server.Stop();
            }

            Debug.Log("Http listener stop!");
        }

        // 求理Http请求
        private void DoProcessRequest(object state)
        {
            if (DebugDetailLog)
                Debug.Log("[Http]: DoProcessRequest");

            WorkState workState = state as WorkState;
            Socket client = workState.client;
            IHttpResponse response = workState.response;

            //=============== 解析收到的数据 ==============/
            HttpHeader revHeader = new HttpHeader();
            //读取请求数据
            Byte[] bReceive = new Byte[ReceiveBufferSize];
            client.ReceiveTimeout = 10;
            client.SendTimeout = 10;
            client.Blocking = true;

            int i = client.Receive(bReceive, 0, bReceive.Length, SocketFlags.None);
            string receive_str = Encoding.UTF8.GetString(bReceive);
            revHeader.Parse(receive_str);
            //Head信息与POST数据存在断包的情况,需要再次检查数据有没读完
            if (revHeader.reqType == RequestType.POST)
            {
                if (string.IsNullOrEmpty(revHeader.revPostData))
                {
                    byte[] post_bytes = new byte[revHeader.revContentLength];
                    try
                    {
                        if (DebugDetailLog)
                            Debug.Log("重新读取POST数据");
                        i = 0;
                        while (i != revHeader.revContentLength)
                        {
                            //client.Available等于0时调用Receive()会引发SocketException
                            if (client.Connected && client.Available > 0)
                                i += client.Receive(post_bytes, i, post_bytes.Length, SocketFlags.None);
                            Thread.Sleep(10);
                        }

                        if (i > 0)
                        {
                            string post_data = Encoding.UTF8.GetString(post_bytes);
                            revHeader.revPostData = WebUtility.UrlDecode(post_data);
                        }
                    }
                    catch (SocketException ex)
                    {
                        Debug.Log(ex.Message);
                    }
                }
            }

            //=============== 调式日志 ===================/
            if (DebugDetailLog)
            {
                StringBuilder sb = new StringBuilder();
                sb.AppendFormat("[Http] 请求的URL: {0}\n", revHeader.revUrl);
                sb.AppendFormat("[Http] 请求类型: {0}\n", revHeader.reqType.ToString());
                sb.AppendFormat("[Http] 版本号: {0}\n", revHeader.revHttpVersion);
                if (revHeader.reqType == RequestType.POST)
                    sb.AppendFormat("[Http] POST数据: {0}\n", revHeader.revPostData);
                Debug.Log(sb.ToString());
            }

            //=============== 准备要返回的数据 =============/
            byte[] rspBytes = null;
            string contentType = HttpContentType.JSON;
            if (response != null)
            {
                response.OnRequest(revHeader);
                rspBytes = response.OnResponseContent();
                if (!string.IsNullOrEmpty(response.ContentType))
                    contentType = response.ContentType;
                response.OnDispose();
            }
            else
            {
                string error_msg = "{error_msg: \"no data\"}";
                rspBytes = Encoding.ASCII.GetBytes(error_msg);
            }

            //=============== 向客户端返回数据 =============/
            HttpHeader header = new HttpHeader();
            // 设置HTTP头信息
            header.SetStatusCode(revHeader.revHttpVersion, 200, "OK");
            header.SetContentLength(rspBytes != null ? rspBytes.Length : 0);
            header.SetContentType(contentType);

            // 发送HTTP头信息及文件内容
            SendToBrowser(header.GetBytes(), ref client);
            SendToBrowser(rspBytes, ref client);

            // 关闭连接
            client.Close();
        }

        private void SendToBrowser(byte[] bSendData, ref Socket socket)
        {
            int numBytes = 0;

            try
            {
                if (socket.Connected)
                {
                    if ((numBytes = socket.Send(bSendData, bSendData.Length, 0)) == -1)
                        Debug.Log("Socket Error cannot Send Packet");
                    else
                    {
                        Debug.LogFormat("No. of bytes send {0}", numBytes);
                    }
                }
                else
                    Debug.Log("连接失败....");
            }
            catch (Exception e)
            {
                Console.WriteLine("发生错误 : {0} ", e);
            }
        }

       public static string GetLocalIP()
        {
            string IP = "127.0.0.1";
            try
            {
                string HostName = Dns.GetHostName();
                IPHostEntry IpEntry = Dns.GetHostEntry(HostName);
                for (int i=0; i < IpEntry.AddressList.Length; i++)
                {
                    //AddressFamily.InterNetwork为IPv4
                    if (IpEntry.AddressList[i].AddressFamily == AddressFamily.InterNetwork)
                    {
                        IP = IpEntry.AddressList[i].ToString();
                        break;
                    }
                }
            }
            catch (Exception ex)
            {
                Debug.Log(ex.Message);
            }

            return IP;
        }
    }

    #region HTTP头信息
    public class HttpHeader
    {
        private string statusCode = "HTTP/1.1 200 OK\r\n";
        private string server = "Server: Simple-WebServer/1.0\r\n";
        private string date = "Date: Thu, 13 Jul 2016 05:46:53 GMT\r\n";
        private string contentType = "Content-Type: text/html\r\n";
        private string acceptRanges = "Accept-Ranges: bytes\r\n";
        //特别注意: 头信息的最后一行有两个空行,否则浏览器无法正确显示内容。
        private string contentLength = "Content-Length: 2291\r\n\r\n";

        public RequestType reqType = RequestType.GET; //请求类型
        public string revHttpVersion = "HTTP/1.1";
        public string revUrl; //请求的完整url
        public string revUrlPage; //url中的面页
        public Dictionary<string, string> revUrlParams; //url参数
        public int revContentLength = 0; //内容长度
        public string revPostData;//接收到的POST数据

        public void SetStatusCode(string version, int code, string msg)
        {
            statusCode = string.Format("HTTP/{0} {1} {2}\r\n", version, code, msg);
        }

        public void SetContentType(string type)
        {
            contentType = string.Format("Content-Type: {0}\r\n", type);
        }

        public void SetContentLength(int length)
        {
            contentLength = string.Format("Content-Length: {0}\r\n\r\n", length);
        }

        public string GetString()
        {
            date = string.Format("Date: {0:R}\r\n", DateTime.Now);
            string header = statusCode + server + date + acceptRanges + contentLength;
            return header;
        }

        public byte[] GetBytes()
        {
            return Encoding.UTF8.GetBytes(GetString());
        }

        public void Parse(string data)
        {
            int head_end_index = data.IndexOf("\r\n\r\n");
            string head_info = data.Substring(0, head_end_index).Trim();
            string post_info = data.Substring(head_end_index + 4).Trim();
            if (!string.IsNullOrEmpty(post_info) && post_info[0] != '\0')
            {
                int end_index = post_info.IndexOf('\0');
                if (end_index != -1)
                    this.revPostData = post_info.Substring(0, end_index);
                else
                    this.revPostData = post_info;
                this.revPostData = WebUtility.UrlDecode(this.revPostData);
            }
            else
            {
                this.revPostData = string.Empty;
            }

            string[] head_rows = head_info.Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries);
            string row0 = head_rows[0];

            this.reqType = row0.StartsWith("GET") ? RequestType.GET : RequestType.POST;
            this.revHttpVersion = row0.EndsWith("HTTP/1.1") ? "1.1" : "1.0";
            this.revUrl = row0.Replace("GET", "").Replace("POST", "").Replace("HTTP/1.1", "").Replace("HTTP/1.0", "").Trim();
            
            //根据需要解析其他字段
            for (int i=1; i < head_rows.Length; i++)
            {
                string[] arr = head_rows[i].Split(':');
                string key = arr[0].Trim();
                string value = arr[1].Trim();
                if ("Content-Length" == key)
                {
                    revContentLength = int.Parse(value);
                    break;
                }
            }

            //解析url
            if (!string.IsNullOrEmpty(this.revUrl))
            {
                if (this.revUrl[0] == '/')
                {
                    this.revUrl = this.revUrl.Substring(1);
                }

                //判断URL后面是否跟了参数
                if (this.revUrl.StartsWith("?"))
                {
                    //解析url参数
                    int index = this.revUrl.IndexOf("?");
                    this.revUrlPage = this.revUrl.Substring(0, index);
                    string param_str = this.revUrl.Substring(index + 1);
                    string[] param_arr = param_str.Split('&');
                    this.revUrlParams = new Dictionary<string, string>();
                    if (param_arr != null && param_arr.Length > 0)
                    {
                        for (int i=0; i<param_arr.Length; i++)
                        {
                            string param = param_arr[i];
                            string[] kv = param.Split('=');
                            if (kv != null && kv.Length == 2 && !this.revUrlParams.ContainsKey(kv[0]))
                            {
                                this.revUrlParams[kv[0]] = kv[1];
                            }
                        }
                    }
                }
                else
                {
                    this.revUrlPage = this.revUrl;
                }
            }
        }

        public string GetUrlParam(string key, string defaultValue=null)
        {
            if (string.IsNullOrEmpty(key) || !revUrlParams.ContainsKey(key))
                return defaultValue;
            return revUrlParams[key];
        }
    }
    #endregion
}

1111.png

标签: C#

Powered by emlog  蜀ICP备18021003号   sitemap

川公网安备 51019002001593号