目录
11.1 socket选项SO_RCVTIMEO SO_SNDTIMEO
5 linux网络编程基础api
-
socket地址api:ip地址和端口对,成为soccket地址。
-
socket基础api:sys/socket.h中,包括创建、命名、监听socket;接受连接、发起连接、读写数据、获取地址信息、检测带外标记、读取设置socket选项
-
网络信息api:主机名和ip地址之间的转换、服务名称和端口号之间的转换。netdb.h中。
5.1 socket地址api
主机字节序和网络字节序
-
cpu累加器一次能装4字节,4字节在内存中的排序将影响被累加器装载成的整数的值。
-
分为大端字节序和小端字节序。大端:整数的高位字节在内存的低地址处,低位字节在内存的高地址处。
-
PC常用小端字节序,被称为主机字节序。
-
发送端要把数据转化成大端字节序数据发送,接收端知道发送的是大端字节序数据。大端字节序也成为网络字节序。
-
同一个机器上的2个进程也要考虑字节序的问题。
-
主机字节序和网络字节序之间的相互转换,考虑long和short
unsigned long int htonl ( usigned long int hostlong); unsigned short int htons ( usigned short int hostshort); unsigned long int ntohl ( usigned long int netlong); unsigned short int ntohs ( usigned short int netshort);
通用socket地址
-
结构体sockaddr表示socket地址
-
sa_family_t是地址族类型,与常用协议族对应。pf和af完全相同,经常混用。
-
sa_data存放socket地址值。
-
14字节的sa_data往往无法容纳多数协议族的地址值,因此提供了sockaddr_storage结构体。
专用socket地址
-
为各个协议族提供了专用的socket地址结构体
-
unix本地域协议族:sockaddr_un
-
tcp/ip协议族有sockaddr_in和sockaddr_in6两个专用socket地址结构体,分别用于ipv4和ipv6
ip转换函数
-
字符串通常习惯使用十进制或十六进制,编程中需要转化为二进制数;记录日志需要把整数转化为可读的字符串,因此提供了点分十进制字符串的ipv4和网络字节序正数表示的ipv4地址之间的转换
# include <arpa/inet.h> in_addr_t inet_addr( const char* strptr); //将点分十进制的ipv4转化为网络字节序整数表示的ipv4 int inet_aton(const char * cp, struct in_addr* inp); //与上功能相同,但是转化结果存储与参数imp的地址结构中。返回1或0 表示成功或失败。 char* inet_ntoa( struct in_addr in ); //将网络字节序整数转化为点分十进制ipv4,该函数内部用一个静态变量存储静态结果,因此是不可重入的。
-
以下函数适用于ipv4和ipv6
-
5.2 创建socket
-
socket就是可读、可写、可控制、可关闭的文件描述符。
domain:使用那个协议族,如PF_INET PF_INET6 PF_UNIX
type:指定服务类型,SOCK_STREAM流服务(TCP) SOCK_UGRAM数据包服务(UDP)
protocol:在前两者构成的集合下选择具体的协议,设置0默认协议。
socket会返回一个socket文件描述符,-1表示失败。
5.3 命名socket
-
创建时并未指定使用该地址族中的具体socket地址,要将socket与socket地址绑定。
-
服务器程序中要命名socket,命名后客户端才知道如何连接;客户端不需要命名socket,采用匿名方式,使用操作系统自动分配的socket地址。使用bind函数
5.4 监听socket
-
通过listen系统调用来创建一个监听队列存放待处理的客户连接
sockfd指定了被监听的socket。backlog提示内核监听队列的最大长度。当监听队列的长度超过backlog,服务器不受理新的客户连接。backlog表示处于完全连接状态的socket的上线。典型值是5。
-
完整连接通常会比backlog略大。其他连接处于SYN_RCVD状态,尚未连接。
5.5 接收连接
-
accept接收连接
-
accept只是从监听队列中取出连接,不论连接处于何种状态,也不关心网络状况的变化。因此可能取出的连接已经断开了。
5.6 发起连接
-
服务器用listen被动接收连接,客户端通过connect主动与服务器建立连接。
5.7 关闭连接
-
关闭该连接对应的socket。可以通过关闭普通文件描述符的系统调用完成。
5.8 数据读写
tcp数据读写
-
read、write同样适用于socket。但socket接口也提供了几个专门用于socket数据读写的系统调用。增加了对数据读写的控制。
#include <sys/types.h> #include <sys/socket.h> 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);
-
flag参数为数据收发提供了额外的控制。
udp读写
-
recvfrom和sendto两个用于udp数据报读写的系统调用
通用数据读写函数
-
socket编程接口提供了一堆通用的数据读写系统调用。可用于tcp和udp。
带外标记
地址信息函数
-
想知道连接socket的本端socket地址和远端socket地址时使用。
5.11 socket选项
-
下面两个系统调用专门用来读取和设置socket文件描述符属性。
-
常用的socket选项
SO_REUSEADDR选项
-
可以通过该选项强制使用处于time_wait状态的连接占用的socket地址。
SO_RCVBUF SO_SNDBUF
-
分别表示TCP接收缓冲区和发送缓冲区的大小。通过这两个可以设置缓冲区的大小,系统会对其加倍并且不小于最小值。
接收缓冲区的最小值是256、发送缓冲区的最小值是2048字节。也可以直接修改内核参数。
SO_RCVLOWAT SO_SNDLOWAT
-
表示tcp接受和缓冲区的低水位标记。
SO_LINGER选项
-
控制close系统调用在关闭tcp连接时的行为。
5.12 网络信息api
-
telnet可以调用某些网络信息api来实现主机名到ip地址的转换,以及服务名称到端口号的转换。
gethostbyname 和 gethostbyaddr
-
分别是 根据主机名称获取主机的完整信息、根据ip地址获取主机的完整信息。
-
返回的hostnet结构体类型的指针
getservbyname 和 getservbyport
-
分别事 根据名称获得某个服务的完整信息 根据端口号获取某个服务的完整信息。
-
是不可重入的、非线程安全的。在原函数后加_r(re-entrant)就是可重入版本。
getaddrinfo
-
通过主机名获得ip地址或通过服务名获得端口号。分别用了gethostbyname getservbyname
-
返回信息
-
eg
getnameinfo
-
通过socket地址同时后的以字符串表示的主机名和服务名,内部分别使用 gethostbyaddr getservbyport
6 高级I/O函数
-
高级的IO函数,比基础的IO函数有更优秀的性能。
-
用于创建文件描述符的函数,pipe、dup、dup2
-
用于读写数据的函数,readv/writev、sendfile、mmap/munmap、splice、tee
-
控制IO行为和属性的函数,包括fcntl
-
6.1 pipe函数
-
创建管道用于进程间通信。
6.2 dup和dup2函数
-
标准输入重定向到一个文件或者把标准输出重定向到一个网络连接。用下面的函数实现:
6.3 readv和writev函数
-
readv将数据从文件描述符读到分散的内存块中,分散读;writev将多块分散的内存写入文件描述符中,集中写。
6.4 sendfile函数
-
在两个文件描述符之间直接传递数据(在内核中操作),避免内核缓冲区和用户缓冲区之间的数据拷贝,被称为零拷贝。
6.5 mmap函数和munmap函数
-
mmap用于申请一段内存空间,用于进程间通信的共享内存,也可以将文件直接映射;munmap释放由mmap创建的这段内存空间。
6.6 splice函数
-
用于两个文件描述符之间移动数据,零拷贝操作。
6.7 tee函数
-
用于在两个管道文件描述符之间复制数据,零拷贝操作。
6.8 fcntl函数
-
提供了对文件描述符的各种控制操作。
-
常用操作与参数
7 linux服务器程序规范
服务器程序规范:
-
以后台进程形式运行。没有控制终端,因此不会意外接收到用户收入,守护进程的父进程通常是init进程。
-
有一套日志系统,至少能输出日志到文件,有的高级服务器能输出日志到专门的udp服务器。
-
以某个专门的非root身份运行,如mysqld、httpd、syslogd等后台进程,分别由自己的运行账户mysql、apache、syslog
-
可配置的。能处理很多命令行选项,一次运行的选项太多,可以用配置文件来管理。
-
服务器进程通常会在启动的时候生成一个PID文件并存入/var/run目录中,以记录该后台进程的PID。
-
需要考虑系统资源和限制,比如进程可用文件描述符总数和内存总量。
7.1 日志
linux系统日志
-
linux提供了守护进程syslogd处理系统日志,现在常用升级版rsyslogd
-
接收用户进程输出的日志,也能接收内核日志。
syslog函数
-
应用程序使用syslog和rsyslogd守护进程通信。
-
改变默认输出方式
7.2 用户信息
UID、EUID、GID、EGID
uid是真是的用户id、euid是有效的用户id
切换用户
7.3 进程间关系
进程组
-
每个进程都隶属于一个进程组,除了pid信息外,还有进程组id即pgid。
会话
-
有关联的进程组形成一个会话session
用ps命令查看进程关系
7.4 系统资源限制
-
程序收资源限制的影响,如物理设备、系统策略、具体实现的限制。
-
资源限制类型
7.5 改变工作目录和根目录
7.6 服务器程序后台化
-
以守护进程的方式运行。
8 高性能服务器程序框架
-
服务器结构为
-
IO处理单元。IO处理单元的四种IO模型和两种高效事件处理模式。
-
逻辑单元。两种高效并发模式,以及高效的逻辑处理方式-有限状态机。
-
存储单元,服务器可选模块。
-
8.1 服务器模型
c/s模型
-
客户端都通过访问服务器来获取所需资源
-
创建监听socket,绑定端口,调用listen等到客户链接。客户端使用connect连接。
-
工作流程
p2p模型
8.2 服务器编程框架
-
服务器基本框架
8.3 I/O模型
-
分为阻塞I/O和非阻塞I/O。默认是阻塞的。
-
几种IO模型的差异
8.4 两种高效的事件处理模式
-
reactor和proactor。同步IO模型通常用于实现reactor模式,异步IO模型用于实现proactor模式。
reactor模式
proactor模式
-
所有的io操作都交给主线程和内核处理。工作线程只负责业务逻辑。
模拟proactor模式
8.5 两种高效的并发模式
-
并发编程有多进程和多线程2种方式。
半同步/半异步模式
-
并发模式中的同步异步指的是程序是否完全按照代码序列的顺序执行。
-
半同步半反应堆模式
-
相对高效的半同步半异步模式
-
领导者/追随者模式
-
多个工作线程轮流获得事件源时间,轮流监听、分发并处理事件。
-
句柄集
-
线程集
-
事件处理器和具体的事件处理器
8.6 有限状态机
-
逻辑单元内部的高效编程方法
-
p141 双状态机处理http请求
8.7 提高服务器性能的其他建议
池
数据复制
上下文切换和锁
9 I/O复用
-
io复用可以同时监听多个文件描述符
-
客户端处理多个socket 非阻塞connect
-
客户端同时处理用户输入和网络连接 聊天室程序
-
TCP服务器同时处理监听socket和连接socket。 最多的场合
-
服务器同时处理TCP UDP请求 回射服务器
-
服务器监听多个端口,处理多种服务。 xinetd服务器
-
-
IO复用本身是阻塞的。要实现并发,需要使用多进程或多线程手段。
9.1 select系统调用
-
在一段指定时间内,监听用户感兴趣的文件描述符上的可读、可写、异常事件。
-
同步非阻塞
select api
-
slect系统调用的函数
-
fd_set结构体
-
timeout参数设置select函数的超时时间。
文件描述符就绪条件
处理带外数据
-
socket接收到带外数据时将select返回,此时处于异常状态,具体处理过程见P148,在accept socket后用slect系统调用分别处理可读和异常两种类型。
9.2 poll系统调用
-
与select相似,指定时间内轮询一定数量的文件描述符,测试是否有就绪者。
9.3 epoll系列系统调用
内核事件表
-
linux特有的io复用函数,与select、poll不同。
epoll_wait
-
主要接口
LT和ET模式
EPOLLONESHOT事件
9.4 三组IO复用函数的比较
9.5 IO复用高级应用—非阻塞connect
p163
9.6 IO复用高级应用—聊天室程序
p165
9.7 IO复用高级应用—同时处理tcp和udp服务
9.8 超级服务xinetd
xinetd配置文件
xinetd工作流程
-
工作流程示意图
10 信号
-
信号是用户、系统或进程发送给目标进程的信息。
-
前台进程,用户通过输入特殊的终端符号发送信号,比如ctrl+c 中断。
-
系统异常。如浮点异常和非法内存段访问。
-
系统状态变化。如alarm定时器到期将引起sigalrm信号。
-
运行kill命令或调用kill函数。
服务器程序必须处理一些常见的信号,以免异常终止。
-
10.1 linux信号概述
发送信号
-
一个进程给其他进程发送信号的api是kill函数。
信号处理方式
-
进程接收信号时,需要定义一个接收函数处理。
linux信号
中断系统调用
10.2 信号函数
signal系统调用
sigaction系统调用
10.3 信号集
信号集函数
进程信号掩码
被挂起的信号
10.4 统一事件源
p184 代码
10.5 网络编程相关信号
SIGHUP
SIGPIPE
SIGURG
11 定时器(容器)
-
网络程序需要处理的第三类事件:定时事件
-
两种高效的管理定时器的容器:时间轮和时间堆。
-
linux提供了3种定时方式:
-
socket选项SO_RCVTIMEO SO_SNDTIMEO
-
SIGALRM信号
-
IO复用系统调用的超时参数。
-
11.1 socket选项SO_RCVTIMEO SO_SNDTIMEO
11.2 SIGALRM
基于升序链表的定时器
处理非活动连接
11.3 IO复用系统调用的超时函数
11.4 高性能定时器
时间轮
时间堆
12 高性能I/O框架库Libevent
-
linux服务器需要处理IO时间、信号、定时时间,考虑如下问题
-
统一事件源,利用IO复用系统调用来管理所有事件。
-
可移植性。
-
对并发编程的支持。各执行实体如何协同处理客户连接、信号和定时器,以避免竞态条件。
-