【unity】socket聊天室

前言

之前接触过多人联机游戏的开发,但使用的是现成的框架。今天来使用.NET原生的socket来做一个聊天室。

关于Socket

Socket是什么

Socket是为了方便使用TCP/IP协议栈而抽象出来的一组接口。

Socket解决了什么问题

我们在建立通信时,只需理解Socket接口,而复杂的TCP/IP协议族则隐藏在Socket接口后面,我们无需理会。服务端和客户端双方利用一对Socket就可以建立连接通信,这大大方便了网络通信。

连接步骤

使用Socket建立连接步骤如下:

  1. 服务端监听某个端口;

  2. 客户端向服务端地址和端口发起Socket请求;

  3. 服务端接收连接请求后创建Socket连接,并维护这个连接队列;

  4. 客户端和服务端已建立起了双向通信,客户端与服务端就可以彼此发送消息。

烟雨大佬的博客中有他的实现,自己亲手跑一跑就能理解其流程了,指路->
Socket学习笔记-烟雨迷离半世殇

下面是我的结果:

image

image

聊天室

看烟雨的博客了解到了FGUI,遂使用它来完成聊天室界面。

ChatRoom仓库指路->Unity-ChatRoom

广播消息

仅仅是客户端和服务端建立连接还不够,因为每个客户端发的消息都要显示在聊天框中,这意味着服务端收到消息后,还要再做一遍转发,转发给在聊天室内的所有客户端。

在烟雨的实现的基础上,我在服务端中定义了一个List<Socket>
每当有客户端连接成功,就添加进其中;
每当有客户端退出连接,就从中移除;
每当接收到消息,就遍历它逐个做消息转发。

连接关闭

这里我遇到了一些问题。

哪一方主动关闭连接

这个看情况,大部分时候是客户端主动关闭,比如客户端要退出了;但有时候也由服务端主动关闭,比如某个客户端很久都没有动作,俗称挂机,为了避免资源的不必要占用,可以由服务端主动关闭连接。

如何关闭连接

客户端主动发起关闭如下:

clientSocket.Shutdown(SocketShutdown.Both); clientSocket.Close();

对于面向连接的协议,建议在调用 Close() 方法前先调用 Shutdown() 方法。这能够确保在已连接的Socket关闭前,其上的所有数据都发送和接收完成。

Close()会关闭远程主机连接,并释放所有与此Socket相关的托管资源和未托管资源。关闭后,该Socket对象的Connected属性会设置为false

还有两个关闭连接的API,如下:

//关闭Socket连接,并根据提供给方法的参数决定是否允许重用实例。 clientSocket.Disconnect();

//释放当前Socket实例所使用的未托管资源,并且提供可选操作来释放当前Socket实例所使用的托管资源。 clientSocket.Dispose();

常见错误

  1. 无法访问已释放的对象;
    image
    socket.close()后,这个socket又一次被调用时,就会出现该错误。
    建议在关闭连接之后使用Socket对象的Connected属性来做一些判断,跳出原调用路径。

  2. 远程主机强迫关闭了一个现有的连接。
    image
    一般来说,有一方不按规范主动中断连接就会这样。
    有博客说可以通过异常处理的方式,通过异常来提示网络不正常,然后跳出该程序分支。这个方法似乎挺万能的,比方说多人联机网络波动导致退出,应该就可以这样解决。

消息过长

我们设置的消息缓冲区为1024字节,当客户端发送的消息超出缓冲区长度,比如发送1025个字节,如下:

image

根据我们的Message类的约定,每条消息的前4个字节总是记录着这条消息本应该有多长。

public static byte[] GetBytes(string data) { byte[] dataBytes = Encoding.UTF8.GetBytes(data); int dataLength = dataBytes.Length; byte[] lengthBytes = BitConverter.GetBytes(dataLength); byte[] Bytes = lengthBytes.Concat(dataBytes).ToArray(); Console.WriteLine("发出消息字节数 : " + Bytes.Length); return Bytes; }

服务端会比较消息“前4个字节记录的原长度”和“收到的这一条消息的长度”,并根据判断结果执行相应操作。这里是提示消息过长,如下。

//解析数据 public String ReadMessage() { while (true) { if (startIndex <= 4) { return "发生分包"; } //限制单次传输消息在缓冲区长度范围内 int count = BitConverter.ToInt32(data, 0); if ((startIndex - 4) >= count) { string s = Encoding.UTF8.GetString(data, 4, count); Console.WriteLine("[解析得数据:]" + s); Array.Copy(data, count + 4, data, 0, startIndex - 4 - count); startIndex -= (count + 4); return s; } else { break; } } return "超出缓冲区长度"; }

image

服务端向客户端发送同理。

显然,如果要正确传输过长消息,则需要分批发送,使得每一批消息的长度都比接收方的缓冲区长度小。在此之前需要约定好消息传输的协议,比如以何种符号作为包的开头和结尾等等,这也能处理粘包和半包的问题。这是计网的知识了。

参考资料

Socket学习笔记-烟雨迷离半世殇

Socket.EndReceive 方法

C#中Socket的简单使用

C#中Socket关闭 Close、Dispose、Shutdown、Disconnect


__EOF__

本文作者OtusScops
本文链接https://www.cnblogs.com/OtusScops/p/16885905.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   AshScops  阅读(468)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
点击右上角即可分享
微信分享提示