当前位置:网站首页>基于epoll实现聊天室(内含定时器,处理客户连接状态)
基于epoll实现聊天室(内含定时器,处理客户连接状态)
2022-07-16 07:18:00 【强尼爆紫】
服务器端需包含定时器头文件sort_timer_lst.h
#ifndef LST_TIMER
#define LST_TIMER
#include <time.h>
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define BUFFER_SIZE 64
class util_timer; //前向声明
struct client_data {
sockaddr_in address;
int sockfd;
char buf[BUFFER_SIZE];
util_timer *timer;
int index;
};
//定时器类
class util_timer {
public:
util_timer():prev(NULL),next(NULL) {
}
public:
time_t expire; //任务超时时间
void (*cb_func)(client_data *); //任务回调函数
//回调函数处理的用户数据,由定时器的执行者传递给回调函数
client_data *user_data;
util_timer *prev;//指向上一个定时器
util_timer *next;//指向下一个定时器
};
//定时器链表,它是一个升序、双向链表,且带有头节点和尾结点
class sort_timer_lst {
public:
sort_timer_lst() : head(NULL), tail(NULL) {
}
//链表被销毁时,删除其中所有定时器
~sort_timer_lst() {
util_timer *tmp = head;
while(tmp) {
head = tmp->next;
delete tmp;
tmp = tmp->next;
}
}
//将目标定时器timer添加到链表中
void add_timer(util_timer *timer) {
if(!timer) {
return;
}
if(!head) {
head = tail = timer;
return;
}
if(timer->expire < head->expire) {
timer->next = head;
head->prev = timer;
head = timer;
return;
}
add_timer(timer, head); //这是add_timer重载的一种方式,实现指定位置插入
}
//当某个定时任务发生变化时,调整对应定时器在链表中的位置,当前只考虑被调整的
//定时器超时时间延长的情况
void adjust_timer(util_timer* timer) {
if(!timer) {
return;
}
util_timer *tmp = timer->next;
if(!tmp || (timer->expire < tmp->expire)) {
return;
}
//如果是链表的头节点,取出重新插入。
if(timer == head) {
head = head->next;
head->prev = NULL;
timer->next = NULL;
add_timer(timer, head);
}
//如果是中间结点,同样取出重新插入
else {
timer->prev->next = timer->next;
timer->next->prev = timer->prev;
add_timer(timer, timer->next);
}
}
//将目标定时器timer从链表中删除
void del_timer(util_timer* timer) {
if(!timer) {
return;
}
//下边整个条件成立表示链表中只有一个定时器
if((timer == head) && (timer == tail)) {
delete timer;
head = NULL;
tail = NULL;
return;
}
//走到这里说明链表中至少有两个结点,如果删除的是头节点
if(timer == head) {
head = head->next;
head->prev = NULL;
delete timer;
return;
}
//如果是尾结点
if(timer == tail) {
tail = timer->prev;
timer->prev->next = NULL;
delete timer;
return;
}
//如果位于中间
timer->prev->next = timer->next;
timer->next->prev = timer->prev;
delete timer;
}
//SIGALRM信号每次被触发就在其信号处理函数(如果是统一事件源则在主函数处理)中执行一次tick函数,以处理链表上到期的任务。
void tick() {
if(!head) {
return;
}
printf("检查定时器链表(每隔20秒检查一次)\n");
time_t cur = time(NULL);//获取当前时间
util_timer *tmp = head;
while(tmp) {
if(tmp->expire > cur) {
break;
}
//调用定时器的回调函数,执行定时任务
tmp->cb_func(tmp->user_data);
//执行完定时器中的定时任务之后,就将他删除,并重置头节点
head = tmp->next;
if(head) {
head->prev = NULL;
}
delete tmp;
tmp = head;
}
}
private:
//一个重载的辅助函数
void add_timer(util_timer* timer, util_timer * lst_head) {
util_timer *prev = lst_head;
util_timer *tmp = prev->next;
while(tmp) {
if(tmp->expire > timer->expire) {
timer->next = tmp;
timer->prev = tmp->prev;
tmp->prev->next = timer;
tmp->prev = timer;
break;
}
prev = tmp;
tmp = tmp->next;
}
//如果遍历完链表还没找到合适的,则插到链表尾部
if(!tmp) {
prev->next = timer;
timer->prev = prev;
timer->next = NULL;
tail = timer;
}
}
private:
util_timer *head;
util_timer *tail;
};
#endif
服务器端
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <signal.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <pthread.h>
#include "sort_timer_lst.h"
#define FD_LIMIT 65535
#define MAX_EVENT_NUMBER 1024
#define TIMESLOT 20
#define USER_LIMIT 5
static int pipefd[2];
static sort_timer_lst timer_lst;
static int epollfd = 0;
int setnonblock(int fd) {
int oldoption = fcntl(fd, F_GETFL);
int newoption = oldoption | O_NONBLOCK;
fcntl(fd, F_SETFL, newoption);
return oldoption;
}
void addfd(int epollfd, int fd) {
epoll_event event;
event.data.fd = fd;
event.events = EPOLLIN | EPOLLET | EPOLLRDHUP;
epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event);
setnonblock(fd);
}
void sig_handler(int sig) {
int save_errno = errno;
int msg = sig;
send(pipefd[1], &msg, 1, 0);
errno = save_errno;
}
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);
assert((sigaction(sig, &sa, NULL) != -1));
}
void timer_handler() {
//定时处理任务,实际上就是调用tick函数
timer_lst.tick();
//因为alarm调用只会引起一次SIGALRM信号, 所以需要重新定时,不断触发SIGALRM信号
alarm(TIMESLOT);
}
//定时器回调函数
void cb_func(client_data* user_data) {
epoll_ctl(epollfd, EPOLL_CTL_DEL, user_data->sockfd, 0);
assert(user_data);
close(user_data->sockfd);
// printf("close fd %d \n", user_data->sockfd);
printf("客户 %s 断开网络连接! \n", inet_ntoa(user_data->address.sin_addr));
}
int main(int argc, char* argv[]) {
if(argc <= 2) {
printf("usage : %s ip_addr port_number!\n", basename(argv[0]));
return 1;
}
const char *ip = argv[1];
int port = atoi(argv[2]);
struct sockaddr_in address;
bzero(&address, sizeof(address));
address.sin_family = AF_INET;
address.sin_addr.s_addr = inet_addr(ip);
address.sin_port = htons(port);
int listenfd = socket(PF_INET, SOCK_STREAM, 0);
assert(listenfd >= 0);
int ret = bind(listenfd, (struct sockaddr *)&address, sizeof(address));
assert(ret != -1);
ret = listen(listenfd, 5);
assert(ret != -1);
epoll_event events[MAX_EVENT_NUMBER];
int epollfd = epoll_create(5);
assert(epollfd != -1);
addfd(epollfd, listenfd);
ret = socketpair(PF_UNIX, SOCK_STREAM, 0, pipefd);
assert(ret != -1);
setnonblock(pipefd[1]);
addfd(epollfd, pipefd[0]);
//设置信号处理函数
addsig(SIGALRM);
addsig(SIGTERM);
bool stop_server = false;
client_data *users = new client_data[FD_LIMIT];
client_data *fds = new client_data[USER_LIMIT];
int user_count = 0;
bool timeout = false;
alarm(TIMESLOT); // 定时
while(!stop_server) {
int number = epoll_wait(epollfd, events, MAX_EVENT_NUMBER, -1);
if((number < 0) && (errno != EINTR)) {
printf("epoll failure\n");
break;
}
for (int i = 0; i < number; i++) {
int sockfd = events[i].data.fd ;
if(sockfd == listenfd) {
struct sockaddr_in client_address;
bzero(&client_address, sizeof(client_address));
socklen_t client_len;
int confd = accept(listenfd, (struct sockaddr *)&client_address, &client_len);
addfd(epollfd, confd);
users[confd].address = client_address;
users[confd].sockfd = confd;
users[confd].index = user_count;
//创建定时器
util_timer *timer = new util_timer;
timer->user_data = &users[confd];
timer->cb_func = cb_func;
time_t cur = time(NULL);
timer->expire = cur + 3 * TIMESLOT;
printf("加入一位客户 ip:%s , port: %d \n", inet_ntoa(client_address.sin_addr), ntohs(client_address.sin_port));
users[confd].timer = timer;
timer_lst.add_timer(timer);
fds[user_count].sockfd = confd;
fds[user_count].address = client_address;
user_count++;
} else if ((sockfd == pipefd[0]) && (events[i].events & EPOLLIN)) {
int sig;
char signals[1024];
ret = recv(pipefd[0], signals, sizeof(signals), 0);
if(ret == -1) {
// handle the error;
continue;
} else if (ret == 0) {
continue;
} else {
for (int i = 0; i < ret; i++) {
switch(signals[i]) {
case SIGALRM: {
//用timeout变量标记有定时任务需要处理,但不立即处理,因为它的优先级不是很高,我们优先处理其他更重要的任务。
timeout = true;
break;
}
case SIGTERM: {
stop_server = true;
}
}
}
}
} else if (events[i].events & EPOLLRDHUP) {
//这个要放在EPOLLIN前进行判断。
fds[users[sockfd].index] = fds[user_count-1];
user_count--;
cb_func(&users[sockfd]);
timer_lst.del_timer(users[sockfd].timer);
} else if (events[i].events & EPOLLIN) {
memset(users[sockfd].buf, '\0', BUFFER_SIZE);
ret = recv(sockfd, users[sockfd].buf, BUFFER_SIZE - 1, 0);
//printf("get %d bytes of client data %s from %d\n", ret, users[sockfd].buf, sockfd);
printf("用户 %s 发来数据: %s", inet_ntoa(users[sockfd].address.sin_addr), users[sockfd].buf);
util_timer *timer = users[sockfd].timer;
if(ret < 0) {
//如果发生读错误,则关闭连接,并移除其对应的定时器
if(errno != EAGAIN) {
cb_func(&users[sockfd]);
if(timer) {
timer_lst.del_timer(timer);
}
}
} else if (ret == 0) {
//如果对方关闭了连接, 我们也关闭连接,并移除定时器
cb_func(&users[sockfd]);
if(timer) {
timer_lst.del_timer(timer);
}
} else {
//如果某个客户连接上有数据可读,则我们需要调整该连接对应的定时器,以延迟该连接被关闭的时间。
if(timer) {
time_t cur = time(NULL);
timer->expire = cur + 3 * TIMESLOT;
printf("用户 %s 的定时器刷新\n",inet_ntoa(users[sockfd].address.sin_addr));
timer_lst.adjust_timer(timer);
}
}
for (int i = 0; i < user_count; i++) {
if(fds[i].sockfd == sockfd) {
continue;
}
send(fds[i].sockfd, users[sockfd].buf, strlen(users[sockfd].buf), 0);
}
} else {
// others;
}
}
//最后处理定时时间,因为I/O事件有更高的优先级,但这样将导致定时任务不能精确的而按照预期时间进行。
if(timeout) {
timer_handler();
timeout = false;
}
}
close(listenfd);
close(pipefd[0]);
close(pipefd[1]);
delete[] users;
return 0;
}
//客户端,可以直接使用telnet远程登录
telnet ip地址 端口号即可
边栏推荐
- VS2019 内联汇编开发
- 进程和线程的区别
- Surmonter les défis de la transformation numérique en élaborant une vision
- The application of Lora and lorawan wireless technology in the Internet of things
- Kotlin SQLite URL escape character (escape) (I)
- Summary of domestic open source mirror websites
- 2022 pole technology communication - anmou technology opens a new chapter of commercialization
- Codeforces Round #806 (Div. 4)
- RMAN detailed tutorial (II) -- backup, inspection, maintenance, recovery
- Ciphertext on RGB image -- illegal data hiding
猜你喜欢
![[C language elementary level] function learning report](/img/06/510f418caa4d69fd5baf7c3ada98c6.png)
[C language elementary level] function learning report

gradle

进程和线程的区别

How long can zynq PL interrupt pulses be captured by CPU

从小米10发布来看编译优化

Flutter精品学习路线(知识分类+学习资料)

How should Amazon sellers prevent association? Don't cut the bill! (detailed explanation of evaluation self support number)

redis 配置,集群安装与扩容

Jinglianwen technology data acquisition company: strictly control the construction period and deliver high-quality data for AI enterprises

计算两个矩阵的行向量之间的欧式距离
随机推荐
软考(中级软件设计师)考试信息
PostgreSQL is now installed
Comparison table of wireless transmission technical parameters of Internet of things
RMAN detailed tutorial (I) -- basic command code
Kingbasees v8r6 ksql turn off auto submit
Codeforces Round #806 (Div. 4)
[untitled]
Tensorflow2.0进阶学习-RNN生成音频 (十二)
MIPI CSI、DSI、UFS、C-PHY、D-PHY、M-PHY概念理解
Error establishing connection between MySQL and idea
PostgreSQL 现在安装
Andersen global enters Rwanda
Compilation optimization from the release of Xiaomi 10
CVPR 2022 | improve the utilization efficiency of small data sets, Fudan et al. Proposed layered cascaded vit network
Enable sandbox function and use in win10
Free SSL certificate application and deployment practice
Mobile communication helps the new move of summer grain storage, and scientific and technological means are more effective
[batch dos-cmd command - summary and summary] - symbolic link, hard link, soft link, directory link (mklink)
Can SQL also do AI? you 're right! Mlops meetup V3 review openmlbd+sqlflow+byzer
基于时间戳的唯一标识符的轻量级跟踪方法