WebSocket的握手是基于HTTP的,所以我们所有的后续文章,还是基于我们前面的HTTP服务器。 项目托管地址:https://github.com/hooow-does-it-work/http
0、判断一个HTTP请求为WebSocket握手请求
HTTP请求的Connection标头值为Upgrade,Upgrade标头的值为websocket,即表示当前请求为WebSocket握手请求。 我们在HttpRequest 类中增加如下属性来判断。
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握手请求的判断。
request = HttpRequest.Capture(stream);
if (request.IsWebSocket)
{
if(!OnWebSocketInternal(request, stream))
{
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;
}
byte[] keyBytes = Encoding.ASCII.GetBytes(webSocketKey);
keyBytes = keyBytes.Concat(ProtocolUtils.Salt).ToArray();
string secWebSocketAcceptKey = ProtocolUtils.SHA1(keyBytes);
HttpResponser responser = new HttpResponser(101);
responser["Upgrade"] = "websocket";
responser["Connection"] = "Upgrade";
responser["Sec-WebSocket-Accept"] = secWebSocketAcceptKey;
responser.WriteHeader(stream);
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消息的解析。
|