基于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);
}
}