前言
提示:篇博客主要测试了聊天室的实现,这一片博客讲一下相关问题。
一、服务器模型
epoll +多线程
while (1)
{
if ((nfs = epoll_wait(ep_fd, ep_ids, events, -1)) < 0)
{
if (nfs == -1)
{
if (errno != EINTR)
my_err("epoll_wait", __LINE__);
}
}
for (int i = 0; i < nfs; i++)
{
if (ep_ids[i].data.fd == lid) //有新的客户端连接
{
if ((cid = accept(lid, (struct sockaddr *)&client_addr, &client_len)) < 0)
{
my_err("accept", __LINE__);
}
printf("连接到新的客户端:%s\n", inet_ntoa(client_addr.sin_addr));
printf("accept:%d\n", cid);
ep_id.data.fd = cid;
ep_id.events = EPOLLIN;
epoll_ctl(ep_fd, EPOLL_CTL_ADD, cid, &ep_id);
}
else if (ep_ids[i].events & EPOLLIN) //读数据
{
if ((tsize = recv(ep_ids[i].data.fd, packs, sizeof(pack), 0)) < 0)
{
my_err("recv", __LINE__);
}
else if (tsize == 0) //对端客户端关闭
{
close(ep_ids[i].data.fd);
node *t = head;
node *o;
pthread_mutex_lock(&lock);
while (t->next != NULL)
{
if (t->next->id == ep_ids[i].data.fd)
{
if (t->next == end)
{
end = t;
}
o = t->next;
t->next = t->next->next;
free(o);
break;
}
t = t->next;
}
pthread_mutex_unlock(&lock);
epoll_ctl(ep_fd, EPOLL_CTL_DEL, ep_ids[i].data.fd, &ep_id);
}
else
{
packs->send_id = ep_ids[i].data.fd;
if (packs->cho == 'x' || packs->cho == 'y') //接受文件的时候需要多次触发,所以先将此事件移除epoll,发完后在加入即可。
{
epoll_ctl(ep_fd, EPOLL_CTL_DEL, ep_ids[i].data.fd, &ep_id);
}
if (pthread_create(&pid, NULL, body, (void *)packs))
{
my_err("thread_create", __LINE__);
}
pthread_detach(pid);
}
}
}
}
epoll我只用来监听,读的事件,然后进入线程去写,每次进行一项功能然后进入一个线程,感觉效率有点低,在文件传输时候,如果文件稍大一点,接受文件的时候需要多次触发,所以先将此事件移除epoll,发完后在加入即可,也可以采用epoll的只能触发一次属性 EPOLLONESHOT( 只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里)
二、忽视SIGPIPE信号
客户端异常退出后服务器也就被杀死,当时一直找不到问题,看了龙哥的博客后才找到问题,不得不说龙哥yyds,龙哥博客原文,龙哥原文介绍(首先我们TCP是全双工的,客户端强制退出将导致仅关闭了一端的管道,也就是说对端还可发数据,但第一次发送数据将会导致对端被发送RST报文,第二次就会接收到SIGPIPE信号,而SIGPIPE信号的默认行为为程序退出,所以我们要屏蔽SIGPIPE信号。)
signal(SIGPIPE, SIG_IGN);
三.私聊设计
首先,服务器在在线链表中查找是否在线,在线就写入数据库,服务器会给对方发一条提示,并等待对方去读,然后对方去读消息,写消息,然后自己在写。这个设计有个问题就是等待对方发消息时候,你自己阻塞在这里,所以感觉有点问题,,应该改用非阻塞更好。这里需要注意正在聊天中其他人发送消息过来,要注意处理
四.群聊设计
这里和私聊一样就是需要遍历群聊成员链表就行逐一发送消息,然后写进去,比私聊简单点。
五.文件传输
感觉文件实现也是很方便,在文件传输时候,如果文件稍大一点,接受文件的时候需要多次触发,所以先将此事件移除epoll,发完后在加入即可,
发送文件
发送文件就很方便c 的 api sendfile直接调用
sprintf(s, "select file_name from files where recv_name=\'%s\' and id>0", recv_pack->send_name);
t = mysql_select(s, recv_pack, 3);
if (t == 0)
{
strcpy(recv_pack->work, "你暂时没有需要接收的文件");
send_t(recv_pack, recv_pack->send_id);
return;
}
else
strcpy(recv_pack->work, "正在接收中请稍后");
pthread_mutex_lock(&mysqs);
flag = mysql_query(&mysql, s);
if (flag)
{
mysql_error(&mysql);
}
result = mysql_store_result(&mysql);
pthread_mutex_unlock(&mysqs);
if (result)
{
while ((row = mysql_fetch_row(result)) != 0)
{
for (unsigned int i = 0; i < mysql_num_fields(result); i++)
{
if (row[i])
{
strcpy(file, row[i]);
break;
}
}
}
}
fd = open(file, O_RDONLY);
fstat(fd, &buf);
recv_pack->id = buf.st_size;
send_t(recv_pack, recv_pack->send_id);
sendfile(recv_pack->send_id, fd, NULL, buf.st_size);
close(fd);
recv_t(recv_pack, recv_pack->send_id);
if (strcmp(recv_pack->work, "yes") == 0)
{
sprintf(s, "update file set id=0 where file_name=\'%s\' and id>0", file);
}
ep_id.data.fd = recv_pack->send_id; //加入epoll
ep_id.events = EPOLLIN;
epoll_ctl(ep_fd, EPOLL_CTL_ADD, recv_pack->send_id, &ep_id); //将该文件描述符添加到epolli
接受文件
sprintf(s, "insert into message(recv_name,send_name,id,works)values(\'%s\',\'%s\',%d,\'%s\')",
recv_pack->recv_name, recv_pack->send_name, 10, files);
mysql_in_del(s);
sprintf(s, "insert into files(recv_name,send_name,file_name,id)values(\'%s\',\'%s\',\'%s\',1)",
recv_pack->recv_name, recv_pack->send_name, files);
mysql_in_del(s);
while (nfs--)
{
if (filesize < 1023)
recvsize = filesize;
filesize -= 1023;
recv(recv_pack->send_id, sizefile, recvsize, 0);
fd = open(files, O_WRONLY | O_CREAT | O_APPEND, 0644);
write(fd, sizefile, recvsize);
}
close(fd);
ep_id.data.fd = recv_pack->send_id;
ep_id.events = EPOLLIN;
epoll_ctl(ep_fd, EPOLL_CTL_ADD, recv_pack->send_id, &ep_id); //将该文件描述符添加到epoll
strcpy(recv_pack->work, "已经成功发送");
send_t(recv_pack, recv_pack->send_id);
六.数据库注意问题
自己在写数据库中,数据库的事务已经保证了原子性,但是我会遇到一些地方出问题,必须加锁,所以我就无脑全部加锁,为了不是太影响效率把数据库锁的粒度弄小一点就行了。
七.注意逻辑问题
设计时候要注意各种逻辑问题,二次登陆,二次加好友,二次加群,等等,尤其是聊天室这种大一点的东西一定要构思设计好,要不然中间很痛苦。
总结
确实感觉设计上还是有点问题,功能点都实现,感觉自己写的代码十分不规范,bug满天飞,而且实现的十分普通链表+锁,没有做到性能上的优化,尤其是聊天室这种大一点的东西,自己还是太菜了…希望自己大二好好学吧。