首页 > 其他分享 >基于webapi的websocket聊天室(番外二)

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

时间:2024-05-23 22:18:51浏览次数:9  
标签:webapi Upgrade websocket string 番外 client WebSocket new

我比较好奇的是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);
        }
    }

标签:webapi,Upgrade,websocket,string,番外,client,WebSocket,new
From: https://www.cnblogs.com/ggtc/p/18206016

相关文章

  • netcore webapi部署到docker容器,api调用后显示中文乱码
    vs2022webapi部署到docker容器,api调用后显示中文乱码。原因是:源代码文件不是utf-8编码(用vscode打开是乱码,在vscode修改后,再提交,正常)解决方法:在中文环境下用过微软家Visualstudio的都知道,新建文件的保存编码都默认为当前系统语言,所以你的文件编码永远都是GB2312,非常令人蛋......
  • websocket和http的区别
    1、websocket1.1介绍WebSocket是一种实时通信协议,它允许客户端和服务器之间进行双向通信,而不需要每次请求都重新建立连接。WebSocket是HTML5中的新功能,它建立在HTTP协议之上,通过握手协议来建立持久化的连接。WebSocket的握手协议比HTTP的握手协议更简单,因为WebSocket......
  • .net webapi 处理前端请求跨域问题
    1.打开 Program.cs文件,在 varapp=builder.Build();语句前添加如下代码builder.Services.AddCors(o=>o.AddPolicy("any",p=>p.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod()));2.在varapp=builder.Build();语句后添加 app.UseCors();app.UseCors();......
  • 基于webapi的websocket聊天室(番外一)
    上一篇我已经实现了聊天室,并且在协议中实现了4种类型的消息传输。其实还可以添加video,audio,live等等类型。不过假如把目前的协议看作RCP1.0版的话,这个版本就只支持有限的4种消息。精力有限啊。也许RCP2.0就可以把video,audio类型加进去?这不是这篇番外考虑的。而是我在定义和实现......
  • .net 4.8 webApi 文件下载部分代码记录
    privateHttpResponseMessageExportData(stringids,Func<string,string>exportFunc,stringdataNotExistsMsg){varfilePath=exportFunc.Invoke(ids);//检查文件是否存在if(!File.Exists(filePath)){......
  • EDP .Net开发框架--WebApi
    平台下载地址:https://gitee.com/alwaysinsist/edp按分类管理EDP所提供的WebApi接口,以供其他应用调用。WebApi接口不仅可以进行访问控制管理,同时还提供了版本管理,同一WebApi接口支持多个不同版本以满足接口调用方的多版本支持。WebApi接口的数据是通过调用业务方法来获取的,而业......
  • 基于webapi的websocket聊天室(四)
    上一篇实现了多聊天室。这一片要继续改进的是实现收发文件,以及图片显示。效果问题websocket本身就是二进制传输。文件刚好也是二进制存储的。文件本身的传输问题不太,但是需要传输文件元数据,比如文件名和扩展名之类的。这很必要,如果我们想知道怎么展示这个文件的话。比如这个......
  • java netty 实现 websocket 服务端和客户端双向通信 实现心跳和断线重连 完整示例
    javanetty实现websocket服务端和客户端双向通信实现心跳和断线重连完整示例maven依赖<dependency><groupId>io.netty</groupId><artifactId>netty-all</artifactId><version>4.1.97.Final</version></dependency>服务端一个接口IGet......
  • windows 安装.net6core webapi
    windows安装.net6corewebapi:1.下载安装dotnet-hosting-6.0.0-win.exe(Windows HostingBundle--runtime)https://dotnet.microsoft.com/en-us/download/dotnet/6.02..netcorewebapi右键发布publish,复制发布的文件到windows服务器iis站点webapi3.启动windowspowersh......
  • 用curl调试简单webapi
    curl,即用户url。windows自带(据说新版的linux也自带),可以发送请求,用来简单调试webapi很合适。使用:cmd下直接输入命令。 例子:对于模型类 publicrecordStu(stringXm,intNl); post表单:curl-XPOST-d"Xm=ZS&Nl=20"http://localhost:5205/weatherforecast接收的weba......