线程相关系统调用
创建线程
创建线程,并指定运行属性(运行的函数、参数等);
int pthread_create(pthread_t*thread,const pthread_attr_t*attr,void*(*start_routine)(void*),void*arg)
socket相关系统调用
Socket的相关状态:
- 未初始化(Uninitialized):Socket对象已经被创建,但还未进行初始化或绑定。
- 已创建(Created):Socket对象已经被创建,并且已经初始化,但还未绑定到特定的IP地址和端口号。
- 已绑定(Bound):Socket已经绑定到了一个特定的本地IP地址和端口号,可以用于监听连接或进行通信。
- 监听(Listening):只适用于服务器端Socket。Socket正在监听传入的连接请求,处于等待连接状态。
- 连接已建立(Connected):只适用于客户端Socket。Socket已经成功与服务器端建立连接。
- 可读(Readable):Socket接收缓冲区中有数据可供读取。
- 连接建立完成,服务端的Socket即可读;
- 数据到达,服务端的Socket读缓冲区内有数据就可读;
- 可写(Writable):Socket发送缓冲区有足够的空间可以写入数据。
- 发送缓冲区空间充足即可写;
- 连接建立后,客户端的Socket即可写;
- 连接关闭(Closed):Socket已经关闭,不能再进行读写或通信。
- 连接中断(Disconnected):Socket的连接由于某种原因被中断或丢失。
1. 创建socket
返回一个仅指明协议,未绑定socket地址的socket文件描述符;
int socket(int domain,int type,int protocol)
- domain:使用的底层协议族;
- TCP/IP协议族中可选:PF_INET、PF_INET6
- type:指定服务类型;SOCK_STREAM\SOCK_DGRAM和SOCK_NONBLOCK\SOCK_CLOEXEC之间相与
- TCP/IP协议族中,SOCK_STREAM常用于TCP,SOCK_DGRAM常用与UDP;
- SOCK_NONBLOCK:设置其非阻塞;
- SOCK_CLOEXEC:fork子进程时是否关闭此socket
- protocol:在上面两个参数下仍还有可选的协议,使用此参数,一般不用,直接设为0
2. 命名socket
指定socket文件描述符(sockfd)绑定一个socket地址;
针对同一个addr,可以同时绑定多个socket,比如同时绑定tcp、udp;
int bind(int sockfd,const struct sockaddr*my_addr,socklen_t addrlen)
- my_addr:socket地址;
- addrlen:地址长度;
3. 监听socket
socket绑定了地址后,仍不能接受连接;
需要监听此socket,本质是创建一个监听队列,来存放待处理处理的客户端连接;
int listen(int sockfd,int backlog)
- sockfd:监听的文件描述符;
- backlog:全连接队列长度上限(通常完整连接数比backlog大);(2.2版本后)
- 半连接队列上限由/proc/sys/net/ipv4/cp_max_syn_backlog设置
4. 接受连接accept
本质是服务端从全连接队列中获取一个socket; socket已经完成3次握手,处于ESTABLISHED状态;
int accept(int sockfd,struct sockaddr*addr,socklen_t*addrlen)
- sockfd:已经监听过的socket文件;
- addr:存放远端socket地址;accept成功后通过与之通信;
5. 建立连接connect
客户端通过此函数与服务端主动建立连接
int connect(int sockfd,const struct sockaddr*serv_addr,socklen_t addrlen)
- sockfd:连接建立完成后,由此socket标识此连接,进行读写通信
- serv_addr:服务端socket地址
- addrlen:地址长度
6. 关闭连接close
close:并非关闭连接,而是fd引用计数-1,当引用计数为0则关闭连接; 多进程的程序中,每次fork都会将父进程的socket引用计数+1; shutdown:立即终止连接
int close(int fd) // 引用计数-1 int shutdown(int sockfd,int howto) // 立即终止连接
- fd:要关闭的socket文件描述符
- howto:关闭的操作
- SHUT_RD:关闭读,此socket的缓冲区都会丢弃;不可读;
- SHUT_RD:关闭写,不可再执行写操作;
- SHUT_RDWR:关闭读写;
7. 配置socket
// 获取socket配置 int getsockopt(int sockfd,int level,int option_name,void*option_value, socklen_t*restrict option_len) // 设置socket配置 int setsockopt(int sockfd,int level,int option_name, const void*option_value, socklen_t option_len)
可用选项:
- SO_RCVBUF:接受缓冲区大小;最小256字节 SO_SNDBUF:发送缓冲区大小;最小2048字节,确保足够的空闲处理拥塞
- SO_RCVLOWAT:接受缓冲区低水位标记;默认1 byte SO_SNDLOWAT:发送缓冲区低水位标记;默认1 byte 用于IO复用时判断socket是否可读可写,当缓冲区字节数大于低水位,通知应用程序,socket可读可写;
- SO_REUSEADDR:强制使用处于TIME_WAIT的socket地址;
TCP数据读写
ssize_t recv(int sockfd,void*buf,size_t len,int flags) ssize_t send(int sockfd,const void*buf,size_t len,int flags)
UDP数据读写
ssize_t recvfrom(int sockfd,void*buf,size_t len,int flags, struct sockaddr*src_addr,socklen_t*addrlen) ssize_t sendto(int sockfd,const void*buf,size_t len,int flags, const struct sockaddr*dest_addr,socklen_t addrlen)
IO读写系统调用
一次完整的read/write将发生:
- 4次切换:2次用户到内核的切换、2次内核到用户的切换;
- 4次拷贝:2次CPU拷贝,2次DMA拷贝;
read
ssize_t read(int fd, void *buf, size_t count)
- fd:要打开的文件描述符;
- *buf:要读取到的内存缓冲区地址;
- count:要读取的字节数
1、用户程序发起read(),CPU切换到内核态;首先检查数据是否在page cache中,如果可用,无需磁盘访问,直接从page cache拷贝数据到指定的用户缓冲区,read()返回; 2、如果page cache未命中,将执行磁盘读取;CPU请求DMA控制器,由DMA控制器去读取磁盘数据;将数据拷贝到内核缓冲区,不需要CPU参与;拷贝完成,通过中断通知CPU拷贝结果; 3、CPU响应中断后,将数据拷贝到用户缓冲区,read()函数返回;
write
ssize_t write(int fd, const void *buf, size_t count)
- fd:要打开的文件描述符;
- *buf:要写入的数据缓冲区地址
- count:要读取的字节大小;
1、用户程序发起write(),CPU切换到内核态,根据数据缓冲区的地址,将数据拷贝到内核空间(因为用户空间不能直接访问内核空间数据) 2、CPU将数据写入page cache后,就立即返回,不需要等待写入磁盘; 3、写入page cache后,此页数据会被标记为脏页,由操作系统后台pdflush线程异步完成落盘操作; 4、如果数据的一致性要求比较高,可用使用:fsync()、fdatasync()函数,强制将page cache落盘;函数将阻塞,直到落盘成功或异常;
Zero Copy
IBM-Zero Copy Zero-Copy:是一种数据传输技术或优化策略;仅用于不对文件进行修改,直接发送数据到socket; 目标:是在数据传输过程中尽量减少数据的复制次数,从而降低CPU和内存的开销,提高数据传输的效率; 实现方式:
mmap
用于将文件映射到进程的地址空间,将文件数据映射为内存中的一段虚拟地址,从而允许应用程序通过内存访问文件数据; 使用mmap,可用做到不在用户空间和内核空间进行数据拷贝,可以方便地对文件进行随机访问,实现读写操作; 对于需要对文件进行读写修改的操作,mmap相比于read/write更高效;
sendfile
1、调用sendfile(),切换到内核态; 2、查询PageCache,有数据直接CPU Copy即可;无数据,通过DMA发起IO请求; 3、DMA复制数据到PageCache,并异步通知CPU完成; 4、CPU Copy数据到Socket缓冲区,再通过DMA拷贝到网卡,完成发送; 5、sendfile()返回,切换回用户态;
与read/write相比:
- 少了两次上下文切换;和一次CPU Copy;但是sendfile无法修改数据,用户线程完全不参与数据操作;
- CPU直接将内核PageCache数据,拷贝至Socket缓冲区;减少拷贝到用户态的次数;
- 只能用于文件到Socket的拷贝;