核心代码,所有代码在这里下载
#include "unixnet.h"
#include "chat.h"
#include "sys/epoll.h"
/**
* 改进说明:使用epoll进行事件回调,多用户可以处于输入命令状态(不再阻塞在登陆处)
*/
int main (int argc, char *argv[])
{
int listen_fd;
socklen_t cli_len;
struct sockaddr_in cli_addr,serv_addr;
int ret,flags;
int re_use_addr=1;
char recv_buf[MAXLINE];
int i;
//epoll 描述符
int efd;
struct epoll_event event;
struct epoll_event events[MAXUSERS];
//初始化槽位
for(i=0; i<MAXUSERS; i++)
{
chater[i].slot_status =SLOT_FREED ;
chater[i].sock_fd =-1;
chater[i].cmd .cmd_type =-1;
}
/*AF_INET指定ipv4,SOCK_STREAM制定流模式,0为tcp*/
listen_fd = socket(AF_INET,SOCK_STREAM,0);
if(listen_fd==-1)
{
perror("create listen fd");
exit(1);
}
//服务器地址清零
bzero(&serv_addr ,sizeof(serv_addr));
serv_addr.sin_family=AF_INET;
//接受所有ip的请求
serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);
//设置端口
serv_addr.sin_port=htons(SERVER_PORT );
setsockopt(listen_fd, SOL_SOCKET,SO_REUSEADDR,(void
*)&re_use_addr,sizeof(int));
//绑定描述符到端口
ret=bind(listen_fd,(struct sockaddr
*)&serv_addr,sizeof(serv_addr));
if(ret<0)
{
perror("bind server port");
exit(1);
}
//开始监听端口请求,并设置最大的请求队列
listen(listen_fd,LISTENQ);
//设置为非阻塞io
if((flags=fcntl(listen_fd,F_GETFL,flags)<0))
{
perror("F_SETFL error");
}
//使用位域
flags|=O_NONBLOCK;
if(fcntl(listen_fd,F_SETFL,flags)<0)
{
perror("F_SETFL error");
}
//创建一个epoll句柄
efd = epoll_create(MAXUSERS);
//对连接套接字设置监听事件,并设置触发模式,最后加入到epoll中进行回调
event.data.fd = listen_fd;
event.events = EPOLLET| EPOLLIN;
epoll_ctl(efd, EPOLL_CTL_ADD, listen_fd, &event);
printf("listen_fd %d\n",listen_fd);
//循环处理所有事件
while(1){
int n,i;
//等待直到事件到来,规律性sleep,所有不会导致cpu 100%
n = epoll_wait(efd, events, MAXUSERS,-1);
for(i = 0;i < n;++i){
//如果是一个异常事件,则释放资源
if ((events[i].events & EPOLLERR) ||
(events[i].events & EPOLLHUP) ||
(!(events[i].events & EPOLLIN))) {
fprintf (stderr, "epoll error\n");
free_slot(findSlotIndexByConnFd(events[i].data.fd));
epoll_ctl(efd,EPOLL_CTL_DEL,events[i].data.fd,NULL);
continue;
//此时代表获得了一个连接建立事件,可以创建一个连接,本质上也是一个EPOLLIN
}else if(listen_fd == events[i].data.fd){
int infd = -1;
//accept接受连接直到失败(由于使用ET触发所以需要使用循环以保所有事件都得到处理)
while((infd = accept(listen_fd,(struct sockaddr *)&cli_addr,&cli_len)) > 0){
//获得一个空槽,并将连接设置为非阻塞
get_free_slot(infd, "no_login");
event.data.fd = infd;
event.events = EPOLLIN | EPOLLET;
epoll_ctl(efd, EPOLL_CTL_ADD, infd, &event);
writen(infd,message[2],strlen(message [2]));
printf("accept one\n");
}
if(infd == -1){
printf("%d\n",errno);
}
//在这里处理退出事件|用户名登陆事件|正常的cmd命令
}else{
int infd = events[i].data.fd;
int index = -1;
char *ptr;
index = findSlotIndexByConnFd(infd);
//从内核读数据到用户缓冲区
ptr=&(chater[index].buffer[chater[index].next_char]);
if(index != -1){
while((n=read(infd ,ptr,1))==1)
{
if(*ptr=='\n')
{
*(ptr+1)='\0';
chater[index].next_char =0;
break;
}
//在这里丢弃过长的字符串
if(++chater[index].next_char ==MAXLINE)
--chater[index].next_char ;
else
++ptr;
}
//退出事件:当n==0代表连接丢失,当n==-1且errno!=EWOULDBLOCK代表不是因为暂时没有数据抛出错误
if(n == 0 || (n == -1&&errno!=EWOULDBLOCK)){
free_slot(findSlotIndexByConnFd(events[i].data.fd));
epoll_ctl(efd,EPOLL_CTL_DEL,events[i].data.fd,NULL);
printf("user %s use slot_index %d logout\n\n",chater[index].user_id, index);
continue;
}
//登陆用户名
if(!chater[index].user_assigned){
copyStringSkipSpace(chater[index].user_id,chater[index].buffer);
chater[index].user_assigned = 1;
printf("user %s use slot_index %d login\n\n",chater[index].user_id, index);
//处理cmd.
}else{
handle_cmd(index);
}
}
}
}
}
}