C#实现WebSocket服务器:(01)握手

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消息的解析。

  • 0
    点赞
  • 8
    收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页
评论

打赏作者

Anlige

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值