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

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

效果

  • http请求
    消息打印
    基于webapi的websocket聊天室(番外二)
    响应解析
    基于webapi的websocket聊天室(番外二)

  • websocket请求
    消息打印
    基于webapi的websocket聊天室(番外二)
    使用聊天室测试
    基于webapi的websocket聊天室(番外二)

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

  • 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("rn")[0];             var lines= startLine.Split("rn");             httpMethod = lines[0].Split(' ')[0];             path = lines[0].Split(' ')[1];             //头部             var headerslines= str.Split("rnrn")[0].Split("rn");             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);         }     } 
发表评论

评论已关闭。

相关文章