我比较好奇的是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因为是无连接的,所以请求处理过程应该是这样

  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的websocket聊天室(番外二)的更多相关文章

  1. 基于springboot的websocket聊天室

    WebSocket入门 1.概述 1.1 Http #http简介 HTTP是一个应用层协议,无状态的,端口号为80.主要的版本有1.0/1.1/2.0. #http1.0/1.1/2.0 1.HTT ...

  2. 使用.NET Core和Vue搭建WebSocket聊天室

    博客地址是:https://qinyuanpei.github.io.  WebSocket是HTML5标准中的一部分,从Socket这个字眼我们就可以知道,这是一种网络通信协议.WebSocket是 ...

  3. 基于flask的网页聊天室(四)

    基于flask的网页聊天室(四) 前言 接前天的内容,今天完成了消息的处理 具体内容 上次使用了flask_login做用户登录,但是直接访问login_requare装饰的函数会报401错误,这里可 ...

  4. 基于flask的网页聊天室(一)

    基于flask的网页聊天室(一) 基本目标 基于flask实现的web聊天室,具有基本的登录注册,多人发送消息,接受消息 扩展目标 除基本目标外添加当前在线人数,消息回复,markdown支持,历史消 ...

  5. websocket聊天室

    目录 websocket方法总结 群聊功能 基于websocket聊天室(版本一) websocket方法总结 # 后端 3个 class ChatConsumer(WebsocketConsumer ...

  6. WebSocket聊天室demo

    根据Socket异步聊天室修改成WebSocket聊天室 WebSocket特别的地方是 握手和消息内容的编码.解码(添加了ServerHelper协助处理) ServerHelper: using ...

  7. Netty入门(一)之webSocket聊天室

    一:简介 Netty 是一个提供 asynchronous event-driven (异步事件驱动)的网络应用框架,是一个用以快速开发高性能.高可靠性协议的服务器和客户端. 换句话说,Netty 是 ...

  8. 基于flask的网页聊天室(三)

    基于flask的网页聊天室(三) 前言 继续上一次的内容,今天完成了csrf防御的添加,用户头像的存储以及用户的登录状态 具体内容 首先是添加csrf的防御,为整个app添加防御: from flas ...

  9. 基于flask的网页聊天室(二)

    基于flask的网页聊天室(二) 前言 接上一次的内容继续完善,今天完成的内容不是很多,只是简单的用户注册登录,内容具体如下 具体内容 这次要加入与数据哭交互的操作,所以首先要建立相关表结构,这里使用 ...

  10. 用Java构建一个简单的WebSocket聊天室

    前言 首先对于一个简单的聊天室,大家应该都有一定的概念了,这里我们省略用户模块的讲解,而是单纯的先说说聊天室的几个功能:自我对话.好友交流.群聊.离线消息等. 今天我们要做的demo就能帮我们做到这一 ...

随机推荐

  1. 【FAQ】HarmonyOS SDK 闭源开放能力 —Scan Kit

    1.问题描述 Scan Kit扫描专用底层码流接口需要鉴权,鉴权失败后功能还能用吗? 解决方案 如果已经申请过白名单,因为异常导致的鉴权失败会优先放通,保障业务成功. 2.问题描述 调用Scan Ki ...

  2. ODBC批量merge中出现主键冲突的分析

    ODBC 批量 merge 中出现主键冲突的分析 一. 文档概括 客户某个 merge 语句运行时,发生主键冲突报错. 经分析,其原因如下: 由于 merge 语句中,ON 里的判断条件(谓词)中存在 ...

  3. IP路由的工作原理

    一.路由 路由在网络中起到什么作用? • 路由器负责将数据报文在IP网段之间进行转发 • 路由是指导路由器如何进行数据转发的路径信息 IP之间连通的前提是什么? • 沿途的每台路由器上都有到达目的网段 ...

  4. Spring Cloud Stream 源码解析

    Spring Cloud Stream 是一个消息驱动微服务的框架.   应用程序通过inputs 或者outputs 来与 Spring Cloud Stream 中binder 交互,通过我们配置 ...

  5. 《c#高级编程》第4章C#4.0中的更改(六)——动态绑定

    一.概念 下面是一些代码示例,说明C#动态绑定的上述特点: 1. 延迟确定类型 ```dynamic obj = GetDynamicObject(); // 获取动态对象obj.DoSomethin ...

  6. Crazy Excel:Excel中的泥石流

    Crazy Excel又名:疯狂Excel.是一款PC端的Excel软件工具,该软件支持windows, mac os等主流操作系统. 正如其名,作者在设计之初就加入了一些疯狂的设计,目的是创作出更加 ...

  7. 力扣745(java&python)-达到终点数字(中等)

    题目: 在一根无限长的数轴上,你站在0的位置.终点在target的位置. 你可以做一些数量的移动 numMoves : 每次你可以选择向左或向右移动.第 i 次移动(从  i == 1 开始,到 i ...

  8. 力扣43(java)-字符串相乘(中等)

    题目: 给定两个以字符串形式表示的非负整数 num1 和 num2,返回 num1 和 num2 的乘积,它们的乘积也表示为字符串形式. 注意:不能使用任何内置的 BigInteger 库或直接将输入 ...

  9. 为什么我要迁移 SpringBoot 到函数计算

    简介: 面对流量洪峰,我们再也不会手忙脚乱了,函数计算自动会帮我们扩容!很好的解决了我们的 API 场景和不定时执行各种不同任务的场景. 作者:榴莲   为什么要迁移? 我们的业务有很多对外提供服务的 ...

  10. 【ESSD技术解读-01】 云原生时代,阿里云块存储 ESSD 快照服务如何被企业级数据保护所集成?

    ​简介: 本文描述了阿里云块存储快照服务基于高性能 ESSD 云盘提升快照服务性能,提供轻量.实时的用户体验及揭秘背后的技术原理.依据行业发展及云上数据保护场景,为企业用户及备份厂商提供基于快照高级特 ...