Linux网络编程(3):socket、tcp通信流程、套接字的使用、tcp三次握手四次挥手

socket

简介

所谓socket(套接字),就是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象。一个套接字就是网络上进程通信的一端,提供了应用层进程利用网络协议交换数据的机制。从所处的地位来讲,套接字上联应用进程,下联网络协议栈,是应用程序通过网络协议进行通信的接口,是应用程序与网络协议根进行交互的接口。

socket可以看成是两个网络应用程序进行通信时,各自通信连接中的端点,这是一个逻辑上的概念。它是网络环境中进程间通信的API,也是可以被命名和寻址的通信端点,使用中的每一个套接字都有其类型和一个与之相连进程。通信时其中一个网络应用程序将要传输的一段信息写入它所在主机的socket中,该socket通过与网络接口卡(NIC)相连的传输介质将这段信息送到另外一台主机的socket中,使对方能够接收到这段信息。socket 是由IP地址和端口结合的,提供向应用层进程传送数据包的机制。

socket本身有“插座”的意思,在Linux环境下,于示进程间网络通信的特殊文件类型。本质为内核借助缓冲区形成的伪文件。既然是文件,那么理所当然的,我们可以使用文件描述符引用套接字。与管道类似的,Linux系统将其封装成文件的目的是为了统一接口,使得读写套接字和读写文件的操作一致。区别是管道主要应用于本地进程间通信,而套接字多应用于网络进程间数据的传递。

字节序

简介

现代CPU的累加器一次都能装载 (至少) 4字节 (这里考虑32位机) ,即一个整数。那么这4字节在内存中排列的顺序将影响它被累加器装载成的整数的值,这就是字节序问题。在各种计算机体系结构中,对于字节、字等的存储机制有所不同,因而引发了计算机通信领域中一个很重要的问题,即通信双方交流的信息单元(比特、字节、字、双字等等)应该以什么样的顺序进行传送。如果不达成一致的规则,通信双方将无法进行正确的编码/译码从而导致通信失败。

字节序,顾名思义字节的顺序,就是大于一个字节类型的数据在内存中的存放顺序(一个字节的数据当然就无需谈顺序的问题了)。

字节序分为大端字节序 (Big-Endian) 和小端字节序 (Little-Endian)(目前大部分主机采用的字节序) 。大端字节序是指一个整数的高位(进制位,如十进制的个位、十位、百位、千位等)字节(23-31 bit)存储在内存的低地址处,低位字节(0-7 bit),存储在内存的高地址处;小端字节序则是指整数的高位字节存储在内存的高地址处,而低位字节则存储在内存的低地址处。

字节序举例

  • 小端字节序:
    原始数据为0x 12 34 56 78和0x 11 22 33 44,两位一字节,都分别是四字节的数据。
  • 大端字节序:
    原始数据为0x 12 34 56 78和0x 11 22 33 44

字节序转换函数

当格式化的数据在两台使用不同字节序的主机之间直接传递时,接收端必然错误的解释之。解决问题的方法是:发送端总是把要发送的数据转换成大端字节序数据后再发送,而接收端知道对方传送过来的数据总是采用大端字节序,所以接收端可以根据自身采用的字节序决定是否对接收到的数据进行转换(小端机转换,大端机不转换)。

网络字节顺序是TCP/IP中规定好的一种数据表示格式,它与具体的CPU类型、操作系统等无关,从而可以保证数据在不同主机之间传输时能够被正确解释,网络字节顺序采用大端排序方式

BSD Socket提供了封装好的转换接口,方便程序员使用。包括从主机字节序到网络字节序的转换函数:htons、htonl;从网络字节序到主机字节序的转换函:ntohs、ntohl。

h - host主机,主机字节序
to - 转换
n - network网络,网络字节序
s - unsigned short,2字节
1 - unsigned int,4字节

#include <arpa/inet.h>

//转换端口。端口号是一个2字节16位的整数
uint16_t htons(uint16_t hostshort);    //主机字节序 -> 网络字节序
uint16_t ntohs(uint16_t netshort);     //主机字节序 -> 网络字节序

//转换IP。IP地址由4个字节,是一个32位的二进制数,
uint32_t htonl(uint32_t hostlong);    //主机字节序 -> 网络字节序
uint32_t ntohl(uint32_t netlong);     //主机字节序 -> 网络字节序

socket地址

socket地址其实是一个结构体,封装端口号和IP等信息。后面的socket相关的api中需要使用到这个socket地址。

通用socket地址

socket网络编程接口中表示socket地址的是结构体sockaddr,其定义如下:

#include <bits/socket.h>

struct sockaddr 
{
    sa_family_t sa_family;
    char        sa_data[14];
};

typedef unsigned short int sa_family_t;

sa_family成员是地址族类型(sa_family_t)的变量。地址族类型通常与协议族类型对应。常见协议族(protocol family,也称domain)和对应的地址族如下所示:

协议族 地址族 描述
PF_UNIX AF_UNIX UNIX本地域协议族
PF_INET AF_INET TCP/IPv4协议族
PF_INET6 AF_INET6 TCP/IPv6协议族

宏PF_*和AF_*都定义在bits/socket.h头文件中,且后者与前者有完全相同的值,所以二者通常混用。
sa_data成员用于存放socket地址值。但是,不同的协议族的地址值具有不同的含义和长度,如下所示:

协议族 地址值含义和长度
PF_UNIX 文件的路径名,长度可达到108字节
PF_INET 16 bit 端口号和 32 bit IPv4 地址,共6字节
PF_INET6 16 bit 端口号,32 bit 流标识,128 bit IPv6地址,32 bit 范围ID,共26字节

由上表可知,14字节的 sa_data 根本无法容纳多数协议族的地址值。因此,Linux 定义了下面这个新的通用的socket地址结构体,这个结构体不仅提供了足够大的空间用于存放地址值,而且是内存对象的。

#include <bits/socket.h>

struct sockaddr_storage
{
    sa_family_t sa_family;
    unsigned long int __ss_align;
    char __ss_padding[ 128 - sizeof(__ss_align) ];
};

typedef unsigned short int sa_family_t;

专用socket地址

很多网络编程函数诞生早于IPv4协议,那时候都使用的是struct sockaddr结构体,为了向前兼容,现在sockaddr退化成了 (void*) 的作用,传递一个地址给函数,至于这个函数是sockaddr_in还是sockaddr_in6,由地址族确定,然后函数内部再强制类型转化为所需的地址类型。

UNIX本地域协议族使用如下专用的socket地址结构体:

#include <sys/un.h>

struct sockaddr_un
{
    sa_family_t sin_family;
    char sun_path[108]; 
};

TCP/IP协议族有sockaddr_in 和sockaddr_in6两个专用的socket地址结构体,它们分别用于IPv4和IPv6:

#include <netinet/in.h>

struct sockaddr_in
{
    sa_family_t sin_family;    /*__SOCKADDR_COMMON(sin_) */
    in_port_t sin_port;          /* Port number. */
    struct in_addr sin_addr;    /* Internet address. */

    /* Pad to size of 'struct sockaddr'. */
    unsigned char sin_zero[sizeof(struct sockaddr) - __SOCKADDR_COMMON_SIZE - sizeof(in_port_t) - sizeof(struct in_addr)];
};

struct in_addr
{
    in_addr_t s_addr;
};

struct sockaddr_in6
{
    sa_family_t sin6_family;
    in_port_t sin6_port;     /* Transport layer port # */
    uint32_t sin6_flowinfo;     /* IPv6 flow information */
    struct in6_addr sin6_addr;     /* IPv6 address */
    uint32_t sin6_scope_id;     /* IPv6 scope-id */
};

typedef unsigned short uint16_t;
typedef unsigned int uint32_t;
typedef uint16_t in_port_t;
typedef uint32_t in_addr_t;
#define __SOCKADDR_COMMON_SIZE (sizeof (unsigned short int))

所有专用socket地址(以及sockaddr_storage)类型的变量在实际使用时都需要转化为通用socket地址类型sockaddr(强制转化即可),因为所有socket编程接口使用的地址参数类型都是sockaddr。

IP地址转换

通常,人们习惯用可读性好的字符串来表示IP地址,比如用点分十进制字符串表示IPv4地址,以吸用十六进制字符串表示IPv6地址。但编程中我们需要先把它们转化为整数(二进制数)方能使用。而记录日志时则相反,我们要把整数表示的IP地址转化为可读的字符串。下面3个函数可用于用点分十进制字符串表示的IPv4地址和用网络字节序(已经转换成大端字节序)整数表示的IPv4地址之间的转换:

//旧函数,只适用IPv4,不推荐使用。指令 man 函数名 查看详情
#include <arpa/inet.h>

in_addr_t inet_addr(const char *cp);    
/*
功能:点分十进制的IPv4地址转换为网络字节序整数表示的IPv4地址。注意,转换后是大端字节序
参数:
    cp:点分十进制的IPv4地址
返回值:
    整数表示的地址
*/

int inet_aton(const char *cp, struct in_addr *inp);
/*
功能:点分十进制的IPv4地址转换为网络字节序整数表示的IPv4地址。注意,转换后是大端字节序
参数:
    cp:点分十进制的IPv4地址
    inp:结构体,成员变量是整数表示的地址,传出参数
返回值:
    成功返回 1
    失败返回 0,并设置错误号
*/

char *inet_ntoa(struct in_addr in);
/*
功能:网络字节序整数表示的IPv4地址转换为点分十进制的IPv4地址。
参数:
    in:结构体,成员变量是整数表示的地址
返回值:
    点分十进制的IPv4地址
*/

下面这对更新的函数也能完成前面3个函数同样的功能,并且它们同时适用IPv4地址和IPv6地址:

#include <arpa/inet.h>

//p:点分十进制的IP地址字符串,n:网络字节序整数表示的IP地址

int inet_pton(int af, const char *src, void *dst);
/*
 参数:
    af:地址族,AF_INET、AF_INET6
    src:需要转换的点分十进制的IP地址字符串
    dst:转换的结果,传出参数
返回值:
    成功返回 1
*/


const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
/*
参数:
    af:地址族,AF_INET、AF_INET6
    src:需要转换的网络字节序整数表示的IP地址的地址
    dst:转换的结果,传出参数
    size:第三个参数的大小(数组的大小)
返回值:
    返回转换后的数据的地址(字符串),和dst是一样的
*/

TCP通信流程

TCP与UDP的区别

TCP通信流程

服务器端(被动接受连接的角色):

  1. 创建一个用于监听的套接字
    - 监听:监听有客户端的连接
    - 套接字:这个套接字其实就是一个文件描述符
  2. 将这个用于监听的文件描述符和本地的IP和端口绑定(IP和端口就是服务器的地址信息)
    - 客户端连接服务器的时候使用的就是这个IP和端口
  3. 设置监听,监听的fd开始工作
  4. 阻塞等待,当有客户端发起连接,解除阻塞,接受客户端的连接,会得到一个和客户端通信的套接字(fd)
  5. 通信
    - 接收数据
    - 发送数据
  6. 通信结束,断开连接

客户端:

  1. 创建一个用于通信的套接字(fd)
  2. 连接服务器,需要指定连接的服务器的IP和端口
  3. 连接成功了,客户端可以直接和服务器通信
    - 接收数据
    - 发送数据
  4. 通信结束,断开连接

套接字函数

#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>     //包含了这个头文件,上面两个就可以省略

int socket(int domain, int type, int protocol);
/*
功能:创建一个套接字。指令 man 2 socket 查看详情。
参数
    domain:协议族
        AF_INET:ipv4
        AF_INET6:ipv6
        AF_UNIx,AF_LOCAL:本地
    type:通信过程中使用的协议类型,多个可选的选项,以下两个为常用选项
        SOCK_STREAM:流式协议
        SOCK_DGRAM:报式协议
    protocol:具体的一个协议。一般写0,写0时依据type的不同有不同的结果
        type选择SOCK_STREAM:流式协议,默认使用TCP
        type选择SOCK_DGRAM:报式协议,默认使用UDP
返回值:
    成功:返回文件描述符,操作的就是内核缓冲区。
    失败:-1
*/


int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); // 给socket命名
/*
功能:绑定,将fd和本地的 IP + 端口 进行绑定。指令 man 2 bind 查看详情。
参数:
    sockfd:通过socket函数得到的文件描述符
    addr:需要绑定的socket地址,这个地址封装了ip和端口号的信息
    addrlen:第二个参数结构体占的内存大小
返回值:
    成功:0
    失败:-1
*/


int listen(int sockfd, int backlog); 
/*
功能:在一个套接字上监听连接。指令 man 2 listen 查看详情。
参数:
    sockfd:通过socket函数得到的文件描述符
    backlog:它指定了已经完成连接正等待应用程序接收的套接字队列的长度。指令 vim或cat /proc/sys/net/core/somaxconn 查看可以指定的最大值。某些主机是128或4096,由于计算机的速度很快,所以实际只需要指定一个比较小的数即可,如果不放心,可以指定128。
返回值:
    成功:0
    失败:-1
*/


int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
/*
功能:接收客户端连接,默认是一个阻塞的函数,阻塞等待客户端连接。指令 man 2 accept 查看详情。
参数:
    sockfd:用于监听的文件描述符
    addr:传出参数,记录了连接成功后客户端的地址信息(ip, port)
    addrlen:第二个参数的对应的内存大小。由于是指针,所以需要设置一个变量储存第二个参数的大小,然后在这里传入地址
返回值:
    成功:返回用于通信的文件描述符
    失败:-1
*/


int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
/*
功能:客户端连接服务器。指令 man 2 connect 查看详情。
参数:
    sockfd:用于通信的文件描述符
    addr:客户端要连接的服务器的地址信息
    addrlen:第二个参数的内存大小。与accept函数不同,不是指针,在这里sizeof即可。
返回值:
    成功:0
    失败:-1
*/


ssize_t write(int fd, const void *buf, size_t count);
ssize_t read(int fd, void *buf, size_t count);

TCP三次握手

TCP是种面向连接的单播协议,在发送数据前,通信双方必须在彼此间建立一条连接。所谓的“连接”,其实是客户端和服务器的内存里保存的一份关于对方的信息,如IP地址、端口号等。

TCP可以看成是一种字节流,它会处理IP层或以下的层的包、复以吸错误问题。在连接的建立过程中,双方需要交换一些连接的参数。这些参数可以放在TCP头部。

TCP提供了一种可靠、面向连接、字节流、传输层的服务,采用三次握手建立一个连接。采用四次挥手来关闭一个连接。

三次握手的目的是保证双方互相之间建立了连接。

三次握手发生在客户端连接的时候,当调用connect(),底层会通过TCP协议进行三次握手。

  • 16位端口号(port number):告知主机报文段是来自哪里(源端口)以及传给哪个上层协议或应用程序(目的端口)的。进行TCP通信时,客户端通常使用系统自动选择的临时端口号。

  • 32位序号(sequence number):一次TCP通信(从TCP连接建立到断开)过程中某一个传输方向上的字节流的每个字节的编号。假设主机A和主机B进行TCP通信,A发送给B的第一个 TCP 报文段中,序号值被系统初始化为某个随机值ISN(Initial Sequence Number,初始序号值)。那么在该传输方向上(从A到B),后续的TCP报文段中序号值将被系统设置成ISN加上该报文段所携带数据的第一个字节在整个字节流中的偏移。例如,某个TCP报文段传送的数据是字节流中的第1025 ~ 2048字节,那么该报文段的序号值就是ISN + 1025。另外一个传输方向(从B到A)的TCP报文段的序号值也具有相同的含义。

  • 32位确认号(acknowledgement number):用作对另一方发送来的TCP报文段的响应。其值是收到的TCP报文段的序号值 + 标志位长度(SYN,FIN) + 数据长度。假设主机A和主机B进行TCP通信,那么A发送出的TCP报文段不仅携带自己的序号,而且包含对B发送来的TCP报文段的确认号。反之,B发送出的TCP报文段也同样携带自己的序号和对A发送来的报文段的确认序号。

  • 4位头部长度(head length):标识该TCP头部有多少个32 bit(4字节)。因为4位最大能表示15,所以TCP头部最长是60字节。

  • 6位标志位包含如下几项:
    ○ URG标志,表示紧急指针(urgent pointer)是否有效。
    ○ ACK标志,表示确认号是否有效。我们称携带ACK标志的TCP报文段为确认报文段。
    ○ PSH标志,提示接收端应用程序应该立即从TCP接收缓冲区中读走数据,为接收后续数据腾出空间(如果应用程序不将接收到的数据读走,它们就会一直停留在TCP接收缓冲区中)。
    ○ RST标志,表示要求对方重新建立连接。我们称携带RST标志的TCP报文段为复位报文段。
    ○ SYN标志,表示请求建立一个连接。我们称携带SYN标志的TCP报文段为同步报文段。
    ○ FIN标志,表示通知对方本端要关闭连接了。我们称携带FIN标志的TCP报文段为结束报文段。

  • 16位窗口大小(window size):是TCP流量控制的一个手段。这里说的窗口,指的是接收通告窗口(Receiver Window,RWND)。它告诉对方本端的TCP接收缓冲区还能容纳多少字节的数据,这样对方就可以控制发送数据的速度。

  • 16位校验和(TCP checksum):由发送端填充,接收端对TCP报文段执行CRC算法以校验TCP报文段在传输过程中是否损坏。注意,这个校验不仅包括TCP头部,也包括数据部分。这也是TCP可靠传输的一个重要保障。

  • 16位紧急指针(urgent pointer):是一个正的偏移量。它和序号字段的值相加表示最后一个紧急数据的下一个字节的序号。因此,确切地说,这个字段是紧急指针相对当前序号的偏移,不妨称之为紧急偏移。TCP的紧急指针是发送端向接收端发送紧急数据的方法。

第一次握手:

  1. 客户端将SYN标志位置为1
  2. 生成一个随机的32位的序号seq = J
    第二次握手:
  3. 服务器端接收客户端的连接:ACK = 1
  4. 服务器会回发一个确认序号:ack = 客户端的序号 + 数据长度 + SYN/FIN(按一个字节算)
  5. 服务器端会向客户端发起连接请求: SYN = 1
  6. 服务器会生成一个随机序号:seq = K
    第三次握手:
  7. 客户单应答服务器的连接请求: ACK = 1
  8. 客户端回复收到了服务器端的数据: ack = 服务端的序号 + 数据长度 + SYN/FIN(按一个字节算)

TCP滑动窗口

滑动窗口(Sliding window)是一种流量控制技术。早期的网络通信中,通信双方不会考虑网络的拥挤情况直接发送数据。由于大家不知道网络拥塞状况,同时发送数据,导致中间节点阻塞掉包,谁也发不了数据,所以就有了滑动窗口机制来解决此问题。滑动窗口协议是用来改善吞吐量的一种技术,即容许发送方在接收任何应答之前传送附加的包。接收方告诉发送方在某一时刻能送多少包(称窗口尺寸)。TCP中采用滑动窗口来进行传输控制I动窗口的大小意味着接收方还有多大的缓冲区可以用于接收数据。发送方可以通过滑动窗口的大小来确定应该发送多少字节的数据。当滑动窗口为0时,发送方一般不能再发送数据报。

滑动窗口是TCP中实现诸如ACK确认、流量控制、拥塞控制的承载结构。

窗口可以理解为缓冲区的大小,滑动窗口的大小会随着发送数据和接收数据而变化,通信的双方都有发送缓冲区和接收数据的缓冲区
服务器:

  • 发送缓冲区(发送缓冲区的窗口)
  • 接收缓冲区(接收缓冲区的窗口)

客户端:

  • 发送缓冲区(发送缓冲区的窗口)
  • 接收缓冲区(接收缓冲区的窗口)


发送方的缓冲区:

  • 白色格子:空闲的空间
  • 灰色格子:数据已经被发送出去了,但是还没有被接收
  • 紫色格子:还没有发送出去的数据

接收方的缓冲区:

  • 白色格子:空闲的空间
  • 紫色格子:已经接收到的数据

MSS(Maximum Segment Size):通知最大可接收量。表示该报文段的发送方可以处理的最大报文段长度,也就是它在本连接的每个TCP分节中愿意接受的最大数据量。发送端TCP使用接收端的MSS值作为所发送字节的最大大小。图中服务器端的MSS是1024,表示服务器端每次愿意接受的最大数据量是1024,所以图中发送端每次发送1024大小的数据给客户端。
win:窗口大小

  1. 客户端向服务器发起连接,客户端的滑动窗口是4096,MSS是1460
  2. 服务器接收连接情况,告诉客户端,服务器的窗口大小是6144,MSS是1024
  3. 第三次握手
  4. 4-9客户端连续给服务器发送了6k的数据,每次发送1k
  5. 第10次,服务器告诉客户端:发送的6k数据已经接收到,存储在缓冲区中,缓冲区数据已经处理了2k,窗口大小是2k
  6. 第11次,服务器告诉客户端:发送的6k数据已经接收到,存储在缓冲区中,缓冲区数据已经处理了4k,窗口大小是4k
  7. 第12次,客户端给服务器发送了1k的数据
  8. 第13次,第一次挥手,客户端主动请求和服务器断开连接,并且给服务器发送了1k的数据
  9. 第14次,服务器回复ACK 8194,a:同意断开连接的请求,b:告诉客户端经接受到方才发的2k的数据,c:滑动窗口2k
  10. 第15、16次,通知客户端滑动窗口的大小
  11. 第17次,第三次挥手,服务器端给客户端发送FIN,请求断开连接
  12. 第18次,第四次挥手,客户端同意了服务器端的断开请求。

TCP四次挥手

四次挥手发生在断开连接的时候,在程序中当调用了close(会使用TCP协议进行四次挥手)。
客户端和服务器端都可以主动发起断开连接,谁先调用close()谁就是发起。
因为在TCP连接的时候,采用三次握手建立的的连接是双向的,在断开的时候需要双向断开。

TCP状态转换


上图中ACK均应改为ack,且修改后的ack前面应加上“ACK=1”

  • 2MSL (Maximum Segment Lifetime),两倍报文段寿命
    主动断开连接的一方,最后进入一个TIME_WAIT状态,这个状态会持续:2msl
    msl:官方建议:2分钟,实际是30s
    当TCP连接主动关闭方接收到被动关闭方发送的FIN和最终的ACK后,连接的主动关闭方必须处于TIME _WAIT状态并持续2MSL时间。
    这样就能够让TCP连接的主动关闭方在它发送的ACK丢失的情况下重新发送最终的ACK。
    主动关闭方重新发送的最终ACK并不是因为被动关闭方重传了ACK (它们并不消耗序列号,被动关闭方也不会重传),而是因为被动关闭方重传了它的FIN。实上,被动关闭方总是重传FIN直到它收到一个最终的ACK。

在主动关闭方第二次挥手后进入TIME_WAIT状态,并开启时间等待计时器(2MSL),如果主动关闭方在2MSL时间内再次接收到了被动关闭方的挥手请求,此时重新启动计时器2MSL
这个2MSL最大程度的保证了能够正确关闭,
情况1:假设主动关闭方发送的挥手应答因为网络等原因未被接收到(最坏情况是MSL),此时被动关闭方重新发送挥手请求到主动关闭方(最坏情况是MSL),共计2MSL)
情况2:假设防止之前的传输有未及时到达的报文段,2MSL可以保证在网络上无此连接的相关报文段。

第一次挥手丢失,会发生什么?
答:主动断开方重传FIN请求报文
第二次挥手丢失,会发生什么?
答:由于ACK报文不会重传,所以主动断开方会重传FIN报文
第三次挥手丢失,会发生什么?
答:被动断开方会重传FIN+ACK报文
第四次挥手丢失,会发生什么?
答:同第三次挥手

  • 半关闭
    当TCP链接中A向B发送FIN请求关闭,另一端B回应ACK之后(A 端进入FIN_WAIT2状态),并没有立即发送FIN给A,A方处于半连接状态(半开关),此时A可以接收B发送的数据,但是A已经不能再向B发送数据。
    从程序的角度,可以使用API来控制实现半连接状态:
#include <sys/socket.h>
int shutdown(int sockfd, int how);
/*
参数:
    sockfd:需要关闭的socket的描述符
    how:允许为shutdown操作选择以下几种方式: 
        SHUT_RD(0):关闭sockfd的读功能,此选项将不允许sockfd进行读操作。该套接字不再接收数据,任何当前在套接字接受缓冲区的数据将被无声的丢弃掉。
        SHUT_WR(1):关闭sockfd的写功能,此选项将不允许sockfd进行写操作。进程不能在对此套接字发出写操作。
        SHUT_RDWR(2):关闭sockfd的读写功能。相当于调用shutdown两次:首先是以SHUT_RD,然后以SHUT_WR。
*/

使用close中止一个连接,但它只是减少描述符的引用计数,并不直接关闭连接,只有当描述符的引用计数为0时才关闭连接。shutdown 不考虑描述符的引用计数,直接关闭描述符。也可选择中止一个方向的连接,只中止读或只中止写。
注意:

  1. 如果有多个进程共享一个套接字,close 每被调用一次,计数减1。直到计数为0时,也就是所用进程都调用了close,套接字将被释放。
  2. 在多进程中如果一个进程调用了 shutdown(sfd, SHUT_RDWR)后,其它的进程将无法进行通信。但如果一个进程close(sfd)将不会影响到其它进程。
posted @ 2022-09-21 09:47  小肉包i  阅读(350)  评论(0)    收藏  举报