C#实现WebSocket服务器:(03)消息收发的封装

前文我们实现了WebSocket消息的读取和发送:C#实现WebSocket服务器:(02)消息帧分析和代码实现
但是我们所有的逻辑都是写在OnWebSocket方法的,并不方便扩展,现在我们封装一个Messager抽象类,封装消息的读取和发送。

Messager类封装

我们将在类中暴露一些必要的方法。
下面我们用消息这个词,而不是,因为一条消息可能是由多个帧(例如Text帧+N个Continuation帧)组成的。

方法说明
Accept在OnWebSocket方法中,调用具体Messager的Accept方法,开始接收客户端的消息
OnConnected客户端新连接
OnDisconnected客户端断开连接
OnNewFrame服务器收到新帧,主要用于调试了展示用,尽量不要在这里处理业务逻辑
OnText服务器收到文本消息
OnBinary服务器收到二进制消息
Close关闭WebSocket,服务端会向客户端发送Close帧,服务端收到客户端的Close响应后,真正关闭连接
Send发送消息给客户端,实现了多个重载,用来发送不同类型的消息
Ping发送Ping帧给客户端,客户端会响应一个Pong帧给服务器

其他方法和帧处理我们都不暴露出来了,程序内部会自动处理。
例如:服务器收到Ping帧会自动响应Pong帧给客户端、Close帧也会自动处理。
Github上有具体的代码实现:https://github.com/hooow-does-it-work/http/blob/main/src/WebSocket/Messager.cs
我们就不拷贝过来了,占篇幅。

Messager类的使用

Messager类为抽象类,不能直接实例化,需要我们实现自己的的Messager类,继承Messager抽象类即可。
下面实现了一个Messager用来作测试用。
这里也可以找到源码:https://github.com/hooow-does-it-work/http/blob/main/demo/WebSocketMessager.cs

/// <summary>
/// 实现一个Messager类
/// </summary>
public class MyMessager : Messager
{
    private EndPoint _remoteEndPoint = null;
    public MyMessager(Stream stream) : base(stream) {

        //获取客户端的连接信息
        if(stream is BufferedNetworkStream networkStream)
        {
            _remoteEndPoint = networkStream.BaseSocket.RemoteEndPoint ;
        }
    }

    /// <summary>
    /// 客户端新连接
    /// </summary>
    protected override void OnConnected()
    {
        Console.WriteLine($"{DateTime.Now:HH:mm:ss} > 新客户端连接:{_remoteEndPoint}");
    }

    /// <summary>
    /// 客户端断开连接
    /// </summary>
    protected override void OnDisconnected()
    {
        Console.WriteLine($"{DateTime.Now:HH:mm:ss} > 连接断开:{_remoteEndPoint}");
    }

    /// <summary>
    /// 接收到新的帧,仅展示下
    /// </summary>
    /// <param name="frame"></param>
    protected override void OnNewFrame(Frame frame)
    {
        var color = Console.ForegroundColor;
        Console.ForegroundColor = ConsoleColor.DarkYellow;
        Console.WriteLine($"{DateTime.Now:HH:mm:ss} > 接收到新帧;帧类型:{frame.OpCode},结束帧:{frame.Fin},携带掩码:{frame.Mask},长度:{frame.PayloadLength}");
        Console.ForegroundColor = color;
    }

    /// <summary>
    /// 收到Text消息时的实现
    /// 里面定义两个特使的消息:close和ping,用来测试服务器主动发送Close和Ping帧。
    /// </summary>
    /// <param name="payload">完整消息</param>
    protected override void OnText(string payload)
    {
        Console.WriteLine($"{DateTime.Now:HH:mm:ss} > 文本数据:{payload}");
        if(payload == "close")
        {
            Send($"服务器接收到close指令,关闭连接。");
            Close();
            return;
        }
        if (payload == "ping")
        {
            Send($"服务器接收到ping指令,发送ping。");
            Ping();
            return;
        }
        Send($"服务器接收到文本数据:{payload}");
    }

    /// <summary>
    /// 接收到二进制消息
    /// 二进制消息是通过流来读取的,不像Text,直接一股脑读取全部消息。
    /// </summary>
    /// <param name="inputStream">输入流</param>
    protected override void OnBinary(Stream inputStream)
    {
        //为了测试,我们把二进制消息读取到字节数组。
        byte[] payload = StreamUtils.ReadAllBytes(inputStream);
        Console.WriteLine($"{DateTime.Now:HH:mm:ss} > 二进制数据,长度:{payload.Length}");
        Send($"服务器接收到二进制数据,长度:{payload.Length}");
    }
}

测试

我们使用上面实现的MyMessager进行测试。
实例化一个服务器,使用MyMessager来接收和发送消息。

public class HttpServer : HttpServerBase
{
    public HttpServer() : base()
    {
        //设置根目录
        WebRoot = Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "web"));
    }
    protected override void OnWebSocket(HttpRequest request, Stream stream)
    {
        //使用自己实现的Messager类处理业务。
        //可以根据请求的Url不同去实例化不同的Messager,处理不同的业务逻辑。
        new MyMessager(stream).Accept();
    }
}

启动服务器,浏览器访问:http://127.0.0.1:4189/websocket.html

1、连接服务器后,分别发送:hello world!ping在这里插入图片描述

控制台输出的黄色部分,可以看到浏览器发送给服务器的各种帧。
我们在测试代码里面对内容为ping的文本消息进行了特殊处理,服务器收到这个消息后会主动给客户端发送Ping帧,然后客户端回复一个Pong帧。

2、我们再向服务器发送close文本。

在这里插入图片描述
可以看到,服务器接收到close文本后,向客户端发送了Close帧,浏览器回复给服务器一个Close帧。
这就是Close帧的逻辑:一方发送Close帧后,对方需要回复一个Close帧。
服务器收到回复的Close帧后,就关闭连接了,不会再响应任何数据给客户端。

3、连接服务器后,直接点击关闭按钮。

这时候,浏览器主动发送Close帧给服务器。
在这里插入图片描述
服务器收到Close帧后,向浏览器回复了一个Close帧,我们在Messager里面对这个帧设置了状态码和原因。
所以浏览器在红色部分显示了我们设置的信息。
在这里插入图片描述

总结

目前为止,从握手,到帧分析,到逻辑封装都已经完成。
握手和帧的分析和读取是关键。
消息处理的封装只是为了方便业务逻辑的实现,业务层只要关心必要的接口即可,必要但不是必须的。
是不是可以着手实现一个聊天室了?

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Anlige

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

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

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

打赏作者

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

抵扣说明:

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

余额充值