实现功能
模型
服务器
使用了epoll模型,并为每一个处理请求新开一个线程进行处理,并进行线程分离,使它们结束后可以自动释放资源。如果使用线程池自然是更好的,但本人能力不足怕写成多线程难以处理。
epoll模型如下
for(;;)
{
nfds = epoll_wait(epfd,events,EVENTS_MAX_SIZE,-1);//等待可写事件
for(int i=0;i<nfds;i++)
{
if(events[i].data.fd==sock_fd) //服务器套接字接收到一个连接请求
{
if(connect_size>MAX_CONTECT_SIZE)
{
printf("%d %d\n",connect_size,nfds);
sleep(3);
perror("到达最大连接数!\n");
break;
// continue;
}
cliaddr_len=sizeof(cliaddr);
conn_fd=accept(events[i].data.fd,(struct sockaddr*)&cliaddr,&cliaddr_len);
if(conn_fd<=0)
{
perror("error in accept\n");
printf("%s\n",strerror(errno));
// continue;
break;
}
connect_size++;
printf("received from %s at PORT %d\n",inet_ntop(AF_INET,&cliaddr.sin_addr,str,sizeof(str)),ntohs(cliaddr.sin_port));
ev.data.fd= conn_fd;
ev.events =EPOLLIN | EPOLLONESHOT | EPOLLRDHUP;
epoll_ctl(epfd,EPOLL_CTL_ADD,conn_fd,&ev);
}
/*
*/
else{
temp->epfd=epfd;
temp->conn_fd=events[i].data.fd;
pth1=pthread_create(&pth1,NULL,solve,temp);
}
}
}
采用阻塞的方式等待可读事件,我们为每一個收到的请求单独开一个线程进行处理。
服务器存储消息
服务器将接受到的信息都调用mysql接口存储在数据库中。
客户端
客户端采用了简单的单线程方法,单独开一个线程持续的接受信息并进行处理,而主函数则只对处理完的信息进行显示。
传输信息
服务器和客户端之间使用传输一个定长的结构体
struct work {
char tye; //种类
int sid; //发送者
int rid; //接受者
char name[20]; //名字
char password[20]; //密码
int ret; //返回值
char mes[1000]; //信息
};
虽然显得很笨重,但却很简单。
处理信息
无论是客户端还是服务器,受到一个操作请求时就会先辨别tye,然后再进入专门处理的函数对信息进行处理。
当我们进行操作完成,需要发送一个标志代表处理已经完成。
比如说,客户端希望获取未读信息时,就会向服务器发送请求,并进行等待,服务器受到请求后会逐条发送信息,发送完毕后再发送一个特殊的标志代表该动作已经完成。收到标志后,客户端才会对处理完成的信息进行读取。
客户端存储消息
客户端把消息消息存储在链表中,在本程序中定义了诸多宏函数在List.h中来对链表进行处理。无论是未读信息,还是已读信息,好友列表,文件列表,群信息,群列表都存储在客户端的链表中。在需要时,客户端会对链表中的信息进行更新。
typedef struct {
int sid;
int rid;
char mes[1000];
}mes;
typedef struct mes_node{
mes data;
struct mes_node*prev;
struct mes_node*next;
}mes_node_t,*mes_list_t;
发送文件与接受文件
我们使用sendfile函数来发送一个文件,但是这里有一个问题,那就是接收方需要知道文件的大小。所以当一方表明要发送文件时,会先将文件名和大小发送过去。发送用sendfile会很简单,但接受时就得老实的调用recv来循环接受文件。
//接受文件,len为文件的大小
out=creat(filename,0664);
int len=s1.ret;
char buf[5000];
while(len>0 ){
memset(buf,'\0',sizeof(buf));
num=recv(s->conn_fd,buf,4096,0);
len=len-num;
write(out,buf,num);
}