第二章 传输层
TCP套接字各个状态解释说明
CLOSED: 这个没什么好说的了,表示初始状态。
LISTEN: 这个也是非常容易理解的一个状态,表示服务器端的某个SOCKET处 于监听状态,可以接受连接了。
SYN_RCVD: 这个状态表示接受到了SYN报 文,在正常情况下,这个状态是服务器端的SOCKET在建立TCP连接时的三次握手会话过程中的一个中间状态,很短暂,基本上用netstat你是很难看到这种状态的,除非你特意写了一个客户端测试程序,故意将三次TCP握手 过程中最后一个ACK报文不予发送。因此这种状态时,当收到客户端的ACK报文 后,它会进入到ESTABLISHED状态。
SYN_SENT: 这个状态与SYN_RCVD遥想呼应,当客户端SOCKET执行CONNECT连接时,它首先发送SYN报文,因此也随即它会进入到了SYN_SENT状态,并等待服务端的发送三次握手中的第2个报文。SYN_SENT状态表示客户端已发送SYN报文。
ESTABLISHED:这个容易理解了,表示连接已经建立了。
FIN_WAIT_1: 这个状态要好好解释一下,其实FIN_WAIT_1和FIN_WAIT_2状态的真正含义都是表示等待对方的FIN报 文。而这两种状态的区别是:FIN_WAIT_1状态实际上是当SOCKET在ESTABLISHED状态时,它想主动关闭连接,向对方发送了FIN报文,此时该SOCKET即进入到FIN_WAIT_1状态。而当对方回应ACK报文后,则进入到FIN_WAIT_2状态,当然在实际的正常情况 下,无论对方何种情况下,都应该马上回应ACK报文,所以FIN_WAIT_1状态一般是比较难见到的,而FIN_WAIT_2状态还有时常常可以用netstat看到。
FIN_WAIT_2:上面已经详细解释了这种状态,实际上FIN_WAIT_2状态下的SOCKET,表示半连接,也即有一方要求close连接,但另外还告诉对方,我暂时还有点 数据需要传送给你,稍后再关闭连接。
TIME_WAIT: 表示收到了对方的FIN报 文,并发送出了ACK报文,就等2MSL后即可回到CLOSED可用状态了。如果FIN_WAIT_1状态下,收到了对方同时带FIN标 志和ACK标志的报文时,可以直接进入到TIME_WAIT状态,而无须经过FIN_WAIT_2状态。
CLOSING: 这种状态比较特殊,实际情况中应该是很少见,属于一种比较罕见的例外状态。正常情况下,当你发 送FIN报文后,按理来说是应该先收到(或同时收到)对方的ACK报 文,再收到对方的FIN报文。但是CLOSING状态表示你发送FIN报文后,并没有收到对方的ACK报 文,反而却也收到了对方的FIN报文。什么情况下会出现此种情况呢?其实细想一下,也不难得出结论:那就是如果双方几乎在同时close一 个SOCKET的话,那么就出现了双方同时发送FIN报文的情况,也即会出现CLOSING状态,表示双方都正在关闭SOCKET连接。
CLOSE_WAIT: 这种状态的含义其实是表示在等待关闭。怎么理解呢?当对方close一 个SOCKET后发送FIN报文给自己,你系统毫无疑问地会回应一个ACK报文 给对方,此时则进入到CLOSE_WAIT状态。接下来呢,实际上你真正需要考虑的事情是察看你是否还有数据发送给对方,如果没有的话, 那么你也就可以close这个SOCKET,发送FIN报文给对方,也即关闭连接。所以你在CLOSE_WAIT状态下,需要完成的事情是等待你去关闭连接。
LAST_ACK: 这个状态还是比较容易好理解的,它是被动关闭一方在发送FIN报 文后,最后等待对方的ACK报文。当收到ACK报文后,也即可以进入到CLOSED可用状态了。
第三、四章 TCP套接字编程
1 | socket connect bind listen accept close getsockname getpeername |
第五章 SIGCHLD、SIGPIPIE 等异常情况分析
第六章 I/O复用 :select poll
第七章 套接字选项
以下套接字是由TCP已经连接的套接字从监听套接字继承来
1
2
3SO_DEBUG SO_DONTROUTE SO_KEEPALIVE SO_LINGER SO_OOBINLINE
SO_RCVBUF SO_RCVLOWAT SO_SNDBUF SO_SNDLOWAT
TCP_MAXSEG TCP_NODELAYSO_BROADCAST
开启或者关闭进程的广播消息能力注意仅适用与UDP,并且还必须是支持广播消息的网络上比如:以太网、令牌环网等
SO_DEBUG
仅支持TCP,开启本选项时TCP在该套接字发送接收的所有分组保留详细跟踪信息。信息保存在内核的某个环形缓冲区。
SO_DONTROUTE
本选项规定外出的分组将绕过底层协议的正常路由机制
SO_ERROR
N/ASO_KEEPALIVE
给以他tcp套接字设置保活选项后,如果在两个小时内该套接字任一方向都没有数据交换,tcp就自动给对端发送一个保持存活探测分节,这个是对端必须响应的tcp分节,它会导致一下三种情况之一:
- 对端以期望的ACK响应
- 对端以RST响应,它告知本端TCP:对端已经崩溃且已经重启。该套接字的待处理错误置为ECONNRESET,套接字本身则被关闭
- 对端对保持存活探测分节没有任何响应。 如果根本没有响应该套接字的待处理错误置为ETIMEOUT套接字本身则被关闭
另外可能还需要结合一下选项设置间隔、次数:1
2
3
4
5
6
7
8int keepAlive = 1;
Setsockopt(listenfd, SOL_SOCKET, SO_KEEPALIVE, (void*)&keepAlive, sizeof(keepAlive)); //打开自动探测
int keepIdle = 1000;
int keepInterval = 10;
int keepCount = 10;
Setsockopt(listenfd, SOL_TCP, TCP_KEEPIDLE, (void *)&keepIdle, sizeof(keepIdle)); //首次探测需要空闲多久才探测
Setsockopt(listenfd, SOL_TCP,TCP_KEEPINTVL, (void *)&keepInterval, sizeof(keepInterval));//两次探测时间间隔
Setsockopt(listenfd,SOL_TCP, TCP_KEEPCNT, (void *)&keepCount, sizeof(keepCount)); //发送几次探测
SO_LINGER
本选项制定close函数对面向链接的协议如何操作。默认的操作是立即返回(l_onoff=0);如果l_onoff != 0 && l_linger!=0 那么将会等待数据和FIN的确认后close才返回(至于等待多久由l_linger 指定,如果该值指定的时间太短,那么close将会返回-1且errno将会被设置成EWOULDBLOCK)
本选项设置后close返回只是告诉我们先前发送的数据和FIN已由对端TCP确认,而不能告诉我们对端应用进程是否已经读取数据。如果没有设置该选项,那么我们连对端TCP是否确认了数据都不能知道SO_OOBINLINE
开启本选项带外数据将被留在正常的输入队列中,这种情况下接收函数指定量MSG_OOB标志不能用来读取带外数据。
SO_RCVBUF SO_SNDBUF
这两个选项用来设置TCP、UDP套接字接收、发送缓冲区大小。在设置这两个选项时函数调用顺序需要注意:
`TCP是在建立连接时通过SYN分节与对端互换得到的,对于客户端SO_RCVBUF必须在connect之前设置;对于服务器则必须在调用listen之前设置,因为accept知道TCP三次握手完成之后才会创建并返回已连接套接字。套接字缓冲区大小总是有新建的已连接的套接字从监听套接字继承而来。建议设置缓冲区大小为相应连接的MSS值4倍·[这点可以一句TCP快速恢复算法的工作机制:TCP发送端使用3个重复确认来检测某个分节是否丢失,如果发现分节丢失,接收端将给新收到的每个分节发送一个重复的确认,如果窗口大小不足以存放4个这样的分节那么就不可能连发三个重复确认,从而无法激活快速恢复算法]SO_RCVLOWAT SO_SNDLOWAT
接收低水位 发送低水位 :TCP UDP SCTP接收低水位默认为1;TCP 发送低水位默认位2048。UDP页使用发送低水位,由于UDP套接字的发送缓冲区中可用的字节数从不改变(因为UDP并不为进程传递给它的数据保留副本),只要一个UDP套接字的发送缓冲区大小大于该套接字的低水位是UDP套接字总是可写的。
SO_RCVTIMEO SO_SNDTIMEO
套接字接收、发送超时设置
SO_REUSEADDR
它允许启动一个监听服务其并且捆绑在其众所周知的端口,即使以前建立的该端口用作他们的本地端口
它允许在同一端口上启动同一服务器(单个进程)的多个实例,只要每个实例绑定一个不同的本地ip地址即可
对于TCP我们绝不可能启动捆绑相同IP地址和相同端口的多个服务器,也就是说我们不能在启动198.69.10.2:80 的服务器后又在启动198.69.10.2:80的另外一个服务器,解释我们设置量SO_REUSEADDR也不行。
该选项需要在bind之前设置SO_REUSEPORT
本选项允许完全重复的绑定不过只有在想要捆绑同一个ip和端口的每个套接字都指定量本套接字选项才行
SO_TYPE
本选项返回的是套接字的类型(整数值),例如:SOCK_STREAM SOCK_DGRAM。该选项通常由启动是继承了套接字的进程使用
SO_USELOOPBACK
本选项仅用于AF_ROUTE(路由域)的套接字他的默认设置为打开(
这是唯一一个默认值位打开而不是关闭的SO_xx二元套接字选项
).
开启本选项相应的套接字将接收在其上发送的任何数据报的一个副本IP_HDRINCL
它是给一个原始的IP套接字设置的那么我们必须位所有在该原始套接字删该法送的数据包构造自己的IP首部(一般情况下该首部是有内核构造的不过类似traceroute需要自己构造,以取代IP置于该首部中的某些字段)
以下字段存在例外:- IP总是计算并存储IP首部校验和
- 如果我们将IP标识字段置为0,内核将设置该字段
- 如果源IP地址是INADDR_ANY,IP将把它设置位外出接口的主IP地址
- IP首部中有的字段必须以主机字节序填写,有些字段必须以网络字节序填写
IP_OPTIONS
设置本选项将允许我们设置IPv4首部中设置IP选项
IP_RECVDSTADDR
该选项导致所有接收UDP数据包的目的IP地址由recvmsg函数作为服务数据返回
IP_RECVIF
该选项导致所有接收UDP数据包的接收接口由recvmsg函数作为服务数据返回
IP_TOS
该选项允许TCP UDP SCTP 套接字设置IP首部中的服务类型字段
IP_TTL
本选项允许设置获取系统用在从某个给定套接字发送的单播组上的默认TTL值(多播由IP_MULTICAST_TTL选项设置)
TCP_MAXSEG
本选项允许我们获取或者设置TCP链接的最大分节大小(MSS),返回值是我们的TCP可以发送给对端的最大数据量,它通常是由对端使用SYN分节通告MSS,除非我们的TCP选项使用一个比对端通告的MSS小的值。
TCP_NODELAY
开启本选项监会禁止TCP Nagle算法,默认情况下该算法是启动的。
第八章 UDP套接字编程
UDP的connect函数
UDP socket也可以调用connect,然而虽然调用了connect但是它不会跟TCP一样进行三次握手。内核只是检查
是否存在立即可知的错误(例如目的不可达)和记录对端的IP地址和端口号
注意通过socket创建的UDP默认是位连接的;同时已链接的UDP套接字仅仅与一个IP地址交换数据报,因为connect到广播或者多播地址也是可能的。
对于已经链接的UDP有以下特点:- 再也不能给该UDP套接字输出操作指定目的IP地址和端口号,也就是是我们使用sendto时第五个参数(套接字地址接口指针)必须为NULL,第六个参数必须为0(套接字地址结构大小);或者直接使用write、send、sendmsg进行输出
- 同时也不必使用recvfrom来获取数据报的发送者,而改用read、write或者recvmsg
- 已链接的UPD套接字引发的异步错误会返回给他们所在的进程,而未连接的UDP套接字不接受任何异步错误。
对于已经连接的UDP可以调用多次connect(指定新的ip地址和端口号),这点与TCP不同,tcp套接字只允许调用依次connect
如何断开UPD链接? 再次调用connect此时sin_family 指定为AF_UNSPEC 这样就会断开已经链接的套接字,可能会返回EAFNOSUPPORT,不过没有关系。
链接与未链接的UDP套解字性能如何呢?
对于未链接的UDP套解字每次发送数据时呢和暂时链接该套接字发送数据,然后断开。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
1
2
3
4
5
6
7
8
9
10 int main()
11 {
12 struct sockaddr_in addr;
13 bzero(&addr,sizeof(addr));
14 addr.sin_family = AF_INET;
15 addr.sin_port = htons(80);
16 inet_pton(AF_INET,"127.0.0.1",&addr.sin_addr);
17
18 int sock = socket(AF_INET,SOCK_DGRAM,0);
19 int ret=connect(sock,(struct sockaddr*)&addr,sizeof(addr));
20 LOG("connect 1 ret=%d\n",ret);
21 ret=connect(sock,(struct sockaddr*)&addr,sizeof(addr));
22 LOG("connect 2 ret=%d\n",ret);
23
24 addr.sin_port = htons(8080);
25 ret=connect(sock,(struct sockaddr*)&addr,sizeof(addr));
26 LOG("connect 3 ret=%d\n",ret);
27 ret = write(sock,"123",3);
28 LOG("write ret=%d: %s\n",ret,strerror(errno));
29 addr.sin_family = AF_UNSPEC;
30 ret = connect(sock,(struct sockaddr*)&addr,sizeof(addr));
31 LOG("connect 4 ret=%d\n",ret);
32 ret = write(sock,"123",3);
33
34 LOG("write ret=%d: %s\n",ret,strerror(errno));
35 return 0;
36 }
output:
udp_connect.cpp main 23:connect 1 ret=0
udp_connect.cpp main 25:connect 2 ret=0
udp_connect.cpp main 29:connect 3 ret=0
udp_connect.cpp main 30:write ret=3: Success
udp_connect.cpp main 33:connect 4 ret=0 ====>close UPD connect
udp_connect.cpp main 35:write ret=-1: Destination address required