如果我不能很好地解释它,这有点奇怪而且道歉。
我使用以下简单代码发送我从队列弹出的消息,使用简单的TCP套接字,消息通过localhost发送到同一台机器上的另一个端口 -
try
{
Socket.Select(null, writeList, null, 120000000/*120 seconds*/);
}
catch (SocketException e)
{
log.Error("Select returned an error waiting to send... " + e.Message + " Errorcode: " + e.ErrorCode);
connected = false;
socket.Close();
}
bool readyToWrite = false;
for (int i = 0; i < writeList.Count; i++)
{
readyToWrite = true;
}
if (readyToWrite)
{
try
{
//log.Debug("Sending message type: " + message.header.msgType);
socket.Send(message, message.header.dataLength, SocketFlags.None);
//log.Debug("Message sent");
}
catch (SocketException e)
{
log.Error(e.Message + " Error code: " + e.ErrorCode);
connected = false;
socket.Close();
}
}
else
{
log.Error("Not able to write - stopping sender thread and closing socket");
connected = false;
socket.Close();
}
这通常可以正常工作,实际上我的应用程序最初会向另一端发送几条消息而没有问题。
然而,我然后快速连续向队列中添加10条左右的消息,这些消息会弹出并发送正常,貌似 - 日志语句显示Send()返回正常,当我查看网络跟踪时,似乎是另一条终于承认了他们。
但事实并非如此。另一端在一个循环中调用select(),一秒超时,并且一直没有数据要读,直到大约30秒后(每次都相同),所有消息一次到达另一端
来自连接另一端的C ++代码 -
while (m_bRunning && bOK && !bReadyToRead)
{
m_bIsAlive = true;
switch(pSocket->Select(1, true))
{
case 1: // Ready to read
//TRACE("Data ready to be read from RAM\n");
bReadyToRead = true;
break;
case 0: // Select timed out
if (GetTickCount() > dwTimeout)
{
bOK = false;
}
// else No action needed
break;
default: // Error detected
TRACE("Select returned error...\n");
bOK = false;
break;
}
}
// Try and read a message header
iBytesExpected = sizeof(RAM_HEADER);
while ((m_bRunning && bOK) && (iBytesSoFar < iBytesExpected))
{
m_bIsAlive = true;
iBytesRead = pSocket->Read(pWritePos, iBytesExpected-iBytesSoFar);
C ++ select包装器看起来像这样 -
int CRawSocket::Select(ULONG ulTimeoutSeconds, bool bCheckRead)
{
int iResult = -1; // Error by default
int iSelectReturn = 0;
fd_set readSet;
fd_set writeSet;
struct timeval timeout;
timeout.tv_sec = ulTimeoutSeconds;
timeout.tv_usec = 0;
FD_ZERO(&readSet);
FD_ZERO(&writeSet);
if (bCheckRead)
{
FD_SET(m_hSocket, &readSet);
iSelectReturn = select(1, &readSet, NULL, NULL, &timeout);
}
else
{
FD_SET(m_hSocket, &writeSet);
iSelectReturn = select(1, NULL, &writeSet, NULL, &timeout);
}
if(iSelectReturn != SOCKET_ERROR)
{
if (FD_ISSET(m_hSocket, &readSet))
{
iResult = 1; // Ready to READ
}
else if (FD_ISSET(m_hSocket, &writeSet))
{
iResult = 2; // Ready to WRITE
}
else
{
iResult = 0; // Select TIMED OUT
}
}
else
{
const int e = WSAGetLastError();
ERRORLOG("Select socket error %lu\n", e);
iResult = -1; // Some error occurred
}
return iResult;
}
读取方法 -
int CReadWriteSocket::Read(void *pData, int nLen)
{
char* pcData = (char* )pData;
int n = nLen;
// if data size is bigger then network buffer
// handle it nice
do
{
int r1 = ::recv (m_hSocket, pcData, n, 0);
if (r1 == SOCKET_ERROR)
{
int e = WSAGetLastError();
if (e == WSAEWOULDBLOCK)
{
return nLen - n;
}
else
{
TRACE("Socket Read error %d\n", e);
return -1; // error other than would block detected
}
}
else if (r1 == 0) // Connection has closed
{
TRACE("Socket appears to have closed (zero bytes read)\n");
return -1; // Show this as an "error"
}
else if (r1 < 0)
{
ASSERT(0);
return nLen - n;
}
pcData += r1;
n -= r1;
} while (n > 0);
ASSERT(n == 0);
return nLen;
}
我完全感到困惑,因为这似乎是我在整个地方使用的标准代码,我从未见过像这样的问题。
有人建议尝试使用NoDelay套接字选项,但这没有任何效果 - 事实上,这不会导致我所知道的长度延迟。
任何建议都将不胜感激!感谢。
答案 0 :(得分:4)
TCP是一种流协议。您不能假设您发送()的'数据包'将被交付和接收。在发送端,Nagle算法尝试组合在单独的Send()调用中写入的数据,以优化数据的传递。在接收端,您将读取()存储在TCP缓冲区中的内容。如果有任何延迟,如果是传输的数据包的组合。发送器和接收器之间的路由器使这一点变得更加复杂,它们被允许分段IP分组,将大分组变成多个小分组,以适应传输信道的最大分组大小(MTU)。
换句话说,没有可靠的方法来确保数据包以发送方式传送。一个简单的解决方法是首先传输数据包的 size 。在接收端,您首先读取该大小,然后知道如何计算接收的字节以重建数据包。
答案 1 :(得分:1)
我不确定这是不是你的问题而且我正在处理过去的模糊记忆,所以请原谅我的模糊性。
我似乎记得Socket.Select()
将返回,表明如果流有错误,它将无数据读取。您可能想尝试将流列表的副本传递到错误列表参数,看看是否有任何错误。
无论writelist
中的内容是什么,您的代码似乎都在发送到同一个套接字。我会修改它以便在writelist
上运行,即使你只有一个套接字。否则你将尝试发送到套接字,即使它尚未表明它已准备好接收数据(即如果Socket.Select()
由于某些其他原因而返回,例如我上面的预感)。如果您使用多个套接字操作,这可能会导致写入操作被阻止,并且可能成为您正在目睹的延迟的原因。
最后,您可以在设置标志后立即退出readyToWrite
循环。更好的是你可以将其重新编码为:
bool readyToWrite = writelist.Any();
但我仍建议您使用foreach
上的writelist
循环替换它:
foreach (Socket sock in writelist)
{
// do stuff
}
答案 2 :(得分:0)
这很奇怪。请考虑使用WireShark跟踪消息并查看消息的去向。除非我遗漏了一些明显的东西,否则我很难理解为什么这些消息相信它们已被收到但实际上却没有。客户可以等待吗?
尝试int.MaxValue而不是120000000
答案 3 :(得分:0)
您的客户端代码是什么样的?
通常,我会说,因为你在运行,你只是在本地发送消息,你看到的行为几乎肯定是由Nagle的算法引起的,但设置NoDelay属性会禁用它。
答案 4 :(得分:0)
我的错误,似乎是最后阻塞阅读的问题。