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

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

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

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

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

    /// 
    /// 接收到新的帧,仅展示下
    /// 
    /// 
    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;
    }

    /// 
    /// 收到Text消息时的实现
    /// 里面定义两个特使的消息:close和ping,用来测试服务器主动发送Close和Ping帧。
    /// 
    /// 完整消息
    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}");
    }

    /// 
    /// 接收到二进制消息
    /// 二进制消息是通过流来读取的,不像Text,直接一股脑读取全部消息。
    /// 
    /// 输入流
    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!pingC#实现WebSocket服务器:(03)消息收发的封装_第1张图片

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

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

C#实现WebSocket服务器:(03)消息收发的封装_第2张图片
可以看到,服务器接收到close文本后,向客户端发送了Close帧,浏览器回复给服务器一个Close帧。
这就是Close帧的逻辑:一方发送Close帧后,对方需要回复一个Close帧。
服务器收到回复的Close帧后,就关闭连接了,不会再响应任何数据给客户端。

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

这时候,浏览器主动发送Close帧给服务器。
C#实现WebSocket服务器:(03)消息收发的封装_第3张图片
服务器收到Close帧后,向浏览器回复了一个Close帧,我们在Messager里面对这个帧设置了状态码和原因。
所以浏览器在红色部分显示了我们设置的信息。
C#实现WebSocket服务器:(03)消息收发的封装_第4张图片

总结

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

你可能感兴趣的:(WebSocket,HTTP服务器,c#,websocket,http)