该项目使用的技术:
1.C++STL
2.生产者消费者模型
3.多线程技术,线程的同步与互斥
4.网络编程
5.开源jsoncpp库
6.ncurses库
7.自定义协议,登录注册认证
实现的基本想法:项目分可分为3个模块:
1.登录、注册和退出 2.用户发送消息,服务器接收 3.服务端的数据转发
1.登录和注册(TCP)
注册:将自己的注册信息通过TCP发给服务器,服务器接收下来并保存该用户的信息在用户信息管理的容器中,并且返回给用户的一个登录ID。
登录:输入ID和密码,然后将其发送给服务器,经过服务器验证并通过后,将用户的ID和地址信息保存在用户列表容器中,否则重新登录。
请求中的协议:当使用登录和注册功能的时候,向用户发送一个类似于简单HTTP协议的一个数据报,由服务器来判断是登录还是注册。包的组成:第一行为使用的方法(登录还是注册),第二行记录正文的长度,第三行为空行,区分报头和正文,第四行是正文。
2.发送数据(UDP)
(1)客户端将用户输入的数据发送给服务器
(2)服务器接收消息之后放入数据池中
3.数据转发
(1)服务器从数据池中取出数据
(2)取出用户在线列表,通过遍历将数据发送给所有在线洪湖
(3)客户端接收到服务器发送的数据,将其输出到屏幕上
注:当输入“ESC“的时候,服务器会在用户在线列表中删除这个用户,这个用户不会在收到消息,并向其他用户发送一条下线消息提醒。
#pragma once
#include
#include
#include
#include "ProtocolUtil.hpp"
#include "Message.hpp"
#include "Window.hpp"
#include
#define TCP_PORT 8080
#define UDP_PORT 8888
class ChatClient;
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(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()
{
udp_sock = SocketApi::Socket(SOCK_DGRAM);
}
bool ConnectServer()
{
tcp_sock = SocketApi::Socket(SOCK_STREAM);
return SocketApi::Connect(tcp_sock,peer_ip,TCP_PORT);
}
bool Register()//依据自定义协议和json串的序列化和反序列化
{ //注册完成以后连接服务器 发送请求
if( Util::RegisterEnter(nick_name,school,passwd) && ConnectServer()){
Request rq;
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";
//向TCP发送请求
Util::SendRequest(tcp_sock,rq);
//接收请求
recv(tcp_sock,&id,sizeof(id),0);//返回注册成功后的id
bool res = false;
if(id >= 10000){//验证id是否符合标准
res = true;
std::cout<<"Register Success! Your Login ID Is: "<= 10000){//验证id是否符合标准
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);
UdpSend(sendString);
std::cout<<"Login Success! Your Login ID Is: "<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(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 recvString;
std::string showString;
std::vector online;
for(;;){
Message msg;
UdpRecv(recvString);
msg.ToRecvValue(recvString);//将收到的消息反序列化
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;//张三-北大
if(msg.Type() == LOGINOUT_TYPE)
{
Util::delUser(online,f);
}
else
Util::addUser(online,f);
showString +="# ";
showString += msg.Text();
w.PutMessageToOutput(showString);//向output窗口放数据
w.PutUserToOnline(online);
}
}
~ChatClient()
{
}
};
#include
#include "ChatClient.hpp"
static void Usage(std::string proc)
{
std::cout<<"Usage: "<";
std::cin>>s;
}
// ./ChatClient ip
int main(int argc, char *argv[])
{
if(argc != 2){
Usage(argv[0]);
exit(1);
}
ChatClient *cp = new ChatClient(argv[1]);
cp->InitClient();//初始化客户端
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!"<ConnectServer()){
std::cout<<"connect success"<
#pragma once
#include
#include
#include "ProtocolUtil.hpp"
#include "UserManager.hpp"
#include "Log.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)
{}
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);//注册的用户的基本信息
}
unsigned int LoginUser(const unsigned int &id, const std::string &passwd,const std::string &ip,int port)
{
return um.Check(id,passwd);
}
//UDP
void Product()
{
std::string message;
struct sockaddr_in peer;
Util::RecvMessage(udp_work_sock,message,peer);//通过udp接收一个消息
std::cout<< "debug: recv message: "<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();//作为字符串 asInt 作为整形
std::string school = root["school"].asString();
std::string passwd = root["passwd"].asString();
unsigned int id = sp->RegisterUser(name,school,passwd);//注册的id
send(sock,&id,sizeof(id),0);//返回注册的id
}else if(rq.method == "LOGIN"){
unsigned int id = root["id"].asInt();//取出id
std::string passwd = root["passwd"].asString();//取出密码
//检验,将用户放入在线列表中
unsigned int ret = sp->LoginUser(id,passwd,ip,port);
send(sock,&ret,sizeof(ret),0);
}
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"<
#include
#include "ChatServer.hpp"
static void Usage(std::string proc)
{
std::cout<<"Usage: "<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;
}
#pragma once
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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()
{}
};//定制一个简单的http报文
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);//反序列化
}
static std::string IntToString(int x)
{
std::stringstream ss;
ss << x;
return ss.str();
}
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::npos是一个常数 它等于size_type 类型
//可表示的最大数字,表示一个不存在的位置
}
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);//插入正文中
}
}
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);
}
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;
}
}
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 &online,std::string &f)
{
for (auto it = online.begin();it != online.end(); it++)
{
if(*it == f){
return;
}
}
online.push_back(f);
}
static void delUser(std::vector &online,std::string &f)
{
for (auto it = online.begin();it != online.end();)
{
if(*it == f){
it = online.erase(it);
}
else
++it;
}
return;
}
};
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(const int &sock, std::string peer_ip,const 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;
}
};
#pragma once
#include
#include
#include
#include
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;//用户申请的id
std::unordered_map users;//用户
std::unordered_map 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 DelOnLineUser(unsigned int id)
{
Lock();
online_users.erase(id);
UnLock();
}
std::unordered_map OnLineUser()
{
Lock();
auto online = online_users;
UnLock();
return online;
}
~UserManager()
{
pthread_mutex_destroy(&lock);
}
};
#pragma once
#include
#include
#include "ProtocolUtil.hpp"
#include "json/json.h"
#define NORMAL_TYPE 0
#define LOGIN_TYPE 1
#define LOGINOUT_TYPE 2
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()
{
}
};
#pragma once
#include
#include
#include
#include
//只有空 或者满的情况才能让生产者和消费者的下标一样
//消费者不能超过生产者
//生产者不能超过消费者一个圈
class DataPool{
private:
std::vector 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_)
{
sem_init(&data_sem,0,0);//初始化data_sem 这些数据是消费者需要关心的,所以初始化为0
sem_init(&blank_sem,0,cap);//初始化blank_sem 这些空格子是生产者需要关心的,所以设置成最大容量
product_step = 0;
consume_step = 0;
}
void PutMessage(const std::string &msg)
{
sem_wait(&blank_sem);//对blank_sem进行P操作
pool[product_step] = msg;//向pool中放入数据
product_step++; //向后面走
product_step %= cap;//不能越界 进行循环
sem_post(&data_sem);//对data_sem进行V操作
}
void GetMessage(std::string &msg)
{
sem_wait(&data_sem);//对data_sem进行P操作
msg = pool[consume_step];//从pool中获得数据
consume_step++;
consume_step %= cap;
sem_post(&blank_sem);//对blank_sem进行V操作
}
~DataPool()
{
sem_destroy(&data_sem);//释放信号量
sem_destroy(&blank_sem);
}
};
#pragma once
#include
#include
#include
#include
#include
#include
#include
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(std::string &message)
{
char buffer[1024];
memset(buffer,0,sizeof(buffer));
wgetnstr(input,buffer,sizeof(buffer));//在input窗口中输入
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 &online_user)
{
int size = online_user.size();
delwin(online);
DrawOnline();
for(auto i = 0; i < size; i++)
{
PutStringToWin(online,i+1,2,online_user[i]);
}
}
void Welcome()
{
const std::string welcome = "welcome to my chat system!";
int num = 1;
int x,y;
int dir = 0;//从左往右 否则等于1
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);
}
}
~Window()
{
endwin();//释放窗口
pthread_mutex_destroy(&lock);//销毁锁
}
};
#pragma once
#include
#include
#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<<'['<