一.功能
该项目是基于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