畅所语聊

这是一个实现多人在线聊天的小项目

思想:首先这个项目分为三个模块(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;
}

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值