编写一个基于libevent实现的tcp服务端:
1 创建socket,获得lfd--socket();
2 设置端口复用:
int opt=1;
setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
3 绑定--bind();
4 监听--listen();
5 创建地基--struct event_base*base=event_base_new();
6 创建lfd对应的事件节点
struct event *ev=event_new(base,lfd,EV_READ | EV_PERSIST,conncb,(void *)base); //持续监听lfd的读事件。若有事件发生,则调用conncb回调函数。
7 将lfd对应的事件节点上地基
event_add(base,NULL);
8 进入事件循环--event_base_dispatch(base);
9 释放资源
event_base_free(base);
event_base_free(ev);
10回调函数:
void conncb(evutil_socket_t fd, short events, void *arg)
{//监听文件描述符的回调函数
struct event_base*base=(struct event_base*)arg//获得地基
//接收新的连接
int cfd=accept(lfd,NULL,NULL);
if(cfd<0)
{
}
struct event *ev=event_new(base,cfd,EV_READ | EV_PERSIST,read,(void *)ev);
}
void readcb(evutil_socket_t fd, short events, void *arg)
{
struct event *ev=(struct event *)arg//获得此通信文件描述符的事件
n=read(fd,buf,sizeof(buf));
if(n<=0)
{
close(fd);
//从base地基上删除此事件(事件从未决态变非未决态)
event_del(ev);
event_free(ev);
}
write(fd,buf,strlen(buf));
}
完整代码实现:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<unistd.h>
#include<event2/event.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include<arpa/inet.h>
#include<ctype.h>
struct event *conev=NULL;
void readcb(evutil_socket_t fd, short event, void *arg)
{
//通信文件描述符的事件的回调函数
char buf[128];
int i;
memset(buf,0x00,sizeof(buf));
int n=read(fd,buf,sizeof(buf));
if(n<=0)
{
printf("read error or client close\n");
close(fd);//关掉文件描述符
event_del(conev);//下树
}
else
{
for(i=0;i<n;i++)
{
buf[i]=toupper(buf[i]);
}
write(fd,buf,n);
}
}
void conncb(evutil_socket_t fd, short event, void *arg)
{
//监听文件描述符的事件的回调函数
struct event_base*base=(struct event_base*)arg;//强转类型,获得地基
int cfd=accept(fd,NULL,NULL);//获取新的连接
if(cfd>0)
{
conev=event_new(base,cfd,EV_READ | EV_PERSIST,readcb,NULL);//创建一个新的事件节点
if(conev==NULL)
{
event_base_loopexit(base,NULL);//退出event_base_dispatch循环
}
event_add(conev,NULL);//上地基
}
}
int main()
{
int lfd=socket(AF_INET,SOCK_STREAM,0);
if(lfd<0)
{
perror("socket error");
return -1;
}
//设置端口复用
int opt=1;
setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(int));
struct sockaddr_in sev;
bzero(&sev,sizeof(sev));
sev.sin_family=AF_INET;
sev.sin_port=htons(8888);
inet_pton(AF_INET,"192.168.230.130",&sev.sin_addr.s_addr);
int ret=bind(lfd,(struct sockaddr *)&sev,sizeof(sev));//绑定
if(ret<0)
{
perror("bind error");
return -1;
}
ret=listen(lfd,128);//设置监听
if(ret<0)
{
perror("listen error");
return -1;
}
//struct event_base *event_base_new(void);
struct event_base*base=event_base_new();创建地基
if(base==NULL)
{
printf("new error\n");
return -1;
}
//struct event *event_new(struct event_base *, evutil_socket_t, short, event_callback_fn, void *);
//创建一个lfd的事件节点
struct event *ev=event_new(base,lfd,EV_READ | EV_PERSIST,conncb,(void *)base);
if(ev==NULL)
{
printf("event_new error\n");
return -1;
}
//int event_add(struct event *ev, const struct timeval *timeout);
event_add(ev,NULL);//将此事件节点上树,事件从非未决态变未决态
//int event_base_dispatch(struct event_base *);
event_base_dispatch(base);//进入循环,等待事件由未决态变激发态,并执行事件的回调函数
//int event_del(struct event *);
//void event_free(struct event *);
event_free(ev);//释放lfd的事件节点
event_base_free(base);//释放地基
close(lfd);//关掉监听文件描述符
return 0;
}
缺点:我们使用了struct event *conev=NULL;,用全局变量conev来保存通信文件描述符的事件,但是如果有多个客户端连接时,conev只保存了最后一个通信文件描述符的事件,所以当我们执行event_del(conev);语句时,只是将最后一个通信文件描述符的事件下地基,让最后一个通信文件描述符无法通信。
解决方法:
创建一个结构体数组,让每一个通信文件描述符的事件有不同的内存空间。
struct f_event
{
int num
int fd;
struct event *conev;
};
改进代码:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<unistd.h>
#include<event2/event.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include<arpa/inet.h>
#include<ctype.h>
struct f_event//定义一个结构体
{
int num;
int fd;
struct event*conev;
};
struct f_event evs[128];
void readcb(evutil_socket_t fd, short event, void *arg)
{
struct f_event *nowev=(struct f_event*)arg;//强转类型,并用指针接收,可以修改fd的值
char buf[128];
int i;
memset(buf,0x00,sizeof(buf));
int n=read(fd,buf,sizeof(buf));
if(n<=0)
{
printf("read error or client close\n");
close(nowev->fd);
event_del(nowev->conev);
nowev->fd=-1;//表面该位置空闲
}
else
{
printf("[%d],n==[%d],buf==[%s]\n",nowev->num,n,buf);
for(i=0;i<n;i++)
{
buf[i]=toupper(buf[i]);
}
write(fd,buf,n);
}
}
void conncb(evutil_socket_t fd, short event, void *arg)
{
int i=0;
struct event_base*base=(struct event_base*)arg;
int cfd=accept(fd,NULL,NULL);
if(cfd>0)
{
for(i=0;i<128;i++)
{
if(evs[i].fd==-1)
{
evs[i].num=i;
evs[i].fd=cfd;
evs[i].conev=event_new(base,cfd,EV_READ | EV_PERSIST,readcb,(void*)&evs[i]);//直接把结构体作为参数传入
if(evs[i].conev==NULL)
{
event_base_loopexit(base,NULL);
}
else
{
event_add(evs[i].conev,NULL);
break;//超级重要,不然会一直循环,让i==128
}
}
}
if(i==128)
{
printf("there is no enough space\n");
close(cfd);
}
}
}
int main()
{
int j=0;
for(j=0;j<128;j++)
{
evs[j].fd=-1;//初始化,fd为-1表示空闲
}
int lfd=socket(AF_INET,SOCK_STREAM,0);
if(lfd<0)
{
perror("socket error");
return -1;
}
int opt=1;
setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(int));
struct sockaddr_in sev;
bzero(&sev,sizeof(sev));
sev.sin_family=AF_INET;
sev.sin_port=htons(8888);
inet_pton(AF_INET,"192.168.230.130",&sev.sin_addr.s_addr);
int ret=bind(lfd,(struct sockaddr *)&sev,sizeof(sev));
if(ret<0)
{
perror("bind error");
return -1;
}
ret=listen(lfd,128);
if(ret<0)
{
perror("listen error");
return -1;
}
//struct event_base *event_base_new(void);
struct event_base*base=event_base_new();
if(base==NULL)
{
printf("new error\n");
return -1;
}
//struct event *event_new(struct event_base *, evutil_socket_t, short, event_callback_fn, void *);
struct event *ev=event_new(base,lfd,EV_READ | EV_PERSIST,conncb,(void *)base);
if(ev==NULL)
{
printf("event_new error\n");
return -1;
}
//int event_add(struct event *ev, const struct timeval *timeout);
event_add(ev,NULL);
//int event_base_dispatch(struct event_base *);
event_base_dispatch(base);
//int event_del(struct event *);
//void event_free(struct event *);
event_free(ev);
event_base_free(base);
close(lfd);
return 0;
}
可以 看到关掉第一个通信文件描述符后,最后一个通信文件描述符也可以正常通信。