我比较好奇的是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. 仅需30行代码,轻松集成HMS Core视频编辑服务屏幕录制能力

    现如今,手机录屏是必不可少的能力之一.对于游戏领域作者来说,在平时直播玩游戏.制作攻略.操作集锦时,不方便切屏,这时在游戏内如果有一个录制按钮就可以随时开启,记录下每个精彩瞬间,减少后期剪辑工作量:在 ...

  2. openGauss每日一练第6天

    学习地址 https://www.modb.pro/course/133 学习目标 学习 openGauss 创建模式.修改模式属性和删除模式 模式是一组数据库对象的集合,主要用于控制对数据库对象的访 ...

  3. 在python中通过面向对象方式,实现烤地瓜案例

    例子:烤地瓜,不同时间,反馈不同状态,并给不同状态地瓜加入不同味道 烤地瓜时间 0-3分钟,生的 4-7分钟,半生不熟的 8-12分钟,熟了 12分钟以上,已烤熟,糊了 用户可以按自己的意思添加调料 ...

  4. HDC2021技术分论坛:吐司盒子?芝士码?HarmonyOS音视频测试来啦

    作者:lifusheng,用户体验技术专家 当下,音视频无处不在,很多设备和应用都涉及音视频.因而,对于HarmonyOS开发者们来说,如何对鸿蒙生态产品进行音视频测试是一个非常重要的问题. 华为Ha ...

  5. 国庆的一些blog 书写

    前言 国庆估计出不去了,所以吧,把文档准备下. 正文 1.docker 微服务,整理微软开源shop框架. 2.rpa 这个东西,我第一次接触是因为android测试的时候,每次修改代码,都需要全部测 ...

  6. 对中间件概念的理解,如何封装 node 中间件

    一.是什么 中间件(Middleware)是介于应用系统和系统软件之间的一类软件,它使用系统软件所提供的基础服务(功能),衔接网络上应用系统的各个部分或不同的应用,能够达到资源共享.功能共享的目的 在 ...

  7. 【笔记】Java相关大杂烩①

    [笔记]Java相关大杂烩 Java 程序的执行流程是? *.java 文件-->*.class 文件-->类装载器-->字节码校验器-->解释器-->操作系统平台 Ja ...

  8. 第二課:Mirth培養興趣之旅 ——由定時刷庫接口編程講起

    1.准备工作 1.1 本机安装vs2019:(https://visualstudio.microsoft.com/zh-hans/) 1.2 本机安装win64的MariaDB 10.3.27版本数 ...

  9. 剑指 Offer II 018(Java). 有效的回文(简单)

    题目: 给定一个字符串 s ,验证 s 是否是 回文串 ,只考虑字母和数字字符,可以忽略字母的大小写. 本题中,将空字符串定义为有效的 回文串 . 示例 1: 输入: s = "A man, ...

  10. 力扣1075(MySQL)-项目员工Ⅰ(简单)

    题目: 项目表 Project: 员工表 Employee: 请写一个 SQL 语句,查询每一个项目中员工的 平均 工作年限,精确到小数点后两位. 查询结果的格式如下:    解题思路: 建表语句: ...