17-如何正确使用TCP
why netcat?

netcat 基本功能简介
-
信号发生器:发送数据。nc > /dev/zero、类似chargen服务
-
负载:接收数据。nc > /dev/null、类似discard服务
-
通过dd产生定量数据,通过nc发送测试网络带宽,dd /dev/zero | nc、类似ttcp服务
-
两台机器之间通过nc拷贝文件,nc < file, nc > file、类似scp服务
how/when 关闭TCP连接
-
为什么出现TCP不可靠的错觉
-
发送所有数据,紧接着close,最后的一些字节丢失
-
-
出现错误的情况:send() + close()
-
当发送缓冲区还有数据,而调用close()会发送RST,强行使得TCP连接断开
-
SO_LINGER无助于解决这一问题,不要使用SO_LINGER(???)
-
-
sender关闭连接的正确步骤:send() + shutdown(WR) + read->0 + close()
-
这里的shutdown(WR)会导致receiver的read返回0
-
-
receiver关闭连接的正确步骤:read()->0 + 如果没有数据要发送就调用close()
-
这里的close(),会导致sender的read返回0
-
-
或者在应用层设计更好的协议。因为此处的协议有权限(恶意客户端,一直不close,导致服务端不能关闭连接。解决方法:为每一个连接超时器,超时不活跃连接,直接close。)
一个传输文件的例子
代码出处:
这个例子是为例复现TCP”不可靠“的场景。
打开文件(filename),然后每次读一字节数据到buf,再将buf的数据发送给reciever,直至read返回0。再调用stream->shutdownWrite(),其实就是shutdown(SHUT_WR)。然后循环read,直至read返回0,才调用close(代码中是通过TcpStream的析构来close)。
void sender(const char* filename, TcpStreamPtr stream)
{
FILE* fp = fopen(filename, "rb");
if (!fp)
return;
printf("Sleeping 10 seconds.\n");
sleep(10);
printf("Start sending file %s\n", filename);
char buf[8192];
size_t nr = 0;
while ( (nr = fread(buf, 1, sizeof buf, fp)) > 0)
{
stream->sendAll(buf, nr);
}
fclose(fp);
printf("Finish sending file %s\n", filename);
// Safe close connection
printf("Shutdown write and read until EOF\n");
stream->shutdownWrite();
while ( (nr = stream->receiveSome(buf, sizeof buf)) > 0)
{
// do nothing
}
printf("All done.\n");
// TcpStream destructs here, close the TCP socket.
}
测试步骤
-
服务端:先在将服务端运行起来:将ttcp文件从端口1234发出

-
客户端:在同一台主机上运行netcat当作客户端,并通过wc计算接收数据的大小,无论客户端期间是否发送数据,接收的数据都与原来服务端发送的文件的大小一致。

-
下面复现错误,将sender函数的重复read直至返回0注释,也即是将安全关闭连接的操作去除。

-
重新编译,按照上面的步骤运行。服务端:

-
客户端:可见第二次的大小与原来文件的大小不一致。

原因:
服务端 sender 将文件发送完之后,close(fd)关闭连接。而由于客户端通过nc发送的数据还没有读,因此会发送RST报文丢弃数据立刻终止连接,而不是发送FIN报文等待所有数据都发送完才正常断开连接(发送RST,还是FIN 取决于close()时,local tcp buffer中是否还有未读完的数据)。
在客户端收到 RST 包时,丢弃所有数据,然后立刻断开连接。
参考链接:

浙公网安备 33010602011771号