第二篇-深入解析高性能服务器编程
头文件汇总
fcntl()位于<fcntl.h>
,ioctl()位于<sys/ioctl.h>
,errno位于<errno.h>
<sys/socket.h>:
//函数:
int socket(int domain, int type, int protocol);
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
int listen(int sockfd, int backlog);
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
int shutdown(int sockfd, int how);
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);
//数据结构:
struct sockaddr //通用的套接字地址结构。
<sys/types.h>:
//数据类型
socklen_t
size_t
pid_t
<netinet/in.h>:
//数据结构:
struct in_addr //IPv4地址结构。
struct sockaddr_in //Internet套接字地址结构。
//函数:
uint32_t htonl(uint32_t hostlong); // 主机字节序到网络字节序的长整型转换。
uint16_t htons(uint16_t hostshort); // 主机字节序到网络字节序的短整型转换。
uint32_t ntohl(uint32_t netlong); // 网络字节序到主机字节序的长整型转换。
uint16_t ntohs(uint16_t netshort); //网络字节序到主机字节序的短整型转换。
<arpa/inet.h>:
//函数:
const char *inet_ntoa(struct in_addr in); // 将in_addr结构转换为可读的IPv4地址字符串。
int inet_aton(const char *cp, struct in_addr *inp); // 将可读的IPv4地址字符串转换为in_addr结构。
unsigned int inet_addr(const char *cp); // 将IPv4地址的点分十进制字符串转换为网络字节序的长整型。
<unistd.h>:
int close(int fd); // 关闭一个文件描述符。
ssize_t read(int fd, void *buf, size_t count); // 从文件描述符读取数据。
ssize_t write(int fd, const void *buf, size_t count); // 向文件描述符写入数据。
<sys/un.h>
(Unix域socket特有的头文件):
struct sockaddr_un // Unix域套接字地址结构。
第5章 基础API
socket
PF_xxx和AF_xxx完全一样
理论上建立socket时是指定协议,应该用PF_xxxx,设置地址时应该用AF_xxxx
//服务端:
//ipv4的sockaddr结构体
struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
inet_pton(AF_INET, "127.0.0.1", &server.sin_addr);
server.sin_port = htons(8080);
//SOCK_DGRAM是数据包,UDP协议
//失败返回-1
int sock = socket(PF_INET, SOCK_STREAM, 0);
//绑定和监听
//失败返回-1
int ret = bind(sock, (struct sockaddr *)&server, sizeof(server));
ret = listen(sock,5);//最多有5个全连接
//接收客户端连接
struct sockaddr_in client;
socklen_t client_addr_len = sizeof(client);
//失败返回值小于0,成功返回一个可使用的socket
//客户端socket地址存放在client中
//第三个参数传入的是一个指针,accept函数可能需要修改实际大小
int cnnfd = accept(sock, (struct sockaddr*)&client, &client_addr_len);
//读
memset(buffer, 0, BUF_SIZE);
ret = recv(cnnfd, buffer, BUF_SIZE-1, 0);
//写
ret = send(cnnfd, data, strlen(data),0);
//关闭同客户端的连接
close(cnnfd);
//关闭监听端口
close(sock);
//客户端
//设置要连接的服务端地址
struct sockaddr_in server;
//...设置过程
//连接
int sock = socket(PF_INET, SOCK_STRSTREAM, 0);
//连接失败返回-1
int ret = connect(sock, (struct sockaddr*)&server, sizeof(server));
//...收发数据
close(sock);
UDP通信因为没有连接,所以需要每次传入对方的地址
int ret = recvfrom(sock, buf, BUF_SIZE, flags,
(struct sockaddr*)&client, &client_addr_len);
int ret = sendto(sock, buf, len, flags,
(struct sockaddr*)&client, &client_addr_len);
通用系统调用:
ssize_t recvmsg(int sockfd, struct msghdr* msg, int flags);
ssize_t sendmsg(int sockfd, struct msghdr* msg, int flags);
//msghdr定义
struct msghdr
{
void *msg_name; /* Address to send to/receive from. */
socklen_t msg_namelen; /* Length of address data. */
struct iovec *msg_iov; /* Vector of data to send/receive into. */
size_t msg_iovlen; /* Number of elements in the vector. */
void *msg_control; /* Ancillary data (eg BSD filedesc passing). */
size_t msg_controllen; /* Ancillary data buffer length.
!! The type should be socklen_t but the
definition of the kernel is incompatible
with this. */
int msg_flags; /* 无需直接指定,会复制函数调用的第三个参数 */
};
如果是TCP连接,通用系统调用的msghdr中的地址需要置为NULL
网络信息API
#include <netdb.h>
- gethostbyname和gethostbyaddr
//同样,addr是struct sockaddr类型,type是指PF_INET等
struct hostent* gethostbyname(const char* name);
struct hostent* gethostbyaddr(const void * addr, size_t len, int type);
//hostent定义
/* Description of data base entry for a single host. */
struct hostent
{
char *h_name; /* Official name of host. */
char **h_aliases; /* Alias list. */
int h_addrtype; /* Host address type. */
int h_length; /* Length of address. */
char **h_addr_list; /* List of addresses from name server. */
};
- getservbyname和getservbyport获取服务信息
//proto可以是"tcp"、"udp",NULL
struct servent* getservbyname(const char* name, const char* proto);
struct servent* getservbyname(int port, const char* proto);
上述四个函数都是不可重入的,可重入版本后面加上
_r
通过这些函数可以方便地获取本地地址、服务地址等
第6章 高级IO函数
pipe
#include <unistd.h>
int pipe(int fd[2]);
fd[0]读,fd[1]写
//双向管道
int fd[2];
int res = socketpair(PF_UNIX, SOCK_STRSTREAM, 0, fd);
dup和dup2
用于复制文件描述符
#include <unistd.h>
readv和writev
#include <sys/uio.h>
从文件描述符读到分散的内存块中,从多个分散的内存块中合并写入文件描述符
分散读,集中写
相当于简化版的recvmsg和sendmsg
例如HTTP服务返回内容,头部一块,内容一块
//header_buf, file_buf struct iovec iv[2]; iv[0].iov_base = header_buf; iv[0].iov_len = strlen(header_buf); iv[1].iov_base = file_buf; iv[1].iov_len = file_len; ret = writev(connfd, iv, 2);
读也类似
sendfile
#include<sys/sendfile.h>
两个文件描述符之间直接传递数据,不用拷贝到用户态
ssize_t sendfile(int out_fd, int in_fd, off_t* offset, size_t count);
专门用于通过网络传输文件,out_fd
必须是socket,in_fd
必须是真实文件,不能是socket或管道
mmap和mummap
#include <sys/mman.h>
splice和tee
include <fcntl.h>
splice
两个文件描述符移动数据,也是不用拷贝到用户态
零拷贝
tee
两个管道文件描述符间复制数据,零拷贝操作
fcntl
include <fcntl.h>
file control,提供了对文件描述符的各种控制操作(ioctl也可以)
例如将一个文件描述符设置为非阻塞
//第二个参数标明执行什么功能,第三个参数作为这个功能的参数
int old_option = fcntl(fd, F_GETFL);
int new_option = old_option | O_NONBLOCK;
fcntl(fd, F_SETFL, new_option);
第7章 Linux服务器程序规范
第8章 高性能服务器程序框架
IO处理单元负责接收客户连接和数据,然后交由一个逻辑单元处理。
请求队列是个单元之间通信方式的抽象(池)
第9章 IO复用
epoll
#include <sys/epoll.h>
两种触发模式LT(Level Trigger)和ET(Edge Trigger)
LT只要有数据就触发,所以允许程序这次不处理,下次处理;ET要求程序必须处理,因为下次不会再通知。
LT和ET这么叫源于数字信号,水平触发是只要高于某个阈值就触发,边沿触发是发生变化时触发。
// 创建
//返回一个文件描述符,表示内核事件表,size是内核事件表大小
int epoll_create(int size);
//操作内核事件表
//op如EPOLL_CTL_ADD,EPOLL_CTL_MOD,EPOLL_CTL_DEL等等
//成功返回0,失败返回-1
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
struct epoll_event
{
uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
} __EPOLL_PACKED;
//epoll_data_t是一个联合体,可以是fd,可以是void *ptr
//检查是否就绪
//返回就绪文件描述符数量,失败返回-1
//将所有就绪事件从内核事件表复制到events指向的数组
int epoll_wait(int epfd, struct epoll_event* events, int maxevents,
int timeout);
事件:
- EPOLLIN
- 有数据可读
- EPOLLET
- 表明使用ET模式
- EPOLLONSHOT
- 只触发一次,可保证同一时间只有一个工作线程处理该socket
- 用完重新设置oneshot
- EPOLLHUP
- 对方挂断
第10章 信号
#include <signal.h>
//两种方式设置信号
extern __sighandler_t signal (int __sig, __sighandler_t __handler)
__THROW;
/* Get and/or set the action for signal SIG. */
extern int sigaction (int __sig, const struct sigaction *__restrict __act,
struct sigaction *__restrict __oact) __THROW;
//sighandler处理函数指针
typedef void (*__sighandler_t) (int);
/* Structure describing the action to be taken when a signal arrives. */
struct sigaction
{
/* Signal handler. */
#if defined __USE_POSIX199309 || defined __USE_XOPEN_EXTENDED
union
{
/* Used if SA_SIGINFO is not set. */
__sighandler_t sa_handler;
/* Used if SA_SIGINFO is set. */
void (*sa_sigaction) (int, siginfo_t *, void *);
}
__sigaction_handler;
# define sa_handler __sigaction_handler.sa_handler
# define sa_sigaction __sigaction_handler.sa_sigaction
#else
__sighandler_t sa_handler;
#endif
/* Additional set of signals to be blocked. */
__sigset_t sa_mask;
/* Special flags. */
int sa_flags;
/* Restore handler. */
void (*sa_restorer) (void);
};
一个例子:
void addsig(int sig){//设置信号及其处理函数
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_handler = sig_handler;
sa.sa_flags |= SA_RESTART;
sigfillset(&sa.sa_mask);
int ret = sigaction(sig,&sa,NULL);
assert(ret != -1);
}
在以上这段代码中,设置SA_RESTART
flag的作用是当一些系统调用阻塞期间出现信号,系统调用将中断(并设置errno为EINTR错误),该flag可恢复这些系统调用。
sa_mask
是屏蔽信号集,信号处理期间屏蔽其他信号,避免重复触发
可在sig_handler
中将信号通过PF_UNIX
的socket发送,并通过epoll统一处理