套接字
一个TCP连接套接字对是一个定义该连接的两个端点的四元组:本地IP,本地TCP端口,外地IP,外地TCP端口。套接字对唯一标志一个网络上的每个TCP连接。
套接字:(IP地址, 端口号),就是IP地址+端口
IPv4套接字
网际套接字地址结构,以sockaddr_in
命名,在<netinet/in.h>
头文件种。以下这个结构体就可以看作是一个套接字。
1 | struct in_addr { |
What does a type followed by _t (underscore-t) represent?
What does “sin” mean in sin_addr, sin_family etc,?
希望上面2个连接可以帮助解决为什么这socket
的变量名为什么这么奇葩的疑惑。
_t
可以看作是用来定义一种数据类型。来防止因为操作系统或架构的原因,导致原生的数据类型有不同
对于POSIX规范来说,只需要实现下面三个字段就行了
sin_family
sa_family_t
:可以是任何无符号整数类型,通常是8位
sin_addr
in_addr_t
:IPv4地址,一般为uint32_t
sin_port
in_port_t
:需要是16位无符号整数类型,来表示端口
套接字地址在作为参数传进任何套接字函数的时候,通常以引用(指针)的方式。在<sys/socket.h>
头文件种,定义了一个通用的套接字地址结构,因为上面的sockaddr_in
也只是POSIX的标准。需要一个通用标准。
1 | struct sockaddr { |
这样才不必在不同函数中,发现套接字结构体不一样,于是我们的bind
函数变为
1 | int bind(int, struct sockaddr *, socklen_t) |
所以,在声明了sockaddr_in
后,往往需要进行强制转换为sockaddr
1 | bind(sockfd, (struct sockaddr *) &serv, sizeof(serv)); |
我们传递参数还需要传递参数的长度,sizeof
是无法从指针获取到结构体的大小的,只能获取到指针的大小。所以这里结构的长度也作为一个参数来传递。当指针和指针所指的内容的大小都传递给了内核,内核才知道要从用户进程复制多少数据到内核空间进来。这也是为什么那么多socket
函数要传长度的原因
从内核到进程传递套接字结构的函数有4个,所以它们都有套接字的指针和大小这2个参数
accept
recvfrom
getsockname
getpeername
至于为什么有时要把数值大小也作为指针传递进去,既然我们用户线程要告诉内核需要复制多少,在返回结果的时候,内核也要告诉我们用户线程返回的结果多大,我们才知道复制多少,而不至于越界。
套接字地址结构在进程和内核之间反复传递,使用套接字都是内核中的系统调用
大小端模式
2种方式并没有什么特别不一样,网络协议需要指定一个网络字节序。规定使用大端字节序来传递
1 |
|
h
:代表host
n
:代表network
s
:代表short
l
:代表long
字节操作函数
1 |
|
地址转换函数
转换ASCII字符串到网络字节序的二进制
1 |
|
IO操作
- 一次read操作所返回的数据可能少于所要求的数据
- 一次write操作的返回值也可能少于指定输出的字节数
readn和writen的功能是读、写指定的N字节数据,并处理返回值小于要求值的情况
这两个函数只是按需多次调用read和write直至读、写了N字节数据
1 |
|
socket函数
用于创建套接字
1 |
|
family
:指明协议族type
:指明套接字类型protocol
某个协议类型常值
返回套接字描述符
connect函数
TCP客户端用于与服务端建立连接
1 |
|
sockfd
:socket
函数返回的套接字描述符servaddr
:指向套接字地址结构体的指针addrlen
:套接字地址结构体的大小
connect
不必调用bind
,因为内核会确定源IP和一个随机临时端口
非阻塞connect
在一个非阻塞的TCP套接字上调用connect
时,connect
将立刻返回一个EINPROGRESS
错误,不过已经发起的TCP三次握手继续进行。我们可以继续使用select
检测这个连接或成功或失败的已建立条件
- 我们可以把三次握手的时间用在其他事情上面,一次
connect
需要一个RTT时间 - 我们可以使用这个计数同时建立多个连接
- 使用
select
指定超时,缩短超时时间
问题
- 如果服务器在同一主机,连接一般立刻建立
- 当连接成功建立,描述符变得可写
- 当连接建立错误,描述符变得可读可写
非阻塞connect
能使我们在TCP三次握手发送期间做其他处理
bind函数
把本地一个协议地址赋予一个套接字
1 |
|
sockfd
:socket
函数返回的套接字描述符servaddr
:指向套接字地址结构体的指针- 因为
const
,若让内核指定端口,需要调用getsockername
来返回协议地址
- 因为
addrlen
:套接字地址结构体的大小
服务端在启动时绑定一个端口,所以服务端需要使用bind
,绑定设计三个对象
- 套接字
- 地址
- 端口
通配地址:INADDR_ANY
,htonl(INADDR_ANY)
listen函数
做两件事
socket
创建的套接字为一个主动套接字,listen
将其转换为一个被动套接字,用于接受指向该套接字的连接请求- 规定了内核应该认为相应套接字排队的最大连接个数
1 |
|
内核为任何一个监听套接字维护2个队列,见[学习笔记 - TCP/IP](/2021/01/11/tcp-ip-note/#半连接队列和全连接队列)
- 未完成连接队列
- 已完成连接队列
当进程调用accept
时,已完成队列种的队首将返回给进程,如果队列为空,进程将被投入睡眠(阻塞)
accept函数
用于服务端从已完成连接队列的对手返回下一个已完成连接,如果为空,则阻塞
1 |
|
sockfd
:监听套接字ciladdr
:用于返回对端进程的协议地址addrlen
:返回对端进程的协议地址的长度
如果accept
成功,则返回值是由内核自动生成的一个全新的描述符,代表与所返回客户的TCP连接。称为已连接套接字,服务器一般仅仅创建一个监听套接字,他在该服务器生命周期内一致存在。内核为每个由服务器进程接受的客户连接创建一个已连接套接字。完成服务时,对应的已连接套接字就被关闭。
非阻塞accept
1 | for (...) { |
如果客户端在accept
之前发送RST
,服务端会阻塞在accept
上,除非有新连接到达,而无法处理本次其他已经就绪的描述符
- 当使用
select
获悉某个监听套接字上何时有已完成连接准备好被accept
时,把监听套接字设置为非阻塞 - 在后续的
accapt
调用中忽略以下错误:EWOULDBLOCK
,ECONNABORTED
,EPROTO
和EINTR
close函数
用来关闭套接字,终止TCP连接
1 |
|
getsockname函数和getpeername函数
返回与某个套接字关联的本地协议地址,或者返回与某个套接字关联的外地协议地址
1 |
|
shutdown函数
我们需要一种关闭TCP连接其中一半的方法,我们想给服务器发送一个FIN,告诉它我们已经完成了数据发送,但是仍然保持套接字以便读物,由shutdown()
函数来完成。
close()
函数:close()
把描述符的引用计数减1,在计数变为0时才关闭套接字close()
终止读和写两个方向的数据传送
1 |
|
howto
SHUT_RD
:关闭连接的读这一半- 套接字中不再有数据可接受
- 接受缓冲区中现有的数据都会被丢弃
SHUT_WR
:关闭连接的写这一半- 成为半关闭
- 发送缓冲区中现有的数据都会被丢弃
SHUT_RDWR
:都关闭- 与调用
shutdown
二次等效
- 与调用
shutdown()
用于形成半关闭状态
recv函数和send函数
通过参数控制读写数据
1 |
|
sockfd
:套接字描述符buff
:发送或接受缓冲区nbytes
:缓冲区长度flag
:发送或接受数据的控制参数0
:相当于read
和write
MSG_DONTROUTE
:发送数据不查找路由表,适用于局域网,或同一网段NSG_DONTWAIT
:仅本操作非阻塞MSG_OOB
:发送和接受带外数据MSG_PEEK
:接受数据时不从缓冲区移除MSG_WAITALL
:数据量不够时,读操作等待,不返回
套接字描述符是用来标定系统为当前的进程划分的一块缓冲空间的,类似于文件描述符,不过二者有些区别
- 这块缓冲空间并不是一开始就被系统划分给进程的
- 对于server端而言,划分系统缓冲空间的时刻是:当server决定接收来自client的连接请求
- 即
accept
方法成功执行
- 即
- 对于client端而言,划分系统缓冲空间的时刻是:当client端执行
connect
函数正确的时候
- 对于server端而言,划分系统缓冲空间的时刻是:当server决定接收来自client的连接请求
已经发送到网络的数据依然需要暂存在send buffer中,只有收到对方的ack后,kernel才从buffer中清除这一部分数据。接收端将收到的数据暂存在receive buffer中,自动进行确认。但如果socket所在的进程不及时将数据从receive buffer中取出,最终导致receive buffer填满,由于TCP的滑动窗口和拥塞控制,接收端会阻止发送端向其发送数据。
并发服务器
1 | pid_t pid; |
I/O复用
Blocking | NON-Blocking | |
---|---|---|
Synchronous | Read/Write | Read/Write |
Asynchronous | I/O Multiplexing | AIO |
进程需要一种预先告知内核的能力,使得内核一旦发现进程指定的一个或多个I/O条件就绪,就通知进程,这个能力称为I/O复用
进程切换
进程切换会发生一下操作
- 保存处理机上下文,包括程序计数器和其他寄存器。
- 更新PCB信息。
- 把进程的PCB移入相应的队列,如就绪、在某事件阻塞等队列。
- 选择另一个进程执行,并更新其PCB。
- 更新内存管理的数据结构。
- 恢复处理机上下文
进程切换会消耗一定资源,切换就相当于从一个软件换用另一个软件
阻塞I/O
当客户端发起请求的时候,服务端会处理连接,并且会阻塞直到读取了数据。在阻塞期间,服务端不能做其他事情
当产生read系统调用的时候,操作系统会将Server端阻塞,并且切换到内核态。内核把数据从系统空间复制到用户空间。当缓存空了时候,内核会重新唤醒Server端进程,来进行下一次的recv
非阻塞I/O
当产生read系统调用的时候,如果内核中的数据还没有准备好,那么会立刻返回一个error。和阻塞IO不同的就是需要一直询问内核
I/O多路复用
无论是阻塞I/O还是非阻塞I/O,基本上都是一个进程处理一个连接,这样非常的浪费。而I/O多路复用,就是单个进程可以同时处理多个网络连接的I/O。
select
该函数允许进程指示内核等待多个事件中的任何一个发生,并只有在有一个或多个发生或经历一段时间后才唤醒它。
1 |
|
timeout
:告知内核等待所指定的描述符的任何一个就绪可花多长时间- 空指针:永远等下去
- 非空非0:等待一段固定时间
- 非0为0:非阻塞,轮询polling
readset
:测试读描述符writeset
:写描述符exceptest
:异常条件描述符maxfdpl
:指定待测试的描述符个数
select
使用描述符集,通常是一个整数数组,整数中每一位对应一个描述符。如果为32为整数,则该数组第一个元素对应描述符0~31。
1 | // 设置全0 |
流程:
- 创建n个fd
- 创建一个描述符集来标志哪一个fd有数据
- 循环
- 将描述符集全部置为
0
- 调用select进行阻塞
- 循环描述符集所有位,一旦为
1
则表示有信息,进行处理
- 将描述符集全部置为
- 缺点
- 采用轮询的方式扫描文件描述符:O(n)
- 单个进程能够监视的文件描述符的数量存在最大限制,通常是1024
select
需要将句柄数据结构在系统空间和用户空间进行复制- 水平触发,如果有本次没处理的fd,则下一次还是会继续出现在描述符集里
- 描述符集不能重复使用
pselect
1 |
|
pselect
使用timespec
结构pselect
增加了第六个参数,指向信号掩码的指针,该参数允许程序先禁止递交某些信号,再测试由这些当前禁止信号的信号处理函数设置的全局变量,然后调用pselect
,告诉它重新设置信号掩码
poll
1 |
|
fdarray
:指向结构体数组的指针nfds
:结构体数组的元素个数timeout
:指定时时间INFTIM
:用于永远等待0
:立刻返回>0
:等待指定数目的毫秒数
流程:
- 创建n大小的
pollfd
结构体数组 - 循环
- 调用poll进行阻塞
- 循环整个数组,判断
revents
是否置位,置位则表示有信息,进行处理- 把
revents
重新复位 - 处理数据
- 把
- 进行下一次循环
- 缺点
- 采用轮询的方式扫描文件描述符:O(n)
- poll需要将句柄数据结构在系统空间和用户空间进行复制
- 水平触发,如果有本次没处理的fd,则下一次还是会继续触发
- 优点
- 采用链表的方式储存文件描述符,去掉1024的限制
- events可以重用
epoll
假设有100w多个连接,如果使用select()
或者pull()
,则每次都会将100w个结构数据复制到系统空间,经由内核处理后,再复制回来,然后还要再循环一次。无用的消耗巨大。所以select()
或者pull()
一般用于少于1w的并发连接。
epoll通过在内核中申请一个简易的文件系统(红黑树)。
- 调用
epoll_create()
建立一个epoll
对象(在epoll
文件系统中为这个句柄对象分配资源) - 调用
epoll_ctl()
向epoll
对象中添加这100万个连接的套接字 - 调用
epoll_wait()
收集发生的事件的连接
水平触发
内核中的socket接收缓冲区不为空,有数据可读,读事件一直触发
内核中的socket发送缓冲区不满,可以继续写入数据,写事件一直触发
边缘触发
内核中socket接收缓冲区由空变为不为空,数据由不可读变为可读,事件触发(仅一次)。
- LT模式会一直触发可读可写事件,导致效率比较低。ET模式由于读写事件 仅通知一次,可能会存在数据丢失的可能。
- ET模式时,当有多个连接同时到达服务器,epoll_wait会返回多个描述符,由于在ET模式下就绪状态只返回一次, 因此为了防止漏掉连接,需要循环调用accept直到接收全部连接(即返回EAGAIN错误)。
- ET模式时,在读写数据时,同样需要注意读写事件只触发一次的问题,若一次读或写没有处理全部数据, 则会导致数据丢失。解决办法是,accept接收连接时,设置连接的套接字为非阻塞, 并在读写数据时循环调用read/write直到数据全部处理为止(即返回EAGAIN错误)
信号驱动I/O
让内核在描述符就绪的时候发送信号通知我们,此方法不常用
异步I/O
告知内核启动某个操作,并让内核在整个操作(包括将数据从内核复制到我们自己的缓冲区)完成后通知我们。与信号相比,信号驱动I/O是内核告诉我们合适可以启动一个I/O操作,而异步I/O模型是由内核告诉我们I/O操作完成了。
// TODO
描述符就绪条件
条件 | 可读 | 可写 | 异常 |
---|---|---|---|
有数据可读 | o | ||
关闭连接的读一半 | o | ||
给监听套接口准备好新连接 | o | ||
有可用于写的空间 | o | ||
关闭连接的写一半 | o | ||
待处理错误 | o | o | |
TCP带外数据 | o |
套接字准备好读
- 该套接字接收缓冲区中的数据字节数大于等于套接字接收缓冲区低水平标记的当前大小
- 这个标记值一般是1,对这样的套接字读,将返回一个大于0的值,也就是返回准备好读入的数据
- 默认值为:
1
- 该连接的读半部关闭(也就是收到
FIN
的TCP连接),对这样的套接字读,将不阻塞直接返回0(EOF) - 该套接字是一个监听套接字,且已完成的连接数不为0,对这样的套接字
accept()
通常不会阻塞。 - 其上有一个套接字错误等待处理,对这样的套接字读,将不阻塞直接返回-1
套接字准备好写
- 该套接字发送缓冲区中的可用空间字节数大于等于套接字发送缓冲区低水位标记的当前大小
- 如果该套接字设置成非阻塞,写操作将不阻塞返回一个正值
- 默认值为:
2048
- 该连接的写半部分关闭,对这样的套接字写,将产生
SIGPIPE
信号。 - 使用非阻塞
connect()
的套接字已建立连接,或者connect()
已经以失败告终 - 其上有一个套接字错误待处理,对这样的套接字写,将不阻塞直接返回-1
套接字异常条件待处理
当某个套接字上发生错误时,它将又select标记既可读又可写。
- 接收低水平标记和发送低水平标记的目的在于:
- 允许应用进程控制在select返回可读或可写条件之前有多少数据可读或有多大空间可用于写。
- 举个栗子,如果我们知道除非至少存在64个字节的数据,否则我们的应用进程没有任何有效工作,那么我们可以把接收低水平位标记为64,以防止少于64个字节的数据准备好读时select唤醒我们。
错误处理
应答超时
在握手,挥手以及消息传递的状态下,若当前发送的报文期待一个应答报文。在规定时间应答报文没有到达,发送方会重发两次报文(报文重发间隔可设置)。若重发三次后依旧没有收到应答,则向应用返回ETIMEOUT
目的不可达
若报文在传送的过程中,因为找不到路由路径,报文无法到达等引发了ICMP错误,发送方会按照上述的方式重发次报文。若重发结束后依旧没有收到应答,则向应用返回EHOSTUNREACH
或者ENETUNREACH
禁止分片
在IPV4中,报文超过了MTU长度会导致分片,而其存在DF(Don’t Fragment)标识,表示禁止分片。同时IPV6禁止路由器分片,因此在传送的过程中隐含DF位。在传送过程中设置了DF位而超过了MTU,则会向应用返回EMSGSIZE
阻塞时中断
在系统执行慢系统调用(可能被永远阻塞的系统调用)时阻塞,此时捕获到某个信号并进行了处理(系统对某些信号有默认处理方式),在没有设置自动重启的情况下,会向应用返回EINTR
。
一般应对EINTR
的方式是简单的重新调用,但在connect()
返回EINTR
时不能这么做,因为connect()
涉及三次握手的过程,需要使用getsockopt()
获取连接状态。
读写时RST
在调用read()
等阻塞时接收到对端RST
信号时,会返回ECONNREST
。同时对发送方断开的套接字写时也会返回ECONNRESET
写RST套接字
当进程向收到RST
的套接字执行写操作的时候,内核向该进程发送一个SIGPIPE
信号,该信号的默认行为是终止进程,因此进程必须捕获它以免不情愿的被终止
不论进程是捕捉了该信号并从信号处理函数中返回,还是简单忽略该信号,写操作都讲返回EPIPE
错误
首次握手服务端RST
在客户端第一次握手时,若服务端返回RST
报文,立即向应用返回ECONNREFUSED
握手结束客户端RST
在较为繁忙的服务器中,可能出现上图客户端刚经历三次握手后随机发送RST
报文的情况,POSIX指出这种情况errno
设置为ECONNABORTED
,只需要再次调用accept()
即可。
而在Berkeley的实现中,返回EPROTO
错误,代表协议错误,是一种致命错误,由内核把该连接从已完成连接套接字队列中释放,若再次调用accept()
,则不会处理到本次请求,可能导致阻塞。
服务端主机崩溃
由于客户端无法收到服务端的任何回应,会重发处理,最终向应用返回的情况可能为应答超时或目的不可达。
若想尽快的检测出主机崩溃,不主动发送数据也可做到,即套接字选项的SO_KEEPALIVE
(类似心跳机制)
服务端进程终止或关机
服务端由于进程崩溃或者手动kill后,进程终止关闭所有打开的描述符。这导致了其向客户端发送了一个FIN
,客户端则响应了一个ACK
,TCP挥手的前半部分完成,服务端不在发送数据。
但是此时客户端并不知道服务器端已经终止了。当客户端向服务器写数据的时候,由于服务器进程终止,所以响应了RST
。
这种情况下可以由select()
或者poll()
检测到服务端的终止。
- 如果对端TCP发送数据,套接字可读,并且
read()
返回一个大于0的值(读入字节数) - 如果对端TCP发送了
FIN
(对端进程终止),套接字可读,并且read()
返回0(EOF) - 如果对端TCP发送
RST
(对端崩溃并重启),套接字可读,并且read()
返回-1,errno
中含有确切错误码
UDP
- 对于一个UDP套接字,有由它引发的异步错不返回给除非它以连接
- 一个UDP套接字可以连接多个服务端
- 仅在进程已将其UDP套接字连接到恰恰一个对端后,这些异步错误才返回给进程
- UDP没有流量控制
- UDP的通信有界性
- 也就是client发的包只会原封不动的收下,不会分包,合并
- UDP数据包的无序性和非可靠性
如果MTU是1500,Client发送一个8000字节大小的UDP包,那么Server端阻塞模式下接包,在不丢包的情况下,recvfrom(9000)
是收到的是8000
recvfrom函数
1 |
|
sockfd
:同read()
的参数buff
:同read()
的参数nbytes
:同read()
的参数flags
:默认为0from
:指向一个将由该在返回时填写数据报发送者的协议地址的套接字地址结构- 告诉我们谁发送了数据报
addrlen
:套接字地址结构的大小
sendto函数
1 |
|
sockfd
:同read()
的参数buff
:同read()
的参数nbytes
:同read()
的参数flags
:默认为0to
:指向一个将由该在返回时填写数据报发送者的协议地址的套接字地址结构- 告诉我们给谁发送数据报
addrlen
:套接字地址结构的大小
UDP connect函数
不会触发三次握手,内核只是检查是否存在立即可知的错误,记录对端的IP地址和端口号,然后立即返回到调用进程
- 未连接UDP套接字,新创建UDP套接字默认如此
- 已连接UDP套接字,对UDP套接字调用
connect()
的结果- 不能给输出操作指定目的IP地址和端口号
- 不使用
sendto
,而改用write
或send
- 不必使用
recvfrom
以获悉数据包的发送者,而改用read
,recv
或recvmsg
recvfrom
和sendto
在收发时指定地址,而send
,recv
不指定,而是先用connect
进行指定
- 选定了对端,内核只会将帮定对象的对端发来的数据报传给套接口,因此在一定环境下可以提升安全性
- 会返回异步错误,如果对端没启动,默认情况下发送的包对应的ICMP回射包不会给调用进程,如果用了connect
- 发送两个包间不要先断开再连接,提升了效率
套接字类型 | write 或send |
不指定目的sendto |
指定目的地址的sendto |
---|---|---|---|
TCP套接字 | o | o | EISCONN |
UDP套接字,已连接 | o | o | EISCONN |
UDP套接字,未连接 | EDESTADDRREQ |
EDESTADDRREQ |
o |
可以处于下列目的再次调用connect
- 指定新的IP地址和端口号
- 断开套接字
UDP报文大小的影响因素
- UDP协议本身,UDP协议中有16位的UDP报文长度,那么UDP报文长度不能超过
2^16=65536
。- UDP数据包的最大理论长度是
2^16 - 1 - 8 - 20 = 65507
字节
- UDP数据包的最大理论长度是
- 以太网(Ethernet)数据帧的长度,数据链路层的MTU(最大传输单元)。
- socket的UDP发送缓存区大小
- UDP数据包理想长度:1500字节 – IP头(20字节) – UDP头(8字节) = 1472字节
UDP性能
当在一个未连接UDP套接字上调用sendto
时,内核暂时连接该套接字,发送数据包,然后断开连接。
- 连接套接字
- 输出第一个数据包
- 断开套接字连接
- 连接套接字
- 输出第二个数据包
- 断开套接字连接
当应用程序知道子集要给同一目的地址发送多个数据报时,显示连接套接字效率更高,调用connect
后调用两次write
涉及内核执行如下步骤
- 连接套接字
- 输出第一个数据报
- 输出第二个数据报
这种情况下,内核只复制一次含有目的IP地址和端口号的套接字地址结构,而sendto
需要复制两次
对比TCP
- 没有正面确认,丢失分组重传,重复分组检测,分组排序
- 无法动态调整发包
- 没有窗口式流量控制
- 没有慢启动和拥塞避免
- 无法智能利用空闲带宽导致资源利用率低
- UDP无法根据变化进行调整,发包过大或过小,从而导致带宽利用率低下,有效吞吐量较低
改进UDP的成本较高
- 适合UDP
- 对于组播或广播必须使用UDP。
- 对于简单的请求-应答可以使用UDP
- 大量不需要精确的数据:直播
- 高通信实时性:NTP
- 不适合UDP
- 对于精确的大量数据传输不适合UDP(因为需要自己造TCP)
UDP丢包问题
- UDP socket缓冲区满造成的UDP丢包
- UDP socket缓冲区过小造成的UDP丢包
- socket缓冲区过小无法容下该UDP报文,那么该报文就会丢失。
- ARP缓存过期导致UDP丢包
- 没有获取到MAC地址之前,用户发送出去的UDP数据包会被内核缓存到
arp_queue
这个队列中,默认最多缓存3个包
- 没有获取到MAC地址之前,用户发送出去的UDP数据包会被内核缓存到
I/O超时
在套接字I/O操作上设置超时的方法有3种
- 调用
alarm
- 超时时产生
SLAGLRM
信号
- 超时时产生
- 在
select
中阻塞等待I/O(设置超时) - 使用
SO_RCVTIMEO
和SO_SNDTIMEO
- 仅适用套接字描述符
上述三个技术都适用与输入和输出操作,connect
默认为75s超时,也不是不能用。select
能在connect
上设置超时的先决条件时相应的套接字处于非阻塞模式。
- 输入操作
read
:最普通的读readv
:分散读recv
:比read
多个flag
可进程一些控制recvfrom
:指定地址recvmsg
:通用的I/O读函数
- 输出操作
write
:最普通的写writev
:集中写send
:比write
多个flag
可进程一些控制sendto
:指定地址sendmsg
:通用的I/O写函数
UNIX域协议
用于单个主机上执行客户/服务器通信的一种方法(如:docker unix套接字)
- 性能比TCP快几倍
- 可用于不同进程之间传递描述符
- 可以传递用户ID和组ID,提供您额外的安全验证措施
1 | struct sockaddr_un { |
socket
- 创建套接字
unlink
- 先删除这个路径名,防止已经存在
bind
- 绑定
socketpair函数
创建两个随后连接起来的套接字,本函数仅适用UNIX套接字
1 |
|
family
:AF_LOCAL
type
:SOCK_STREAM
:得到结果为流管道类型SOCK_DGRAM
:数据报类型
protocol
:0
sockfd
:返回套接字
要求说明
bind
创建的路径名默认访问权限为0777
- 关联的路径应该是一个绝对路径
connect
中指定的路径名必须是一个当前绑定在某个打开的UNIX域套接字上的路径名- 他们套接字类型必须一致
connect
相当于open
已只写打开的权限- 与TCP套接字相似,都为进程提供一个无记录边界的字节流接口
UNIX系统提供了用于一个进程向任意其他进程传递任一打开的描述符的方法
- 创建一个字节流的或数据包的UNIX套接字
- 发送进程通过调用返回描述符的任一UNIX函数打开一个描述符
- 发送进程创建一个
msghdr
结构,其中含有待传递的描述符 - 接受进程调用
recvmsg
在来自步骤1的UNIX套接字上接受这个描述符
传递一个描述符并不是传递一个描述符号,而是涉及在接受进程中创建一个新的描述符,这个新的描述符和发送进程中的那个描述符指向内核中相同的文件表项
参考
- UNIX网络编程卷1: 套接字联网API(第三版)
- TCP-socket异常情况