【问题标题】:is this the right way to call socket Send/Receive这是调用套接字发送/接收的正确方法吗
【发布时间】:2018-12-01 12:39:22
【问题描述】:

我的讨论基于连接的 TCP、阻塞套接字、同步。

首先关于Receive(),MSDN 说:

如果没有数据可供读取,Receive() 方法将阻塞直到数据可用。如果您使用面向连接的 Socket,Receive() 方法将读取尽可能多的数据缓冲区。

对阻塞的强调让我误解了这段话。后来我做了实验,得出如下结论:如果我想接受10个字节,但是socket缓存中只有6个字节可读,那么调用Receive()一次不会阻塞,直到10个字节可读。只是返回,而是尽可能的读取现在可读的字节,也就是会返回6个字节,所以实际读取的字节数用Receive()的返回值来表示。所以我想如果我想准确接收指定的字节数,我应该调用recvToCnt() 而不是Receive()

private int recvToCnt(Socket socket, byte[] buf, int beginIndex, int cnt)
{
   if (buf.Length < beginIndex + cnt)
      return 0;

   int realCnt = 0;
   while (realCnt < cnt)
   {
      realCnt += socket.Receive(buf, beginIndex + realCnt, cnt - realCnt, SocketFlags.None);
   }
   return realCnt;
}

那么,让我们看看Send()。 MSDN 说:

如果您使用面向连接的协议,Send() 将阻塞,直到缓冲区中的所有字节都发送完毕,除非使用 Socket.SendTimeout() 设置了超时。

我怀疑这个描述的准确性,因为Send()也有返回值,我猜应该和Receive()有类似的阻塞概念。所以我想如果我想确保将指定的字节数发送到套接字缓存中,我应该使用sendToCnt() 而不是发送:

private int sendToCnt(Socket socket, byte[] buf, int beginIndex, int cnt)
{
   if (buf.Length < beginIndex + cnt)
      return 0;

   int realCnt = 0;
   while (realCnt < cnt)
   {
      realCnt += socket.Send(buf, beginIndex + realCnt, cnt - realCnt, SocketFlags.None);
   }
   return realCnt;
}

我的想法正确吗?还有什么想法吗?

【问题讨论】:

  • 如果对方关闭连接或出现错误,您的recvToCnt 代码将无限循环。
  • @DavidSchwartz 如果发生错误,则会引发异常。但是你对对方关闭连接是正确的。代码需要处理Receive()返回0表示连接已经正常关闭的情况。

标签: windows sockets tcp winsock blocking


【解决方案1】:

如果我想接受 10 个字节,但在套接字缓存中只能读取 6 个字节,

套接字接收缓冲区。

... 调用 Receive() 一次不会阻塞,直到 10 个字节可读。只是返回,而是尽可能的读取现在可读的字节,也就是会返回6个字节,所以实际读取的字节数用Receive()的返回值来表示。

正确。

所以我想如果我想准确接收指定字节,我应该调用recvToCnt() 而不是Receive()

您当然应该在循环中调用Receive(),直到您获得所有预期的数据,或者发生错误或流结束,但您发布的代码完全忽略错误和流结束,这是不可接受的。你需要解决这个问题。

那么,我们来看看Send()。 ...我怀疑这个描述的准确性,

不要。没错,在阻塞模式下。

因为Send()也有返回值,我猜应该和Receive()有类似的阻塞概念。

没有。返回值在非阻塞模式下变得重要。在阻塞模式下,它的行为与描述的一样。

所以我想如果我想确保将指定数量的字节发送到套接字缓存中,我应该使用sendToCnt() 而不是发送

套接字发送缓冲区,不,您发布的代码再次忽略错误,这再次是不可接受的。

【讨论】:

  • "您发布的 [recvToCnt()] 代码完全忽略错误和流结束" - 套接字错误引发异常。 recvToCnt() 不需要直接处理异常,让调用者自己决定如何处理。但是你说得对,它至少确实需要处理流尾。 Receive() 当套接字正常关闭时返回 0。 “您发布的 [sendToCnt()] 代码忽略了错误” - Send() 不会在错误时返回值,它会引发异常,包括在套接字关闭时。所以sendToCnt() 不需要处理错误,让调用者处理。
  • "返回值 [of Send()] 在非阻塞模式下变得很重要。在阻塞模式下它的行为与描述的一样" - 不,即使在阻塞模式下,它可以返回比请求更少的字节数(尽管有时它不会需要)。所以你也必须循环调用Send()
  • @Remy:重要的是要注意问题是关于套接字 API 的未指定的面向对象的包装器,而不是关于 Winsock 函数。 (它有些错误)。包装器的行为可能与底层套接字 API 略有不同。
  • @BenVoigt 有问题的包装器是 .NET 的 Socket 类(引号来自 MSDN 的 Socket 文档)。我所说的适用于那个班级。我检查了它的源代码,它不会在内部循环以确保在退出之前读取/发送所有请求的字节,因此您必须循环输入自己的代码,就像我们直接使用 Winsock 函数一样
  • @RemyLebeau Receive() 确实在流结束时返回零,并且 OP 忽略了它,因此他的代码将永远循环。回复Send(),您在这里与 MSDN 争论,而不是与我争论。 Posix 还表示,对于 BSD Sockets API 和 Posix write() 方法,在阻塞模式下必须如此,除非发生中断,我 27 年来一直无法弄清楚其语义。有很多主要来源需要争论。
【解决方案2】:

如果我想接受 10 个字节,但在套接字缓存中只有 6 个字节可读,则调用 Receive() 一次不会阻塞,直到 10 个字节可读。只是返回,而是尽可能的读取现在可读的字节,也就是会返回6个字节,所以实际读取的字节数用Receive()的返回值来表示。

是的。

所以我想如果我想准确接收指定的字节数,我应该调用recvToCnt() 而不是Receive()

是的。

我怀疑这个描述的准确性,因为Send()也有返回值,我猜应该和Receive()有类似的阻塞概念。

是的。它可以返回比请求更少的字节,如果套接字的内核缓冲区有足够的空间容纳至少 1个字节但没有足够的空间容纳所有您发送的字节数。

所以我想如果我想确保将指定数量的字节发送到套接字缓存中,我应该使用sendToCnt() 而不是发送:

是的。

【讨论】:

    猜你喜欢
    • 2021-06-13
    • 1970-01-01
    • 2015-03-09
    • 2021-01-18
    • 2010-11-14
    • 2017-12-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多