上一篇我已经实现了聊天室,并且在协议中实现了4种类型的消息传输。其实还可以添加video,audio,live等等类型。
不过假如把目前的协议看作RCP1.0版的话,这个版本就只支持有限的4种消息。精力有限啊。也许RCP2.0就可以把video,audio类型加进去?
这不是这篇番外考虑的。而是我在定义和实现协议的过程中注意到了一些问题。
系统的网络缓冲区是怎么回事?
因为我自己定义了一个400字节的buffer用来接收消息。如果接收到的消息超出了400字节,WebSocket会给出提示,将EndOfMessage字段设置为false。
这到底是
- 客户端暂停了本次发送,等到服务器再一次执行
ReceiveAsync方法时才继续发送? - 还是
WebSocket对象本身内置了缓冲区?消息全部都暂存在缓冲区?
我使用浏览器开发者工具监视了ws消息发送过程,结合后台断点调试,发现WebSocket采用了第二种方案。把消息全部存在缓冲区,我用buffer读一次,就取出来一点。
既然是缓冲区,那么一个WebSocket对象的内置缓冲区有多大?客户端发送的文件长度超过了WebSocket对象的内置缓冲区的大小时怎么办?
看看websocket协议怎么说

看起来websocket 协议中数据长度上限为 2^127,可以认为没有限制。因而在实际使用中 websocket 消息长度限制只取决于服务器实现。
System.Net.WebSockets.WebSocket对象内部一定使用了一个MemoryStream之类的东西来暂存数据。
问题是那为什么他不直接把那个缓冲区给我?我还要自己再去创建一个缓冲区,从他的缓冲区读数据?我不太清楚。
多个WebSocket与http服务器怎么共用一个端口?
典型的socket监听{IP:Port}
但是我在websocket中没有看到这些信息。
如果你的 Web API 服务器在端口 80 上运行,那么 WebSocket 连接也会使用端口 80 来传输数据
比较疑惑的是,操作系统接收到一个TCP数据包,怎么知道交给http服务器,还是哪个websocket连接?
原来一个TCP端口可以建立多个TCP连接,只要(服务器IP:服务器Port:客户端IP:客户端Port)唯一就行。
看看调试
| TCP连接 | 服务器IP | 服务器Port | 客户端IP | 客户端Port | 连接Id |
|---|---|---|---|---|---|
| 游客_1 | ::1 | 5234 | ::1 | 54008 | 0HN3PID8UHKHN |
| 游客_2 | ::1 | 5234 | ::1 | 54481 | 0HN3PID8UHKHO |
| 游客_3 | ::1 | 5234 | ::1 | 54556 | 0HN3PID8UHKHP |
操作系统就是根据这个表决定把从端口接收的数据发往哪个游客线程。
看看实际连接是怎么样的?

手动管理连接看看?
由于websockt我们看不到客户端发起连接,服务端接收连接的过程,我自己用socket测试一下。
代码非常简单
- 服务器监听端口
- 等待客户端连接,然后维护到一个集合中
- 每接到连接,就开启一个聊天线程
static void Main(string[] args) { //服务器 if (args.Length == 1) { int serverPort = Convert.ToInt32(args[0]); var server = new TcpListener(IPAddress.Parse("127.0.0.1"),serverPort); Console.WriteLine($"TCP服务器 127.0.0.1:{serverPort}"); server.Start(); int cnt = 0; Task.Run(async() => { List<TcpClient> clients= new List<TcpClient>(); while (true) { TcpClient client = await server.AcceptTcpClientAsync(); clients.Add(client); cnt++; var ep = client.Client.RemoteEndPoint as IPEndPoint; Console.WriteLine($"TCP客户端_{cnt} {ep.Address}:{ep.Port}"); //给这个客户端开一个聊天线程 //操作系统将会根据游客端口对应表将控制权交给对应游客线程 StartChat(client); } }).Wait(); } //客户端 else if (args.Length == 3) { int clientPort = Convert.ToInt32(args[0]); int serverPort = Convert.ToInt32(args[1]); string msg = args[2]; var client=new TcpClient(new IPEndPoint(IPAddress.Parse("127.0.0.1"), clientPort)); Console.WriteLine($"TCP客户端 127.0.0.1:{clientPort}"); Task.Run(async () => { await client.ConnectAsync(new IPEndPoint(IPAddress.Parse("127.0.0.1"), serverPort)); Console.WriteLine($"连接到 127.0.0.1:{serverPort}"); //打招呼 var msgBytes=UTF8Encoding.UTF8.GetBytes(msg); await client.Client.SendAsync(msgBytes); //等待数据,阻塞在这里,保持连接 await client.Client.ReceiveAsync(new ArraySegment<byte>(new byte[100])); }).Wait(); } } public static async Task StartChat(TcpClient client) { var buffer = new byte[100]; while (true) { //阻塞接收消息 int msgLength = await client.Client.ReceiveAsync(new ArraySegment<byte>(buffer)); string str=UTF8Encoding.UTF8.GetString(buffer,0,msgLength); Console.WriteLine(str); } } }
我们测试建立一个服务端,和三个连接到这个服务端的客户端
虽然三个游客都向一个服务器端口5234发送消息,但操作系统根据端口连接对应表知道将消息发送给哪个线程。就好像在一个端口划分出来了3个信道。
- 每个信道可以使用具体的http协议或我们写的RCP协议来序列化和反序列化
- 要注意的是,这些不同的连接(TcpClient)是同一个TCPLisener对象到的。这就是在一个程序中使用多种通信协议。
