Web-Server高性能服务器
一、LINUX网络编程基础API
1、socket地址API
1.主机字节序和网络字节序
即小端字节序和大端字节序,linux提供htonl、htons、ntohl、ntohs4个函数来实现主机字节序和网络字节序的转换。
2.通用socket地址
socket网络编程接口中表示socket地址的是sockaddr结构体:
struct socadd{ sa_family_t sa_family; char sa_data[4]; }
sa_family_t为地址族类型(地址族与协议族对应)
linux下的通用socket结构体:
3.专用socket地址
UNIX本地域协议族专用socket地址结构体:
sockaddr_un\sockaddr_in\sockaddr_in6
4.IP地址转换函数
in_addr_t inet_addr(const char * strptr):点分十进制的IPv4地址转换为用网络字节序整数表示的IPv4,失败INADDR_NONE;
int inet_aton(const char* cp, struct in_addr* inp):和inet_addr功能相同,但结果是转化为存储于参数inp指向的地址结构中,成功1,是啊白0;
char * inet_ntoa( struct in_addr in):将用网络字节序整数表示的IPv4地址转化为用点分十进制字符串表示的IPv4地址。
同时适用于IPv4和IPv6的函数:
指定cnt大小的宏定义:
2、创建socket
domain参数:使用哪个底层协议,TCP/IP协议族:PF_INET,PF_INET6,UNIX本地域协议族:PF_UNIX;
type参数:指定服务类型,SOCK_STREAM(流服务,TCP)和SOCK_UGRAM(数据报服务,UDP);
protocal参数:在前两个参数构成的协议集合下,再选择一个具体的协议,默认为0(别动它)。
socket系统调用成功时返回一个socket文件描述符,失败返回-1并设置errno。
3、命名socket
bind将my_addr所指的socket地址分配给未命名的sockfd文件描述符,addrlen参数指出该socket地址长度
bind成功时返回0,失败返回-1并设置errno
两种常见errno:
EACCES:被绑定的地址是受保护的,仅超级用户能访问。
EADDRINUSE:被绑定地址正在使用。
4、监听socket
创建监听队列:
sockfd参数:指定被监听的socket;
backlog参数:提示内核监听队列的最大长度,长度超过将不受理;
listen成功时返回0,失败则返回-1并设置errno。
5、接受连接
从listen监听队列中接受一个连接:
sockfd参数:执行过listen系统调用的监听socket;
addr参数:获取被接受连接的远端socket地址;
addrlen参数:addr参数获取的socket地址的长度;
accept成功返回一个新的连接socket,该socket唯一地标识了被接受的这个连接,服务器可通过读写该socket来与被接受连接对应的客户端通信;
accpet失败返回-1并设置errno。
6、发起连接
客户端主动与服务器建立连接的函数:
sockfd参数:由socket系统调用返回一个socket;
serv_addr参数:服务器监听的socket地址;
addr_len参数:指定这个地址的长度;
connect成功返回0,一旦成功建立连接,sockfd就唯一标识了这个连接,客户端就可以通过读写sockfd来与服务器通信,
失败则返回-1,并设置errno,常见errno:
ECONNREFUSED:目标端口不存在,连接被拒绝。
ETIMEDOUT:连接超时。
7、关闭连接
通过关闭普通文件描述符的系统调用完成:
fd参数是待关闭的socket
close系统调用并非总是立即关闭一个连接,而是将fd的引用计数减1,只有当fd为0时才真正关闭连接,
系统调用默认将使父进程中打开的socket的引用计数加1,因此必须在父进程和子进程中都对该socket执行close调用才能将连接关闭。
强制关闭:
sockfd参数:待关闭的socket;
howto参数:
shutdown成功返回0,失败返回-1并设置errno。
8、数据读写
1.TCP数据读写
TCP数据流读写的系统调用:
recv读socket上数据,buf指定读缓冲区的位置,len指定读缓冲区的大小,flags参数通常为0;
recv成功时返回实际读取到的数据的长度,可能会小于预期长度,这时可能会多次调用recv才能得到完整数据;
recv返回0就意味着通信对方已经关闭连接;
recv出错时返回-1并设置errno。
send写socket上数据,buf指定写缓冲区的位置,len指定写缓冲区的大小;
send成功时返回实际写入数据的长度;
send失败时返回-1并设置errno。
flags参数为数据的收发提供额外的控制:
MSG_OOB选项给应用程序提供了发送和接收带外数据的方法。
2.UDP数据读写
recvfrom读取sockfd上的数据,buf指定读缓冲区的位置,len指定读缓冲区的大小,
因为UDP通信没有连接的概念,所以每次读取数据都需要获取发送端的socket地址,即参数src_addr所指的内容,
addrlen指定src_addr地址的长度。
sendto往sockfd上写数据,buf指定读缓冲区的位置,len指定读缓冲区的大小,
dest_addr参数指定接收端的socket地址,
addrlen指定src_addr地址的长度。
flags和send/recv系统调用的flags一致,返回值也相同。
注意,如果奖最后两个参数都设置为NULL,那么他们也可以用于面向连接的数据读写。
3.通用数据读写函数
TCP和UDP通用的数据读写系统调用:
msghdr结构体定义:
msg_name成员指向一个socket地址,它指定通信双方的socket地址,
对于TCP协议,该成员变量无意义,必须被设置为NULL,因为对数据流socket来说,对方的地址已经知道。
msg_namelen成员指定msg_name所指socket的长度。
msg_ios成员是iovec结构体类型指针:
iovec结构体封装了一块内存的起始位置和长度,
msg_iovlen指定这样的iovec结构对象有多少个。
recvmsg的分散读(scatter read):数据被读取并存在msg_iovlen块分散的内存中,这些内存的位置和长度由msg_iov指向的数组指定。
sendmsg的集中读(gather write):msg_iovlen块分散内存中的数据将被一并发送。
msg_control和msg_controllen成员用于辅助数据的传送。
msg_flags成员无须设定,它会赋值recvmsg/sendmsg的flags参数的内容以影响程序读写过程,
recvmsg调用结束前会将某些更新后的标志设置到msg_flags中。
注,recvmsg和sendmsg的flags参数含义和recv、send一致。
9、带外标记
内核通知应用程序带外数据到达的两种方式:I/O复用和SIGURG信号。
应用程序得到带外数据需接收的通知后,用sockatmark判断sockfd是否处于带外标记:
即下一个被读取到的数据是否是带外数据
socktamark返回1,此时就可以用带MSG_OOB标志的recv调用来接收带外数据,
如果不是,则返回0。
10、地址信息函数
获取一个连接socket的本端socket地址,以及远端的socket地址:
getsockname获取sockfd对应的本端socket地址,并将其存储在address参数指定的内存,该socket地址的长度存储在address_len参数指向变量中,
如果实际长度大于address所指内存区的大小,则该socket地址将被截断,
getsockname成功返回0,失败返回-1并设置errno。
getpeername获取sockfd对应的远端socket地址,其他参数含义和 getsockname一致。
11、socket选项
fcntl系统调用是控制文件描述符熟悉的通用POSIX方法
两个专门用来读取和设置的两个系统调用:
sockfd参数:指定被操作的socket;
level参数:指定要操作哪个协议的选项(属性,如IPv4、IPv6、TCP等);
option_name参数:指定选项的名字(见下表);
option_value参数:被操作选项的值;
option_len参数:被操作选项的长度。
getsockopt和setsockopt成功返回0,失败返回-1并设置errno。
部分重要的socket选项实现: (后续补充2022.04.25记)
1.SO_REUSEADDR选项
2.SO_RCVBUF和SO_SNDBUF选项
3.SO_RCVLOWAT好SO_SNDLOWAT选项
4.SO_LINGER选项
12、网络信息API
1.gethostbyname和gethostbyaddr
gethostbyname函数根据主机名称获取主机的完整信息,gethostbyaddr函数根据IP地址获取主机的完整信息。
gethostbyname获取主机顺序:本地/etc/hosts配置文件中查找主机->DNS服务器。
两个函数的定义:
name参数:指定主机的主机名
addr参数:指定目标主机的IP地址
len参数:指定addr所指IP地址的长度
type参数:指定addr所指IP地址的类型(AF_INET和AF_INET6)
这两个函数的返回值都是hostent结构体类型的指针:
2.getservbyname和getservbyport
getservbyname函数根据名称获取某个服务的完整信息,getservbyport函数根据端口号获取某个服务的完整信息,
它们都是通过读取/etc/service文件来获取服务信息的,其结构体如下:
注:
上面的4个函数都是不可重入的,即非线程安全的。
在netdb.h头文件给出了它们的可重入版本,即在它们的尾部加入 _r(re-entrant)
3.getaddrinfo
getaddrinfo函数即能通过主机名获取IP地址,也能通过服务名获取端口号(内部分别使用了gethostbyname和getservbyname)
getaddinfo定义:
hostname参数:可以接收主机名,也可以接收字符表示的IP地址。
service参数:可以接收服务名,也可以接收字符串表示的十进制端口号。
hints参数:应用程序给getaddrinfo的一个提示,以对getaddrinfo的输出进行更精确的控制。hists的参数为NULL时,表示允许getaddrinfo反馈任何可用的结果。
result参数:指向一个链表,该链表用于存储getaddrinfo反馈的结果。
getaddrinfo反馈的每一条结果都是addrinfo结构体类型的对象,其定义如下:
使用hints参数的时候,可设置ai_flags、ai_family、ai_socktype、ai_protocol四个字段,其他字段必须设置为NULL
由于getaddrinfo会隐式地分配内存,所以必须使用配套的一个函数来释放其分配的内存:
4.getnameinfo
getnameinfo函数能通过socket地址同时获取以字符串表示的主机名和服务名(分别使用了gethostbyaddr和getservport)
getnameinfo函数定义如下:
getnameinfo将返回的主机名存储在艾host参数指向的缓存中,
将服务名存储在serv参数指向的缓存中,
hostlen和servlen参数分别指定这两块缓存的长度,
flags参数表:
getaddrinfo和genameinfo函数成功时返回0,失败时返回错误码:
Linux下的strerror函数能将数值错误码errno转换成易读的字符串,下面的函数也可将上表中的错误码转换成其字符串形式:
二、高级I/O函数
1、pipe函数
2、dup函数和dup2函数
3、readv函数和writev函数
4、sendfile函数
5、mmap函数和munmap函数
6、splice函数
7、tee函数
8、fcntl函数
****参考说明****
《Linux高性能服务器编程》--游双