另一个客户端应用程序可以关闭崩溃的客户端应用程序通过服务器打开的TCP连接吗?

时间:2018-08-26 04:38:12

标签: sockets networking tcp

请考虑以下顺序:

  1. 客户端应用程序(Web浏览器)打开到不同Web服务器的多个TCP连接;
  2. 然后,以太网电缆断开连接;
  3. 然后关闭客户端应用程序;
  4. 以太网电缆保持断开状态几个小时;
  5. 以太网电缆已重新连接;
  6. 我看到了长时间关闭的客户端应用程序已连接到的几台服务器的“ TCP keep-alive”数据包(每60秒,持续数小时)!

通常,当应用程序关闭时,该应用程序将启动每个打开的套接字的关闭,然后TCP层将尝试向每个远程端点发送FIN数据包。如果在物理上可以发送FIN数据包,并且实际发生这种发送,则本地终结点将从ESTABLISHED状态变为FINWAIT_1状态(并等待从远程终结点接收ACK等)。但是,如果物理链接断开,则TCP本地终结点无法发送该FIN,并且服务器仍假定TCP连接仍然存在(并且对客户端的“ close”功能的调用将无限期地阻塞,直到物理链接断开为止)。假设套接字已设置为阻止模式,则重新建立链接,对吗?)。

无论如何,在长时间关闭所有常规联网应用程序(例如,Web浏览器)后重新连接以太网电缆时,我正以60秒的间隔从三个单独的Web服务器接收“ TCP Keep-Alive”数据包几小时!

Wireshark显示将这些TCP Keep-Alive数据包发送到的本地端口号,但是TCPViewnetstat -abno都不显示任何应用程序正在使用的本地端口号。使用Process Explorer查看每个正在运行的进程的“ TCP / IP”属性也不会显示任何匹配的端口号。我不认为由于任何正在进行的子进程(例如,插件应用程序)导致的僵尸“进程记录”(例如,Web浏览器进程)而保留了端口,但是我不确定我的观察是否使用TCPView / netstat / Process Explorer足以排除这种可能性。

考虑到远程Web服务器(例如Akamai服务器)的身份,我相信这些连接是通过“最近”使用Web浏览器建立的。但是,即使关闭了浏览器并且物理链接已中断了几个小时,这些保持活动仍然来自这三个Web服务器。

如果连接出现在TCPView中,我可以简单地选择它们并手动将其关闭。但是,客户端TCP端点似乎早已消失。

与此同时,我感到困惑的是,为什么服务器要重试这么多次才能得到对它们的保持活动数据包的答复。

TCP保持活动行为通常由三个参数控制:\

(1)等待下一次“爆发”或“探测”尝试的时间;

(2)在一次“探测”尝试期间发送每个保持活动数据包之间的时间间隔;

(3)在“爆发”之前的最大“探测”尝试次数被视为失败(因此,TCP连接被视为永久断开)。

对于从三台不同的服务器上看到的TCP保持活动数据包,两次“探针”重试之间的时间间隔正好是60秒。但是,似乎“探针”重试的最大次数是无限的,对于任何服务器来说,这似乎都是一个糟糕的选择!

尽管我很好奇如何创建和维持这种持续不断的保持活动流,但我对如何使用客户端应用程序强制关闭服务器端端点更加感兴趣,这是因为现有的本地TCP端点没有收到那些保持活动的数据包。

我的粗略想法是创建一个应用程序,该应用程序创建一个TCP模式套接字,并绑定(允许端口号复用)到传入的保持活动指向的端口号,然后调用“ open”,然后调用“关闭”,希望服务器端点将使TCP状态转换以一种或另一种方式达到关闭状态!另一种方法可能是创建一个原始模式套接字,并接收TCP保持活动数据包(这只是一个ACK),然后形成并发送适当的FIN数据包(具有正确的序列号等)以在哪里提取数据。终止已久的客户端应用程序显然已关闭),然后在发送最终ACK之前先收到ACK和FIN。

最后一点说明-我知道,这会让人眼花and乱:这里的工作环境是在Windows 7的VirtualBox中运行的Windows XP SP3!因此,我更喜欢可以在Windows XP SP3中实现目标(关闭半开放式TCP连接)的代码或开源应用程序。当然,我可以重新启动快照,这可能会 关闭连接-但是我对学习如何获取有关网络连接状态的更多信息以及如何处理这种情况更感兴趣。 TCP状态问题。

2 个答案:

答案 0 :(得分:1)

我通过编写一个简单程序(将完整的代码显示在下面)将每个本地半开放TCP连接关闭,从而将本地套接字绑定到服务器认为已连接的端口上,从而尝试建立一个新的连接,然后关闭该连接。

(注意:如果连接成功,我发出HTTP GET请求,只是因为我这种情况下的幻像TCP保持活动显然源自普通的HTTP服务器,我想知道我可能会得到什么样的响应。我认为可以删除“发送”和“接收”调用,而不会影响代码获得所需结果的能力。)

在以下代码中, src_port_num 变量表示服务器向其发送“ TCP keep-alive”数据包的客户端端口号(当前未使用)和 dst_ip_cstr 是服务器(例如Akamai Web服务器)的IP地址,而 dst_port_num 是端口号(在我的情况下,它恰好是端口80上的普通HTTP服务器)。

注意!共享此代码并不意味着暗示可以通过了解TCP协议规范来严格解释其工作原理。我只是猜测,声称某个远程端点正在向其发送TCP保持活动数据包的废弃本地端口,并尝试建立与该完全相同的远程端点的新连接,可能会以某种方式促使该远程端点关闭该端点。过时的半开连接-它对我有用

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <winsock2.h>
#pragma comment(lib, "ws2_32.lib")

void main()
{
  // Local IP and port number
  char * src_ip_cstr  = "10.0.2.15";
  int    src_port_num = 4805;

  // Remote IP and port number
  char * dst_ip_cstr  = "23.215.100.98";
  int    dst_port_num = 80;

  int res = 0;
  WSADATA wsadata;
  res = WSAStartup( MAKEWORD(2,2), (&(wsadata)) );
  if (0 != res) { printf("WSAStartup() FAIL\n"); return; }

  printf( "\nSRC IP:%-16s Port:%d\nDST IP:%-16s Port:%d\n\n",
  src_ip_cstr, src_port_num, dst_ip_cstr, dst_port_num );

  sockaddr_in src;
  memset( (void*)&src, 0, sizeof(src) );
  src.sin_family           = AF_INET;
  src.sin_addr.S_un.S_addr = inet_addr( src_ip_cstr );
  src.sin_port             = htons( src_port_num );

  sockaddr_in dst;
  memset( (void*)&dst, 0, sizeof(dst) );
  dst.sin_family           = AF_INET;
  dst.sin_addr.S_un.S_addr = inet_addr( dst_ip_cstr );
  dst.sin_port             = htons( dst_port_num );

  int s = socket( PF_INET, SOCK_STREAM, IPPROTO_TCP );
  if ((-1) == s) { printf("socket() FAIL\n"); return; }

  int val = 1;
  res = setsockopt( s, SOL_SOCKET, SO_REUSEADDR, 
  (const char*)&val, sizeof(val) );
  if (0 != res) { printf("setsockopt() FAIL\n"); return; }

  res = bind( s, (sockaddr*)&src, sizeof(src) );
  if ((-1) == res) { printf("bind() FAIL\n"); return; }

  res = connect( s, (sockaddr*)&dst, sizeof(dst) );
  if ((-1) == res) { printf("connect() FAIL\n"); return; }

  char req[1024];
  sprintf( req, "GET / HTTP/1.1\r\nHost: %s\r\nAccept: text/html\r\n"
  "Accept-Language: en-us,en\r\nAccept-Charset: US-ASCII\r\n\r\n", 
  dst_ip_cstr );
  printf("REQUEST:\n================\n%s\n================\n\n", req );

  res = send( s, (char*)&req, strlen(req), 0 );
  if ((-1) == res) { printf("send() FAIL\n"); return; }

  const int REPLY_SIZE = 4096;
  char reply[REPLY_SIZE];
  memset( (void*)&reply, 0, REPLY_SIZE );
  res = recv( s, (char*)&reply, REPLY_SIZE, 0 );
  if ((-1) == res) { printf("recv() FAIL\n"); return; }
  printf("REPLY:\n================\n%s\n================\n\n", reply );

  res = shutdown( s, SD_BOTH );
  res = closesocket( s );

  res = WSACleanup();
}

有害/羞耻/令人发指的公开信息

正如我在最初的问题中提到的那样,我在运行Windows XP SP3的VirtualBox中通过Wireshark观察到了这些“ TCP keep-alive”数据包,其中主机OS是Windows 7。

当我今天早上醒来,用一杯咖啡和新鲜的眼睛再次观察这种现象时,即使在24小时之后,“ TCP保持活动”数据包仍然每60秒出现一次,我做出了一个有趣的发现:数据包继续从三个不同的IP地址到达,精确地间隔为60秒(但是对于三个IP来说却是交错的),即使我断开了以太网电缆与Internet的连接,!我的主意震撼了!

因此,尽管这三个IP地址确实与我的Web浏览器很久以前连接的真实世界的Web服务器相对应,但是TCP keep-alive数据包显然源自某些本地软件组件。

这个令人震惊的发现并没有改变我对这种情况的看法:从客户端软件的角度来看,我想引起关闭“服务器端”半开放TCP连接。 / p>

在VirtualBox中,选择“设备”->“网络”->“连接网络适配器” ,可以打开或关闭虚拟网络适配器,就像连接或断开虚拟以太网电缆一样。切换到断开状态会导致幻像TCP保持活动数据包停止到达Wireshark。随后切换到连接状态导致TCP保持活动数据包恢复到达Wireshark。

无论如何,我有时需要运行TWICE上方的代码才能成功关闭半开连接。第一次运行代码时,Wireshark会显示一个带有批注“ [TCP ACKed unseen segment]”的数据包,这就是我希望造成的TCP气体点燃混乱,哈哈!因为新的客户端终结点是远程终结点意外的,所以在失败之前,对“ connect”的调用可能会挂起30秒。对于几个僵尸/幻象半开连接,只需运行一次程序就足以导致RST数据包。

我需要反复修改程序,以更改本地端口号,远程IP和远程端口号的组合,以匹配我在Wireshark中观察到的每个幻像TCP保持活动数据包。 (我将实现用户友好的命令行参数留给亲爱的读者(就是您!)。)经过几轮修改和运行程序后,所有僵尸保持活动数据包都停止了。也许有人会说“沉默包”。

EPILOGUE

[在燕尾服中,手握着马提尼酒杯,在黑客的陪伴下,从游艇甲板上凝望着大海] “我从来没有弄清楚那些僵尸包是从哪里来的...是“ VirtualBox仅主机网络”虚拟以太网适配器吗?只有Oracle知道!”

答案 1 :(得分:0)

您无需执行任何操作即可关闭远程套接字,该套接字已内置在TCP协议中。如果系统收到未创建新连接(即已设置SYN)且不属于任何已建立连接的TCP数据包,则系统将以RST数据包进行回复。这样,对等方将知道终结点不再存在,并放弃连接。