一、基本介绍
1、协议
数据链路层 -- 以太网驱动程序
IP、TCP -- 内核中的协议栈
Web客户 -- 用户进程
2、确定机器的大小端,可以通过一个Union来确定,代码如下
3、RST的含义
“复位”,它是TCP在某些错误情况下所发的一种TCP分节。有三个条件可以产生RST:
SYN到达某端口但此端口上没有正在监听的服务器
TCP想取消一个已有连接
TCP接收了一个根本不存在的连接上的分节
4、TCPIP详解对“RST”的解释
一般说来,无论何时一个报文段发往基准的连接出现错误,TCP都会发出一个复位报文段。
到不存在的端口的连接请求
异常终止一个连接,使用SO_LINGER可以提供这种异常关闭的能力
检测半打开连接
如果一方已经关闭或异常终止连接而另一方却还不知道,称半打开。
只要不打算在半打开连接上传输数据,仍处于连接状态的一方就不会检测到另一方已经出现异常。
使用keepalive选项能使TCP的一端发现另一端已经消失。
5、close
每个文件或套接口都有一个访问计数,该访问计数在文件表项中维护,它表示当前指向该文件或套接口的打开的描述字个数。
当调用close时,只是将访问计数减1,只有在访问计数值达到0时,才真正关闭,触发FIN挥手流程。
close的操作依赖于套接口选项SO_LINGER的值。
6、shutdown
如果我们确实想对TCP连接发一个FIN,可以改用shutdown而不是close。
shutdown函数有一个参数,有三个选项:
SHUT_RD 关闭连接的读这一半,不再接受套接口中的数据且现留在套接口接收缓冲区中的数据都作废。
SHUT_WR 关闭连接的写这一半,在TCP场合下,这称为半关闭。
SHUT_RDWR 连接的读这一半和写这一半都关闭。
7、read、write
字节流套接口上的read和write函数所表现的行为不同于通常的文件IO。字节流套接口上的读或写输入或输出的字节数可能
比要求的数量少,但这不是错误状况,原因是内核中套接口的缓冲区可能已达到了极限。此时多需要的是调用者再次
调用read或write函数,以输入或输出剩余的字节。
8、recv、send
recv和send函数提供了和read和write差不多的功能,不过它们提供了第四个参数来控制读写操作。
二、TCP状态
1、共有11中状态,可以通过查看TCP的状态变迁图获得。
CLOSED
LISTEN
SYN_SEND
SYN_RCVD
ESTABLISHED
CLOSE_WAIT
LAST_ACK
FIN_WAIT_1
FIN_WAIT_2
CLOSING
TIME_WAIT
三、套接口选项
1、SO_LINGER套接口选项
此选项指定函数close对面向连接的协议如何操作。缺省操作是close立即返回,但如果有数据残留在套接口发送缓冲区中,系统
将试着将这些数据发送给对方。
SO_LINGER选项使我们可以改变这个缺省设置。此选项要求在用户进程与内核间传递如下结构:
struct linger{
int l_onoff; // 0=off, nonzero=on
int l_linger; // linger time, Posix.1g specifies units as seconds
}
对结构成员的值设置导致以下三种情况中的一种:
1)如果l_onoff为0,则选项关闭,l_linger的值被忽略且缺省设置生效:close立即返回
2)如果l_onoff为非0且l_linger为0,那么当套接口关闭时TCP夭折连接。TCP将丢弃保留在套接口发送缓冲区中的任何数据
并发送一个RST给对方,而不是通常的四分组连接终止序列。
3)如果l_onoff为非0且l_linger也为非0,那么当套接口关闭时内核将拖延一段时间。也就是说,如果在套接口发送缓冲区
中仍残留有数据,进程将处于睡眠状态,一直到所有数据都已发生完且均被对方确认或延迟时间到。如果套接口被设置为
非阻塞型,它将不等待close完成,即使延迟时间为非0也是如此。
当使用SO_LINGER选项的这个特性时,应用进程检查close的返回值是非常重要的,因为,如果在数据发送完并被
确认前延迟时间到的话,close将返回EWOULDBLOCK错误且套接口发送缓冲区中的任何数据都丢失。
当关闭连接的客户端时,根据所调用函数以及是否设置了SO_LINGER选项,可以在以下三个不同的时机返回:
1)close立即返回,不等待
2)close一直拖延到接受了FIN的ACK
3)后跟一个read调用的shutdown一直等到接受了对方的FIN才返回。
2、SO_DONTLINGER选项
此选项若为真,则SO_LINGER选项被禁止
3、SO_REUSEADDR选项
此选项为以下四个不同的目的提供服务:
1)允许启动一个监听服务器并捆绑其众所周知端口,即使以前建立的将此端口用作它们的本地端口的连接仍存在。
2)允许在同一端口上启动同一服务器的多个实例,只要每个实例捆绑一个不同的本地IP地址即可。
3)允许单个进程捆绑同一端口到多个套接口上,只要每个捆绑指定不同的本地IP地址接口。
4)允许完全重复的捆绑:当一个IP地址和端口绑定到某个套接口上时,还允许此IP地址和端口捆绑到另一个套接口上。一般说来,
这个特性仅在支持多播的系统上才有。
我们以下面的建议来总结这个讨论:
1)在所有TCP服务器程序中,在调用bind之前设置SO_REUSEADDR套接口选项。
2)当编写一个同一时刻在同一主机上可以运行多次的多播应用程序时,设置SO_REUSEADDR套接口选项,并将本组的
多播地址作为本地IP地址捆绑。
四、常见TCP状态的产生原因及解决
1、CLOSE_WAIT
程序处于CLOSE_WAIT状态,而不是LAST_ACK状态,说明还没有发FIN给Server,那么可能是在关闭连接之前还有许多数据
要发送或者其他事要做,导致没有发这个FIN。
在检测到错误时,应该调用closesocket保证给对方发送一个FIN过去。
有时候会有数千个连接处于这种状态,是什么原因呢?
a:原因可能是没有设置SO_REUSEADDR选项,当一个端口不行的时候,就换一个新的端口,导致数千个端口处于这种状态。
一般来说,CLOSE_WAIT会维持至少2个小时,可以通过设置TCPIP的keepalive参数来修改这个时间。
2、TIME_WAIT
出现很多TIME_WAIT状态,具体现象一般是对于一个处理大量短连接的服务器,如果是由服务器主动关闭客户端的连接,将导致服务器
端存在大量的处于TIME_WAIT状态的socket,严重影响服务器的处理能力,甚至耗尽可用的socket,停止服务。
解决方法:
1)可以通过设置参数,来缩短TIME_WAIT的等待时间
2)除非必要,应尽量让客户端发起断开操作。
五、总结
网络编程最重要的还是要理解底层的实现机制,至少要清楚协议以及常见的状态变迁,常见的问题处理。
TCPIP协议越通透,网络编程越健壮。
网络编程基本上在任何公司都会涉及到,所以这也是需要最关注的。