先给自己打个广告,本人的微信公众号正式上线了,搜索:张笑生的地盘,主要关注嵌入式软件开发,股票基金定投,足球等等,希望大家多多关注,有问题可以直接留言给我,一定尽心尽力回答大家的问题
**
**
在前面的博客《linux进程间通信—本地socket套接字(七)—多路IO转接服务器实现一个server对应多个client—epoll实现》中,我们使用epoll机制来实现一个server对接多个client的方案。
我们在这里简单回顾一下之前的实现方案,其主要思想是:利用epoll_wait函数的返回值返回当前文件描述符读写事件发生的个数,然后依次遍历被我们添加进去的文件描述符,对比两者之间的文件描述符的值,也就是如下自然语言思想
for (i = 0; i < rval; i++) { // 这里的rval就是epoll_wait的返回值,表示一共有多少个事件发生
== lfd ---> accept
== cfd1 ---> recv
}
考虑一下,这样做确实没有问题,但是效率比较低,因为需要程序去遍历多个文件描述符(结构体成员变量fd),逐个判断,如下
typedef union epoll_data {
void *ptr;
int fd; // 这个就是文件描述符编号
uint32_t u32;
uint64_t u64;
} epoll_data_t;
那么可否提高效率,避免逐个判断呢?这个就是epoll_data_t结构体成员变量*ptr的作用了。
**
**
我们可以思考,这个泛型指针*ptr究竟有什么作用呢?如何可以避免逐个判断的动作,直接知道当前文件描述符发生了什么操作,进而去实现某个操作呢?看下面的简图
构造一个结构体,让ptr指针指向这个结构体,epoll_wait函数的第二个参数是一个传出参数,ptr指针就是data成员变量的ptr指针,将要操作的函数通过call_back注册到这个结构体中,就可以实现我们要的功能了。构造的结构体定义如下
typedef struct myevent_s {
int fd;
int events;
void *arg;
void (*call_back)(int fd, int events, void *arg);
int status;
char buf[BUFLEN];
char send_buf[BUFLEN];
int len;
int send_len;
long last_active;
} myevent_t;
**
**
在写代码之前,我们先看一下利用epoll反应堆机制实现sever的大概过程。文件描述符还是有监听文件描述符(lfd),客户端文件描述符(cfd)。
epoll ---服务器 ---监听 --- cfd ---可读 ---epoll返回 ---read
---cfd从树上摘下 ---重新设置监听cfd写事件,操作 ---等待epoll_wait返回
---处理(回写到客户端) ---cfd从树上摘下 ---重新设置监听cfd读事件,操作
---epoll继续监听
server代码
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define BUFLEN 1024
#define MAX_EVENTS 10
#define SERV_PORT 7809
typedef struct myevent_s {
int fd;
int events;
void *arg;
void (*call_back)(int fd, int events, void *arg);
int status;
char buf[BUFLEN];
char send_buf[BUFLEN];
int len;
int send_len;
long last_active;
} myevent_t;
static int lfd;
static int g_efd;
static myevent_t g_events[MAX_EVENTS+1]; // +1 ---> lfd
static void recvdata(int fd, int events, void *arg);
static void senddata(int fd, int events, void *arg);
/* 初始化 myevent_t */
static void eventset(myevent_t *ev, int fd, void (*call_back)(int fd, int events, void *arg),
void *arg)
{
ev->fd = fd;
ev->events = 0;
ev->arg = arg;
ev->call_back = call_back;
ev->status = 0;
memset(ev->buf, 0, sizeof(ev->buf));
ev->len = 0;
ev->last_active = time(NULL); //调用eventset函数的事件
return ;
}
static void eventadd(int efd, int events, myevent_t *ev)
{
struct epoll_event epv = {0, {0}};
int op, rval = 0;
epv.data.ptr = ev;
epv.events = ev->events = events; //EPOLLIN
if (ev->status == 1) { //已经在红黑树 g_efd里
op = EPOLL_CTL_MOD; //修改其属性
} else {
op = EPOLL_CTL_ADD; //不在红黑树 g_efd里
ev->status = 1; //将其加入到红黑树 g_efd里
}
//先将lfd事件添加到epoll中,因为只有lfd检测到客户端的连接,然后才会
//读取client发过来的数据
rval = epoll_ctl(efd, op, ev->fd, &epv);
if (rval < 0) {
perror("epoll_ctl error");
} else {
printf("event add ok, fd=%d, op=%d, events[%0x]\n", ev->fd, op, events);
}
return ;
}
static void eventdel(int efd, myevent_t *ev)
{
struct epoll_event epv = {0, {0}};
if (ev->status == 0) // 不在红黑树上
return ;
epv.data.ptr = ev;
ev->status = 0; // 修改状态
// 从红黑树上 efd 摘除 ev->fd
epoll_ctl(efd, EPOLL_CTL_DEL, ev->fd, &epv);
return ;
}
static void senddata(int fd, int events, void *arg)
{
myevent_t *ev = (myevent_t *)arg;
int len;
// 写文件描述符
len = send(fd, ev->send_buf, ev->send_len, 0);
// 将该节点从红黑树摘除
eventdel(g_efd, ev);
if (len > 0) {
printf("now send: %s\n", ev->send_buf);
// 将cfd设置给一个 g_events ,并添加回调函数 recvdata
eventset(ev, fd, recvdata, ev);
// 将cfd添加到红黑树 g_efd 中,监听读事件
eventadd(g_efd, EPOLLIN, ev);
} else {
close(ev->fd); // 关闭连接
perror("send error");
}
return ;
}
static void recvdata(int fd, int events, void *arg)
{
myevent_t *ev = (myevent_t *)arg;
int len;
// 读文件描述符
len = recv(fd, ev->buf, sizeof(ev->buf), 0);
eventdel(g_efd, ev); // 将该节点从红黑树摘除
if (len > 0) {
ev->len = len;
ev->buf[len] = '\0';
printf("now recv: %s\n", ev->buf);
memcpy(ev->send_buf, ev->buf, len);
ev->send_len = len;
// 将cfd设置给一个 g_events ,并添加回调函数 senddata
eventset(ev, fd, senddata, ev);
// 将cfd添加到红黑树 g_efd 中,监听写事件
eventadd(g_efd, EPOLLOUT, ev);
} else if (len == 0) {
close(ev->fd);
printf("fd=%d pos=%ld\n", fd, ev - g_events);
} else {
close(ev->fd);
perror("recv error!");
}
return ;
}
static void acceptconn(int lfd, int events, void *arg)
{
struct sockaddr_in cliaddr;
socklen_t clilen;
int i, cfd;
int flag = 0;
char str[INET_ADDRSTRLEN];
cfd = accept(lfd, (struct sockaddr *)&cliaddr, &clilen);
if (cfd < 0) {
perror("accept error\n");
return;
}
do {
// 从 g_events 找一空闲元素, 类似于select中找值为 -1的元素
for (i = 0; i < MAX_EVENTS; i++) {
if (g_events[i].status == 0)
break;
}
if (i >= MAX_EVENTS) {
printf("max connect limit[%d]\n", MAX_EVENTS);
break;
}
flag = fcntl(cfd, F_SETFL, O_NONBLOCK);
if (flag < 0) {
perror("fcntl O_NONBLOCK failed");
break;
}
// 将cfd设置给一个 g_events ,并添加回调函数 recvdata
eventset(&g_events[i], cfd, recvdata, &g_events[i]);
// 将cfd添加到红黑树 g_efd 中,监听读事件
eventadd(g_efd, EPOLLIN, &g_events[i]);
} while(0);
printf("received from %s at PORT %d\n",
inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
ntohs(cliaddr.sin_port));
return ;
}
static int socket_server_init(int efd)
{
struct sockaddr_in servaddr;
int opt = 1;
int rval = 0;
lfd = socket(AF_INET, SOCK_STREAM, 0);
if (lfd < 0) {
perror("create socket fail");
return -1;
}
//将 socket 设为非阻塞
fcntl(lfd, F_SETFL, O_NONBLOCK);
eventset(&g_events[MAX_EVENTS], lfd, acceptconn, &g_events[MAX_EVENTS]);
eventadd(efd, EPOLLIN, &g_events[MAX_EVENTS]);
rval = setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR,
&opt, sizeof(opt));
if (rval < 0) {
perror("setsockopt fail");
return -1;
}
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
rval = bind(lfd, (struct sockaddr*) &servaddr,
sizeof(servaddr));
if (rval < 0 ) {
perror("bind fail");
return -1;
}
rval = listen(lfd, MAX_EVENTS);
if (rval < 0 ) {
perror("listen fail");
return -1;
}
}
int main(int argc, char **argv)
{
struct epoll_event evt;
struct epoll_event evts[MAX_EVENTS+1];
int rval = 0, nready;
int i, checkops = 0;
//创建g_efd
g_efd = epoll_create(MAX_EVENTS);
if (g_efd == -1) {
perror("epoll_create error");
return 0;
}
rval = socket_server_init(g_efd);
if (rval < 0) {
printf("socket_server_init fail\n");
return 0;
}
while (1) {
nready = epoll_wait(g_efd, evts, MAX_EVENTS, -1);
if (nready <= 0) {
perror("epoll_wait error");
return 0;
}
for (i = 0; i < nready; i++) {
myevent_t *ev = (myevent_t *)evts[i].data.ptr;
if ((evts[i].events & EPOLLIN) && (ev->events & EPOLLIN)) {
ev->call_back(ev->fd, evts[i].events, ev->arg);
}
if ((evts[i].events & EPOLLOUT) && (ev->events & EPOLLOUT)) {
ev->call_back(ev->fd, evts[i].events, ev->arg);
}
}
}
}
client代码还是和之前的一样
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define PORT 7809
#define BUFFER_SIZE 1024
#define LOCAL_LOOP_BACK_IP "127.0.0.1"
int main(int argc, char **argv)
{
struct sockaddr_in servaddr;
char sendbuf[BUFFER_SIZE] = {0};
char recvbuf[BUFFER_SIZE] = {0};
int client_fd;
//定义IPV4的TCP连接的套接字描述符
client_fd = socket(AF_INET,SOCK_STREAM, 0);
//set sockaddr_in
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = inet_addr(LOCAL_LOOP_BACK_IP);
servaddr.sin_port = htons(PORT); //服务器端口
//连接服务器,成功返回0,错误返回-1
if (connect(client_fd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)
{
perror("connect");
exit(1);
}
printf("connect server(IP:%s).\n",LOCAL_LOOP_BACK_IP);
//客户端将控制台输入的信息发送给服务器端,服务器原样返回信息
while (fgets(sendbuf, sizeof(sendbuf), stdin) != NULL)
{
send(client_fd, sendbuf, strlen(sendbuf),0); ///发送
if(strcmp(sendbuf,"exit\n")==0)
{
printf("client exited.\n");
break;
}
recv(client_fd, recvbuf, sizeof(recvbuf),0); ///接收
printf("client receive: %s\n", recvbuf);
memset(sendbuf, 0, sizeof(sendbuf));
memset(recvbuf, 0, sizeof(recvbuf));
}
close(client_fd);
return 0;
}