#技术笔记

1.基于TCP的Socket通信流程

先介绍几个重要的结构体和转化函数,后面会用到如bind函数等(如下图)

这里主要是IP地址的填充要用到下面三个函数,这里介绍一下比较绕的第一个函数inet_aton,如下图。

1.1 socket()

socket函数的三个参数,第一个传协议,如 AF_INET 或 AFINET6,用Address Family InterNET很好记;第二个传套接字类型,如SOCK_STREAM,SOCK_DGRAM,一个TCP的一个UDP的,前者TCP,因为TCP是字节流传输,后者是UDP,因为传输的是数据包(DataGRAM),也很好记;第三个传协议,如 IPPROTO_TCP、IPPROTO_UDP,用Internet Protocol + PROtocol + TCP 或UDP来记忆。返回值是一个文件描述符,用于标识创建的套接字。(下图为TCP基本通信流程)

实际上,socket函数本质是在内核态中创建了一个对象,在这个对象中包括了进行网络通信的各种信息,如 Address Family,Type,Protocol 等,还维护了两个重要的缓冲区输入缓冲区SO_RCVBUF和输出缓冲区SO_SNDBUF,这两缓冲区分别用于临时存储从网络接收的数据和待发送到网络的数据。

1.2 bind()

使用bind函数来给socket端点绑定端口和IP,第一个参数传socket函数返回的文件描述符;第二个参数的处理是核心,是一个结构体指针 const struct sockaddr *addr,后面用struct sockaddr_in来强转后传入;第三个参数是第二个参数所传结构体的长度。返回值还是个int,失败是返回-1。一般来说服务器一定要绑定bind,客户端不用。

1.3 listen()

用listen函数对设置好端口和IP的服务器的socket端点监听外部连接请求,第一个参数是socket端点的文件描述符,第二个参数是指定了套接字可以挂起的最大连接数 。一旦启用listen之后,操作系统就知道这套接字是服务器端的套接字,操作系统就不再启用其发送和接收的缓冲区(前面有提两个重要缓冲区),转而在内核维护两个队列,半连接队列和全连接队列。(如下图)

实际上listen函数的第二个参数,用来设置全连接队列的最大长度(不同操作系统不一样),如果队列已满,那么服务端受到任何发起的连接都会直接丢弃。

1.4 connect()

使用connect函数让客户端向服务器发送连接建立请求,三个参数分别如下图。第二个参数依然是个结构体指针,是目标服务器的地址和端口信息,第三个是addr所代表的结构体长度。

客户端使用connect可以不使用bind来绑定,这样客户端会随机选择一个临时端口来作为源端口。如果connect成功,则代表三次握手已经成功了。

1.5 accept()

从服务端的socket端点的全连接队列取出一个连接,三个参数如下图,这个参数列表跟前一个很类似,第二个参数用来获取连接对端/客户端的地址信息,如果不需要对端信息就填NULL,如果第二个参数非NULL,就设置为第二个参数的大小,如果是NULL,就继续填NULL。

accept函数由服务端调用,如果全连接队列为空,那么就会阻塞,一旦accept执行完后,内核就会创建一个新的套接字文件对象,该文件对象关联的文件描述符是accept的返回值,文件对象中最重要的是一个发送缓冲区和一个接收缓冲区,可以用于服务端通过TCP连接发送和接收TCP段。(如下图)

1.6 send() 和 recv()

这两个可以和write 和 read一起记,参数列表如下图,在flags = 0时,send 和 write等同,recv和read等同。

这send和recv两个专门用于网络编程用的函数,在其他情况大多用write和read。

1.7 close()

客户端和服务器使用close关闭,释放端口等资源。客户端或服务器使用close函数关闭连接的时候,可能还有数据留在发送缓冲区中未被发送,close操作会试图发送这些数据,close函数给连接的对端发送FIN用于断开连接的四次挥手,等待另一端也发送FIN包,并且本端回应ACK确认。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐