我在这里看到很多关于套接字的资源。我相信他们都没有涵盖我想知道的细节。在我的应用程序中,服务器执行所有处理并向客户端发送定期更新。
这篇文章的目的是涵盖开发套接字应用程序和讨论最佳实践时所需的所有基本思想。以下是几乎所有基于套接字的应用程序都会看到的基本内容。
1 - 在套接字上绑定和收听
我正在使用以下代码。它在我的机器上运行良好。当我在真实服务器上部署它时,是否需要注意其他事项?
IPHostEntry localHost = Dns.GetHostEntry(Dns.GetHostName());
IPEndPoint endPoint = new IPEndPoint(localHost.AddressList[0], 4444);
serverSocket = new Socket(endPoint.AddressFamily, SocketType.Stream,
ProtocolType.Tcp);
serverSocket.Bind(endPoint);
serverSocket.Listen(10);
2 - 接收数据
我使用了255个大小的字节数组。因此,当我收到超过255个字节的数据时,我需要调用receive方法,直到获得完整数据,对吧?获得完整数据后,我需要追加到目前为止收到的所有字节以获取完整的消息。那是对的吗?或者有更好的方法吗?
3 - 发送数据并指定数据长度
由于TCP无法找到要接收的消息长度,因此我计划在消息中添加长度。这将是数据包的第一个字节。因此客户端系统知道有多少数据可供读取。
还有其他更好的方法吗?
4 - 关闭客户
当客户端关闭时,它会向服务器发送一条消息,指示关闭。服务器将从其客户端列表中删除客户端详细信息。以下是客户端用于断开套接字的代码(消息传递部分未显示)。
client.Shutdown(SocketShutdown.Both);
client.Close();
有任何建议或问题吗?
5 - 关闭服务器
服务器向所有客户端发送消息,指示关闭。每个客户端在收到此消息时将断开套接字。客户端将关闭消息发送到服务器并关闭。一旦服务器收到来自所有客户端的关闭消息,它将断开套接字并停止监听。在每个客户端套接字上调用 Dispose 以释放资源。这是正确的方法吗?
6 - 未知客户端断开连接
有时,客户端可能会断开连接而不通知服务器。我的处理方法是:当服务器向所有客户端发送消息时,检查套接字状态。如果未连接,请从客户端列表中删除该客户端并关闭该客户端的套接字。
任何帮助都会很棒!
答案 0 :(得分:26)
由于这是“开始”,我的答案将坚持使用简单的实现,而不是高度可扩展的实现。在使事情变得更复杂之前,最好先采用简单的方法。
1 - 绑定和倾听
您的代码对我来说似乎很好,我个人使用:
serverSocket.Bind(new IPEndPoint(IPAddress.Any, 4444));
而不是走DNS路线,但我不认为这两种方式都存在问题。
1.5 - 接受客户端连接
只是为了完整性而提到这个......我假设你正在这样做,否则你就不会进入第2步。
2 - 接收数据
我会使缓冲区长度超过255个字节,除非您可以预期所有服务器消息最多为255个字节。我想你想要一个可能大于TCP数据包大小的缓冲区,这样你就可以避免多次读取来接收单个数据块。
我会说选择1500字节应该没问题,或者甚至可以2048代表一个不错的整数。
或者,也许您可以避免使用byte[]
来存储数据片段,而是将服务器端客户端套接字包裹在NetworkStream
中,并包含在BinaryReader
中,以便您可以从套接字中读取消息direclty的组件,而不必担心缓冲区大小。
3 - 发送数据和指定数据长度
您的方法可以正常工作,但显然要求在开始发送之前很容易计算数据包的长度。
或者,如果您的消息格式(其组件的顺序)以某种方式设计,那么客户端将能够在任何时间确定是否应该有更多数据(例如,代码0x01表示接下来将是int和一个字符串,代码0x02表示接下来将是16个字节,等等)。结合客户端的NetworkStream
方法,这可能是一种非常有效的方法。
为了安全起见,您可能希望添加正在接收的组件的验证,以确保您只处理合理的值。例如,如果您收到长度为1TB的字符串的指示,则可能在某处损坏了数据包,并且关闭连接并强制客户端重新连接并“重新开始”可能更安全。这种方法可以在出现意外故障时为您提供非常好的全能行为。
4/5 - 关闭客户端和服务器
就个人而言,如果没有进一步的消息,我会选择Close
;当连接关闭时,您将在连接的另一端阻塞读/写时遇到异常,您将不得不满足。
既然你必须为“未知的断开连接”提供一个强大的解决方案,那么断开任何更复杂的连接通常是毫无意义的。
6 - 未知断开连接
我甚至不相信套接字状态......在客户端/服务器之间的路径上,连接可能会死,而客户端或服务器都没有注意到。
告诉连接意外死亡的唯一保证方法是,当您下次尝试发送连接时。此时,如果连接出现任何问题,您将始终收到异常,指示失败。
因此,检测所有意外连接的唯一万无一失的方法是实现'ping'机制,理想情况下,客户端和服务器会定期向另一端发送消息,只会产生响应消息表示已收到“ping”。
为了优化不必要的ping,您可能希望有一个“超时”机制,只有在一段时间内没有从另一端收到其他流量时才发送ping(例如,如果是最后一次)来自服务器的消息超过x秒,客户端发送ping以确保连接没有通知而死亡。
更高级
如果您需要高可伸缩性,则必须查看所有套接字操作的异步方法(接受/发送/接收)。这些是“开始/结束”变体,但它们使用起来要复杂得多。
我建议不要尝试这个,直到你有一个简单的版本并运行。
另请注意,如果您不打算进一步扩展到几十个客户端,那么无论如何这实际上都不会成为问题。只有当你打算扩展成千上万或几十万个连接的客户端而没有让你的服务器彻底死亡时,才真正需要异步技术。
我可能已经忘记了一大堆其他重要的建议,但这应该足以让您在开始时获得相当强大且可靠的实施
答案 1 :(得分:13)
1 - 在套接字上绑定和收听
对我来说很好看。您的代码将仅将套接字绑定到一个IP地址。如果您只想收听任何IP地址/网络接口,请使用IPAddress.Any
:
serverSocket.Bind(new IPEndPoint(IPAddress.Any, 4444));
为了将来证明,您可能希望支持IPv6。要收听任何IPv6地址,请使用IPAddress.IPv6Any
代替IPAddress.Any
。
请注意,除非使用Dual-Stack Socket,否则无法同时侦听任何IPv4和任何IPv6地址。这将要求您取消设置IPV6_V6ONLY
套接字选项:
serverSocket.SetSocketOption(SocketOptionLevel.IPv6, (SocketOptionName)27, 0);
要使用套接字启用Teredo,您需要设置PROTECTION_LEVEL_UNRESTRICTED
套接字选项:
serverSocket.SetSocketOption(SocketOptionLevel.IPv6, (SocketOptionName)23, 10);
2 - 接收数据
我建议使用NetworkStream
将套接字封装在Stream
中,而不是手动读取数据块。
读取固定数量的字节有点尴尬:
using (var stream = new NetworkStream(serverSocket)) {
var buffer = new byte[MaxMessageLength];
while (true) {
int type = stream.ReadByte();
if (type == BYE) break;
int length = stream.ReadByte();
int offset = 0;
do
offset += stream.Read(buffer, offset, length - offset);
while (offset < length);
ProcessMessage(type, buffer, 0, length);
}
}
NetworkStream
真正闪耀的地方就是你可以像任何其他Stream
一样使用它。如果安全性很重要,只需将NetworkStream
包装在SslStream
中以验证服务器和(可选)具有X.509证书的客户端。压缩的工作方式相同。
var sslStream = new SslStream(stream, false);
sslStream.AuthenticateAsServer(serverCertificate, false, SslProtocols.Tls, true);
// receive/send data SSL secured
3 - 发送数据并指定数据长度
你的方法应该有效,尽管你可能不想继续重新发明轮子并为此设计新的协议。看一下BEEP,或者甚至是protobuf之类的简单内容。
根据您的目标,可能值得考虑在WCF或其他RPC机制之类的套接字上选择抽象。
4/5/6 - 结束&amp;未知断开连接
jerryjvl说什么:-)唯一可靠的检测机制是ping或在连接空闲时发送保持活动。
虽然在任何情况下你都必须处理未知的断开连接,但我个人会保留一些协议元素以在相互协议中关闭连接,而不是在没有警告的情况下关闭它。
答案 2 :(得分:2)
考虑使用异步套接字。您可以在
中找到有关该主题的更多信息