基于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就能帮我们做到这一 ...
随机推荐
- 仅需30行代码,轻松集成HMS Core视频编辑服务屏幕录制能力
现如今,手机录屏是必不可少的能力之一.对于游戏领域作者来说,在平时直播玩游戏.制作攻略.操作集锦时,不方便切屏,这时在游戏内如果有一个录制按钮就可以随时开启,记录下每个精彩瞬间,减少后期剪辑工作量:在 ...
- openGauss每日一练第6天
学习地址 https://www.modb.pro/course/133 学习目标 学习 openGauss 创建模式.修改模式属性和删除模式 模式是一组数据库对象的集合,主要用于控制对数据库对象的访 ...
- 在python中通过面向对象方式,实现烤地瓜案例
例子:烤地瓜,不同时间,反馈不同状态,并给不同状态地瓜加入不同味道 烤地瓜时间 0-3分钟,生的 4-7分钟,半生不熟的 8-12分钟,熟了 12分钟以上,已烤熟,糊了 用户可以按自己的意思添加调料 ...
- HDC2021技术分论坛:吐司盒子?芝士码?HarmonyOS音视频测试来啦
作者:lifusheng,用户体验技术专家 当下,音视频无处不在,很多设备和应用都涉及音视频.因而,对于HarmonyOS开发者们来说,如何对鸿蒙生态产品进行音视频测试是一个非常重要的问题. 华为Ha ...
- 国庆的一些blog 书写
前言 国庆估计出不去了,所以吧,把文档准备下. 正文 1.docker 微服务,整理微软开源shop框架. 2.rpa 这个东西,我第一次接触是因为android测试的时候,每次修改代码,都需要全部测 ...
- 对中间件概念的理解,如何封装 node 中间件
一.是什么 中间件(Middleware)是介于应用系统和系统软件之间的一类软件,它使用系统软件所提供的基础服务(功能),衔接网络上应用系统的各个部分或不同的应用,能够达到资源共享.功能共享的目的 在 ...
- 【笔记】Java相关大杂烩①
[笔记]Java相关大杂烩 Java 程序的执行流程是? *.java 文件-->*.class 文件-->类装载器-->字节码校验器-->解释器-->操作系统平台 Ja ...
- 第二課:Mirth培養興趣之旅 ——由定時刷庫接口編程講起
1.准备工作 1.1 本机安装vs2019:(https://visualstudio.microsoft.com/zh-hans/) 1.2 本机安装win64的MariaDB 10.3.27版本数 ...
- 剑指 Offer II 018(Java). 有效的回文(简单)
题目: 给定一个字符串 s ,验证 s 是否是 回文串 ,只考虑字母和数字字符,可以忽略字母的大小写. 本题中,将空字符串定义为有效的 回文串 . 示例 1: 输入: s = "A man, ...
- 力扣1075(MySQL)-项目员工Ⅰ(简单)
题目: 项目表 Project: 员工表 Employee: 请写一个 SQL 语句,查询每一个项目中员工的 平均 工作年限,精确到小数点后两位. 查询结果的格式如下: 解题思路: 建表语句: ...