• 基于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(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 clients = new List();
                    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(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(ASCIIEncoding.ASCII.GetBytes(header)));
                        memoryStream.Write(new ArraySegment(UTF8Encoding.UTF8.GetBytes(body)));
                        await client.Client.SendAsync(new ArraySegment(memoryStream.ToArray()));
                        Console.WriteLine(header+body);
                        //关闭连接
                        client.Close();
                    }
                }
            }
        }
    
        public class HttpRequet
        {
            /// 
            /// 解析HTTP消息
            /// 
            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]);
                }
            }
    
            /// 
            /// 请求原始消息
            /// 
            public string Str { get; }
            /// 
            /// 请求方法
            /// 
            public string httpMethod { get; internal set; }
            /// 
            /// 请求路径
            /// 
            public string path { get; set; }
            /// 
            /// 头部字段
            /// 
            public Dictionary<string,string> headers { get; set; }
    
            /// 
            /// 判断是否是转协议的请求
            /// 
            /// 
            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对象
            /// 
            /// 
            /// 
            public async Task 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(ASCIIEncoding.ASCII.GetBytes(header)));
                    await client.Client.SendAsync(new ArraySegment(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);
            }
        }
    
  • 相关阅读:
    从一到无穷大 #3 对象存储.浅谈
    【元宇宙】区块链上的游戏,你的所有资产都是开放的
    js监听页面滚动
    基于flowable的upp(统一流程平台)运行性能优化
    在线云原生边缘计算KubeEdge安装配置(二)
    综合练习——三子棋小游戏
    1200*D. Same Differences(数学&推公式)
    程序员必备的IP查询工具
    SUID提权教程
    软考 系统架构设计师系列知识点之软件架构风格(4)
  • 原文地址:https://www.cnblogs.com/ggtc/p/18206016