简体   繁体   English

阻止TCP套接字写然后立即关闭 - 这是一个问题吗?

[英]Blocking TCP socket write then immediatelly close - is it a problem?

I have a sender that TCP-connects, sends block of data, and closes the socket. 我有一个发送器,TCP连接,发送数据块,并关闭套接字。 I'd like to write the simplest, but reliable program that does the above. 我想写一个最简单但可靠的程序来完成上述工作。 The first thing comes into mind (eg in .NET, although the question relevant to sockets in general): 首先想到的是(例如在.NET中,尽管通常与套接字相关的问题):

// assuming LingerOption(false), NoDelay set to whatever
var client = new TcpClient().Connect(server, port);
var stream = client.GetStream();
stream.Write(data, 0, data.Length);
stream.Close();
client.Close();

Now to some questions basing on reading of various MSDN and other materials: 现在来看一些基于阅读各种MSDN和其他材料的问题:

  1. stream.Close() calls Socket.Close(). stream.Close()调用Socket.Close()。 Socket.Close() is said to close immediatelly, discarding network buffer data which was not send. 据说Socket.Close()立即关闭,丢弃未发送的网络缓冲区数据。 This is bad. 这是不好的。 But, Socket.Write documentation says that if the socket is blocking (it is, by default), Socket.Write will block until all the data is sent. 但是,Socket.Write文档说如果套接字阻塞(默认情况下),Socket.Write将阻塞,直到所有数据都被发送。 So there's no problem, right? 所以没有问题,对吧?
  2. In general, can there be a situation, in which the code above will result in receiver not receiving everything what was sent in Write? 一般情况下,是否存在上述代码会导致接收方无法接收Write中发送的所有内容的情况? (assuming 100% reliable network) (假设网络100%可靠)

It's is absolutelly OK to call Close just after Socket.Write. 在Socket.Write之后调用Close是绝对可以的。 Here's what TCP RFC 793 says : 这是TCP RFC 793所说的

"Closing connections is intended to be a graceful operation in the sense that outstanding SENDs will be transmitted (and retransmitted), as flow control permits, until all have been serviced. Thus, it should be acceptable to make several SEND calls, followed by a CLOSE, and expect all the data to be sent to the destination. " “关闭连接是一个优雅的操作,因为流量控制允许传输(和重新传输)未完成的SEND,直到所有服务都被服务。 因此,可以接受多个SEND呼叫,然后是关闭,并期望将所有数据发送到目的地。

The confusion, in part, comes from MSDN documentation : 混淆部分来自MSDN文档

"If you are using a connection-oriented protocol, Send will block until all of the bytes in the buffer are sent " “如果使用面向连接的协议,发送将阻塞,直到所有在缓冲器中的字节被发送

In reality, blocking Write only copies data to outgoing buffer and returns. 实际上,阻止Write只将数据复制到传出缓冲区并返回。 It's the network provider responsibility, under RFC 793, to complete the delivery of the data after Socket.Close is called (as long as the connection is not dead, of course). 根据RFC 793,网络提供商有责任在调用Socket.Close之后完成数据的传递(当然,只要连接没有死亡)。

if the receiver is not reached to Read, you may lose some part of data and this depends on size of buffers used in lower layers. 如果接收器未达到Read,则可能会丢失部分数据,这取决于较低层中使用的缓冲区的大小。

But If the receiver is blocked on Read, so there is no problem while you provide enough buffer when Reading. 但是如果读取器上的接收器被阻塞,那么在读取时提供足够的缓冲区时没有问题。

Assuming a reliable network and executing "Read" before "Write" and enough buffer at receiver side, this code is absolutely reliable! 假设一个可靠的网络并在“写入”之前执行“读取”并在接收器侧执行足够的缓冲区,则此代码绝对可靠!

But the solution!: 但解决方案!:

As i said size of the buffer in receiver side really matters. 正如我所说的接收器端缓冲区的大小真的很重要。 if the buffer doesn't have enough space, you will lose by this code! 如果缓冲区没有足够的空间,你将失去这个代码!

So you have three choices: 所以你有三个选择:

  1. Simply allocate a buffer with a enough size. 只需分配足够大小的缓冲区即可。
  2. Use a library or higher protocols to transfer data (for example we have TransmitFile function in Win32 API for transmitting files) 使用库或更高协议传输数据(例如,我们在Win32 API中具有用于传输文件的TransmitFile函数)
  3. Implement your protocol that before sending data, sends the size of required buffer and then sends actual data. 在发送数据之前实现您的协议,发送所需缓冲区的大小,然后发送实际数据。

If you know maximum size of data, then i offer you 1 Else If you don't have enough time and not interested in protocols , then i offer you 2 Else i offer you 3 如果您知道最大数据量,那么我会为您提供1 Else如果您没有足够的时间而且对协议不感兴趣,那么我会为您提供2 Else我为您提供3

Good luck ;) 祝好运 ;)

The receiver could send wait or retransmit messages to the sender. 接收方可以向发送方发送等待或重发消息。 This could happen if the receiver's recv buffers are full before the entire block of data is received, or if there are lost packets on the network. 如果在接收到整个数据块之前接收器的recv缓冲区已满,或者网络上丢失了数据包,则可能发生这种情况。 So if you quit the client before all the data is exchanged, you lose data. 因此,如果在交换所有数据之前退出客户端,则会丢失数据。

Take a look at this book for your general solution: UNIX Network Programming Volume One by W. Richard Stevens. 看看本书的一般解决方案:W。Richard Stevens的UNIX网络编程第一卷。

Yes it is UNIX, but it is ultimately dealing with TCP/IP comms and that is at the root of the above. 是的,它是UNIX,但它最终处理TCP / IP通信,这是上述的根源。 And sorry, it is not really simple, but it's also not really hard either. 对不起,这不是很简单,但也不是很难。 It's a state machine with more states than your proposal contains. 这是一个状态机,其状态多于您的提案所包含的状态。

TcpClient.Close says "The Close method marks the instance as disposed and requests that the associated Socket close the TCP connection. Based on the LingerState property, the TCP connection may stay open for some time after the Close method is called when data remains to be sent. There is no notification provided when the underlying connection has completed closing." TcpClient.Close说“Close方法将实例标记为已处理,并请求关联的Socket关闭TCP连接。基于LingerState属性,在调用Close方法后,当数据仍然存在时,TCP连接可能会保持打开一段时间当底层连接完成关闭时,没有提供任何通知。“

In short, unless you've fiddled with the LingerState, calling Close will not discard the OS buffer of the data - the TCP stack will try its best to deliver the data you've Written. 简而言之,除非您使用LingerState,否则调用Close将不会丢弃数据的OS缓冲区 - TCP堆栈将尽力传递您编写的数据。

Be sure to catch exceptions though, any of your Write or the 2 Close calls might throw an exception if things went bad - and make sure you dispose the socket and stream in those cases. 确保捕获异常但是,如果事情变坏,任何Write或2 Close调用都可能抛出异常 - 并确保在这些情况下处置套接字和流。

Note also that the Write() call might not block until the data is sent, it'll only block until the data is copied to the TCP stack (and depeding on too many things, TCP might partially send some of that data out on the network before Write returns) 另请注意, Write()调用可能在数据发送之前不会阻塞,它只会阻塞,直到数据被复制到TCP堆栈(并且由于太多东西,TCP可能部分地将一些数据发送到Write回之前的网络)

This is sidestepping your original question, but you didn't say what kind of data you were sending. 这是回避您原来的问题,但您没有说明您发送的是哪种数据。 Why did you decide to use a TCP connection in the first place? 你为什么决定首先使用TCP连接? How often are you writing these blocks of data? 你多久写一次这些数据块? If it's very often, I would recommend one of two things: 如果经常这样,我会推荐两件事之一:

1) Keep the TCP.Stream open. 1)保持TCP.Stream打开。 Otherwise, you could exhaust the TCP stack of available source ports (1025-65535) if you send more than ~64000 blocks in less than 4 minutes. 否则,如果在不到4分钟内发送超过64000个块,则可能耗尽可用源端口的TCP堆栈(1025-65535)。 This would get worse if there are other applications open on the source machine. 如果在源计算机上打开了其他应用程序,情况会变得更糟。 Once this happens your application will hang or error until the first port older than 4 minutes becomes available. 一旦发生这种情况,您的应用程序将挂起或出错,直到超过4分钟的第一个端口可用。

2) Use UDP instead of TCP. 2)使用UDP而不是TCP。 There is no guarantee of delivery, but if this is real-time data, then you don't want to use TCP as it could significantly delay real time data. 无法保证交付,但如果这是实时数据,那么您不希望使用TCP,因为它可能会显着延迟实时数据。

If you are using TCP, the Client.Close() should send a FIN over the TCP stack, and unless the other host has crashed or the network between them has gone down, should guarantee delivery. 如果您使用TCP,则Client.Close()应通过TCP堆栈发送FIN,除非其他主机崩溃或它们之间的网络出现故障,否则应保证交付。

Whether you use TCP or UDP if you are concerned about every block of data arriving at its destination, you should build some application level checking that all blocks arrived in order and uncorrupted... 无论您是使用TCP还是UDP,如果您担心到达目的地的每个数据块,您都应该构建一些应用程序级别检查,确保所有块按顺序到达且未损坏...

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM