int socket(int domain, int type, int protocol);
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
补充:字节序
uint32_t ntohl (uint32_t __netlong);
uint16_t ntohs (uint16_t __netshort);
uint32_t htonl (uint32_t __hostlong);
uint16_t htons (uint16_t __hostshort);
函数名:n代表网络,h代表主机,即n to h,最后的 l 代表整型,4字节,s代表2字节。
struct sockaddr {
__SOCKADDR_COMMON (sa_);
char sa_data[14]; /* Address data. */
};
if(bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) != 0)
int listen(int sockfd, int backlog);
int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);
连接过程:
当全连接队列已满,就会根据/proc/sys/net/ipv4/tcp_abort_on_overflow策略进行处理。
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
发的时候可能没有全发,所以实际开发过程中需要在外进行封装,记录发了多少字节,接收也同理。
服务端经典的C10k问题
思路:在读取socket之前,先查下它的状态,然后只处理ready状态的socket。
// 初始化fdset集合
fd_set readfdset;
FD_ZERO(&readfdset);
FD_SET(listensock,&readfdset);
// readfdset中socket的最大值。
int maxfd = listensock;
优点:
- 单线程处理多个的I/O请求
- 可以设置超时时间
缺陷:
- 可监视的fd数量上限是1024
- 线性扫描fd,效率低
思路:用于数组代替fd_set,消除监视fd数量的限制。
struct pollfd fds[MAXNFDS];
fds[clientsock].fd = clientsock;
fds[clientsock].events = POLLIN;
struct pollfd {
int fd; /* File descriptor to poll. */
short int events; /* Types of events poller cares about. */
short int revents; /* Types of events that actually occurred. */
};
使用:
while (1) {
int infds = poll(fds,maxfd+1,5000);
for (int eventfd=0; eventfd <= maxfd; eventfd++) {
if ((fds[eventfd].revents&POLLIN)==0) continue;
ssize_t isize=read(eventfd,buffer,sizeof(buffer));
// do something
}
}
优点:解决了selecr中fd上限的问题
缺陷:与select一样,遍历获取描述符
思路:只返回状态发生变化的fd。
int epollfd = epoll_create(1);
// 添加监听描述符事件
struct epoll_event ev;
ev.data.fd = listensock;
ev.events = EPOLLIN;
epoll_ctl(epollfd,EPOLL_CTL_ADD,listensock,&ev);
基于事件的触发模式,事件类型:EPOLLIN、EPOLLOUT等。
struct epoll_event {
uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
}
事件中可以携带fd或任意用户数据。
typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
使用:
while (1) {
struct epoll_event events[64];
int infds = epoll_wait(epollfd,events,64,-1);
// 遍历有事件发生的结构数组。
for (int i=0;i<infds;i++) {
if (events[ii].events & EPOLLIN) {
// 读取客户端的数据。
read(events[i].data.fd,buffer,sizeof(buffer));
do_something()
}
}
}
高效的原因:
- 仅返回状态变化的fd
- 使用mmap,在内核与用户空间传递信息
使用限制:
- 当监控fd数量较少(10)时没有优势;
- 仅能在linux上使用;
此种模型,通常只有一个epoll对象,所有的接收客户端连接、客户端读取、客户端写入操作都饱含在一个线程内。该种模型也有一些中间件在用,比如redis。
该模型主要是通过将前面的模型进行改造,将读写的业务逻辑交给具体的线程池来实现,这样可以显示reactor线程对IO的响应,以此提升系统性能。
在这种模型中,主要分为两个部分:mainReactor、subReactors。
mainReactor主要负责接收客户端的连接,然后将建立的连接通过负载均衡的方式分发给subReactors,subReactors来负责具体的每个连接的读写。
组件框架 | epoll触发方式 | reactor模型 |
---|---|---|
redis | 水平触发 | 单reactor模型 |
skynet | 水平触发 | 单reactor模型 |
memcached | 水平触发 | 多reactor模型(多线程) |
nginx | 边缘触发 | 多reactor模型(多进程) |