网络通信 - Socket 套接字(TCP+UDP)
Socket套接字(TCP)
1、socket位于应用层和运输层之间

Socket充当了应用层与TCP/IP协议族之间的通信中介,并由一组接口构成。从设计模式角度来看,在此框架下(Socket)扮演的就是典型的门面角色。它通过将复杂的TCP/IP协议家族巧妙地隐藏在其后的Socket接口之下,并为用户提供了一套相对简单的统一界面就涵盖了所有内容。让 Socket 负责组织数据传输过程以便遵循指定的通信规范或标准。
2、Socket的模型

从服务器端的角度出发,在启动时会执行一系列操作:首先创建一个Socket实例,并将其与指定端口进行绑定;随后开启监听功能以开始接收客户端请求;此时系统会阻塞以等待客户端主动发起连接请求。一旦客户端尝试连接时,在此过程中会自动创建一个新socket对象并尝试与其建立关联;如果连接成功,则表示客户端与服务器之间已形成通信通道。在此通道下,客户端能够发送数据至服务器端进行处理,并接收服务器返回的数据反馈;当所有操作完成后系统将关闭该通道并释放相关资源
3、Socket的函数构成
3.1、socket()函数
int socket(int domain, int type, int protocol);
-
域名:类型(types)。如:AF_INET、AF_INET6、AF_LOCAL、AF_ROUTE等类型。这些类型决定了socket的地址格式,在通信过程中必须遵循相应的地址规范。例如AF_INET指定使用IPv4地址(32位)与端口号(16位)相结合的方式、AF_UNIX则要求使用绝对路径名作为地址。
-
类型:socket类型。常见的包括SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET等类型。
-
协议:即指所使用的传输协议。常见的有IPPROTO_TCP(TCP传输协议)、IPPROTO UDP(UDP传输协议)、IPPROTO SCTP(STCP传输协议)、IPPROTO TIPC(TIPC传输协议)等。
注意:并非任意type与protocol组合都是允许的,例如SOCK_STREAM不能与IPPROTO UDP搭配使用。当protocol参数设置为0时,默认采用与type对应的默认传输协议。- 返回值:成功返回描述符、失败返回-1
3.2、bind()函数
int bind(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen);
- sockfd:表示socket标识符,并对应socket()函数返回值。
- serv_addr:const sockaddr struct 指针变量
//IPv4对应的是:
struct sockaddr_in {
sa_family_t sin_family;
in_port_t sin_port;
struct in_addr sin_addr;
};
struct in_addr {
uint32_t s_addr;
};
- addrlen:对应的是地址的长度。
- 返回值:成功返回0,失败返回-1
3.3、listen()
int listen(int sockfd, int backlog);
*(sockfd): 用于处于监听准备状态的套接字。
backlog表示完成了三次握手并等待被接受的请求连接消息队列长度。(其实这个参数从没有被正式定义过,有的说法是已完成及未完成三次握手的消息队列长度,这方面的信息较为模糊)
- 返回值: 成功时正常返回0;在出现错误时则返回-1
3.4、connect() 函数
int connect(int sockfd, struct sockaddr *serv_addr, socklen_t addrlen);
- 各参数及其绑定函数与 bind() 相同,无需详细说明.
- 客户端通过调用 connect 函数采用 TCP 方式建立连接.
- 返回码:正常情况下返回 0,在异常情况中返回 -1.
3.5、accept() 函数
int accept(int sockfd, struct sockaddr *cli_addr, socklen_t *addrlen);
*(sockfd):服务器端口分配字节节。
*cli_addr:为获取客户端参与通信的地址而设置的一个指针变量。
*addrlen:指定客户端参与通信地址长度信息所需的内存空间大小。
- 返回值:
成功时将新的网络接口配置信息编码并发送给相关设备;
失败时则会报错并返回错误码-1.
注意 :accept函数的第一个参数命名为 listening socket descriptor(即监听端口描述字),而该函数返回的是connected socket descriptor(即已连接端口描述字)。一般情况下,一个服务器进程通常只会生成一个监听端口描述字,在其运行周期内持续存在。内核为每个通过accept函数建立的客户端连接生成一个相应的已连接端口描述字;当服务器完成对某个客户端的服务请求后,则会自动关闭该已连接端口描述字。
3.6、send() 函数
int send(int sockfd, const char *buf, int len, int flags);
套接字sockfd表示将要发送的数据。
缓冲区buf存储了即将发送的数据内容。
数据长度len表示待传输的信息量。
传输选项flags通常设置为默认值0或无指定值。
返回信息包括已传输的数据量len,在成功情况下;若发生错误则返回-1。
3.7、recv()函数
int recv( int sockfd, char *buf, int len, int flags);
- sockfd:用于接收数据的 socket(套接字)。
- buf:所接收数据的缓冲区域起始地址。
- len :buf 的长度。
- flags:发送数据时所使用的选项参数(设置为 0 或 NULL)。
- 返回值:成功时返回 recv 到的数据量;若失败则返回 -1。
注意:在Unix操作系统中,在程序试图接收数据但因网络中断而无法进行时(即发生数据接收失败的情况),相关进程将接收到 SIGPIPE 信号。该进程对此信号的默认处理方式即为终止。
3.8、close()函数
int close(int fd);
- close一个TCP
the default behavior marks the socket as closed, and immediately returns to the calling process. The term cannot be used by the calling process, meaning it cannot be used as the first parameter for recv or send.
4、主机字节序、网络字节序
主机字节序即为我们通常所说的大小端模式:不同类型的处理器具有各自的字节顺序设定,在内存中存储整数时遵循特定的排列规则;这一设定即为主机字节序。参考标准定义中,Big-Endian(大端)和Little-Endian(小端)分别指的是:
\text{Big-Endian: 最高位存放在内存最低地址位置} \\ \text{Little-Endian: 最低位存放在内存最低地址位置}
Little-Endian将低字节放置于内存低端位置,并将高字节放置于内存高端位置。
Big-Endian refers to the arrangement where high-order bytes are placed in the memory's lower address portion, and low-order bytes are positioned in the memory's higher address portion.
网络字节序列:由4个字节构成的32位二进制数值按照以下顺序传递:首先是第0至7位(最低有效位),接着是第8至15位(第二高位),然后是第16至23位(第三高位),最后是第24至31位(最高有效位)。这种排列顺序被称为大端字节序列。值得注意的是,在TCP/IP头部中所有的二进制整数在传输时都必须遵循这一顺序。因此,在计算机术语中将其称为网络字节序列也是合理的。简而言之,“字节序列”就是指在一个包含多个字节数据块中数据存储的具体排列方式——单个数据块内的数据是有特定存储位置顺序的,并非单纯地依据单个数据块本身的物理位置来决定其存储位置。
注意:在将一个地址绑定至socket时,请务必将其转换为网络字节顺序后再赋予socket。
htonl() //"Host to Network Long"
ntohl() //"Network to Host Long"
htons() //"Host to Network Short"
ntohs() //"Network to Host Short"
5、IP地址格式转换
5.1、inet_aton()函数
int inet_aton(const char * strptr, struct in_addr *addrptr)//有效返回1、无效发挥0
负责将strptp指向的点分割成十进制字符串,并经编码生成32位网络序二进制数;该二进制数据则由addrptp保存,并在5.2节中涉及inetrst函数
in_addr_t inet_addr(const char * strptr)
该函数用于将由strptr所指的十进制字符串转换为32位二进制网络序列,并以返回值的形式提供结果。已弃用该功能。
5.3、inet_ntoa()函数
int inet_ntoa(struct in_addr inaddr)//返回指向点分十进制字符串的指针
6、TCP三次握手建立连接,四次握手断开连接

- 当客户端发起connect操作时,产生了连接请求,随后将SYN J包发送给服务器,随后该操作导致connect处于阻塞状态;
- 服务器接收到SYN J包后启动接受函数,进而向客户端发送SYN K ,并确认ACK信息为ACK
J+1,这一过程使得accept函数进入阻塞状态;
- 服务器接收到SYN J包后启动接受函数,进而向客户端发送SYN K ,并确认ACK信息为ACK
当客户端接收到服务器发送的SYN报文K段以及ACK报文J+1后,在随后会返回SYN K确认报文;与此同时,在服务器接收到该SYN报文的ACK响应K+1段时,则会立即返回accept确认;至此完成三次握手过程;连接得以建立。
总结:客户端连接操作在三次握手中第二个环节中返回响应;而服务器端接受操作在三次握手中最后一个环节中也进行了响应。

- 该应用进程首先通过调用close函数主动发起连接关闭请求;
- 此时TCP端发送了一个FIN标志位(M)。
- 另一端接收到该 FIN
M之后执行被动式关闭操作,并核实该 FIN标志位的有效性。 - 其接收也被视为文件结束符并返回给该应用进程。
经过一段时间后,当接收方检测到文件结束符时,应用进程会通过调用close方法关闭相关socket。这一操作使得其对应的TCP连接也会发送一个FIN标志位( FIN N)。
接收到该FIN标志位后,源端的TCP连接进行了确认响应。
Socket套接字(UDP)
1、UDP Socket模型

2、sendto ()函数
int sendto (int sockfd, const void *buf, int len, int flags,
const struct sockaddr *addr, int addrlen);
标识套接口描述字sockfd;
用于传输数据的数据缓冲区buf;
数据长度len;
*表示调用方式的状态位 flags;
(可选)目标套接口地址地址addr;
(可选)*目标地址长度 addrlen。
3、recvfrom()函数
int recvfrom(int sockfd, void *buf, int len, int flags,
struct sockaddr *addr, int *addrlen);
- sockfd:一个接口标识符。
- buf:缓存区。
- len:buf缓存区中数据的长度字段。
- flags:标志位字段。
- addr:返回源套接口地址的指针。
- addrlen:返回addr所指地址的数据长度字段。
