Warning: file_get_contents(/data/phpspider/zhask/data//catemap/3/sockets/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ TCP连接已接受,但写入数据会导致它使用过时的连接_C++_Sockets_Tcp_Berkeley Sockets - Fatal编程技术网

C++ TCP连接已接受,但写入数据会导致它使用过时的连接

C++ TCP连接已接受,但写入数据会导致它使用过时的连接,c++,sockets,tcp,berkeley-sockets,C++,Sockets,Tcp,Berkeley Sockets,服务器(192.168.1.5:3001)正在运行Linux 3.2,设计为一次只接受一个连接。 客户端(192.168.1.18)正在运行Windows 7。该连接是无线连接。这两个程序都是用C++编写的。 它在10个连接/断开周期中有9个工作良好。第十个ish(随机发生)连接让服务器接受该连接,然后当它后来实际写入该连接时(通常是30+s之后),根据Wireshark(参见屏幕截图),它看起来像是在写入一个旧的过时连接,客户端(不久前)已经定义了一个端口号,但服务器尚未定义。因此,客户端和服

服务器(192.168.1.5:3001)正在运行Linux 3.2,设计为一次只接受一个连接。 客户端(192.168.1.18)正在运行Windows 7。该连接是无线连接。这两个程序都是用C++编写的。 它在10个连接/断开周期中有9个工作良好。第十个ish(随机发生)连接让服务器接受该连接,然后当它后来实际写入该连接时(通常是30+s之后),根据Wireshark(参见屏幕截图),它看起来像是在写入一个旧的过时连接,客户端(不久前)已经定义了一个端口号,但服务器尚未定义。因此,客户端和服务器连接似乎不同步——客户端创建新连接,服务器尝试写入前一个连接。每次后续连接尝试都会在处于此断开状态时失败。断开状态可以通过超出最大无线范围半分钟来启动(如前所述,在10种情况下,这是可行的,但有时会导致断开状态)

屏幕截图中的红色箭头指示服务器何时开始发送数据(Len!=0),即客户端拒绝数据并向服务器发送RST的时间点。右边缘下方的彩色点表示所使用的每个客户端端口号的单一颜色。注意一个或两个点如何在该颜色的其余点之后出现(并注意时间列)

问题看起来是在服务器端,因为如果您终止服务器进程并重新启动,它会自行解决(直到下次发生)

希望代码不会太离谱。我将listen()中的queue size参数设置为0,这意味着它只允许一个当前连接,不允许挂起连接(我尝试了1,但问题仍然存在)。当代码中显示“//error”时,所有错误都不会显示为跟踪打印

// Server code

mySocket = ::socket(AF_INET, SOCK_STREAM, 0);
if (mySocket == -1)
{
  // error
}

// Set non-blocking
const int saveFlags = ::fcntl(mySocket, F_GETFL, 0);
::fcntl(mySocket, F_SETFL, saveFlags | O_NONBLOCK);

// Bind to port

// Union to work around pointer aliasing issues.
union SocketAddress
{
  sockaddr myBase;
  sockaddr_in myIn4;
};

SocketAddress address;
::memset(reinterpret_cast<Tbyte*>(&address), 0, sizeof(address));
address.myIn4.sin_family = AF_INET;
address.myIn4.sin_port = htons(Port);
address.myIn4.sin_addr.s_addr = INADDR_ANY;
if (::bind(mySocket, &address.myBase, sizeof(address)) != 0)
{
  // error
}
if (::listen(mySocket, 0) != 0)
{
  // error
}


// main loop
{
  ...
  // Wait for a connection.
  fd_set readSet;
  FD_ZERO(&readSet);
  FD_SET(mySocket, &readSet);
  const int aResult = ::select(getdtablesize(), &readSet, NULL, NULL, NULL);
  if (aResult != 1)
  {
    continue;
  }
  // A connection is definitely waiting.
  const int fileDescriptor = ::accept(mySocket, NULL, NULL);
  if (fileDescriptor == -1)
  {
    // error
  }

  // Set non-blocking
  const int saveFlags = ::fcntl(fileDescriptor, F_GETFL, 0);
  ::fcntl(fileDescriptor, F_SETFL, saveFlags | O_NONBLOCK);

  ...
  // Do other things for 30+ seconds.
  ...
  const int bytesWritten = ::write(fileDescriptor, buffer, bufferSize);
  if (bytesWritten < 0)
  {
    // THIS FAILS!! (but succeeds the first ~9 times)
  }

  // Finished with the connection.
  ::shutdown(fileDescriptor, SHUT_RDWR);
  while (::close(fileDescriptor) == -1)
  {
    switch(errno)
    {
    case EINTR:
      // Break from the switch statement. Continue in the loop.
      break;
    case EIO:
    case EBADF:
    default:
      // error
      return;
    }
  }
}
//服务器代码
mySocket=::套接字(AF_INET,SOCK_STREAM,0);
如果(mySocket==-1)
{
//错误
}
//设置非阻塞
const int saveFlags=::fcntl(mySocket,F_GETFL,0);
:fcntl(mySocket、F|u SETFL、saveFlags | O|u NONBLOCK);
//绑定到端口
//联合起来解决指针别名问题。
联合套接字地址
{
sockaddr-myBase;
myIn4中的sockaddr_;
};
SocketAddress地址;
::memset(重新解释类型和地址),0,sizeof(地址));
address.myIn4.sin_family=AF_INET;
address.myIn4.sin_port=htons(port);
address.myIn4.sin\u addr.s\u addr=INADDR\u ANY;
if(::bind(mySocket,&address.myBase,sizeof(address))!=0)
{
//错误
}
if(::listen(mySocket,0)!=0)
{
//错误
}
//主回路
{
...
//等待连接。
fd_集readSet;
FD_零(读取集和读取集);
FD_集(mySocket和readSet);
const int aResult=::选择(getdtablesize(),&readSet,NULL,NULL,NULL);
如果(aResult!=1)
{
继续;
}
//连接正在等待。
const int fileDescriptor=::accept(mySocket,NULL,NULL);
如果(fileDescriptor==-1)
{
//错误
}
//设置非阻塞
const int saveFlags=::fcntl(fileDescriptor,F_GETFL,0);
::fcntl(文件描述符、F|u SETFL、saveFlags | O|u非块);
...
//做其他事情30秒以上。
...
const int bytesWrite=::write(文件描述符、缓冲区、缓冲区大小);
如果(字节数小于0)
{
//失败!!(但第一次成功~9次)
}
//连接完成。
::shutdown(fileDescriptor,shutu\RDWR);
while(::close(fileDescriptor)=-1)
{
开关(错误号)
{
案例中心:
//从switch语句中断。在循环中继续。
打破
案例EIO:
案例EBADF:
违约:
//错误
返回;
}
}
}
因此,在accept()调用(假设这正是发送SYN数据包的时间点)和write()调用之间的某个地方,客户机的端口会更改为以前使用的客户机端口


所以问题是:服务器怎么可能接受一个连接(并因此打开一个文件描述符),然后通过以前的(现在已过时和失效的)连接/文件描述符发送数据?在系统调用中是否需要缺少某种选项?

我正在提交一个答案,以总结我们在评论中得出的结论,尽管它还不是一个完整的答案。我认为,它确实涵盖了重要的几点

您有一个服务器,一次处理一个客户端。它接受连接,为客户端准备一些数据,写入数据,然后关闭连接。问题在于准备数据的步骤有时比客户愿意等待的时间要长。当服务器忙于准备数据时,客户机放弃了

在客户端,当套接字关闭时,会发送一个FIN,通知服务器客户端没有更多的数据要发送。客户端的套接字现在进入FIN_WAIT1状态

服务器接收FIN并回复ACK。(内核在没有用户空间进程任何帮助的情况下完成确认。)服务器套接字进入关闭等待状态。套接字现在是可读的,但是服务器进程没有注意到,因为它正忙于数据准备阶段

客户端收到FIN的ACK并进入FIN_WAIT2状态。我不知道客户机上的用户空间发生了什么,因为您没有显示客户机代码,但我认为这并不重要

服务器进程仍在为挂起的客户端准备数据。它对其他一切都漠不关心。同时,另一个客户端连接。内核完成握手。这个新客户机在一段时间内不会受到服务器进程的任何关注,但是在内核级别,第二个连接现在在两端建立

最终,服务器的数据准备(针对第一个客户机)完成。它尝试写入()。服务器的内核不知道第一个客户端不再愿意接收数据,因为TCP不在其中进行通信