函数原型:int poll(struct pollfd *fds, nfds_t nfds, int timeout);
功能描述:监视fds中的描述符是否激活
参数描述:
参数 fds:是一个struct pollfd 结构体数组,该数组中存放了多个想要监视的描述符
该结构体结构如下
struct pollfd {
int fd; /* 想要监视的描述符 */
short events; /* 确定fd描述,到底以何种形式进行监视 */
POLLIN:监视fd描述是否可读
POLLOUT:监视fd描述符是否可写
short revents; /* returned events */
一般会随着结构体初始化为0,同步为0
当 revents == 0的时候,表示fd这个描述符没有激活
当 revents == POLLIN的时候,表示fd这个描述符可读激活了
当 revents == POLLOUT的时候,表示fd这个描述可写激活了
};
参数 nfds:fds这个数组的长度
参数 timeout:设置的最大监视时长
注意: 单位是毫秒
0表示不阻塞
-1表示一直阻塞
实例
struct pollfd arr[5] = {0}
arr[0].fd = 0
arr[0].events = POLLIN
poll(arr) 表示监视 标准输入流是否可读
#include
#include
#include
#include
#include
#include
typedef struct sockaddr_un addr_un_t;
void insert_fd(struct pollfd* arr, int* len, struct pollfd fd) {
arr[*len] = fd;
(*len)++;
}
void remove_fd(struct pollfd* arr, int* len, int fd) {
int i;
for (i = 0; i < *len; i++) {
if (fd == arr[i].fd) {
break;
}
}
for (int j = i; j < *len - 1; j++) {
arr[j] = arr[j + 1];
}
(*len)--;
}
int main(int argc, const char *argv[]) {
if (argc != 2) {
printf("请输入端口号\n");
return 1;
}
// 准备一个用来存放所有连接上来的客户端描述符的数组
int client_arr[64] = {};
int client_arr_len = 0; // 这里修正了初始化为0
int port = atoi(argv[1]); // 将字符串转换成int类型port
// 创建服务器套接字
int server = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in addr = {0};
addr.sin_port = htons(port);
addr.sin_addr.s_addr = inet_addr("0.0.0.0");
// 为套接字绑定ip和port
if (bind(server, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
perror("bind");
return 1;
}
// 监听
listen(server, 10);
struct pollfd arr[20] = {0};
int len = 0;
// 将stdin和server放到arr里面去
struct pollfd poll_stdin = {.fd = 0, .events = POLLIN, .revents = 0};
struct pollfd poll_server = {server, POLLIN, 0};
insert_fd(arr, &len, poll_stdin);
insert_fd(arr, &len, poll_server);
while (1) {
poll(arr, len, -1);
for (int i = 0; i < len; i++) {
if (arr[i].revents == 0) {
continue; // 该描述符没激活,直接看下一个描述符
}
int fd = arr[i].fd;
if (fd == server) {
printf("有新客户端连接\n");
// 这里应该接受客户端连接,并创建新的pollfd结构,然后调用insert_fd
// 但由于代码不完整,这里省略了具体实现
} else if (fd == 0) {
char buf[64] = "";
scanf("%63s", buf);
printf("键盘输入数据:%s\n", buf);
} else {
// 剩下的都是客户端描述符了
char buf[64];
int res = read(fd, buf, 64);
if (res == 0) {
close(fd);
} else {
printf("%d#客户端发来消息:%s\n", fd, buf);
}
}
}
}
return 0;
}
函数原型:int epoll_wait(int epfd, struct epoll_event *events,
int maxevents, int timeout);
功能描述:监视 epfd 里面的描述符是否被激活
参数描述:
参数 epfd:epoll_wait的监视列表
epfd其实是一个文件描述符,该描述符所描述的文件,才是epoll_wait的监视列表
所以我们要做的就是,将所有想要被epoll监视的描述符,都放到 epfd 文件中去
参数 events:用来存放所有激活了的描述符的数组,是一个结构体数组,结构如下
struct epoll_event {
uint32_t events; /* 在poll_wait函数里面,这个数据表示激活的描述符是以何种形式激活的 */
epoll_data_t data; /* 该数据里面存放了具体的激活的描述符是谁 */
};
typedef union epoll_data {
void *ptr;
int fd; /* 具体的激活的描述符 */
uint32_t u32;
uint64_t u64;
} epoll_data_t;
其实epoll_wait 针对当前数组只做一件事
将激活的描述符到底是谁,以及该描述符是如何激活的,存放到 events这个数组里面去
参数 maxevents:想要监视的描述符的数量
参数 timout:设定poll_wait最大阻塞时长
0:表示不阻塞
-1:表示永久阻塞
单位为毫秒
返回值:返回激活的描述符的数量
实例
struct epoll_event arr[5]
epoll_wait(epfd,arr) 假设此时 epfd 里面有一个 0 和 一个 server
此时 epoll_wait 监视到 server激活了
arr就会发生改变,改变如下
arr[0].events == EPOLLIN
arr[0].data.fd == server
int epoll_create(int size);
创建一个只能监视size个描述符的监视列表
int epoll_create1(int flags);
flags一般传 EPOLL_CLOEXEC
表示创建一个动态的监视列表,该监视列表的长度会随着想要监视的描述符数量的改变而改变
所以,一般来说,我们想要创建一个监视列表,直接写
int epfd = epoll_create1(EPOLL_CLOEXEC);
返回值:创建出来的监视列表的描述符
函数原型:int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
功能描述:根据op(第2个参数)的不同,针对描述符做出不同的操作
参数描述:
参数 epfd:监视列表自己的描述符
参数 op:具体的操作
EPOLL_CTL_ADD:将fd描述符,添加到epfd监视列表里面去
EPOLL_CTL_DEL:将fd描述符,从epfd中移除
EPOLL_CTL_MOD:更改fd描述的监视方式
参数 event: 注意,这里是一个结构体地址,用来明确想要监视的描述符fd,以何种形式去监视
struct epoll_event {
uint32_t events; /* 在poll_ctl函数里面,用来确定描述符fd的监视方式 */
EPOLLIN :监视描述符是否可读
EPOLLOUT:监视描述符是否可写
epoll_data_t data;
};
typedef union epoll_data {
void *ptr;
int fd; /* 要求和epoll_ctl函数里面第3个参数fd写的一模一样 */
uint32_t u32;
uint64_t u64;
} epoll_data_t;
#include
#include
#include
#include
#include
#include
#include
typedef struct sockaddr_in addr_in_t;
int main(int argc, const char *argv[]) {
if (argc != 2) {
printf("请输入端口号\n");
return 1;
}
int port = atoi(argv[1]);
int server = socket(AF_INET, SOCK_STREAM, 0);
if (server == -1) {
perror("socket");
return 1;
}
addr_in_t addr = {0};
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 或者使用其他有效的IP地址
if (bind(server, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
perror("bind");
return 1;
}
// 监听
listen(server, 10);
// 创建一个可动态扩容的epoll文件描述符
int epfd = epoll_create1(EPOLL_CLOEXEC);
if (epfd == -1) {
perror("epoll_create1");
return 1;
}
// 将server和stdin添加到epfd里面去
struct epoll_event server_event, stdin_event;
server_event.data.fd = server;
server_event.events = EPOLLIN;
stdin_event.data.fd = 0;
stdin_event.events = EPOLLIN;
if (epoll_ctl(epfd, EPOLL_CTL_ADD, server, &server_event) == -1) {
perror("epoll_ctl for server");
return 1;
}
if (epoll_ctl(epfd, EPOLL_CTL_ADD, 0, &stdin_event) == -1) {
perror("epoll_ctl for stdin");
return 1;
}
int fd_count = 50; // 这个变量似乎没有被后续代码使用
while (1) {
struct epoll_event active_fds[50] = {0};
int len = epoll_wait(epfd, active_fds, 50, -1);
if (len == -1) {
perror("epoll_wait");
return 1;
}
// 经过epoll_wait说明有len个描述符激活了
for (int i = 0; i < len; i++) {
int sock = active_fds[i].data.fd;
if (sock == server) {
// 激活的是服务器
printf("有新客户端连接\n");
int client = accept(server, NULL, NULL);
if (client == -1) {
perror("accept");
continue;
}
struct epoll_event client_event;
client_event.data.fd = client;
client_event.events = EPOLLIN;
if (epoll_ctl(epfd, EPOLL_CTL_ADD, client, &client_event) == -1) {
perror("epoll_ctl for client");
close(client);
continue;
}
} else if (sock == 0) {
// 激活的是标准输入流
char buf[64] = {0};
scanf("%63s", buf);
while (getchar() != '\n'); // 清除输入缓冲区
printf("键盘输入了:%s\n", buf);
} else {
// 激活的不是上面两个,只能是客户端了
char buf[64] = {0};
int res = read(sock, buf, 64);
if (res == 0) {
// 客户端断开连接
printf("客户端断开连接\n");
epoll_ctl(epfd, EPOLL_CTL_DEL, sock, NULL);
close(sock);
} else {
printf("客户端发来消息:%s\n", buf);
}
}
}
}
return 0;
}
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
typedef struct sockaddr_in addr_in_t;
typedef struct sockaddr addr_t;
typedef struct sockaddr_un addr_un_t;
enum Type{
TYPE_REGIST,
TYPE_LOGIN
};
typedef struct Pack{
int size;// 用来记录整个协议包的实际大小,占4字节
enum Type type;// 占4个字节
char buf[2048];// 占count个字节
int count; // 用来记录指针的偏移量,也就是buf使用了多少个字节
}pack_t;
void append(pack_t* pack,const char* data){
char* buf = pack->buf;
int len = strlen(data);
*(short*)(buf+pack->count) = len; // 将buf的前2个字节当做short来存储数据len
// memcpy(buf,&len,2);
pack->count += 2;
memcpy(buf+pack->count,data,len);
pack->count += len;
pack->size = pack->count + 8;
}
int main(int argc, const char *argv[])
{
if(argc != 2){
printf("请输入端口号\n");
return 1;
}
// ./server 8888
int port = atoi(argv[1]);// 将字符串 8888 转换成int类型port
int client = socket(AF_INET,SOCK_STREAM,0);
addr_in_t addr = {0};
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = inet_addr("192.168.60.77");
if(connect(client,(addr_t*)&addr,sizeof(addr)) == -1){
perror("connect");
return 1;
}
while(1){
pack_t pack = {0};
pack.type = TYPE_LOGIN;
char name[20] = "";
char pswd[20] = "";
printf("请输入账号:");
scanf("%19s",name);
while(getchar()!=10);
printf("请输入密码:");
scanf("%19s",pswd);
while(getchar()!=10);
// 将name 和 pswd
// 按照之前说好的方式(前2个字节记录数据长度n,紧接着的n个字节记录数据本身)将name和pswd存入 pack.buf 里面去
// 其实就是 把 name 和 pswd 按照既定格式,添加进入协议包
append(&pack,name);
append(&pack,pswd);
// 当协议包里面拥有name和pswd之后,就可以将协议包发送给服务器了
write(client,&pack,pack.size);// 协议包总共占据 pack.count + 4字节,pack.count的长度,根据 name 和 pswd 的长度动态决定
}
return 0;
}