畅聊系统

一.功能
该项目是基于UDP的。实现一个群聊的功能,多个用户给服务器传输数据,服务器会将信息返回到每个人的界面上。
二.模块介绍
1.服务端客户端模块:主要实现接收客户端消息以及发送消息给客户端,主要依赖UDP实现。
2.注册登录模块:首先需要发送注册内容,这里首先需要用户输入信息,然后设置密码,先输入密码,然后核对密码,两次密码输入一样,便注册成功了,然后解析应答状态并获取用户id,然后保存服务端返回的用户id;登录:首先发送登录数据,然后解析登录状态,登录成功后则返回用户界面。用户进行注册、登录时,为了保证数据的可靠性,这里用到了TCP协议并且进行短链接(用的时候链接,不用的时候断开),这样可以减少服务器的开销,避免大量链接挂在服务器上,影响服务器性能,甚至导致服务器瘫痪。

struct Reg ri;
      std::cout<<"Please Enter Your NickName:";
      std::cin>>ri.NickName;
      std::cout<<"Please Enter Your School";
      std::cin>>ri.School;
      while(1)
      {
        std::cout<<"Please Enter Your password:";
        std::string PasswdOne;
        std::cin>>PasswdOne;
        std::cout<<"Please Enter Your Password again";
        std::string PasswdTwo;
        std::cin>>PasswdTwo;
        if(PasswdOne==PasswdTwo)
        {
          strcpy(ri.PassWord,PasswdOne.c_str());
          break;
        }
        else
        {
          printf("The passwords did not match twice");
        }
      }
      send_size=send(TcpSock_,&ri,sizeof(ri),0);
      if(send_size<0)
      {
        LOG(ERROR,"Send Register type failed")<<std::endl;
        return false;
      }
      //:3.解析应答状态和获取用户ID
      struct ReplyInfo resp;
      ssize_t recv_size=recv(TcpSock_,&resp,sizeof(resp),0);
      if(recv_size<0)
      {
        LOG(ERROR,"Recv Register response failed")<<std::endl;
        return false;
      }
      else if(recv_size == 0)
      {
        LOG(ERROR, "Peer shutdown connect") << std::endl;
        return false;

      }

      if(resp.Status != REGS)
      {
        LOG(ERROR, "Register Failed") << std::endl;
        return false;

      }
      LOG(INFO, "Register success UserId is ") << resp.UserId_ << std::endl;
      //:保存服务端返回的UserID;
      me_.NickName_=ri.NickName;
      me_.School_=ri.School;
      me_.Password_=ri.PassWord;
      me_.UserId_=resp.UserId_;
      close(TcpSock_);
      return true;
    }
    bool Login()
    {
      if(!Connect2Server())
      {
        return false;
      }
      //:发送登录标识
      char type = LOGIN;
      ssize_t send_size = send(TcpSock_, &type, 1, 0);
      if(send_size < 0)
      {
        LOG(ERROR, "Send Login type failed") << std::endl;
        return false;

      }
      //;发送登录数据
      struct Login li;
      li.UserId= me_.UserId_;
      strcpy(li.PassWord, me_.Password_.c_str());                              
      send_size = send(TcpSock_, &li, sizeof(li), 0);
      if(send_size < 0)                                               
      {
        LOG(ERROR, "Send Login data failed") << std::endl;
        return false;                                                                      
      }
      //:解析登录状态
      struct ReplyInfo resp;
      ssize_t recv_size = recv(TcpSock_, &resp, sizeof(resp), 0);
      if(recv_size < 0)
      {
        LOG(ERROR, "Recv Login response failed") << std::endl;
        return false;

      }
      else if(recv_size == 0)
      {
        LOG(ERROR, "Peer shutdown connect") << std::endl;
        return false;

      }

      if(resp.Status != LOGIN)
      {
        LOG(ERROR, "Login failed Status is ") << resp.Status << std::endl; 
        return false;

      }
      LOG(INFO, "Login success") << std::endl;
      return true;
    }

3.用户管理模块:只有注册登录的人才可以向服务端发送消息,如果已经注册,则将当前用户的状态改为已注册,将当前用户的信息插入到map中,key为注册用户的id,value为其对应的注册信息。

4.聊天模块:当用户进行聊天室,为了避免大量链接挂在服务器上,所以聊天时用的是UDP协议。UDP协议的优点是不用建立链接,速度快,且不会出现数据粘包的问题。其缺点是容易丢包,但从二者的利弊来看,使用UDP是比较合适的。在聊天时,当用户发送一条消息时,其会实例化一个包含用户注册信息的一个类,对该类所实例化出的对象进行序列化操作,这里我调用json库来完成;然后将数据发送给服务端,服务端会创建出两个线程,一个向数据池中发送数据,一个从数据池中取出数据,并将取出来的数据广播给所有在用户列表的用户,用户收到报文时,进行反序列化操作,若该消息的发送用户在用户列表中,则将消息打印到用户端的屏幕上。

    //:udp数据的收发
    bool SendMsg(const std::string& msg)
    {
      struct sockaddr_in peeraddr;
      peeraddr.sin_family = AF_INET;
      peeraddr.sin_port = htons(UdpPort_);
      peeraddr.sin_addr.s_addr = inet_addr(SvrIp_.c_str());
      ssize_t send_size = sendto(UdpSock_, msg.c_str(), msg.size(), 0, (struct sockaddr*)&peeraddr, sizeof(peeraddr));
      if(send_size < 0)
      {
        LOG(ERROR, "Send Msg to Server Failed\n");
        return false;                                                             
      }
      return true;

    }
    bool RecvMsg(std::string* msg)
    {
      char buf[MESSAGE_MAX_SIZE];
      memset(buf, '\0', sizeof(buf));
      struct sockaddr_in svraddr;
      socklen_t svraddrlen = sizeof(svraddr);
      ssize_t recv_size = recvfrom(UdpSock_, buf, sizeof(buf) - 1, 0, (struct sockaddr*)&svraddr, &svraddrlen);
      if(recv_size < 0)
      {
        LOG(ERROR, "recv msg from server failed");
        return false;

      }
      (*msg).assign(buf, recv_size);
      return true;

    }

数据池:

class MsgPool
{
  public:
    MsgPool()
    {
      Capacity=MSG_POOL_SIZE;
      pthread_mutex_init(&MsgQueLock,NULL);
      pthread_cond_init(&ConQue,NULL);
      pthread_cond_init(&ProQue,NULL);
    }
    ~MsgPool()
    {
      pthread_mutex_destroy(&MsgQueLock);
      pthread_cond_destroy(&ConQue);
      pthread_cond_destroy(&ProQue);
    }
    void PushMsg(std::string &msg)
    {
        pthread_mutex_lock(&MsgQueLock);
        while(isFull())
        {
          pthread_cond_wait(&ProQue,&MsgQueLock);
        }
        MsgQue_.push(msg);
        pthread_mutex_unlock(&MsgQueLock);
        pthread_cond_signal(&ConQue);

    }
    void PopMsg(std::string *msg)
    {
      pthread_mutex_lock(&MsgQueLock);
      while(MsgQue_.empty())
      {
        pthread_cond_wait(&ConQue,&MsgQueLock);
      }
     *msg=MsgQue_.front();
      MsgQue_.pop();
      pthread_mutex_unlock(&MsgQueLock);
      pthread_cond_signal(&ProQue);
    }
  private:
    bool isFull()
    {
      if(MsgQue_.size()==Capacity)
      {
        return true;
      }
      return false;
    }
    private:
      std::queue<std::string> MsgQue_;
    //:约束下队列大小,防止异常情况
    size_t Capacity;
    //:互斥
    pthread_mutex_t MsgQueLock;
    //:同步
    //:消费者条件变量
    pthread_cond_t ConQue;
    //:生产者条件变量
    pthread_cond_t ProQue;
};

源码地址:https://github.com/GeniusPanYiHao/LINUX/tree/master/project-chat

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值