TCP的网络连接建立过程

套接字描述符
套接字描述符是int类型的。套接字描述符是文件描述符的一种,是UNIX系统中内核对各种类型文件的标识。

套接字地址结构体
sockaddr_in结构体
struct sockaddr_in {
     short sin_family; 
/* Address family 一般来说 AF_INET(地址族)PF_INET(协议族 )*/
     unsigned short sin_port; 
/* Port number (必须要采用网络数据格式,普通数字可以用htons()函数转换成网络数据格式的数字) */
     struct in_addr sin_addr; 
/* Internet address 网络地址 */
     unsigned char sin_zero[8];
/* Same size as struct sockaddr 没有实际意义,只是为了跟SOCKADDR结构在内存中对齐 */
};

sockaddr结构体
这个网络地址结构体在大小上和sockaddr_in一致,但是字段上划分的比较省略。

struct sockaddr { 
unsigned short sa_family; // 2 bytes address family, AF_xxx 
char sa_data[14]; // 14 bytes of protocol address 
};

该结构体是在操作系统内部处理时使用的, 我们编写程序时不使用它,而应该使用sockaddr_in结构体。


服务器端的套接字:
在服务器端一般会有两种套接字。一种是socket函数所创建的服务器连接监听套接字,另一个是accept函数所创建的客户端连接通讯套接字。
前者,一直在为服务器监听端口的连接请求,即处于LISTEN状态,当新连接请求到达时(收到客户端发来的SYN报文)该套接字会复制出一个新套接字,并让新套接字进入SYN_RECEIVED状态,新套接字发回SYN-ACK报文给客户端,当新套接字收到客户端发来的ACK连接确认时,则进入ESTABLISHED状态(三次握手完毕),并会唤醒accept的阻塞或者事件到达信号。
后者,就是这个复制来的新套接字,其只为这一个客户端的通讯服务。

1、创建服务器端的套接字

server_sockfd = socket(PF_INET, SOCK_STREAM, 0))

参数一,是协议族,PF_NET或AF_NET,但这俩在linux里其实是同一个宏。

参数二,是套接字的类型,流套接字类型为SOCK_STREAM、数据报套接字类型为SOCK_DGRAM、原始套接字SOCK_RAW。

参数三,是通信类型,在Internet通讯域中,此参数一般取值为0,系统会根据套接字的类型决定应使用的传输层协议。

该函数会在操作系统内核中创建一个套接字,并把描述符返回出来,该套接字用于监听服务器端口的连接请求。在Linux下如果失败则返回-1。


2、将服务器端套接字绑定到特定端口

bind(server_sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr))

参数一,是要绑定的服务器端套接字的描述符。

参数二,是指向服务器端的网络地址结构体的指针。

参数三,是地址结构体的大小。

该函数将服务器套接字与服务器端口绑定起来,下一步的监听就会只监听该端口。函数成功返回0,失败返回-1。


3、监听服务器端的套接字描述符

listen(server_sockfd, 5);  

参数一,是要监听的套接字描述符。

参数二,是连接请求队列(未完成连接队列?)的最大长度。但这个参数并不被用来控制客户端连接数量。

该函数将刚才创建好的套接字设定为被动连接套接字(服务器一般是被动的)。没有错误时会返回0。

对于一个监听着的套接字,内核会为其维持着两个队列。

1、未完成的连接队列。

listen_sock结构用于保存SYN_RECV状态的连接请求块,所以也叫半连接队列。

An incomplete connection queue, which contains an entry for each SYN that has arrived from a client for which the server is awaiting completion of the TCP three-way handshake. These sockets are in the SYN_RCVD state (Figure 2.4).

2、已完成的连接队列。

A completed connection queue, which contains an entry for each client with whom the TCP three-way handshake has completed. These sockets are in theESTABLISHED state (Figure 2.4).

启动监听时,做的工作主要包括:
1. 创建半连接队列,全连接队列。
2. 初始化sock的一些变量,把它的状态设为TCP_LISTEN。
3. 检查端口是否可用,防止bind()后其它进程修改了端口信息。
4. 把sock连接进入监听哈希表listening_hash中。


4、等待客户端的连接请求

client_sockfd = accept(server_sockfd, (struct sockaddr *)&remote_addr, &sin_size)

参数一,正在监听的服务器端套接字描述符。

参数二,这是函数返回方式的参数,用于存储接收到的连接的客户端网络地址结构体。

参数三。这也是函数返回的数据,用于存储参数二所指向区域的长度。

从未完成的连接队列中提取一个连接并处理,如果队列里没有连接请求,函数会阻塞在这个地方。如果失败则返回-1,如果成功则返回一个新的套接字描述符,表示与该客户端连接的套接字,之后服务器对客户端的数据通信(send/recv)都是使用该套接字。


客户端的套接字:

在客户端只有一个套接字,先是用于与服务器端建立连接,之后用于与服务器端通信。建立连接时该套接字发送SYN报文给服务器,进入SYN_SENT状态。当收到服务器的SYN_ACK报文后进入ESTABLISHED状态,开始正式通信。

1、创建客户端的套接字(和服务器端的创建方式一致)

2、将客户端套接字连接到服务器端

connect(client_sockfd, (struct sockaddr *)&remote_addr, sizeof(struct sockaddr))

参数一,客户端的套接字描述符。

参数二,指向服务器端的网络地址结构体。

参数三,服务器端网络地址结构体的大小。

这时客户端会向服务器端发出请求连接(SYN报文),等待服务器端的accept函数来响应。成功则返回0,失败返回-1。


TCP连接建立阶段的状态
LISTEN     
服务器端状态。
等待连接请求。服务器端调用listen()函数即进入该LISTEN状态。

SYN-SENT     
客户端状态。
等待服务器的连接响应。客户端调用connect()函数即进入该状态,同时向服务器端发送SYN报文。

SYN-RECEIVED     
服务器端状态。
等待客户端的连接确认。服务器端收到了SYN报文,则进入该状态,并发回SYN-ACK报文。

ESTABLISHED
服务器/客户端状态。
客户端在收到服务器的SYN-ACK报文后从connect()阻塞中唤醒,进入该状态;服务器端在收到客户端的连接确认报文后从accept()阻塞中唤醒,进入该状态。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章