C# 套接字编程入门 - 最佳实践
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1162950/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Getting started with socket programming in C# - Best practices
提问by Navaneeth K N
I have seen many resources here on SO about Sockets. I believe none of them covered the details which I wanted to know. In my application, server does all the processing and send periodic updates to the clients.
我在这里看到了很多关于套接字的资源。我相信他们都没有涵盖我想知道的细节。在我的应用程序中,服务器完成所有处理并向客户端发送定期更新。
Intention of this post is to cover all the basic ideas required when developing a socket application and discuss the best practices. Here are the basic things that you will see with almost all socket based applications.
这篇文章的目的是涵盖开发套接字应用程序时所需的所有基本思想并讨论最佳实践。以下是您将在几乎所有基于套接字的应用程序中看到的基本内容。
1 - Binding and listening on a socket
1 -绑定和监听套接字
I am using the following code. It works well on my machine. Do I need to take care about something else when I deploy this on a real server?
我正在使用以下代码。它在我的机器上运行良好。当我在真实服务器上部署它时,我需要注意其他事情吗?
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 - Receiving data
2 -接收数据
I have used a 255 sized byte array. So when I am receiving data which is more than 255 bytes, I need to call the receive method until I get the full data, right? Once I got the full data, I need to append all the bytes received so far to get the full message. Is that correct? Or is there a better approach?
我使用了一个 255 大小的字节数组。所以当我接收超过 255 字节的数据时,我需要调用接收方法直到我得到完整的数据,对吗?获得完整数据后,我需要附加到目前为止收到的所有字节以获取完整消息。那是对的吗?或者有更好的方法吗?
3 - Sending data and specifying the data length
3 -发送数据并指定数据长度
Since there is no way in TCP to find the length of the message to receive, I am planning to add the length to the message. This will be the first byte of the packet. So client systems knows how much data is available to read.
由于在 TCP 中无法找到要接收的消息的长度,因此我打算将长度添加到消息中。这将是数据包的第一个字节。所以客户端系统知道有多少数据可以读取。
Any other better approach?
还有其他更好的方法吗?
4 - Closing the client
4 -关闭客户端
When client is closed, it will send a message to server indicating the close. Server will remove the client details from it's client list. Following is the code used at client side to disconnect the socket (messaging part not shown).
当客户端关闭时,它会向服务器发送一条消息,指示关闭。服务器将从其客户端列表中删除客户端详细信息。以下是客户端用于断开套接字的代码(消息部分未显示)。
client.Shutdown(SocketShutdown.Both);
client.Close();
Any suggestions or problems?
有什么建议或问题吗?
5 - Closing the server
5 -关闭服务器
Server sends message to all clients indicating the shutdown. Each client will disconnect the socket when it receives this message. Clients will send the close message to server and close. Once server receives close message from all the clients, it disconnects the socket and stop listening. Call Disposeon each client sockets to release the resources. Is that the correct approach?
服务器向所有客户端发送消息,指示关闭。每个客户端在收到此消息时都会断开套接字。客户端将关闭消息发送到服务器并关闭。一旦服务器收到来自所有客户端的关闭消息,它就会断开套接字并停止侦听。在每个客户端套接字上调用Dispose以释放资源。这是正确的方法吗?
6 - Unknown client disconnections
6 -未知客户端断开连接
Sometimes, a client may disconnect without informing the server. My plan to handle this is: When server sends messages to all clients, check the socket status. If it is not connected, remove that client from the client list and close the socket for that client.
有时,客户端可能会在不通知服务器的情况下断开连接。我的处理计划是:当服务器向所有客户端发送消息时,检查套接字状态。如果未连接,则从客户端列表中删除该客户端并关闭该客户端的套接字。
Any help would be great!
任何帮助都会很棒!
回答by jerryjvl
Since this is 'getting started' my answer will stick with a simple implementation rather than a highly scalable one. It's best to first feel comfortable with the simple approach before making things more complicated.
由于这是“入门”,我的答案将坚持使用简单的实现而不是高度可扩展的实现。在使事情变得更复杂之前,最好先对简单的方法感到满意。
1 - Binding and listening
Your code seems fine to me, personally I use:
1 - 绑定和聆听
您的代码对我来说似乎很好,我个人使用:
serverSocket.Bind(new IPEndPoint(IPAddress.Any, 4444));
Rather than going the DNS route, but I don't think there is a real problem either way.
而不是走 DNS 路线,但我认为无论哪种方式都没有真正的问题。
1.5 - Accepting client connections
Just mentioning this for completeness' sake... I am assuming you are doing this otherwise you wouldn't get to step 2.
1.5 - 接受客户端连接
只是为了完整性才提到这一点......我假设您正在这样做,否则您将无法进入第 2 步。
2 - Receiving data
I would make the buffer a little longer than 255 bytes, unless you can expect all your server messages to be at most 255 bytes. I think you'd want a buffer that is likely to be larger than the TCP packet size so you can avoid doing multiple reads to receive a single block of data.
2 - 接收数据
我会让缓冲区比 255 字节长一点,除非您可以期望所有服务器消息最多为 255 字节。我认为您需要一个可能大于 TCP 数据包大小的缓冲区,这样您就可以避免多次读取以接收单个数据块。
I'd say picking 1500 bytes should be fine, or maybe even 2048 for a nice round number.
我会说选择 1500 字节应该没问题,或者甚至 2048 是一个不错的整数。
Alternately, maybe you can avoid using a byte[]
to store data fragments, and instead wrap your server-side client socket in a NetworkStream
, wrapped in a BinaryReader
, so that you can read the components of your message direclty from the socket without worrying about buffer sizes.
或者,也许您可以避免使用 abyte[]
来存储数据片段,而是将服务器端客户端套接字NetworkStream
包装在 a 中BinaryReader
,包装在 a 中,以便您可以从套接字直接读取消息的组件,而无需担心缓冲区大小。
3 - Sending data and specifying data length
Your approach will work just fine, but it does obviously require that it is easy to calculate the length of the packet before you start sending it.
3 - 发送数据并指定数据长度
您的方法将工作得很好,但它显然需要在开始发送之前轻松计算数据包的长度。
Alternately, if your message format (order of its components) is designed in a fashion so that at any time the client will be able to determine if there should be more data following (for example, code 0x01 means next will be an int and a string, code 0x02 means next will be 16 bytes, etc, etc). Combined with the NetworkStream
approach on the client side, this may be a very effective approach.
或者,如果您的消息格式(其组件的顺序)以某种方式设计,以便客户端可以随时确定是否应该有更多数据跟随(例如,代码 0x01 表示接下来将是一个 int 和一个字符串,代码 0x02 表示接下来将是 16 个字节,等等)。结合NetworkStream
客户端的方法,这可能是一个非常有效的方法。
To be on the safe side you may want to add validation of the components being received to make sure you only process sane values. For example, if you receive an indication for a string of length 1TB you may have had a packet corruption somewhere, and it may be safer to close the connection and force the client to re-connect and 'start over'. This approach gives you a very good catch-all behaviour in case of unexpected failures.
为了安全起见,您可能需要添加对接收到的组件的验证,以确保您只处理合理的值。例如,如果您收到长度为 1TB 的字符串的指示,则您可能在某处发生了数据包损坏,关闭连接并强制客户端重新连接并“重新开始”可能会更安全。这种方法在发生意外故障时为您提供了非常好的包罗万象的行为。
4/5 - Closing the client and the server
Personally I would opt for just Close
without further messages; when a connection is closed you will get an exception on any blocking read/write at the other end of the connection which you will have to cater for.
4/5 - 关闭客户端和服务器 就
我个人而言,我会选择Close
没有进一步的消息;当连接关闭时,您将在连接的另一端遇到任何阻塞读/写的异常,您必须满足该异常。
Since you have to cater for 'unknown disconnections' anyway to get a robust solution, making disconnecting any more complicated is generally pointless.
由于无论如何您都必须满足“未知断开连接”才能获得可靠的解决方案,因此使断开连接变得更加复杂通常毫无意义。
6 - Unknown disconnections
I would not trust even the socket status... it is possible for a connection to die somewhere along the path between client / server without either the client or the server noticing.
6 - 未知的断开连接
我什至不相信套接字状态......连接可能会在客户端/服务器之间的路径上的某处死亡,而客户端或服务器都没有注意到。
The only guaranteed way to tell a connection that has died unexpectedly is when you next try to sendsomething along the connection. At that point you will always get an exception indicating failure if anything has gone wrong with the connection.
告诉意外终止的连接的唯一保证方法是下次尝试沿连接发送某些内容时。此时,如果连接出现任何问题,您将始终收到指示失败的异常。
As a result, the only fool-proof way to detect all unexpected connections is to implement a 'ping' mechanism, where ideally the client and the server will periodically send a message to the other end that only results in a response message indicating that the 'ping' was received.
因此,检测所有意外连接的唯一万无一失的方法是实现“ping”机制,理想情况下,客户端和服务器将定期向另一端发送一条消息,该消息只会导致响应消息表明收到'ping'。
To optimise out needless pings, you may want to have a 'time-out' mechanism that only sends a ping when no other traffic has been received from the other end for a set amount of time (for example, if the last message from the server is more than x seconds old, the client sends a ping to make sure the connection has not died without notification).
为了优化不必要的 ping,您可能需要一种“超时”机制,该机制仅在一段时间内没有从另一端接收到其他流量时才发送 ping(例如,如果来自终端的最后一条消息)服务器超过 x 秒,客户端发送 ping 以确保连接没有在没有通知的情况下终止)。
More advanced
If you want high scalability you will have to look into asynchronous methods for all the socket operations (Accept / Send / Receive). These are the 'Begin/End' variants, but they are a lot more complicated to use.
更高级
如果您想要高可扩展性,您将不得不研究所有套接字操作(接受/发送/接收)的异步方法。这些是“开始/结束”变体,但使用起来要复杂得多。
I recommend against trying this until you have the simple version up and working.
我建议不要尝试这个,直到你有简单的版本并且可以工作。
Also note that if you are not planning to scale further than a few dozen clients this is not actually going to be a problem regardless. Async techniques are really only necessary if you intend to scale into the thousands or hundreds of thousands of connected clients while not having your server die outright.
另请注意,如果您不打算扩展超过几十个客户端,无论如何这实际上都不是问题。仅当您打算扩展到数千或数十万个连接的客户端而不让您的服务器彻底死机时,才真正需要异步技术。
I probably have forgotten a whole bunch of other important suggestions, but this should be enough to get you a fairly robust and reliable implementation to start with
我可能已经忘记了一大堆其他重要的建议,但这应该足以让你开始一个相当健壮和可靠的实现
回答by dtb
1 - Binding and listening on a socket
1 -绑定和监听套接字
Looks fine to me. Your code will bind the socket only to one IP address though. If you simply want to listen on any IP address/network interface, use IPAddress.Any
:
对我来说看起来不错。不过,您的代码只会将套接字绑定到一个 IP 地址。如果您只想侦听任何 IP 地址/网络接口,请使用IPAddress.Any
:
serverSocket.Bind(new IPEndPoint(IPAddress.Any, 4444));
To be future proof, you may want to support IPv6. To listen on any IPv6 address, use IPAddress.IPv6Any
in place of IPAddress.Any
.
为了面向未来,您可能希望支持 IPv6。要侦听任何 IPv6 地址,请使用IPAddress.IPv6Any
代替IPAddress.Any
。
Note that you cannot listen on any IPv4 and any IPv6 address at the same time, except if you use a Dual-Stack Socket. This will require you to unset the IPV6_V6ONLY
socket option:
请注意,您不能同时侦听任何 IPv4 和任何 IPv6 地址,除非您使用Dual-Stack Socket。这将要求您取消设置IPV6_V6ONLY
套接字选项:
serverSocket.SetSocketOption(SocketOptionLevel.IPv6, (SocketOptionName)27, 0);
To enable Teredowith your socket, you need to set the PROTECTION_LEVEL_UNRESTRICTED
socket option:
要使用您的套接字启用Teredo,您需要设置PROTECTION_LEVEL_UNRESTRICTED
套接字选项:
serverSocket.SetSocketOption(SocketOptionLevel.IPv6, (SocketOptionName)23, 10);
2 - Receiving data
2 -接收数据
I'd recommend using a NetworkStream
which wraps the socket in a Stream
instead of reading the chunks manually.
我建议使用 aNetworkStream
将套接字包装在 a 中,Stream
而不是手动读取块。
Reading a fixed number of bytes is a bit awkward though:
不过,读取固定数量的字节有点尴尬:
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);
}
}
Where NetworkStream
really shines is that you can use it like any other Stream
. If security is important, simply wrap the NetworkStream
in a SslStream
to authenticate the server and (optionally) the clients with X.509 certificates. Compression works the same way.
其中NetworkStream
值得称道的是,你可以使用它像任何其他Stream
。如果安全性很重要,只需将 包装NetworkStream
在 a 中SslStream
以使用 X.509 证书对服务器和(可选)客户端进行身份验证。压缩的工作方式相同。
var sslStream = new SslStream(stream, false);
sslStream.AuthenticateAsServer(serverCertificate, false, SslProtocols.Tls, true);
// receive/send data SSL secured
3 - Sending data and specifying the data length
3 -发送数据并指定数据长度
Your approach should work, although you probably may not want to go down the road to reinventing the wheel and design a new protocol for this. Have a look at BEEPor maybe even something simple like protobuf.
您的方法应该有效,尽管您可能不想重新发明轮子并为此设计新协议。看看BEEP或者甚至是像protobuf这样简单的东西。
Depending on your goals, it might be worth thinking about choosing an abstraction above sockets like WCF or some other RPC mechanism.
根据您的目标,可能值得考虑在套接字之上选择抽象,如 WCF 或其他一些 RPC 机制。
4/5/6 - Closing & Unknown disconnections
4/5/6 -关闭和未知的断开连接
What jerryjvlsaid :-) The only reliable detection mechanism are pings or sending keep-alives when the connection is idle.
什么jerryjvl说:-)唯一可靠的检测机构是ping或发送保持有效指示当连接处于空闲状态。
While you have to deal with unknown disconnections in any case, I'd personally keep some protocol element in to close a connection in mutual agreement instead of just closing it without warning.
虽然在任何情况下您都必须处理未知的断开连接,但我个人会保留一些协议元素以在相互同意的情况下关闭连接,而不是在没有警告的情况下关闭它。
回答by Dzmitry Huba
Consider using asynchronous sockets. You can find more information on the subject in
考虑使用异步套接字。您可以在以下位置找到有关该主题的更多信息