WebSocket的握手是基于HTTP的,所以我们所有的后续文章,还是基于我们前面的HTTP服务器。
项目托管地址:https://github.com/hooow-does-it-work/http
HTTP专栏地址:https://blog.csdn.net/moasp/category_11332350.html
0、判断一个HTTP请求为WebSocket握手请求
HTTP请求的Connection标头值为Upgrade,Upgrade标头的值为websocket,即表示当前请求为WebSocket握手请求。
我们在HttpRequest
类中增加如下属性来判断。
/// <summary>
/// 判断请求是不是WebSocket请求
/// </summary>
public bool IsWebSocket
{
get
{
string connection = _headers["connection"];
string upgrade = _headers["Upgrade"];
return !string.IsNullOrEmpty(connection)
&& connection.ToLower() == "upgrade"
&& !string.IsNullOrEmpty(upgrade)
&& upgrade.ToLower() == "websocket";
}
}
1、握手
HttpServerBase
类的NewClient重载中增加WebSocket握手请求的判断。
//捕获一个HttpRequest
request = HttpRequest.Capture(stream);
///如果是WebSocket,调用相应的处理方法
if (request.IsWebSocket)
{
if(!OnWebSocketInternal(request, stream))
{
//WebSocket处理异常,关闭基础流
stream.Close();
}
return;
}
发现是WebSocket请求后,交给OnWebSocketInternal
方法处理。
读取客户端发送的Sec-WebSocket-Key
标头,拼接上WebSocket协议固定的一个Salt值:258EAFA5-E914-47DA-95CA-C5AB0DC85B11
。
然后对拼接好的值使用SHA1算法计算摘要,对摘要进行Base64编码后,作为响应给客户端的Sec-WebSocket-Accept
标头值。
SHA1计算的摘要大小为20字节,Base64编码后,变为28字节,所以Sec-WebSocket-Accept值的长度是28。
private bool OnWebSocketInternal(HttpRequest request, Stream stream)
{
string webSocketKey = request.Headers["Sec-WebSocket-Key"];
if(string.IsNullOrEmpty(webSocketKey))
{
OnBadRequest(stream, "header 'Sec-WebSocket-Key' error");
return false;
}
//获取客户端发送来的Sec-WebSocket-Key字节数组
byte[] keyBytes = Encoding.ASCII.GetBytes(webSocketKey);
//拼接上WebSocket的Salt,固定值:258EAFA5-E914-47DA-95CA-C5AB0DC85B11
keyBytes = keyBytes.Concat(ProtocolUtils.Salt).ToArray();
//计算HASH值,作为响应给客户端的Sec-WebSocket-Accept
string secWebSocketAcceptKey = ProtocolUtils.SHA1(keyBytes);
//响应101状态码给客户端
HttpResponser responser = new HttpResponser(101);
responser["Upgrade"] = "websocket";
responser["Connection"] = "Upgrade";
//设置Sec-WebSocket-Accept头
responser["Sec-WebSocket-Accept"] = secWebSocketAcceptKey;
//发送响应
responser.WriteHeader(stream);
//开始WebSocket消息的接收和发送
OnWebSocket(request, stream);
return true;
}
至此,握手完成,后续消息的读取和写入,交给OnWebSocket来处理,OnWebSocket为虚方法,可以在子类实现相应的逻辑。
基类的OnWebSocket方法,只是单纯的关闭了基础流。
2、握手测试
实现一个服务器。
把文件https://github.com/hooow-does-it-work/http/blob/main/bin/Release/web/websocket.html放在WebRoot目录,作为WebSocket的测试页。
public class HttpServer : HttpServerBase
{
public HttpServer() : base()
{
//设置根目录
WebRoot = Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "web"));
}
protected override void OnWebSocket(HttpRequest request, Stream stream)
{
base.OnWebSocket(request, stream);
}
}
运行服务器,浏览器访问:http://127.0.0.1:4189/websocket.html
可以看到,服务端响应了101状态和正确的Sec-WebSocket-Accept标头,WebSocket握手成功。
客户端发送了一条消息,因为我们默认服务端的OnWebSocket
没有实现消息的读写,只是关闭了基础流,所以页面显示,连接成功后立即被关闭了。
3、总结
1、三个重要的请求标头:Connection、Upgrade、Sec-WebSocket-Key
2、三个重要的响应标头:Connection、Upgrade、Sec-WebSocket-Accept
3、Sec-WebSocket-Accept值的生成和101状态码。
后面文章进行WebSocket消息的解析。