Linux网络编程笔记

Linux网络编程笔记
Linux网络编程笔记

服务器在accept()后一直阻塞到客户端connect()进行TCP三路握手。

Linux网络编程笔记
第一次握手:建立连接时,客户端发送 syn 包(tcp协议中syn位置1,序号为J)到服务器,并进入 SYN_SEND 状态,等待服务器确认;
第二次握手:服务器收到 syn 包,必须确认客户的 SYN,同时自己也发送一个 SYN 包,即 SYN+ACK包(tcp协议中syn位置1,ack位置1,序号K,确定序号为J+1),此时服务器进入 SYN_RECV 状态;
第三次握手:客户端收到服务器的 SYN+ACK 包,向服务器发送确认包 ACK(tcp协议中ack位置1,确认序号K+1),此包发送完毕,客户端和服务器进入 ESTABLISHED 状态,完成三次握手。
三次握手必要性:为了保证服务端能收接受到客户端的信息并能做出正确的应答而进行前两次(第一次和第二次)握手,为了保证客户端能够接收到服务端的信息并能做出正确的应答而进行后两次(第二次和第三次)握手。

关闭时四次挥手

客户端 服务器
Linux网络编程笔记
一般要求客户端先关闭
1)客户端 A 在应用层调用close时会激发底层发送一个 FIN(tcp协议中FIN位置1、序号为M,结合上图分析)请求,用来关闭客户 A 到服务器 B 的数据传送,客户端A此时处于半关闭状态(应用层无法接收数据但底层还可以接收数据);
2)服务器 B 底层收到客户端A的FIN时会做两件事
第1件事:收到客户端A的FIN时底层会主动回发一个ACK(tcp协议中ACK位置1,确认序号M+1)
第2件事:收到客户端A的FIN时,导致服务器B的应用层read()返回0(告诉服务器B应用层:客户端A关闭了)
3)服务器B应用层调用close()激发底层给客户端 A 发送一个 FIN(tcp协议中FIN位置1、序号为N),这是服务器B已处于半关闭状态;
4)客户端 A 底层回发 ACK(tcp协议中ACK位置1,确认序号N+1) 给服务器B,这是客户端A、服务器B都处于完全关闭状态,回收相应的资源。

为什么连接的时候是三次握手,关闭的时候却是四次握手?
因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉Client端,“你发的FIN报文我收到了”。只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步握手。

socket 编程的基本函数

int socket(int family, int type, int protocol):该函数用于建立一个 socket 连接,可指定 socket 类型等信息。在建立了 socket 连接之后,可对 sockaddr 或 sockaddr_in 结构进行初始化,以保存所建立的 socket 地址信息。

int bind(int sockfd, struct sockaddr *my_addr, int addrlen):该函数是用于将本地 IP 地址绑定到端口号,若绑定其他 IP 地址则不能成功。另外,它主要用于 TCP 的连接,而在 UDP 的连接中则无必要。

int listen(int sockfd, int backlog):在服务端程序成功建立套接字和与地址进行绑定之后,还需要准备在该套接字上接收新的连接请求。此时调用 listen()函数来创建一个等待队列,在其中存放未处理的客户端连接请求。listen()函数不会阻塞,它主要做的事情为,将该套接字和套接字对应的连接队列长度告诉 Linux 内核,然后,listen()函数就结束。

client_fd = int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen):服务端程序调用 listen()函数创建等待队列之后,调用 accept()函数等待并接收客户端的连接请求。它通常从由 bind()所创建的等待队列中取出第一个未处理的连接请求。

int connect(int sockfd, struct sockaddr *serv_addr, int addrlen):该函数在 TCP 中是用于 bind()的之后的 client 端,用于与服务器端建立连接,而在 UDP中由于没有了 bind()函数,因此用 connect()有点类似 bind()函数的作用。通常的情况,客户端的 connect() 函数默认会一直阻塞,直到三次握手成功或超时失败才返回(正常的情况,这个过程很快完成)。

int send(int sockfd, const void *msg, int len, int flags),
int recv(int sockfd, void *buf,int len, unsigned int flags)
:这两个函数分别用于发送和接收数据,都会阻塞。可以用在 TCP 中,也可以用在 UDP 中。当用在 UDP 时,可以在 connect()函数建立连接之后再用。
服务器:前面几个函数使用的文件描述符为sockfd,recv使用accept()函数的返回值client_fd 作为该函数的sockfd描述符。send()继续使用sockfd作为描述符。
客户端:继续使用sockfd作为描述符。