基于webapi的websocket聊天室(番外二)
我比较好奇的是webapi服务器怎么处理http请求和websocket请求。有了上一篇番外的研究,这里就可以试着自己写个非常简易的webapi服务器来接收这两种请求。
效果
http请求
消息打印
响应解析
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因为是无连接的,所以请求处理过程应该是这样
- 客户端解析
clientMsg
获取要连接的服务器 - 客户端根据请求先建立tcp连接
- 客户端发送
ASCII.GetBytes("Get /httppath....")
- 服务端接收后
GetString(clientMsg)
- 服务端根据请求路径执行对应方法
Action()
- 服务端发送
ASCII.GetBytes("HTTP/1.1 200....")
- 服务端关闭连接
websocket发送的第一条消息也是采用http格式,流程相似,但是要保持连接,所以请求处理过程有所差异
- 客户端解析
clientMsg
获取要连接的服务器 - 客户端根据请求先建立tcp连接
- 客户端发送
GetBytes("Get /httppath....")
,然后,调用等待消息发方法阻塞线程awite ReciveAsync()
- 服务端接收后
GetString(clientMsg)
- 服务端看到消息头部包含三个字段
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: xxx
,开一个接收消息的线程 - 服务端发送
GetBytes("HTTP/1.1 101...")
- 服务端在接收消息的线程中写个while循环,判断监听客户端发来的消息,并调用对应方法处理
- 客户端收到消息后判断是101消息,开一个接收消息的线程
- 客户端在接收消息的线程中写个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的websocket聊天室(番外二)的更多相关文章
- 基于springboot的websocket聊天室
WebSocket入门 1.概述 1.1 Http #http简介 HTTP是一个应用层协议,无状态的,端口号为80.主要的版本有1.0/1.1/2.0. #http1.0/1.1/2.0 1.HTT ...
- 使用.NET Core和Vue搭建WebSocket聊天室
博客地址是:https://qinyuanpei.github.io. WebSocket是HTML5标准中的一部分,从Socket这个字眼我们就可以知道,这是一种网络通信协议.WebSocket是 ...
- 基于flask的网页聊天室(四)
基于flask的网页聊天室(四) 前言 接前天的内容,今天完成了消息的处理 具体内容 上次使用了flask_login做用户登录,但是直接访问login_requare装饰的函数会报401错误,这里可 ...
- 基于flask的网页聊天室(一)
基于flask的网页聊天室(一) 基本目标 基于flask实现的web聊天室,具有基本的登录注册,多人发送消息,接受消息 扩展目标 除基本目标外添加当前在线人数,消息回复,markdown支持,历史消 ...
- websocket聊天室
目录 websocket方法总结 群聊功能 基于websocket聊天室(版本一) websocket方法总结 # 后端 3个 class ChatConsumer(WebsocketConsumer ...
- WebSocket聊天室demo
根据Socket异步聊天室修改成WebSocket聊天室 WebSocket特别的地方是 握手和消息内容的编码.解码(添加了ServerHelper协助处理) ServerHelper: using ...
- Netty入门(一)之webSocket聊天室
一:简介 Netty 是一个提供 asynchronous event-driven (异步事件驱动)的网络应用框架,是一个用以快速开发高性能.高可靠性协议的服务器和客户端. 换句话说,Netty 是 ...
- 基于flask的网页聊天室(三)
基于flask的网页聊天室(三) 前言 继续上一次的内容,今天完成了csrf防御的添加,用户头像的存储以及用户的登录状态 具体内容 首先是添加csrf的防御,为整个app添加防御: from flas ...
- 基于flask的网页聊天室(二)
基于flask的网页聊天室(二) 前言 接上一次的内容继续完善,今天完成的内容不是很多,只是简单的用户注册登录,内容具体如下 具体内容 这次要加入与数据哭交互的操作,所以首先要建立相关表结构,这里使用 ...
- 用Java构建一个简单的WebSocket聊天室
前言 首先对于一个简单的聊天室,大家应该都有一定的概念了,这里我们省略用户模块的讲解,而是单纯的先说说聊天室的几个功能:自我对话.好友交流.群聊.离线消息等. 今天我们要做的demo就能帮我们做到这一 ...
随机推荐
- openGauss/MOGDB时间消耗相关视图
openGauss/MOGDB 时间消耗相关视图 本文出处:https://www.modb.pro/db/388212 数据库版本 openGauss/MOGDB-2.1.1 一.显示当前用户在各个 ...
- 简单3步,OpenHarmony上跑起ArkUI分布式小游戏
转自:OpenAtom OpenHarmony 在9月30日更新的 OpenHarmony3.0 LTS 上,标准系统新增支持了方舟开发框架(ArkUI).分布式组网和 FA 跨设备迁移能力等新特性, ...
- HarmonyOS崩溃服务能力全新上线,帮你高效解决崩溃问题!
原文:https://mp.weixin.qq.com/s/YmYzNXsvsqt8U8JX3vlmrg,点击链接查看更多技术内容. 一.为什么需要崩溃服务能力 用户在使用原子化服务时,出现卡顿.缓慢 ...
- 进阶 stack smashing--canary 报错利用 && environ泄露栈地址
进阶 stack smashing--canary 报错利用 && environ泄露栈地址 这部分是对进阶stack smashing的使用,以及对 environ的认识,我们可以看 ...
- Excel 表间关联运算的示例
用 Excel 处理数据时,经常会涉及到多页 sheet 数据之间的关联运算需求,用 vlookup 可以完成部分简单关联,但较复杂的情况时仍然不太方便,常常需要多次操作才能完成.另外,当要做关联的文 ...
- pytest接口自动化搭建经验
前言:目前公司的主要产品是一个web类型的产品:需要做一些自动化,目前的想法是只做接口自动化,不做ui的一个自动化,目前的思路是先对主流程做正常校验,后期再对每一个接口做校验: 一.版本信息: pyt ...
- Linux下安装配置OpenResty服务器
OpenResty是一款基于Nginx和Lua的高性能Web平台,在nginx基础之上集成了大量的lua库,第三方模块等,以便搭建各种处理高并发.可扩展的Web应用.服务或网关,并且OpenResty ...
- 【笔记】GO内建容器--数组
go内建容器--数组 1.数量要写在类型的前面 2.可通过_省略变量 3.不仅是range,任何地方都可以使用_来省略变量 4.如果只要i,则可写成for i := range numbers 5.[ ...
- 力扣74(java&python)-搜索二维矩阵(中等)
题目: 编写一个高效的算法来判断 m x n 矩阵中,是否存在一个目标值.该矩阵具有如下特性: 每行中的整数从左到右按升序排列.每行的第一个整数大于前一行的最后一个整数. 示例 1: 输入:matri ...
- 力扣2(java&python)-两数相加(中等)
题目: 给你两个 非空 的链表,表示两个非负的整数.它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字. 请你将两个数相加,并以相同形式返回一个表示和的链表. 你可以假设除了数 ...