由于这是“入门”,我的答案将坚持简单的实现,而不是高度可扩展的实现。在让事情变得更复杂之前,最好先熟悉简单的方法。
1 - 绑定和监听
你的代码对我来说似乎很好,我个人使用:
serverSocket.Bind(new IPEndPoint(IPAddress.Any, 4444));
而不是走 DNS 路线,但我认为这两种方式都不存在真正的问题。
1.5 - 接受客户端连接
只是为了完整性而提及这一点...我假设您正在这样做,否则您将无法执行第 2 步。
2 - 接收数据
我会让缓冲区稍微长于 255 字节,除非您可以预期所有服务器消息最多为 255 字节。我认为您需要一个可能大于 TCP 数据包大小的缓冲区,这样您就可以避免多次读取来接收单个数据块。
我想说选择 1500 字节应该没问题,或者甚至选择 2048 字节就可以了。
或者,也许您可以避免使用byte[]
存储数据片段,并将服务器端客户端套接字包装在NetworkStream
,包裹在BinaryReader
,这样您就可以直接从套接字读取消息的组成部分,而不必担心缓冲区大小。
3 - 发送数据并指定数据长度
您的方法可以很好地工作,但它显然需要在开始发送数据包之前轻松计算数据包的长度。
或者,如果您的消息格式(其组件的顺序)的设计方式使得客户端可以随时确定是否应该有更多数据跟随(例如,代码 0x01 意味着下一个将是一个 int 和一个字符串,代码 0x02 表示接下来是 16 个字节,等等)。结合NetworkStream
对于客户端的方法来说,这可能是一个非常有效的方法。
为了安全起见,您可能需要添加对收到的组件的验证,以确保您只处理合理的值。例如,如果您收到长度为 1TB 的字符串的指示,则可能在某处出现数据包损坏,关闭连接并强制客户端重新连接并“重新开始”可能会更安全。这种方法为您提供了非常好的包罗万象的行为,以防出现意外故障。
4/5 - 关闭客户端和服务器
就我个人而言,我会选择Close
没有进一步的消息;当连接关闭时,您将在连接另一端的任何阻塞读/写上遇到异常,您必须满足这一点。
由于无论如何您都必须考虑“未知的断开连接”以获得可靠的解决方案,因此使断开连接变得更加复杂通常是毫无意义的。
6 - 未知的断开连接
我什至不相信套接字状态......连接可能会在客户端/服务器之间的路径上的某个地方终止,而客户端或服务器都没有注意到。
判断连接意外终止的唯一有保证的方法是当您下次尝试send沿着连接的东西。此时,如果连接出现任何问题,您总是会收到指示失败的异常。
因此,检测所有意外连接的唯一万无一失的方法是实现“ping”机制,理想情况下客户端和服务器将定期向另一端发送一条消息,该消息只会产生一条响应消息,表明该连接收到“ping”。
为了优化不必要的 ping,您可能需要一个“超时”机制,仅当在设定的时间内没有从另一端收到其他流量时才发送 ping(例如,如果来自另一端的最后一条消息)服务器已超过 x 秒,客户端发送 ping 以确保连接没有在没有通知的情况下终止)。
更先进
如果您想要高可扩展性,则必须研究所有套接字操作(接受/发送/接收)的异步方法。这些是“开始/结束”变体,但使用起来要复杂得多。
我建议不要尝试此操作,直到您安装了简单版本并可以使用。
另请注意,如果您不打算扩大规模超过几十个客户端,那么无论如何这实际上都不会成为问题。只有当您打算扩展到数千或数十万个连接的客户端,同时又不想让您的服务器彻底崩溃时,异步技术才是真正必要的。
我可能已经忘记了一大堆其他重要的建议,但这应该足以让你开始一个相当健壮和可靠的实现