IO复用函数
IO复用函数
IO复用函数是操作系统提供的可以同时用来处理对个socket(甚至文件描述符)上的读写事件函数。
基本作用:
- 同时处理多个socket
- 用于检测socket读写事件
常用的IO复用函数
- select(Windows/Linux)
- poll(Linux)
- epoll模型(epoll_create、epoll_ctl、epoll_wait)(Linux)
- WSAEventSelect(Windows)
- WSAAsyncSelect(Windows)
- WSAPoll(Windows)
- IOCP模型(Windows)
select
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
void FD_CLR(int fd, fd_set *set);
int FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* microseconds */
};
基本用法
- 定义一个readset或者writeset的fd_set集合(FD_ZERO);
- 根据业务需求,将需要检测读(写)事件的socket绑定到fd_set(FD_SET);
- 调用select函数,传入fd_set,并且设置超时时间;
- select函数返回后,我们需要调用FD_ISSET判断fd_set中是否有感兴趣的读写事件。
重难点
- 第一个参数问题:Linux操作系统,必须设置待检测的fd最大值加1,Windows随意设置,但是为了代码跨平台,建议和Linux保持一致;
- 三个fd_set类型的参数设置为NULL,功能类似sleep函数;
- 最后一个参数timeout,如果设置为NULL,select会一直阻塞等待,直到任何socket有期望的事件;如果timeout时间值设置为0,则select会立即检测一次,并立即返回;如果timeout设置的时间值大于0,则等待相关时间值。
- 不要假设select对最后一个参数值进行修改,是否修改的行为是未定义的。
select函数的优缺点
缺点:
- 有数量限制,一般是1024;
- 当同一时刻,活跃的socket(有事件的socket)不多时,select函数需要遍历,效率较低。
优点:
当同一时刻,活跃的socket较多的时候,效率高。
例程
服务端代码
用select监听tcp socket读的例子。
/*************************************************************************
> File Name: server.c
> Author:
> Mail:
> Created Time: Fri 28 Jul 2023 09:27:59 AM CST
************************************************************************/
/*
1. 定义一个readset或者writeset的fd_set集合(FD_ZERO);
2. 根据业务需求,将需要检测读(写)事件的socket绑定到fd_set(FD_SET);
3. 调用select函数,传入fd_set,并且设置超时时间;
4. select函数返回后,我们需要调用FD_ISSET判断fd_set中是否有感兴趣的读写事件。
*/
/*
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
void FD_CLR(int fd, fd_set *set);
int FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);
*/
/*************************************************************************
> File Name: server.c
> Author:
> Mail:
> Created Time: Wed 02 Aug 2023 09:40:26 AM CST
************************************************************************/
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "list.h"
#define BUF_SIZE (1024)
void read_data(fd_set read_fds);
typedef struct node {
struct list_head list;
int fd;
} node_t;
typedef struct fd_list {
struct list_head head;
int max_fd;
fd_set read_fds;
} fd_list_t;
static fd_list_t list;
void fd_list_init(int max_fd) {
INIT_LIST_HEAD(&list.head);
list.max_fd = max_fd;
FD_ZERO(&list.read_fds);
FD_SET(max_fd, &list.read_fds);
}
int tcp_server_socket(char *addr, char *port) {
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {
printf("socket failed, errmsg: %s\n", strerror(errno));
return -1;
}
struct sockaddr_in serv_addr = {0};
serv_addr.sin_family = AF_INET;
inet_pton(AF_INET, addr, &serv_addr.sin_addr);
serv_addr.sin_port = htons(atoi(port));
if (-1 == bind(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr))) {
printf("bind failed, errmsg: %s\n", strerror(errno));
close(sockfd);
return -1;
}
if (-1 == listen(sockfd, 128)) {
printf("listen failed, errmsg: %s\n", strerror(errno));
close(sockfd);
return -1;
}
return sockfd;
}
void tcp_client_accept(int listen_fd) {
struct sockaddr_in addr = {0};
socklen_t len = sizeof(socklen_t);
int connfd = accept(listen_fd, (struct sockaddr *)&addr, &len);
if (connfd == -1) {
printf("accept failed, errmsg: %s\n", strerror(errno));
}
node_t *node = (node_t *)malloc(sizeof(node_t));
node->fd = connfd;
list_add_tail(&node->list, &list.head);
if (connfd > list.max_fd) {
list.max_fd = connfd;
}
FD_SET(connfd, &list.read_fds);
return;
}
void close_all_fd(void) {
node_t *node = NULL, *next = NULL;
list_for_each_entry_safe(node, next, &list.head, list) {
list_del(&node->list);
close(node->fd);
free(node);
}
}
int main(int argc, char *argv[]) {
if (argc != 3) {
printf("%s ip port\n", argv[0]);
return -1;
}
int ret = 0;
int listen_fd = tcp_server_socket(argv[1], argv[2]);
fd_list_init(listen_fd);
struct sockaddr_in clnt_addr = {0};
socklen_t len = sizeof(clnt_addr);
fd_set read_fds;
struct timeval timeout = {0};
while (1) {
FD_ZERO(&read_fds);
read_fds = list.read_fds;
timeout.tv_sec = 1;
timeout.tv_usec = 0;
ret = select(list.max_fd + 1, &read_fds, NULL, NULL, &timeout);
if (ret < 0) {
printf("select failed, errmsg: %s\n", strerror(errno));
break;
} else if (ret == 0) {
printf("select timeout\n");
continue;
} else {
if (FD_ISSET(listen_fd, &read_fds)) {
tcp_client_accept(listen_fd);
} else {
read_data(read_fds);
}
}
}
close_all_fd();
close(listen_fd);
return 0;
}
void read_data(fd_set read_fds) {
int ret = 0;
char buf[BUF_SIZE] = {0};
node_t *node = NULL, *next = NULL;
list_for_each_entry_safe(node, next, &list.head, list) {
if (FD_ISSET(node->fd, &read_fds)) {
memset(buf, 0, BUF_SIZE);
ret = recv(node->fd, buf, BUF_SIZE, 0);
if (ret <= 0) {
printf("recv remote error or close, errmsg: %s\n", strerror(errno));
FD_CLR(node->fd, &list.read_fds);
list_del(&node->list);
close(node->fd);
free(node);
} else {
printf("fd: %d recv %d bytes, message: %s\n", node->fd, ret, buf);
}
}
}
}
客户端代码
/*************************************************************************
> File Name: client.c
> Author:
> Mail:
> Created Time: Mon 31 Jul 2023 10:50:43 AM CST
************************************************************************/
#include <errno.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
int main(int argc, char* argv[]) {
if (argc != 3) {
printf("%s ip port\n", argv[0]);
return -1;
}
int ret = 0;
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {
printf("socket failed, errmsg: %s\n", strerror(errno));
return -1;
}
struct sockaddr_in serv_addr = { 0 };
serv_addr.sin_family = AF_INET;
inet_pton(AF_INET, argv[1], &serv_addr.sin_addr.s_addr);
serv_addr.sin_port = htons(atoi(argv[2]));
ret = connect(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
if (ret == -1) {
printf("connect %s:%hu failed, errmsg: %s\n", argv[1], atoi(argv[2]), strerror(errno));
close(sockfd);
return -1;
}
char buf[128] = "hello select!";
while (1) {
ret = send(sockfd, buf, strlen(buf), 0);
if (ret < 0) {
printf("send buf: %s to %s:%s failed, errmsg: %s\n",
buf, argv[1], argv[2], strerror(errno));
break;
} else if (ret == 0) {
printf("remote close\n");
break;
} else {
printf("send buf: %s successful\n", buf);
}
sleep(1);
}
close(sockfd);
return 0;
}
内核链表头文件
/*-
* Copyright (c) 2011 Felix Fietkau <nbd@openwrt.org>
* Copyright (c) 2010 Isilon Systems, Inc.
* Copyright (c) 2010 iX Systems, Inc.
* Copyright (c) 2010 Panasas, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice unmodified, this list of conditions, and the following
* disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef _LINUX_LIST_H_
#define _LINUX_LIST_H_
#include <stddef.h>
#include <stdbool.h>
#define prefetch(x)
#ifndef container_of
#define container_of(ptr, type, member) \
({ \
const __typeof__(((type *) NULL)->member) *__mptr = (ptr); \
(type *) ((char *) __mptr - offsetof(type, member)); \
})
#endif
struct list_head {
struct list_head *next;
struct list_head *prev;
};
#define LIST_HEAD_INIT(name) { &(name), &(name) }
#undef LIST_HEAD
#define LIST_HEAD(name) struct list_head name = LIST_HEAD_INIT(name)
static inline void
INIT_LIST_HEAD(struct list_head *list)
{
list->next = list->prev = list;
}
static inline bool
list_empty(const struct list_head *head)
{
return (head->next == head);
}
static inline bool
list_is_first(const struct list_head *list,
const struct list_head *head)
{
return list->prev == head;
}
static inline bool
list_is_last(const struct list_head *list,
const struct list_head *head)
{
return list->next == head;
}
static inline void
_list_del(struct list_head *entry)
{
entry->next->prev = entry->prev;
entry->prev->next = entry->next;
}
static inline void
list_del(struct list_head *entry)
{
_list_del(entry);
entry->next = entry->prev = NULL;
}
static inline void
_list_add(struct list_head *_new, struct list_head *prev,
struct list_head *next)
{
next->prev = _new;
_new->next = next;
_new->prev = prev;
prev->next = _new;
}
static inline void
list_del_init(struct list_head *entry)
{
_list_del(entry);
INIT_LIST_HEAD(entry);
}
#define list_entry(ptr, type, field) container_of(ptr, type, field)
#define list_first_entry(ptr, type, field) list_entry((ptr)->next, type, field)
#define list_last_entry(ptr, type, field) list_entry((ptr)->prev, type, field)
#define list_for_each(p, head) \
for (p = (head)->next; p != (head); p = p->next)
#define list_for_each_safe(p, n, head) \
for (p = (head)->next, n = p->next; p != (head); p = n, n = p->next)
#define list_for_each_entry(p, h, field) \
for (p = list_first_entry(h, __typeof__(*p), field); &p->field != (h); \
p = list_entry(p->field.next, __typeof__(*p), field))
#define list_for_each_entry_safe(p, n, h, field) \
for (p = list_first_entry(h, __typeof__(*p), field), \
n = list_entry(p->field.next, __typeof__(*p), field); &p->field != (h);\
p = n, n = list_entry(n->field.next, __typeof__(*n), field))
#define list_for_each_entry_reverse(p, h, field) \
for (p = list_last_entry(h, __typeof__(*p), field); &p->field != (h); \
p = list_entry(p->field.prev, __typeof__(*p), field))
#define list_for_each_prev(p, h) for (p = (h)->prev; p != (h); p = p->prev)
#define list_for_each_prev_safe(p, n, h) for (p = (h)->prev, n = p->prev; p != (h); p = n, n = p->prev)
static inline void
list_add_new(struct list_head *_new, struct list_head *head)
{
_list_add(_new, head, head->next);
}
static inline void
list_add_tail(struct list_head *_new, struct list_head *head)
{
_list_add(_new, head->prev, head);
}
static inline void
list_move(struct list_head *list, struct list_head *head)
{
_list_del(list);
list_add_new(list, head);
}
static inline void
list_move_tail(struct list_head *entry, struct list_head *head)
{
_list_del(entry);
list_add_tail(entry, head);
}
static inline void
_list_splice(const struct list_head *list, struct list_head *prev,
struct list_head *next)
{
struct list_head *first;
struct list_head *last;
if (list_empty(list))
return;
first = list->next;
last = list->prev;
first->prev = prev;
prev->next = first;
last->next = next;
next->prev = last;
}
static inline void
list_splice(const struct list_head *list, struct list_head *head)
{
_list_splice(list, head, head->next);
}
static inline void
list_splice_tail(struct list_head *list, struct list_head *head)
{
_list_splice(list, head->prev, head);
}
static inline void
list_splice_init(struct list_head *list, struct list_head *head)
{
_list_splice(list, head, head->next);
INIT_LIST_HEAD(list);
}
static inline void
list_splice_tail_init(struct list_head *list, struct list_head *head)
{
_list_splice(list, head->prev, head);
INIT_LIST_HEAD(list);
}
typedef int (*list_comp) (void *k1, void *k2);
static inline void order_list_add(struct list_head *head, struct list_head *_new, list_comp comp)
{
struct list_head *pos;
struct list_head *nex;
int diff;
list_for_each_safe(pos, nex, head)
{
diff = (*comp)(pos, _new); // small --> big
if(diff > 0)
{
break;
}
}
list_add_tail(_new, pos);
}
static inline void list_for_each_del_safe(struct list_head *head)
{
struct list_head *pos;
struct list_head *nex;
list_for_each_safe(pos, nex, head)
{
list_del(pos);
}
}
#endif /* _LINUX_LIST_H_ */
重难点
- 第一个参数问题:Linux操作系统,必须设置待检测的fd最大值加1,Windows随意设置,但是为了代码跨平台,建议和Linux保持一致;
- 三个fd_set类型的参数设置为NULL,功能类似sleep函数;
- 最后一个参数timeout,如果设置为NULL,select会一直阻塞等待,直到任何socket有期望的事件;如果timeout时间值设置为0,则select会立即检测一次,并立即返回;如果timeout设置的时间值大于0,则等待相关时间值。
- 不要假设select会对最后一个参数值进行修改,是否修改的行为是未定义的(也就是说select每次调用都需要去重新设置超时时间)。
select函数的优缺点
缺点:
- 有数量限制,一般是1024;
- 当同一时刻,活跃的socket(有事件的socket)不多时,select函数需要遍历,效率较低。
优点:
当同一时刻,活跃的socket较多的时候,效率高。
poll函数
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
struct pollfd {
int fd; /* file descriptor */
short events; /* requested events */
short revents; /* returned events */
};
//pollfd pf[2];
Windows上等价于WSAPoll函数。
跟select的逻辑不通,poll不再告知内核监听的文件描述范围,而是通过struct pollfd结构体数组,精确通知内核用户所关心的文件描述符。
我们来看一下struct pollfd结构体的具体成员,以及各个成员所代表的具体含义。
struct pollfd {
int fd; /* file descriptor */
short events; /* requested events */
short revents; /* returned events */
};
- fd:表示一个用户层所关心的文件描述符。
- events: 是一个输入参数,通过位操作的方式设置用户程序所关心的文件描述符事件(读、写)。
- revents: 是一个输出参数,内核同样通过位操作的方式返回发生的文件描述符事件。
events和revents能够设置的值都定义在<poll.h>头中,有以下几种可能
- POLLIN, 读事件
- POLLPRI, 读事件,但表示紧急数据,例如tcp socket的带外数据
- POLLWRNORM, 读事件,表示有普通数据可读
- POLLRDBAND, 读事件,表示有优先数据可读
- POLLOUT, 写事件
- POLLWRNORM, 写事件,表示有普通数据可写
- POLLWRBAND, 写事件,把表示有优先数据可写
- POLLRDHUP, 写事件,表示有优先数据可写
- POLLRDHUP(since Linux 2.6.17),Stream socket的一端关闭了连接(注意是stream socket,我们知道还有raw socket,dgram socket),或者是写端关闭了连接,如果要使用这个事件,必须定义_GNU_SOURCE 宏。这个事件可以用来判断链路是否发生异常(当然更通用的方法是使用心跳机制)。要使用这个事件,得这样包含头文件:
#define _GNU_SOURCE
#include <poll.h>- POLLERR,仅用于内核设置传出参数revents,表示设备发生错误
- POLLHUP,仅用于内核设置传出参数revents,表示设备被挂起,如果poll监听的fd是socket,表示这个socket并没有在网络上建立连接,比如说只调用了socket()函数,但是没有进行connect。
- POLLNVAL,仅用于内核设置传出参数revents,表示非法请求文件描述符fd没有打开
poll返回值
positive number,表示struct pollfd结构体数组中有多少个非0的revents,换句话说,就是这一次调用poll发生哪些事件。
等于0,表示timeout到时,并且没有文件描述符准备好
等于-1,内部发生了错误,errno将会被设置
大于0,表示监听成功的文件描述符数量
当poll返回值为-1时,表示poll出错,errno将被设置,errno的取值有4种可能
EFAULT ,参数struct pollfd结构体数组不在用户地址空间,比如传入的参数nfds比实际的数组要大。
EINTR ,被信号中断.
EINVAL , nfds 超出了RLIMIT_NOFILE值.
ENOMEM ,内核没有足够的内存装载struct pollfd结构体数组
poll原理
poll的功能和select的功能一样,参数不同,从而导致用法也略微不同,相对于select来说,用起来也稍微好用一些,poll的底层原理也和select差不多。
例程
服务端代码
/*************************************************************************
> File Name: server.c
> Author:
> Mail:
> Created Time: Wed 02 Aug 2023 05:57:24 PM CST
************************************************************************/
#include <poll.h>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <netinet/in.h>
int tcp_server_socket(char *addr, char *port) {
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {
printf("socket failed, errmsg: %s\n", strerror(errno));
return -1;
}
struct sockaddr_in serv_addr = {0};
serv_addr.sin_family = AF_INET;
inet_pton(AF_INET, addr, &serv_addr.sin_addr);
serv_addr.sin_port = htons(atoi(port));
if (-1 == bind(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr))) {
printf("bind failed, errmsg: %s\n", strerror(errno));
close(sockfd);
return -1;
}
if (-1 == listen(sockfd, 128)) {
printf("listen failed, errmsg: %s\n", strerror(errno));
close(sockfd);
return -1;
}
return sockfd;
}
int tcp_client_accept(int listen_fd) {
struct sockaddr_in addr = { 0 };
socklen_t len = sizeof(socklen_t);
int connfd = accept(listen_fd, (struct sockaddr *)&addr, &len);
if (connfd == -1) {
printf("accept failed, errmsg: %s\n", strerror(errno));
return -1;
}
printf("accept %s:%hu successful\n", inet_ntoa(addr.sin_addr), htons(addr.sin_port));
return connfd;
}
#define BUF_SIZE 128
#define POLL_SIZE (128)
#define POLL_TIMEOUT (8000)
int main(int argc, char *argv[]) {
if (argc != 3) {
printf("%sip port\n", argv[0]);
return -1;
}
int ret = 0, max_i = 0;
char buf[BUF_SIZE] = { 0 };
int listen_fd = tcp_server_socket(argv[1], argv[2]);
struct pollfd event[POLL_SIZE] = { 0 };
event[0].fd = listen_fd;
event[0].events = POLLRDNORM;
for (int i = 1; i < POLL_SIZE; i++) {
event[i].fd = -1;
}
while (1) {
ret = poll(event, max_i + 1, POLL_TIMEOUT);
if (ret == -1) {
printf("[POLL] poll failed, errmsg: %s\n", strerror(errno));
break;
} else if (ret == 0) {
printf("[POLL] poll timeout!\n");
} else {
if (event[0].revents == POLLRDNORM) {
int i = 0;
int connfd = tcp_client_accept(listen_fd);
for (i = 1; i < POLL_SIZE; i++) {
if (event[i].fd == -1) {
break;
}
}
if (i == POLL_SIZE) {
printf("too many clients\n");
}
event[i].fd = connfd;
event[i].events = POLLRDNORM;
if (max_i < i) {
max_i = i;
}
if (--ret <= 0) {
continue;
}
}
for (int i = 1; i <= max_i; i++) {
if (event[i].fd == -1) {
continue;
}
if (event[i].revents & POLLRDNORM) {
memset(buf, 0, BUF_SIZE);
int nread = read(event[i].fd, buf, BUF_SIZE);
if (nread <= 0) {
printf("read error or close, errmsg: %s\n", strerror(errno));
close(event[i].fd);
event[i].fd = -1;
} else {
printf("recv %d bytes, msg: %s\n", nread, buf);
}
}
}
}
}
}
epoll模型
基本用法
1.调用epoll_create创建一个epollfd;
2.调用epoll_ctl将你感兴趣的socket绑定到epollfd上去(绑定socket,设置监听的事件);
3.调用epoll_wait判断事件是否触发。
epoll模型处理特殊的"读事件"
- 普通的读事件,说明对端通过该socket发送来了数据;
- 特殊的读事件。
1.对于侦听socket,读事件意味着有新的连接到来,接下来我们需要调用accept函数去接受链接;
2.对于普通socket,如果对端关闭了连接,也会触发读事件,调用recv或者read函数,会返回0。
水平模式和边缘模式
在这两种模式下读写事件的差异
-
对于读事件
- 水平模式下,只要有数据可收,水平模式下就会一直触发,即使本次读事件触发时没有把缓冲区的数据全部读取完毕,也会一直触发;
- 边缘模式下,只要有新数据就会触发,但是必须在本次读事件触发时把缓冲区的数据全部读取完毕,否则可能会因为之后没有新数据到来而无法继续读取,导致数据接收不完整。
-
对于写事件
- 水平模式下,我们注册了写事件后,如果socket可写,会一直触发;
- 边缘模式下,我们注册写时间后,写事件会在第一次从不可写到可写时触发;若再次注册写事件,当socket可写时,会再次触发。
ET和LT模式下的读写事件的详细例程
上文介绍了epoll在水平模式和边缘模式下,读写事件的差异,可能部分读者还不是很清楚,这里我们将举出具体例子,分别验证两种模式下,读写事件的差异。首先先让我们构造一个读事件场景。
- 场景1 epoll监听读事件在不同模式下的具体差异
- 假设我们需要从TCP客户端上传一个文件到TCP服务端,该文件原名为
client.txt,当文件上传到TCP服务器后,服务器会把接收到的文件数据重新写入一个名为server.txt的文件中,然后我们使用wc命令分别对client.txt和server.txt进行字数统计,看看在不同模式下上传的文件是否保持完整。其中client.txt内容如下:
- 假设我们需要从TCP客户端上传一个文件到TCP服务端,该文件原名为
WASHINGTON -- The Trump administration on Monday paused all military aid to Ukraine, Bloomberg reported.
The pause, which Trump ordered Secretary of Defense Pete Hegseth to execute, will remain in effect until President Donald Trump determines that Ukraine's leaders "demonstrate a good-faith commitment to peace" with Russia, a senior Defense Department official was quoted as saying.
All US military equipment not currently in Ukraine, said the official, will be paused, including weapons in transit on aircraft and ships or those waiting in transit areas in Poland.
The move comes after Trump's meeting with Ukrainian President Volodymyr Zelensky turned into a tense shouting match at the White House on Friday, leading to the cancellation of an anticipated bilateral materials deal.
The draft deal, as US media reported, included the establishment of a fund to be jointly owned by Ukraine and the United States, to which Ukraine will contribute 50 percent of its revenues from the future monetization of natural resources, including critical minerals, oil and gas.
Earlier on Monday, Trump wrote in a Truth Social post that the United States "will not put up with" what he perceived as Zelensky's hesitancy about reaching a US-brokered peace deal with Russia.
"This is the worst statement that could have been made by Zelenskyy," Trump said in the post in which he included a link to a report by The Associated Press quoting Zelensky as saying late Sunday that the end of Ukraine's conflict with Russia "is still very, very far away."
But Trump also hinted on Monday that a deal to open up Ukraine's minerals to US investment is still on the table despite his frustration with Kyiv.
- 分析
首先,我们为了能够体现边缘模式下没有新的触发事件而无法被读取,客户端每次发送的数据字节数都大于服务端每次接收的字节数(这里我们采用客户端每次发送128字节,而服务端每次接收64字节),这样就导致若要完整上传整个文件,则发送次数小于接收次数,就可以体现没有新触发事件的情形下,ET模式和LT模式的区别。
LT模式下读事件实例
- 客户端代码
#include <errno.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define FILE_NAME "client.txt"
int main(int argc, char* argv[]) {
if (argc != 3) {
printf("%s ip port\n", argv[0]);
return -1;
}
int ret = 0;
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {
printf("socket failed, errmsg: %s\n", strerror(errno));
return -1;
}
struct sockaddr_in serv_addr = { 0 };
serv_addr.sin_family = AF_INET;
inet_pton(AF_INET, argv[1], &serv_addr.sin_addr.s_addr);
serv_addr.sin_port = htons(atoi(argv[2]));
ret = connect(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
if (ret == -1) {
printf("connect %s:%hu failed, errmsg: %s\n", argv[1], atoi(argv[2]), strerror(errno));
close(sockfd);
return -1;
}
int file = open(FILE_NAME, O_RDONLY);
if (file == -1) {
printf("open %s failed, errmsg: %s\n", FILE_NAME, strerror(errno));
close(sockfd);
return -1;
}
char buf[128] = { 0 };
while (1) {
memset(buf, 0, 128);
int size_read = read(file, buf, 128);//客户端每次发送128字节
if (size_read == 0) {
break;
}
ret = write(sockfd, buf, size_read);
if (ret < 0) {
printf("write to %s:%s failed, errmsg: %s\n",
argv[1], argv[2], strerror(errno));
break;
} else if (ret == 0) {
printf("write to %s:%s remote close\n", argv[1], argv[2]);
break;
}
}
close(file);
close(sockfd);
return 0;
}
- 服务端代码
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define FILE_NAME "server.txt"
#define EPOLL_SIZE (128)
#define EPOLL_TIMEOUT (1000)
static int epollfd = 0;
// 设置非阻塞套接字
void set_nonblocking(int fd) {
int flags = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}
void epoll_add_event(int epfd, int fd, uint32_t state) {
struct epoll_event event;
event.events = state;
event.data.fd = fd;
if (epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event) == -1) {
printf("[EPOLL] add fd: %d to epollfd: %d for EPOLLIN failed, errmsg: %s\n",
fd, epfd, strerror(errno));
return;
}
}
void epoll_del_event(int epfd, int fd) {
if (epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL) == -1) {
printf("[EPOLL] del fd: %d from epollfd: %d failed, errmsg: %s\n",
fd, epfd, strerror(errno));
return;
}
}
void epoll_mod_event(int epfd, int fd, uint32_t state) {
struct epoll_event event;
event.events = state;
event.data.fd = fd;
if (epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &event) == -1) {
printf("[EPOLL] mod fd: %d from epfd: %d failed, errmsg: %s\n",
fd, epfd, strerror(errno));
return;
}
}
int tcp_server_socket(char *addr, char *port) {
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {
printf("socket failed, errmsg: %s\n", strerror(errno));
return -1;
}
struct sockaddr_in serv_addr = {0};
serv_addr.sin_family = AF_INET;
inet_pton(AF_INET, addr, &serv_addr.sin_addr);
serv_addr.sin_port = htons(atoi(port));
if (-1 == bind(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr))) {
printf("bind failed, errmsg: %s\n", strerror(errno));
close(sockfd);
return -1;
}
if (-1 == listen(sockfd, 128)) {
printf("listen failed, errmsg: %s\n", strerror(errno));
close(sockfd);
return -1;
}
return sockfd;
}
int tcp_client_accept(int listen_fd) {
struct sockaddr_in addr = { 0 };
socklen_t len = sizeof(socklen_t);
int connfd = accept(listen_fd, (struct sockaddr *)&addr, &len);
if (connfd == -1) {
printf("accept failed, errmsg: %s\n", strerror(errno));
return -1;
}
// 设置非阻塞套接字
set_nonblocking(connfd);
epoll_add_event(epollfd, connfd, EPOLLIN);//监听读时间,默认为水平模式
return connfd;
}
void read_fd_data(int fd, int file) {
char buf[64] = {0};
int ret = read(fd, buf, 64);//服务端每次读取64字节数据
if (ret == 0) {
printf("read peer close, fd: %d\n", fd);
epoll_del_event(epollfd, fd);
close(fd);
return;
} else if (ret == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
printf("data not fully read!\n");//这里永远不会执行到
}
printf("read peer failed, fd: %d, errmsg: %s\n", fd, strerror(errno));
epoll_del_event(epollfd, fd);
close(fd);
return;
}
int size_write = write(file, buf, ret);
if (size_write != ret) {
printf("write file: %s failed, errmsg: %s\n", FILE_NAME, strerror(errno));
}
}
int main(int argc, char *argv[]) {
if (argc != 3) {
printf("%s ip port\n", argv[0]);
return -1;
}
int file = open(FILE_NAME, O_CREAT | O_WRONLY | O_APPEND);
if (file == -1) {
printf("create file: %s failed, errmsg: %s\n", FILE_NAME, strerror(errno));
return -1;
}
int lsfd = tcp_server_socket(argv[1], argv[2]);
if (lsfd == -1) {
printf("tcp server socket failed\n");
return -1;
}
epollfd = epoll_create(EPOLL_SIZE);
if (epollfd == -1) {
printf("create epoll fd failed, errmsg: %s\n", strerror(errno));
return -1;
}
epoll_add_event(epollfd, lsfd, EPOLLIN);
int ret = 0, fd = 0;
struct epoll_event events[EPOLL_SIZE] = { 0 };
while (1) {
ret = epoll_wait(epollfd, events, EPOLL_SIZE, EPOLL_TIMEOUT);
if (ret < 0) {
if (errno == EINTR) {
printf("interrupt by signal\n");
continue;
}
printf("epoll_wait error, errmsg: %s\n", strerror(errno));
break;
} else if (ret == 0) {
continue;
} else {
for (int i = 0; i < ret; i++) {
fd = events[i].data.fd;
if (events[i].events & EPOLLIN) {
if (fd == lsfd) {
if (-1 == tcp_client_accept(lsfd)) {
printf("tcp client accept failed\n");
continue;
}
}else {
read_fd_data(fd, file);
}
}
}
}
}
close(lsfd);
epoll_del_event(epollfd, fd);
close(epollfd);
return -1;
}
我们分别编译服务端和客户端,并运行。
ydqun@ydqhost lt % gcc client.c -o client [0]
ydqun@ydqhost lt % ./server 127.0.0.1 8888 [0]
read peer close, fd: 6
^C
ydqun@ydqhost et % ./client 127.0.0.1 8888
接着我们再分别统计client.txt和server.txt的字节数。
ydqun@ydqhost lt % wc -c client.txt [130]
1694 client.txt
ydqun@ydqhost lt % sudo wc -c server.txt [0]
1694 server.txt
ydqun@ydqhost lt %
从上述统计结果我们可以看出,即使在客户端发送次数小于服务端接收次数的情况下(服务端epoll没有新触发情形),服务端仍然能够完整的接收完所有数据。
ET模式下读事件实例
- 客户端代码
ET模式下,首先我们的客户端代码与LT模式下客户端的代码保持一致,无需修改。 - 服务端代码
ET模式下,我们只需要在epoll_add_event(epollfd, connfd, EPOLLIN)添加EPOLLET宏定义,设置为epoll ET模式即可,即修改为epoll_add_event(epollfd, connfd, EPOLLIN | EPOLLET).
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define FILE_NAME "server.txt"
#define EPOLL_SIZE (128)
#define EPOLL_TIMEOUT (1000)
static int epollfd = 0;
// 设置非阻塞套接字
void set_nonblocking(int fd) {
int flags = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}
void epoll_add_event(int epfd, int fd, uint32_t state) {
struct epoll_event event;
event.events = state;
event.data.fd = fd;
if (epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event) == -1) {
printf("[EPOLL] add fd: %d to epollfd: %d for EPOLLIN failed, errmsg: %s\n",
fd, epfd, strerror(errno));
return;
}
}
void epoll_del_event(int epfd, int fd) {
if (epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL) == -1) {
printf("[EPOLL] del fd: %d from epollfd: %d failed, errmsg: %s\n",
fd, epfd, strerror(errno));
return;
}
}
void epoll_mod_event(int epfd, int fd, uint32_t state) {
struct epoll_event event;
event.events = state;
event.data.fd = fd;
if (epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &event) == -1) {
printf("[EPOLL] mod fd: %d from epfd: %d failed, errmsg: %s\n",
fd, epfd, strerror(errno));
return;
}
}
int tcp_server_socket(char *addr, char *port) {
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {
printf("socket failed, errmsg: %s\n", strerror(errno));
return -1;
}
struct sockaddr_in serv_addr = {0};
serv_addr.sin_family = AF_INET;
inet_pton(AF_INET, addr, &serv_addr.sin_addr);
serv_addr.sin_port = htons(atoi(port));
if (-1 == bind(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr))) {
printf("bind failed, errmsg: %s\n", strerror(errno));
close(sockfd);
return -1;
}
if (-1 == listen(sockfd, 128)) {
printf("listen failed, errmsg: %s\n", strerror(errno));
close(sockfd);
return -1;
}
return sockfd;
}
int tcp_client_accept(int listen_fd) {
struct sockaddr_in addr = { 0 };
socklen_t len = sizeof(socklen_t);
int connfd = accept(listen_fd, (struct sockaddr *)&addr, &len);
if (connfd == -1) {
printf("accept failed, errmsg: %s\n", strerror(errno));
return -1;
}
// 设置非阻塞套接字
set_nonblocking(connfd);
epoll_add_event(epollfd, connfd, EPOLLIN | EPOLLET);//监听读时间,默认为水平模式
return connfd;
}
void read_fd_data(int fd, int file) {
char buf[64] = {0};
int ret = read(fd, buf, 64);
if (ret == 0) {
printf("read peer close, fd: %d\n", fd);
epoll_del_event(epollfd, fd);
close(fd);
return;
} else if (ret == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
//在ET模式下,这里本应该继续读取直到数据读完。
printf("data not fully read!\n");//这里永远不会执行到
}
printf("read peer failed, fd: %d, errmsg: %s\n", fd, strerror(errno));
epoll_del_event(epollfd, fd);
close(fd);
return;
}
int size_write = write(file, buf, ret);
if (size_write != ret) {
printf("write file: %s failed, errmsg: %s\n", FILE_NAME, strerror(errno));
}
}
int main(int argc, char *argv[]) {
if (argc != 3) {
printf("%s ip port\n", argv[0]);
return -1;
}
int file = open(FILE_NAME, O_CREAT | O_WRONLY | O_APPEND);
if (file == -1) {
printf("create file: %s failed, errmsg: %s\n", FILE_NAME, strerror(errno));
return -1;
}
int lsfd = tcp_server_socket(argv[1], argv[2]);
if (lsfd == -1) {
printf("tcp server socket failed\n");
return -1;
}
epollfd = epoll_create(EPOLL_SIZE);
if (epollfd == -1) {
printf("create epoll fd failed, errmsg: %s\n", strerror(errno));
return -1;
}
epoll_add_event(epollfd, lsfd, EPOLLIN);
int ret = 0, fd = 0;
struct epoll_event events[EPOLL_SIZE] = { 0 };
while (1) {
ret = epoll_wait(epollfd, events, EPOLL_SIZE, EPOLL_TIMEOUT);
if (ret < 0) {
if (errno == EINTR) {
printf("interrupt by signal\n");
continue;
}
printf("epoll_wait error, errmsg: %s\n", strerror(errno));
break;
} else if (ret == 0) {
continue;
} else {
for (int i = 0; i < ret; i++) {
fd = events[i].data.fd;
if (events[i].events & EPOLLIN) {
if (fd == lsfd) {
if (-1 == tcp_client_accept(lsfd)) {
printf("tcp client accept failed\n");
continue;
}
}else {
read_fd_data(fd, file);
}
}
}
}
}
close(lsfd);
epoll_del_event(epollfd, fd);
close(epollfd);
return -1;
}
我们分别编译服务端和客户端,并运行。
ydqun@ydqhost et % gcc server.c -o server [0]
ydqun@ydqhost et % gcc client.c -o client [0]
ydqun@ydqhost et % ./server 127.0.0.1 8888 [0]
^C
ydqun@ydqhost et % ./client 127.0.0.1 8888
还是一样,我们再分别统计client.txt和server.txt的字节数。
ydqun@ydqhost et % wc -c client.txt server.txt [1]
1694 client.txt
960 server.txt
2654 total
此时,我们发现server.txt的字节数远小于client.txt的字节数,这就验证了在ET模式下,需要在每次读事件触发时,必须一次性读取所有数据,否则可能会因为之后没有新数据到来而无法继续读取,导致数据接收不完整。而LT模式下只要有数据,便会一直触发。
- 场景2 epoll监听写事件在不同模式下的具体差异
- 假设我们是从服务器下载一个文件,名字为
server.txt,当TCP客户端成功连接TCP服务端后,服务端会注册写事件,然后把server.txt的文件数据发送给客户端,每次写事件触发时都会打印日志记录,然后我们通过观察日志,来看一下epoll在不同模式下,触发写事件的差异。其中server.txt的内容如下:
CAIRO — Egypt's Foreign Minister Badr Abdelatty said on Sunday that a Gaza reconstruction plan, which ensures Palestinians remain in their land, is ready and will be presented at an emergency Arab summit in Cairo on Tuesday.
Meanwhile, the United Nations, the Arab Parliament, the Muslim World League and many countries around the world have strongly condemned Israel for blocking aid into Gaza.
The reconstruction plan will not be purely Egyptian or Arab, but will gain international support and funding to ensure its implementation, Abdelatty said.
"We will hold intensive talks with major donor countries once the plan is adopted at the upcoming Arab Summit," he said at a news conference. "We will ensure that the results of the Arab Summit are presented to the world in the best possible way."
Asked about the second phase of the cease-fire agreement between Israel and Hamas, Abdelatty said Egypt will continue its efforts to ensure the truce is maintained and negotiations for the second phase can begin.
The first phase of the fragile ceasefire deal expired over the weekend. Abdelatty reaffirmed Egypt's commitment to the originally agreed-upon cease-fire that had been scheduled to move into the second phase. "It will be difficult, but with goodwill and political determination, it can be achieved," he said.
Former intelligence chief of Saudi Arabia, Turki Al-Faisal, said Israel bears financial responsibility for the damage it has inflicted on Gaza and the West Bank.
Financial responsibility
Appearing on Arab News' Frankly Speaking program, Turki laid out his case for why Israel should bear financial responsibility for the damage it has inflicted on Gaza and the West Bank — rather than the Gulf states footing the bill.
"I have been saying this for some time now that there should be a fund, a worldwide fund, for the reconstruction, not just in Gaza, but also in the West Bank. And Israel should be forced to chip into that fund," he said.
Israel on Sunday blocked the entry of aid trucks into Gaza as a standoff over the truce that has halted fighting for the past six weeks escalated. Abdelatty said the use of aid as a weapon of collective punishment could not be permitted.
The decision has also sparked fierce condemnation from Palestinian factions, international organizations and regional countries.
The Popular Front for the Liberation of Palestine called the Israeli move "a flagrant violation of the cease-fire" and an indication that Israel seeks to evade the second phase of the agreement.
Faisal Aranki, a member of the Palestine Liberation Organization's Executive Committee, told Xinhua News Agency that the decision will worsen Gaza's already severe shortages of essential goods and medical supplies.
In a statement on X, UN Secretary-General Antonio Guterres called for "humanitarian aid to flow back into Gaza immediately and for the release of all hostages", and urged "all parties to make every effort to prevent a return to hostilities in Gaza".
Tom Fletcher, UN under-secretary-general for humanitarian affairs and emergency relief coordinator, called Israel's decision "alarming".
Jordan's Foreign Ministry spokesman Sufyan Qudah said Israel's decision "threatens to reignite the situation" in Gaza, adding that Israel must "stop using starvation as a weapon against innocent Palestinians who are under siege, especially during the holy month of Ramadan".
Mike Gu in Hong Kong contributed to this story.
Agencies - Xinhua
LT模式下写事件实例
- 客户端代码
#include <errno.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define FILE_NAME "client.txt"
int main(int argc, char* argv[]) {
if (argc != 3) {
printf("%s ip port\n", argv[0]);
return -1;
}
int ret = 0;
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {
printf("socket failed, errmsg: %s\n", strerror(errno));
return -1;
}
struct sockaddr_in serv_addr = { 0 };
serv_addr.sin_family = AF_INET;
inet_pton(AF_INET, argv[1], &serv_addr.sin_addr.s_addr);
serv_addr.sin_port = htons(atoi(argv[2]));
ret = connect(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
if (ret == -1) {
printf("connect %s:%hu failed, errmsg: %s\n", argv[1], atoi(argv[2]), strerror(errno));
close(sockfd);
return -1;
}
int file = open(FILE_NAME, O_CREAT | O_APPEND | O_WRONLY);
if (file == -1) {
printf("open %s failed, errmsg: %s\n", FILE_NAME, strerror(errno));
close(sockfd);
return -1;
}
char buf[128] = { 0 };
while (1) {
ret = read(sockfd, buf, 128);
if (ret < 0) {
printf("read from %s:%s failed, errmsg: %s\n",
argv[1], argv[2], strerror(errno));
break;
} else if (ret == 0) {
printf("read from %s:%s remote close\n", argv[1], argv[2]);
break;
}
int size_write = write(file, buf, ret);
if (size_write != ret) {
printf("write file: %s failed, errmsg: %s\n", FILE_NAME, strerror(errno));
}
}
close(file);
close(sockfd);
return 0;
}
- 服务端代码
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define FILE_NAME "server.txt"
#define EPOLL_SIZE (128)
#define EPOLL_TIMEOUT (1000)
static int epollfd = 0;
// 设置非阻塞套接字
void set_nonblocking(int fd) {
int flags = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}
void epoll_add_event(int epfd, int fd, uint32_t state) {
struct epoll_event event;
event.events = state;
event.data.fd = fd;
if (epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event) == -1) {
printf("[EPOLL] add fd: %d to epollfd: %d for EPOLLIN failed, errmsg: %s\n",
fd, epfd, strerror(errno));
return;
}
}
void epoll_del_event(int epfd, int fd) {
if (epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL) == -1) {
printf("[EPOLL] del fd: %d from epollfd: %d failed, errmsg: %s\n",
fd, epfd, strerror(errno));
return;
}
}
void epoll_mod_event(int epfd, int fd, uint32_t state) {
struct epoll_event event;
event.events = state;
event.data.fd = fd;
if (epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &event) == -1) {
printf("[EPOLL] mod fd: %d from epfd: %d failed, errmsg: %s\n",
fd, epfd, strerror(errno));
return;
}
}
int tcp_server_socket(char *addr, char *port) {
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {
printf("socket failed, errmsg: %s\n", strerror(errno));
return -1;
}
struct sockaddr_in serv_addr = {0};
serv_addr.sin_family = AF_INET;
inet_pton(AF_INET, addr, &serv_addr.sin_addr);
serv_addr.sin_port = htons(atoi(port));
if (-1 == bind(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr))) {
printf("bind failed, errmsg: %s\n", strerror(errno));
close(sockfd);
return -1;
}
if (-1 == listen(sockfd, 128)) {
printf("listen failed, errmsg: %s\n", strerror(errno));
close(sockfd);
return -1;
}
return sockfd;
}
int tcp_client_accept(int listen_fd) {
struct sockaddr_in addr = { 0 };
socklen_t len = sizeof(socklen_t);
int connfd = accept(listen_fd, (struct sockaddr *)&addr, &len);
if (connfd == -1) {
printf("accept failed, errmsg: %s\n", strerror(errno));
return -1;
}
// 设置非阻塞套接字
set_nonblocking(connfd);
epoll_add_event(epollfd, connfd, EPOLLOUT);//监听写事件,默认为水平模式
return connfd;
}
void write_fd_data(int fd, int file) {
char buf[128] = {0};
int size_read = read(file, buf, 128);
if (size_read == 0) {
printf("file already read finish.\n");
return;
}
int ret = write(fd, buf, size_read);
if (ret == 0) {
printf("write peer close, fd: %d\n", fd);
epoll_del_event(epollfd, fd);
close(fd);
return;
} else if (ret == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
printf("data not fully write!\n");
}
printf("write peer failed, fd: %d, errmsg: %s\n", fd, strerror(errno));
epoll_del_event(epollfd, fd);
close(fd);
return;
}
}
int main(int argc, char *argv[]) {
if (argc != 3) {
printf("%s ip port\n", argv[0]);
return -1;
}
int file = open(FILE_NAME, O_RDONLY);
if (file == -1) {
printf("create file: %s failed, errmsg: %s\n", FILE_NAME, strerror(errno));
return -1;
}
int lsfd = tcp_server_socket(argv[1], argv[2]);
if (lsfd == -1) {
printf("tcp server socket failed\n");
return -1;
}
epollfd = epoll_create(EPOLL_SIZE);
if (epollfd == -1) {
printf("create epoll fd failed, errmsg: %s\n", strerror(errno));
return -1;
}
epoll_add_event(epollfd, lsfd, EPOLLIN);
int ret = 0, fd = 0;
struct epoll_event events[EPOLL_SIZE] = { 0 };
while (1) {
ret = epoll_wait(epollfd, events, EPOLL_SIZE, EPOLL_TIMEOUT);
if (ret < 0) {
if (errno == EINTR) {
printf("interrupt by signal\n");
continue;
}
printf("epoll_wait error, errmsg: %s\n", strerror(errno));
break;
} else if (ret == 0) {
continue;
} else {
for (int i = 0; i < ret; i++) {
fd = events[i].data.fd;
if (events[i].events & EPOLLIN) {
if (fd == lsfd) {
if (-1 == tcp_client_accept(lsfd)) {
printf("tcp client accept failed\n");
continue;
}
}
}
if (events[i].events & EPOLLOUT) {
write_fd_data(fd, file);
}
}
}
}
close(lsfd);
epoll_del_event(epollfd, fd);
close(epollfd);
return -1;
}
编译并运行
ydqun@ydqhost lt % ./server 127.0.0.1 8888
file already read finish.
file already read finish.
file already read finish.
file already read finish.
file already read finish.
file already read finish.
file already read finish.
file already read finish.
.....中间省略
^C
ydqun@ydqhost lt % ./client 127.0.0.1 8888
运行结果显示,我们可以看到一直触发写事件,这里符合LT模式下,只要socket可写,会一直触发。
ET模式下写事件实例
需要把epoll_add_event(epollfd, connfd, EPOLLOUT)改成epoll_add_event(epollfd, connfd, EPOLLOUT | EPOLLET),且每次写事件能够发送完,则需要取消监听写事件,另外当发送返回值为-1时,且错误码为EAGAIN或者EWOULDBLOCK时,则需要重新注册写事件,同时还需要注意数据残留,避免发送缓冲区数据混乱。
浙公网安备 33010602011771号