聊天室项目分析


前言

提示:篇博客主要测试了聊天室的实现,这一片博客讲一下相关问题。


一、服务器模型

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满天飞,而且实现的十分普通链表+锁,没有做到性能上的优化,尤其是聊天室这种大一点的东西,自己还是太菜了…希望自己大二好好学吧。

参与评论 您还未登录,请先 登录 后发表或查看评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:数字20 设计师:CSDN官方博客 返回首页

打赏作者

语絮斌

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值