基于webapi的websocket聊天室(番外二)

我比较好奇的是webapi服务器怎么处理http请求和websocket请求。有了上一篇番外的研究,这里就可以试着自己写个非常简易的webapi服务器来接收这两种请求。

效果

  • http请求
    消息打印
    image
    响应解析
    image

  • websocket请求
    消息打印
    image
    使用聊天室测试
    image

其实两种请求差不多,就只是一些头部字段有差别

  • http消息

    //客户端发送的消息
    string clientMsg = @"Get /httppath?msg=你好 HTTP/1.1
    CustomField:f1
    CustomField2:f2
    ";
    
    //服务端发送的消息
    string serverMsg = @"HTTP/1.1 200
    CustomField2:f2
    
    数据以收到";
    
  • websocket消息

    //客户端发送的消息
    string clientMsg = @"Get /httppath HTTP/1.1
    Upgrade: websocket
    Connection: Upgrade
    Sec-WebSocket-Key: xxxxx
    ";
    
    //服务端发送的消息
    string serverMsg = @"HTTP/1.1 101 Switching Protocols
    Upgrade: websocket
    Connection: Upgrade
    Sec-WebSocket-Accept: xxxxx
    ";
    

伪代码分析

http头部是ASCII编码。body部分默认也是,除非指定了content-type
http因为是无连接的,所以请求处理过程应该是这样

  1. 客户端解析clientMsg获取要连接的服务器
  2. 客户端根据请求先建立tcp连接
  3. 客户端发送ASCII.GetBytes("Get /httppath....")
  4. 服务端接收后GetString(clientMsg)
  5. 服务端根据请求路径执行对应方法Action()
  6. 服务端发送ASCII.GetBytes("HTTP/1.1 200....")
  7. 服务端关闭连接

websocket发送的第一条消息也是采用http格式,流程相似,但是要保持连接,所以请求处理过程有所差异

  1. 客户端解析clientMsg获取要连接的服务器
  2. 客户端根据请求先建立tcp连接
  3. 客户端发送GetBytes("Get /httppath...."),然后,调用等待消息发方法阻塞线程awite ReciveAsync()
  4. 服务端接收后GetString(clientMsg)
  5. 服务端看到消息头部包含三个字段Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: xxx,开一个接收消息的线程
  6. 服务端发送GetBytes("HTTP/1.1 101...")
  7. 服务端在接收消息的线程中写个while循环,判断监听客户端发来的消息,并调用对应方法处理
  8. 客户端收到消息后判断是101消息,开一个接收消息的线程
  9. 客户端在接收消息的线程中写个while循环,判断监听服务端发来的消息,并调用对应方法处理

写一个 HTTP & WebSocket 服务器和客户端

  • 首先是解析消息
    var buffer = new byte[1024*4];
    int msgLength = await client.Client.ReceiveAsync(new ArraySegment<byte>(buffer));
    string str=UTF8Encoding.UTF8.GetString(buffer,0,msgLength);
    Console.WriteLine(str);
    HttpRequet request = new HttpRequet(str);
    
  • 核心思想是判断消息是不是符合websocket连接请求格式,而这非常容易
    public bool IsWebsocket()
    {
    	if (this.headers.ContainsKey("Connection") && this.headers["Connection"] == "Upgrade" 
    	&& this.headers.ContainsKey("Upgrade") && this.headers["Upgrade"] == "websocket")
    		return true;
    	else
    		return false;
    }
    
  • 然后是根据消息判断如何处理
    //转websocket的消息
    if (request.IsWebsocket())
    {
    	//用tcp连接构造一个WebSocket对象
    	WebSocket webSocket =await request.AcceptWebsocket(client, request.headers["Sec-WebSocket-Key"]);
    }
    //其他HTTP消息
    else
    {
    	string header = @$"HTTP/1.1 200
    CustomField2: f2
    content-type: text/html; charset=utf-8
    
    ";
    	string body = "数据以收到";
    }
    

完整代码

TCP与Socket端口测试.cs
    internal class Program
    {
        static void Main(string[] args)
        {
            //服务器
            if (args.Length == 1) {
                StartServer(args[0]);
            }
        }

        private static void StartServer(string args)
        {

            int serverPort = Convert.ToInt32(args);
            var server = new TcpListener(IPAddress.Parse("127.0.0.1"), serverPort);
            Console.WriteLine($"TCP服务器  127.0.0.1:{serverPort}");
            server.Start();
            int cnt = 0;
            Task.Run(async () =>
            {
                List<TcpClient> clients = new List<TcpClient>();
                while (true)
                {
                    TcpClient client = await server.AcceptTcpClientAsync();
                    clients.Add(client);
                    cnt++;
                    var ep = client.Client.RemoteEndPoint as IPEndPoint;
                    Console.WriteLine($"TCP客户端_{cnt}  {ep.Address}:{ep.Port}");
                    //给这个客户端开一个聊天线程
                    //操作系统将会根据游客端口对应表将控制权交给对应游客线程
                    StartChat(client);
                }
            }).Wait();
        }

        public static async Task StartChat(TcpClient client)
        {
            var buffer = new byte[1024*4];
            int msgLength = await client.Client.ReceiveAsync(new ArraySegment<byte>(buffer));
            string str=UTF8Encoding.UTF8.GetString(buffer,0,msgLength);
            Console.WriteLine(str);
            HttpRequet request = new HttpRequet(str);
            //转websocket的消息
            if (request.IsWebsocket())
            {
                WebSocket webSocket =await request.AcceptWebsocket(client, request.headers["Sec-WebSocket-Key"]);
                //发送一条websocket格式的打招呼消息
                var msg = new byte[] {
                        0x15,
                        0xe6,0x98,0x9f,0xe7,0xa9,0xb9,0xe9,0x93,0x81,0xe9,0x81,0x93,0xe5,0xa4,0xa7,0xe5,0xae,0xb6,0xe5,0xba,0xad,
                        0x00,
                        0x15,0x00,0x00,0x00,
                        0xe6,0xac,0xa2,0xe8,0xbf,0x8e,0xe8,0xbf,0x9b,0xe5,0x85,0xa5,0xe8,0x81,0x8a,0xe5,0xa4,0xa9,0xe5,0xae,0xa4
                    };
                await webSocket.SendAsync(msg, WebSocketMessageType.Binary, true, CancellationToken.None);
                //之后采用websocket规定的格式传输消息
                while (!webSocket.CloseStatus.HasValue)
                {
                    await webSocket.ReceiveAsync(buffer,CancellationToken.None);
                }
            }
            //其他HTTP消息
            else
            {
                using (MemoryStream memoryStream = new MemoryStream())
                {
                    string header = @$"HTTP/1.1 200
CustomField2: f2
content-type: text/html; charset=utf-8

";
                    string body = "数据以收到";
                    //响应请求
                    memoryStream.Write(new ArraySegment<byte>(ASCIIEncoding.ASCII.GetBytes(header)));
                    memoryStream.Write(new ArraySegment<byte>(UTF8Encoding.UTF8.GetBytes(body)));
                    await client.Client.SendAsync(new ArraySegment<byte>(memoryStream.ToArray()));
                    Console.WriteLine(header+body);
                    //关闭连接
                    client.Close();
                }
            }
        }
    }

    public class HttpRequet
    {
        /// <summary>
        /// 解析HTTP消息
        /// </summary>
        public HttpRequet(string str)
        {
            Str = str;
            //开始行
            var startLine = str.Split("\r\n")[0];
            var lines= startLine.Split("\r\n");
            httpMethod = lines[0].Split(' ')[0];
            path = lines[0].Split(' ')[1];
            //头部
            var headerslines= str.Split("\r\n\r\n")[0].Split("\r\n");
            headers = new Dictionary<string, string>();
            for (int i = 1; i < headerslines.Length; i++)
            {
                var header = headerslines[i].Split(": ");
                headers.Add(header[0], header[1]);
            }
        }

        /// <summary>
        /// 请求原始消息
        /// </summary>
        public string Str { get; }
        /// <summary>
        /// 请求方法
        /// </summary>
        public string httpMethod { get; internal set; }
        /// <summary>
        /// 请求路径
        /// </summary>
        public string path { get; set; }
        /// <summary>
        /// 头部字段
        /// </summary>
        public Dictionary<string,string> headers { get; set; }

        /// <summary>
        /// 判断是否是转协议的请求
        /// </summary>
        /// <returns></returns>
        public bool IsWebsocket()
        {
            if (this.headers.ContainsKey("Connection") && this.headers["Connection"] == "Upgrade" && this.headers.ContainsKey("Upgrade") && this.headers["Upgrade"] == "websocket")
                return true;
            else
                return false;
        }

        /// <summary>
        /// 响应转协议请求并未用当前连接创建一个WebSocket对象
        /// </summary>
        /// <param name="client"></param>
        /// <returns></returns>
        public async Task<WebSocket> AcceptWebsocket(TcpClient client,string Sec_WebSocket_Key)
        {
            using (MemoryStream memoryStream = new MemoryStream())
            {
                string header = @$"HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: {GenerateResponseKey(Sec_WebSocket_Key)}

";
                memoryStream.Write(new ArraySegment<byte>(ASCIIEncoding.ASCII.GetBytes(header)));
                await client.Client.SendAsync(new ArraySegment<byte>(memoryStream.ToArray()));
                Console.WriteLine(header);

                return WebSocket.CreateFromStream(client.GetStream(), true, null, TimeSpan.FromSeconds(10));
            }
        }

        public static string GenerateResponseKey(string requestKey)
        {
            const string guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
            string concatenated = requestKey + guid;
            byte[] hashed = System.Security.Cryptography.SHA1.Create().ComputeHash(Encoding.UTF8.GetBytes(concatenated));
            return Convert.ToBase64String(hashed);
        }
    }
posted @ 2024-05-23 22:14  ggtc  阅读(346)  评论(0编辑  收藏  举报
//右下角目录