这是一个实现多人在线聊天的小项目
思想:首先这个项目分为三个模块(1.登录和注册、2.用户发送消息,服务端接收消息,并且放入数据池中、3.将数据转发给每个用户)
1.登录和注册
(1)注册:将自己的所有信息使用tcp来进行发送给服务器,服务器接收到这个信息之后,将用户信息保存在一个User容器中,然后返回给用户一个用来登陆的id
(2)登录:输入id 和密码,然后将id和密码发送给服务器,然后服务器在User中查找这个id的用户密码是不是和登录时的密码相同,如果相同的话,返回id,如果失败的话返回-1;并且服务器将登陆成功的用户地址信息和id保存在一个在线用户容器online_user中
(3)协议:在登陆和注册的时候,向服务器发送一种包,这种包是模拟HTTP协议来向服务端发送数据包,然后服务器接收包之后,解析这个包的是注册的包还是登录时的包(这个包分为4部分:第一行是方法(用来区分是注册还是登录),第二行是用来记录正文长度的请求报头,第三行是空行,用来区分请求包头和正文的,第四行是正文(用来保存数据的))
2.发送数据(使用udp来发送数据)
(1)客户端首先将输入的数据发送给服务端
(2)服务器接收到这个消息之后,将数据保存在数据池中
3.转发数据
(1)首先服务器从数据池中取出数据
(2)将取出的数据发送给所有的在线用户(也就是发送给容器online_user中的存放的用户)
(3)接收到服务器发送的数据,然后将这个数据打印出来
输入init 0是退出(原理:向服务器发送一个init 0消息之后,服务器私发一条消息给要退出的用户,然后再将在线用户从在线用户容器中将这个用户删除掉)
图解:
ProtocolUtil.hpp
#pragma once
#include <iostream>
#include <string>
#include <sstream>
#include <vector>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include "json/json.h"
#include "Log.hpp"
#define BACKLOG 5
#define MESSAGE_SIZE 1024
class Request{
public:
std::string method;// REGISTER, LOGIN, LOGOUT
std::string content_length;//报文的长度
std::string blank;//空行
std::string text;//正文
public:
Request():blank("\n")
{}
~Request()
{}
};
class Util{
public:
//注册时候的提示的信息
static bool RegisterEnter(std::string &n_,std::string &s_,std::string &passwd)
{
std::cout<<"Please Enter Nick Name: ";
std::cin >>n_;
std::cout<<"Please Enter School: ";
std::cin >>s_;
std::cout<<"Please Enter Passwd: ";
std::cin >>passwd;
std::string again;
std::cout<<"Please Enter Passwd Again: ";
std::cin >> again;
if(passwd == again){
return true;
}
return false;
}
//登录的提示信息
static bool LoginEnter(unsigned int &id,std::string &passwd)
{
std::cout<< "Please Enter Your ID: ";
std::cin>>id;
std::cout<< "Please Enter Your Passwd: ";
std::cin>>passwd;
return true;
}
//序列化
static void Seralize(Json::Value &value,std::string &outString)
{
Json::FastWriter w;
outString = w.write(value);//序列化
}
//反序列化
static void UnSeralize(std::string &inString,Json::Value &value)
{
Json::Reader r;
r.parse(inString,value,false);//反序列化
}
//将数据从int转换为char型
static std::string IntToString(int x)
{
std::stringstream ss;
ss << x;
return ss.str();
}
//将数据从字符型转换为int型
static int StringToInt(std::string &str)
{
int x;
std::stringstream ss(str);
ss >> x;
return x;
}
//接收数据
static void RecvOneLine(int sock,std::string &outString)
{
char c = 'x';
while(c != '\n'){
ssize_t s = recv(sock,&c,1,0);
if(s > 0){
if(c == '\n'){
break;
}
outString.push_back(c);
}
else{
break;
}
}
}
//TCP接收数据,并且解析数据包
static void RecvRequest(int sock,Request &rq)
{
RecvOneLine(sock,rq.method);
RecvOneLine(sock,rq.content_length);
RecvOneLine(sock,rq.blank);
//取出其中的正文的长度
std::string &cl = rq.content_length;//获得这一行字符串 content_length: 55
std::size_t pos = cl.find(": ");//找到“:”的位置准备获得后面的长度
if(std::string::npos == pos){
return;
}
//现:的后面的第二位开始时正文的数据长度
std::string sub = cl.substr(pos+2);
//将取出的正文的长度转换为整形
int size = StringToInt(sub);
char c;//用作接受字符
//知道了正文的长度,接收正文
for(auto i = 0;i < size;i++)
{
//一次接收一个字符
recv(sock,&c,1,0);
//保存在正文中
(rq.text).push_back(c);
}
}
//TCP发送数据
static void SendRequest(int sock,Request &rq)
{
std::string &method_ = rq.method;
std::string &c_l = rq.content_length;
std::string &b = rq.blank;
std::string &text = rq.text;
send(sock,method_.c_str(),method_.size(),0);
send(sock,c_l.c_str(),c_l.size(),0);
send(sock,b.c_str(),b.size(),0);
send(sock,text.c_str(),text.size(),0);
}
//UDP接收数据
static void RecvMessage(int sock, std::string &message,struct sockaddr_in &peer)
{
char msg[MESSAGE_SIZE];//定长的报文
socklen_t len = sizeof(peer);
ssize_t s = recvfrom(sock,msg,sizeof(msg)-1,0,\
(struct sockaddr*)&peer,&len);//接受报文
if(s <= 0){
LOG("recvfrom message error",WARNING);
}
else{
message = msg;
}
}
//UDP发送数据
static void SendMessage(int sock, const std::string &message,struct sockaddr_in &peer)
{
sendto(sock,message.c_str(),message.size(),0,\
(struct sockaddr*)&peer,sizeof(peer));
}
//判断这个用户在不在在线链表中,如果不在的话,将他添加到在线用户链表中
static void addUser(std::vector<std::string> &online,std::string &f)
{
for (auto it = online.begin();it != online.end(); it++)
{
if(*it == f){
return;
}
}
online.push_back(f);
}
static void SubUser(std::vector<std::string> &online,std::string &f)
{
for(auto it = online.begin(); it != online.end(); it++)
{
if(*it == f)
{
online.erase(it);
break;
}
}
}
};
class SocketApi{
public:
static int Socket(int type)
{
int sock = socket(AF_INET,type,0);
if(sock < 0){
LOG("socket error!",ERROR);
exit(2);
}
}
static void Bind(int sock,int port)
{
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_addr.s_addr = htonl(INADDR_ANY);
local.sin_port = htons(port);
if(bind(sock,(struct sockaddr*)&local,sizeof(local)) < 0)
{
LOG("socket error",ERROR);
exit(3);
}
}
static void Listen(int sock)
{
if(listen(sock,BACKLOG) < 0){
LOG("Listen error",ERROR);
exit(4);
}
}
static int Accept(int listen_sock,std::string &out_ip,int &out_port)
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int sock = accept(listen_sock,(struct sockaddr*)&peer,&len);
if(sock < 0){
LOG("accept error",WARNING);
return -1;
}
out_ip = inet_ntoa(peer.sin_addr);
out_port = htons(peer.sin_port);
return sock;
}
static bool Connect(int sock, std::string peer_ip,int port)
{
struct sockaddr_in peer;
peer.sin_family = AF_INET;
peer.sin_addr.s_addr = inet_addr(peer_ip.c_str());
peer.sin_port = htons(port);
if(connect(sock, (struct sockaddr*)&peer,sizeof(peer)) < 0 ){
LOG("connect error",WARNING);
return false;
}
return true;
}
};
Message.h
#pragma once
#include <iostream>
#include <string>
#include "ProtocolUtil.hpp"
#include "json/json.h"
#define NORMAL_TYPE 0
#define LOGIN_TYPE 1
class Message{
private:
std::string nick_name;
std::string school;
std::string text;
unsigned int id;
unsigned int type;
public:
Message()
{}
Message(const std::string &n,const std::string &s,const std::string &t,const unsigned int &id, unsigned int type_ = NORMAL_TYPE)
: nick_name(n),
school(s),
text(t),
id(id),
type(type_)
{}
//构建正文
void ToSendString(std::string &sendString)
{
Json::Value root;
root["name"] = nick_name;
root["school"] = school;
root["text"] = text;
root["id"] = id;
root["type"] = type;
Util::Seralize(root,sendString);
}
//提取正文中数据
void ToRecvValue(std::string &recvString)
{
Json::Value root;
Util::UnSeralize(recvString,root);
nick_name = root["name"].asString();
school = root["school"].asString();
text = root["text"].asString();
id = root["id"].asInt();
type = root["type"].asInt();
}
const std::string &NickName()
{
return nick_name;
}
const std::string &School()
{
return school;
}
const std::string &Text()
{
return text;
}
const unsigned int &Id()
{
return id;
}
const unsigned int &Type()
{
return type;
}
~Message()
{
}
};
Log.h
#pragma once
#include <iostream>
#include <string>
#define NORMAL 0
#define WARNING 1
#define ERROR 2
const char* log_level[]={
"Normal",
"Warning",
"Error",
NULL,
};
void Log(std::string msg,int level,std::string file,int line)
{
std::cout<<'['<<msg<<']'<<'['<<log_level[level]<<']'<<":"<<\
file<<":"<<line<<std::endl;
}
#define LOG(msg,level) Log(msg,level,__FILE__,__LINE__)
UserManager.hpp
#pragma once
#include <iostream>
#include <string>
#include <unordered_map>
#include <pthread.h>
//存放的是个人的信息类
class User{
private:
std::string nick_name;
std::string school;
std::string passwd;
public:
User()
{
}
User(const std::string &n,const std::string &s,\
const std::string pwd):
nick_name(n),
school(s),
passwd(pwd)
{}
bool IsPasswdTrue(const std::string &passwd_)
{
return passwd == passwd_? true : false;
}
std::string &GetNickName()
{
return nick_name;
}
std::string &GetSchool()
{
return school;
}
~User()
{}
};
//用户管理类
class UserManager{
private:
unsigned int assgin_id;
//存放的是注册的用户的信息
std::unordered_map<unsigned int,User> users;
//存放的是在线的用户
std::unordered_map<unsigned int,struct sockaddr_in> online_users;
pthread_mutex_t lock;
void Lock()
{
pthread_mutex_lock(&lock);
}
void UnLock()
{
pthread_mutex_unlock(&lock);
}
public:
UserManager():assgin_id(10000)
{
pthread_mutex_init(&lock,NULL);
}
//注册的用户信息插入到用户表中
unsigned int Insert(const std::string &n,\
const std::string &s,const std::string &p)
{
Lock();//加锁 防止多个用户同时进行注册操作导致出现错误 保持一致性
unsigned int id = assgin_id++;//注册的id模式
User u(n,s,p);
if(users.find(id) == users.end()){
users.insert({id,u});//假如用户中没有对应的id 那么就将该用户数据插入到用户中
UnLock();
return id;
}
UnLock();
return 1;
}
//登录检测
unsigned int Check(const int &id, const std::string &passwd)
{
Lock();
auto user = users.find(id);
if(user != users.end()){//是否能在存储的用户信息中找到相应id
User &u = user->second;//取出密码
if(u.IsPasswdTrue(passwd)){//判断密码时候正确
UnLock();
return id;
}
}
UnLock();
return 2;
}
//获取用户的信息
void GetUserInfo(const unsigned int &id, std::string &name_, std::string &school_)
{
Lock();
name_ = users[id].GetNickName();
school_ = users[id].GetSchool();
UnLock();
}
//将新登录在线的用户存放在用户列表中
void AddOnLineUser(unsigned int id, struct sockaddr_in &peer)
{
Lock();
auto it = online_users.find(id);
if(it == online_users.end()){
//假如用户在线列表中没有该用户则将该用户加入到用户列表中
online_users.insert({id,peer});
}
UnLock();
}
//将一个用户从用户列表中删除
void SubOnlineUser(unsigned int id)
{
Lock();
online_users.erase(id);
UnLock();
}
//返回在线用户列表
std::unordered_map<unsigned int,struct sockaddr_in> OnLineUser()
{
Lock();
auto online = online_users;
UnLock();
return online;
}
~UserManager()
{
//销毁锁
pthread_mutex_destroy(&lock);
}
};
Window.h
#pragma
//使用ncurses数据库来创建多个窗口
//1.欢迎窗口
//2.消息窗口
//3.在线用户列表窗口
//4.输入窗口
#include <iostream>
#include <string>
#include <string.h>
#include <ncurses.h>
#include <vector>
#include <unistd.h>
#include <cstring>
#include <pthread.h>
class Window{
public:
WINDOW *header;
WINDOW *output;
WINDOW *online;
WINDOW *input;
pthread_mutex_t lock;
public:
Window()
{
initscr();
curs_set(0);//隐藏光标
pthread_mutex_init(&lock,NULL);
}
//刷新串口
void SafeWrefresh(WINDOW *w)
{
pthread_mutex_lock(&lock);//加锁
wrefresh(w);//刷新窗口
pthread_mutex_unlock(&lock);//解锁
}
//欢迎条目的窗口
void DrawHeader()
{
int h = LINES*0.2;
int w = COLS;
int y = 0;
int x = 0;
header = newwin(h,w,y,x);
box(header,0,0);
SafeWrefresh(header);//因为显示器是一个临界资源 多个线程控制的多个窗口同时刷新到显示器上可能会导致窗口变花
}
//信息打印的窗口
void DrawOutput()
{
int h = LINES*0.6;
int w = COLS*0.75;
int y = LINES*0.2;
int x = 0;
output = newwin(h,w,y,x);
box(output,0,0);
SafeWrefresh(output);
}
//在线用户的窗口
void DrawOnline()
{
int h = LINES*0.6;
int w = COLS*0.25;
int y = LINES*0.2;
int x = COLS*0.75;
online = newwin(h,w,y,x);
box(online,0,0);
SafeWrefresh(online);
}
//输入窗口
void DrawInput()
{
int h = LINES*0.2;
int w = COLS;
int y = LINES*0.8;
int x = 0;
input = newwin(h,w,y,x);
box(input,0,0);
std::string tips = "Please Enter# ";
PutStringToWin(input,2,2,tips);
SafeWrefresh(input);
}
void GetStringFromInput(unsigned int id,std::string &message)
{
char buffer[1024];
memset(buffer,0,sizeof(buffer));
//获取从输入窗口输入的数据
wgetnstr(input,buffer,sizeof(buffer));
message = buffer;
//将旧的窗口释放掉
delwin(input);
//重新绘制一个窗口
DrawInput();
}
void PutMessageToOutput(const std::string &message)
{
static int line = 1;
int y,x;
//获取窗口的高度和宽度
getmaxyx(output,y,x);
if(line > y-2){
delwin(output);
DrawOutput();
line = 1;
}
PutStringToWin(output,line++,2,message);
}
//将数据打印到显示框中
void PutStringToWin(WINDOW *w,int y,int x,const std::string &message)
{
mvwaddstr(w,y,x,message.c_str());
SafeWrefresh(w);
}
//将在线的用户打印到显示框中
void PutUserToOnline(std::vector<std::string> &online_user)
{
delwin(online);
DrawOnline();
int size = online_user.size();
for(auto i = 0; i < size; i++)
{
PutStringToWin(online,i+1,2,online_user[i]);
}
SafeWrefresh(online);
}
//打印欢迎框
void Welcome()
{
const std::string welcome = "welcome to my chat system!";
int num = 1;
int x,y;
int dir = 0;
for( ; ; )
{
DrawHeader();
getmaxyx(header,y,x);
PutStringToWin(header,y/2,num,welcome);
if(num > x - welcome.size() - 3){
dir = 1;
}
if(num <= 1){
dir = 0;
}
if(dir == 0){
num++;
}else{
num--;
}
usleep(100000);
delwin(header);
}
}
void Delete()
{
delwin(header);
delwin(online);
delwin(output);
delwin(input);
endwin();
}
~Window()
{
//释放窗口
endwin();
//销毁锁
pthread_mutex_destroy(&lock);
}
};
DataPool.hpp
//存放数据的数据池
#pragma once
#include <iostream>
#include <vector>
#include <semaphore.h>
#include <string>
class DataPool{
private:
std::vector<std::string> pool;
int cap;
sem_t data_sem;
sem_t blank_sem;
int product_step;
int consume_step;
public:
DataPool(int cap_ = 512):
cap(cap_),
pool(cap_)
{
//刚开始的时候,数据池中还没有数据,所以为0
sem_init(&data_sem,0,0);
//刚开始的时候,数据池中可以存放数据的大小为数据池的容量
sem_init(&blank_sem,0,cap);
product_step = 0;
consume_step = 0;
}
void PutMessage(const std::string &msg)
{
//等待数据池中有空位置
sem_wait(&blank_sem);
//将数据放入数据池中
pool[product_step] = msg;
//标记向后移动
product_step++;
product_step %= cap;
//代表存放的数据的个数+1
sem_post(&data_sem);
}
void GetMessage(std::string &msg)
{
//如果数据池中有数据话
sem_wait(&data_sem);
//从数据池中拿数据
msg = pool[consume_step];
//向后移动
consume_step++;
consume_step %= cap;
//增加一个空位置
sem_post(&blank_sem);
}
~DataPool()
{
//释放信号量
sem_destroy(&data_sem);
sem_destroy(&blank_sem);
}
};
ChatServer.hpp
#pragma once
#include <iostream>
#include <pthread.h>
#include "ProtocolUtil.hpp"
#include "UserManager.hpp"
#include "Log.hpp"
#include "Window.hpp"
#include "DataPool.hpp"
#include "Message.hpp"
//服务器类的声明
class ChatServer;
class Param {
public:
ChatServer *sp;
int sock;
std::string ip;
int port;
public:
Param(ChatServer *sp_,int &sock_,const std::string &ip_,const int &port_):
sp(sp_),
sock(sock_),
ip(ip_),
port(port_)
{}
~Param()
{}
};
class ChatServer{
private:
int tcp_listen_sock;
int tcp_port;
int udp_work_sock;
int udp_port;
UserManager um;
DataPool pool;
public:
//构造函数
ChatServer(int tcp_port_ = 8080,int udp_port_ = 8888):
tcp_port(tcp_port_),
tcp_listen_sock(-1),
udp_port(udp_port_),
udp_work_sock(-1)
{}
//初始化套接字
//tcp的话需要创建、绑定、监听
//udp的话只需创建和绑定
void InitServer()
{
tcp_listen_sock = SocketApi::Socket(SOCK_STREAM);//创建套接字
udp_work_sock = SocketApi::Socket(SOCK_DGRAM);
SocketApi::Bind(tcp_listen_sock,tcp_port);//绑定
SocketApi::Bind(udp_work_sock,udp_port);
SocketApi::Listen(tcp_listen_sock);
}
//将注册的用户的信息添加到用户列表中
unsigned int RegisterUser(const std::string &name,const std::string &school,const std::string &passwd)
{
return um.Insert(name,school,passwd);//注册的用户的基本信息
}
//检测登陆的用户的id和密码是否正确
unsigned int LoginUser(const unsigned int &id, const std::string &passwd,const std::string &ip,int port)
{
return um.Check(id,passwd);
}
void SubUser(unsigned int id)
{
um.SubOnlineUser(id);
}
//接收数据
void Product()
{
std::string message;
struct sockaddr_in peer;
//接收客户端发送来的消息
Util::RecvMessage(udp_work_sock,message,peer);
std::cout<< "debug: recv message: "<<message<<std::endl;
Message ms;
ms.ToRecvValue(message);
if(strcmp(ms.Text().c_str(),"init 0") == 0)
{
std::string s("quit");
Message m(ms.NickName(),ms.School(),s,ms.Id());
std::string send;
m.ToSendString(send);
sendto(udp_work_sock,send.c_str(),send.size(),0,(struct sockaddr*)&peer,sizeof(peer));
um.SubOnlineUser(ms.Id());
}
//如果收到的数据不为空的话,
if(!message.empty())
{
Message m;
//将这个接收到的信息反序列化,将其中的信息提取出来
m.ToRecvValue(message);
if(m.Type() == LOGIN_TYPE)
{
//如果这个用户是刚登陆的话,加这个用户添加到在线用户表中
um.AddOnLineUser(m.Id(),peer);
std::string name_,school_;
//获取登录用户的信息
um.GetUserInfo(m.Id(),name_,school_);
//创建一条新的信息
Message new_msg(name_,school_,m.Text(),m.Id(),m.Type());
//将信息序列化
new_msg.ToSendString(message);
}
}
//将数据放到数据池中
pool.PutMessage(message);
}
//从数据池中拿数据发送
void Consume()
{
std::string message;
//新创建一条消息
pool.GetMessage(message);
std::cout<<"debug: send massage: "<<message<<std::endl;
auto online = um.OnLineUser();//获取用户在线列表
for(auto it = online.begin(); it != online.end(); it++){
Util::SendMessage(udp_work_sock,message,it->second);//通过udp向在线列表中的用户发送信息
}
}
//对客户端的进行处理
static void *HandlerRequest(void* arg)
{
Param *p = (Param*)arg;
//首先将信息进行分开保存
int sock = p->sock;
ChatServer *sp = p->sp;
std::string ip = p->ip;
int port = p->port;
delete p;
//线程分离,因为我们不关心他的退出状态,所以直接线程分离,也就不需要线程等待
pthread_detach(pthread_self());
//开始读取客户端发送的消息
Request rq;
Util::RecvRequest(sock,rq);
Json::Value root;
//将这个消息打印出来
LOG(rq.text,NORMAL);
//将这个接收到的字符串反序列化
Util::UnSeralize(rq.text,root);
//如果他是一个注册的请求的话
if(rq.method == "REGISTER"){
//将用户的个人信息保存起来
std::string name = root["name"].asString();
std::string school = root["school"].asString();
std::string passwd = root["passwd"].asString();
//将数据放入到用户表中,如果这个是新的用户的话,给他返回一个登录的id
unsigned int id = sp->RegisterUser(name,school,passwd);//注册的id
send(sock,&id,sizeof(id),0);//返回注册的id
}
//如果这是一个登录的请求的话
//取出信息中的id和密码进行判断,如果id和密码都正确的话,将这个用户放到在线用户表中
else if(rq.method == "LOGIN"){
//取出id和密码
unsigned int id = root["id"].asInt();
std::string passwd = root["passwd"].asString();
//检验,将用户放入在线列表中,如果id或者密码错误的话,返回一个-1,如果没错的话返回他的id
unsigned int ret = sp->LoginUser(id,passwd,ip,port);
//将检测的返回值发送给客户端,好让客户端检测是否发送成功
send(sock,&ret,sizeof(ret),0);
}
//退出
//将退出的用户从在线用户表中去除
//返回退出
else{
// Window w;
unsigned int id = root["id"].asInt();
sp->SubUser(id);
// w.PutUserToOnline(sp->um.OnLineUser());
}
close(sock);
}
//服务器开始运行
void Start()
{
std::string ip;
int port;
for(;;){
//接收由客户端发送的链接
int sock = SocketApi::Accept(tcp_listen_sock,ip,port);
if(sock > 0){
std::cout <<"get a new client"<<ip<<":"<<port<<std::endl;
Param *p = new Param(this,sock,ip,port);
pthread_t tid;
//新创建一个线程去执行这个客户端这个链接的处理
pthread_create(&tid,NULL,HandlerRequest,p);
}
}
}
~ChatServer()
{}
};
ChatClient.hpp
#pragma once
#include <signal.h>
#include <stdio.h>
#include <iostream>
#include <string>
#include <vector>
#include "ProtocolUtil.hpp"
#include "Message.hpp"
#include "Window.hpp"
#include <pthread.h>
#include <sys/syscall.h>
#include <sys/types.h>
#define TCP_PORT 8080
#define UDP_PORT 8888
class ChatClient;
void Menu(int &s)
{
std::cout<<"################################################"<<std::endl;
std::cout<<"#### 1. Register 2.Login #####"<<std::endl;
std::cout<<"#### 3.Exit #####"<<std::endl;
std::cout<<"################################################"<<std::endl;
std::cout<<"Please Select:>";
std::cin>>s;
}
void Select(std::string ip);
std::string Name;
struct ParamPair{
Window *wp;
ChatClient *cp;
};
class ChatClient{
private:
int tcp_sock;//tcp套接字
int udp_sock;//udp套接字
std::string peer_ip;
std::string passwd;
struct sockaddr_in server;
public:
std::string nick_name;
std::string school;
unsigned int id;
public:
//构造函数
//初始化信息
ChatClient()
{}
ChatClient(std::string ip_):peer_ip(ip_)
{
id = 0;
tcp_sock = -1;
udp_sock = -1;
server.sin_family = AF_INET;
server.sin_port = htons(UDP_PORT);
server.sin_addr.s_addr = inet_addr(peer_ip.c_str());
}
//初始化客户端
void InitClient()
{
//创建一个sock套接字
udp_sock = SocketApi::Socket(SOCK_DGRAM);
}
//建立连接
bool ConnectServer()
{
//创建套接字
//建立链接
tcp_sock = SocketApi::Socket(SOCK_STREAM);
return SocketApi::Connect(tcp_sock,peer_ip,TCP_PORT);
}
//注册
//首先输入用户的信息
//然后将信息序列化
//将序列化之后的信息发送给服务器
//服务器接收到之后返回一个id
//检查id是否合理
bool Register()
{
if( Util::RegisterEnter(nick_name,school,passwd) && ConnectServer()){
Request rq;
Name = nick_name;
rq.method = "REGISTER\n";
Json::Value value;
value["name"] = nick_name;
value["school"] = school;
value["passwd"] = passwd;
//将信息序列化
Util::Seralize(value,rq.text);
rq.content_length = "Content_Length: ";
rq.content_length += Util::IntToString((rq.text).size());
rq.content_length += "\n";
//向服务器发送注册的请求
Util::SendRequest(tcp_sock,rq);
//接收服务器返回的信息请求
recv(tcp_sock,&id,sizeof(id),0);
bool res = false;
//检测id是否合理
if(id >= 10000){
res = true;
std::cout<<"Register Success! Your Login ID Is: "<<id<<std::endl;
}
else{
std::cout<< "Register Failed! Code is : "<<id<<std::endl;
}
close(tcp_sock);
return res;
}
}
//登录
//输入id和密码,然后将这个信息序列化发送给服务器
//如果登陆成功的话,在向服务器发送一条消息,让服务器将这个用户保存在在线用户表中
bool Login()
{ //登陆成功后 连接服务器 发送请求
if( Util::LoginEnter(id,passwd) && ConnectServer()){
Request rq;
rq.method = "LOGIN\n";
Json::Value value;
value["id"] = id;
value["passwd"] = passwd;
//序列化
Util::Seralize(value,rq.text);
rq.content_length = "Content_Length: ";
rq.content_length += Util::IntToString((rq.text).size());
rq.content_length += "\n";
//向服务器发送请求
Util::SendRequest(tcp_sock,rq);
//接收请求
unsigned int ret = 0;
bool res = false;
//接收服务器的返回值
recv(tcp_sock,&ret,sizeof(ret),0);
if(ret >= 10000){
//如果登陆成功的话,发送一条自动消息,将这个用户添加到在线用户表中
res = true;
std::string name_ = "None";
std::string school_ = "None";
std::string text_ = "I am Login! talk with me...";
unsigned int type_ = LOGIN_TYPE;
unsigned int id_ = ret;
//首先创建一个信息对象,将要发送的信息添加进去
Message m(name_,school_,text_,id_,type_);
std::string sendString;
//将要发送的消息序列化
m.ToSendString(sendString);
//使用udp进行数据的发送
UdpSend(sendString);
std::cout<<"Login Success! Your Login ID Is: "<<ret<<std::endl;
}
else{
std::cout<< "Login Failed! Code is : "<<ret<<std::endl;
}
close(tcp_sock);
return res;
}
}
//发消息
void UdpSend(const std::string &message)
{
Util::SendMessage(udp_sock,message,server);
}
//接收数据
void UdpRecv(std::string &message)
{
struct sockaddr_in peer;
Util::RecvMessage(udp_sock,message,peer);
}
//欢迎条目
static void *Welcome(void *arg)
{
pthread_detach(pthread_self());//进行线程分离
Window *wp = (Window*)arg;
wp->Welcome();//调用欢迎条目的函数
}
//输入框
static void *Input(void *arg)
{
pthread_detach(pthread_self());
struct ParamPair *pptr = (struct ParamPair*)arg;
Window *wp = pptr->wp;
ChatClient *cp = pptr->cp;
wp->DrawInput();
std::string text;
for(;;){
wp->GetStringFromInput(cp->id,text);//从input窗口中获得字符串
Message msg(cp->nick_name,cp->school,text,cp->id);
std::string sendString;
//将信息序列化
msg.ToSendString(sendString);
cp->UdpSend(sendString);//发送字符串
}
}
//聊天
void Chat()
{
Window w;
pthread_t h,m,l;
struct ParamPair pp = {&w,this};
//创建一个去执行欢迎框的线程
pthread_create(&h,NULL,Welcome,&w);
//创建一个去执行输入的线程
pthread_create(&l,NULL,Input,&pp);
//创建一个输出框
w.DrawOutput();
//创建一个在线用户表
w.DrawOnline();
std::string showString;
std::vector<std::string> online;
//首先接收数据
//将数据反序列化
//将数据显示到输出显示框
//如果这个用户不在用户输出框中的话,将这个用户打印出来
for(;;){
std::string recvString;
Message msg;
//接收数据
UdpRecv(recvString);
int i = 0;
//将数据反序列化
msg.ToRecvValue(recvString);
if(strcmp(msg.Text().c_str(),"init 0") == 0)
{
i = 1;
}
if(strcmp(msg.Text().c_str(),"quit") == 0)
{
return ;
}
//如果这个用户在在线用户表中的话,获取他的姓名和学校
//下面输出消息的时候需要打印出来
if(msg.Id() == id && msg.Type() == LOGIN_TYPE)
{
nick_name = msg.NickName();
school = msg.School();
}
//信息输出的格式的修饰
showString = msg.NickName();
showString += "-";
showString += msg.School();
std::string f = showString;//zhangsan-qinghua
if(i == 0)
{
Util::addUser(online,f);
w.PutUserToOnline(online);
}
else
{
Util::SubUser(online,f);
w.PutUserToOnline(online);
}
showString +="# ";
showString += msg.Text();
//将数据放到输出框
w.PutMessageToOutput(showString);
}
}
//退出
void LoginOut()
{
// 构建loginout报文
Request rq;
rq.method = "LOGOUT\n";
rq.content_length = "Content_Length: ";
rq.content_length += "0";
rq.content_length += "\n";
Json::Value value;
value["id"] = id;
//序列化
Util::Seralize(value,rq.text);
rq.content_length = "Content_Length: ";
rq.content_length += Util::IntToString((rq.text).size());
//发送数据到服务端
Util::SendRequest(tcp_sock,rq);
}
~ChatClient()
{
}
};
void Select(std::string ip)
{
ChatClient *cp = new ChatClient(ip);
cp->InitClient();//xian chushihua kehuduan
int select = 0;
while(1){
Menu(select);
switch(select){
case 1://Register
cp->Register();
break;
case 2://Login
if(cp->Login()){
cp->Chat();
}else{
std::cout<<"Login Failed!"<<std::endl;
}
return 0;
case 3://Exit
exit(0);
break;
default:
exit(1);
break;
}
}
if(cp->ConnectServer()){
std::cout<<"connect success"<<std::endl;
}
}
ChatServer.cc
#include <iostream>
#include "ChatServer.hpp"
// ./ChatServer tcp_port udp_port
static void Usage(std::string proc)
{
std::cout<<"Usage: "<<proc<<" tcp_port udp_port"<<std::endl;
}
void *RunProduct(void *arg)
{
pthread_detach(pthread_self());
ChatServer *sp = (ChatServer*)arg;
for(;;){
sp->Product();//生产
}
}
void *RunConsume(void *arg)
{
pthread_detach(pthread_self());
ChatServer *sp = (ChatServer*)arg;
for(;;){
sp->Consume();//消费
}
}
int main(int argc, char *argv[])
{
if(argc != 3){
Usage(argv[0]);
exit(1);
}
int tcp_port = atoi(argv[1]);
int udp_port = atoi(argv[2]);
ChatServer *sp = new ChatServer(tcp_port,udp_port);
sp->InitServer();
pthread_t c,p;
//接收数据并将数据放入数据池中
pthread_create(&p,NULL,RunProduct,(void*)sp);
//将数据池中的数据发送给在线用户
pthread_create(&c,NULL,RunConsume,(void*)sp);
sp->Start();
return 0;
}
ChatClient.cc
#include <iostream>
#include "ChatClient.hpp"
static void Usage(std::string proc)
{
std::cout<<"Usage: "<<proc<<" peer_ip"<<std::endl;
}
// ./ChatClient ip
int main(int argc, char *argv[])
{
if(argc != 2){
Usage(argv[0]);
exit(1);
}
Select(argv[1]);
return 0;
}