Linux项目--群聊系统

  • 项目名称:群聊系统

  • 系统功能:该系统可以实现多人聊天室的功能,类似于我们常用的交流工具--qq,微信等。当客户端有一个用户发送消息时,其他在线用户都可以收到。同样当自己发送一条消息时,其他在线用户也是可以收到自己发送的消息内容的。
  • 系统简介

       1、客户端从标准输入读出数据之后,将数据进行序列化,发送至网络

       2、服务器端使用多线程+(生产者、消费者模型)

       其中生产者读取网络中传来的数据,将数据信息放到数据池中,再将用户信息加入好友列表中

      消费者从数据池中读取数据,并将信息广播给所有在线的好友用户

  • 技术平台

       开发环境:centos7(64位操作系统)

       编程语言:C语言、C++

       序列化和反序列化工具:jsoncpp

       进行窗口设计的框架:ncurse,ncurses

       操作系统知识:生产者和消费者模型、信号量机制、多线程、socket套接字网络编程

  • 系统的模块划分

 client模块:从标准输入读取用户数据信息,将字符串序列化,发送给服务器;将接收到的数据进行反序列化,输出到标准输出

  comm模块:使用到了json库,可以实现数据的序列化和反序列化

  server模块:收到用户发送的字符串后,将用户信息存储到用户列表中,将数据存储到数据池中,再将数据广播给所有在线用户。服务器端要转发数据给客户端,所以需要维护一张用户列表,该系统使用map实现,使用用户的ip作为key值,使用sockaddr_in作为value值

 data_pool模块:服务器端维持数据池,从数据池中存取数据,数据池实际上是基于生产者和消费者模型的环形队列

 window模块:实现客户端的界面,使用到了ncurse库

 lib模块:提供第三方库模块

 conf模块:提供server的配置文件

 plugin模块:启停服务器的脚本文件

 在通信过程中实现对数据进行序列化和反序列化的原因?

因为不能将客户端输入的内容直接发送给服务器端,是因为用户比较多时,服务器端无法区分消息是由哪个用户所发的, 因此我们需要给客户端发送的每条消息都附加上当前用户的信息。所以服务器端收到的来自客户端发送的消息(是由用户信息和输入框输入的消息拼接而成的)

其次用户退出时,服务器要将该用户从用户列表中删除,因此在拼接信息时增加一个cmd字段,来表示客户端的状态

//udp_client.h文件
#ifndef _udp_client_H_ //ifdef条件编译,确保头文件的编译只进行一次,避免重复编译
#define _udp_client_H_


#include <iostream>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <map>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <fcntl.h>
#include "log.h"


class udp_client
{
public:
    //客户端构造函数声明(传入参数:ip地址和端口号)
	udp_client(const std::string& _ip,int port);
    //初始化的成员函数声明
	int init_client();
    //接收消息的成员函数声明
	int recv_msg(std::string& out);
    //发送消息的成员函数声明
	int send_msg(const std::string& in);
    //将用户加入在线列表声明
	int add_online_user(struct sockaddr_in *client);
    //析构函数声明
	~udp_client();
private:
	udp_client(const udp_client&);
private:
    //私有数据成员,存放建立套接字的ip地址、端口号,创建的套接字文件的文件描述符
	std::string ip;
	int port;
	int sock;
};
#endif
//udp_client.cpp文件
#include "udp_client.h"

//定义构造函数,利用传入的参数(_ip,_port)进行赋值,不做其他操作
udp_client::udp_client(const std::string& _ip,int _port):ip(_ip),port(_port),sock(-1)
{}
//初始化成员函数定义:创建套接字文件,返回该文件的文件描述符
int udp_client::init_client()
{
	sock = socket(AF_INET,SOCK_DGRAM,0);
	if(sock < 0)
	{
		write_log("socket error",FATAL);
		return -1;
	}

}

//从server端接收数据,开辟缓冲区buf,存放读取到的内容,使用recvfrom函数读取消息,带返回作用的参数out指向buf的首地址
int udp_client::recv_msg( std::string& out)
{
	char buf[1024];
	struct sockaddr_in peer;
	socklen_t len = sizeof(peer);
	int ret = recvfrom(sock,buf,sizeof(buf)-1,0,\
			(struct sockaddr*)&peer,&len);
	if(ret > 0)
	{
		buf[ret] = 0;
		out = buf;	
		return 0;
	}
	return -1;
}

//发送数据,定义存放ipv4套接字类型的结构体server,存放服务器端套接字相关信息之前,要先使用htons()将端口号由整型转化为网络字节序列,使用inet_addr()将ip字符串转化为整型形式
int udp_client::send_msg(const std::string & in)
{
	struct sockaddr_in server;
	server.sin_family =AF_INET;
	server.sin_port = htons(port);
	server.sin_addr.s_addr = inet_addr(ip.c_str());
    //使用sendto函数(参数:1.是建立起连接的套接字文件描述符,2.传送的消息3.希望传送到的网络地址)
	int ret = sendto(sock,in.c_str(),in.size(),0,\
			(struct sockaddr*)&server,sizeof(server));
	if(ret < 0 )
	{
		write_log("client send_msg errror",WARNING);
		return -1;
	}
	return 0;
}
//析构函数,sock大于0时,代表该套接字文件存在时
udp_client::~udp_client()
{
	if(sock >0)
		close(sock);
}
//chat_client.cpp文件
#include "udp_client.h"
#include "data.h"
#include "window.h"


typedef struct net_window
{//定义结构体存放用户信息和当前页面信息
	udp_client *cp;
	window *wp;
}net_window_t,*net_window_p;
//定义昵称,学校,定义vector容器存放好友列表,可以动态增加好友
std::string name;
std::string school;
std::vector<std::string> fl;
udp_client *qclient = NULL;
int flag = 1;//退出标志
//提示信息:输入ip和端口号
void usage(const char* arg)
{
	std::cout<<"Usage: "<<arg<<"[client_ip] [client_port]" <<std::endl;
}
//quit函数
void quit(int n)
{
	std::string out;
	data q;
	q.nick_name = name;
	q.school = q.school;
	q.cmd = "QUIT";
	q.msg = "";
	q.data_to_string(out);
	qclient->send_msg(out);
	endwin();
	exit(1);
	//flag = 0;
}
void* show_header(void *arg)
{
	net_window_p obj = (net_window_p)arg;

	window *winp = obj->wp;//当前的页面信息
	udp_client *clientp = obj->cp;//当前的用户信息
	
	winp->create_header();
	wrefresh(winp->header);//用于刷新窗口的头部
	int x=0,y=0;
	getmaxyx(winp->header,y,x);
	std::string msg = "welcome to chat sysytem";
	int i=1;
	//跑马灯实现,先放置消息,然后清屏重画,
	while(1)
	{
		winp->put_str_to_win(winp->header,y/2,i++,msg);
		wrefresh(winp->header);
		usleep(200000);
		winp->clear_win_line(winp->header,y/2,1);
		
		if(i == x - msg.length())
			i=1;		  	
		winp->create_header();
		wrefresh(winp->header);
	}
}
//添加好友,采用迭代器遍历容器,直至容器的最后位置,再使用push_back加入新用户
void add_user(std::string& user)
{
	std::vector<std::string>::iterator iter = fl.begin();
	for(;iter!= fl.end();++iter)
	{	
		if(user == *iter)
			return ;
	}
	fl.push_back(user);
}
//删除用户,利用迭代器遍历,当找到与想要删除的user匹配时,调用erase函数进行删除
void del_user(std::string& user)
{
	std::vector<std::string>::iterator iter = fl.begin();
	for(;iter!= fl.end();)
	{	
		if(user == *iter)
		{
			iter = fl.erase(iter);
			break;
		}
		else
			++iter;
	}

}
void* show_output_fl(void *arg)
{
	net_window_p obj = (net_window_p)arg;
	window *winp = obj->wp;
	udp_client *clientp = obj->cp;
	
	//显示输出窗口:读取数据,反序列化
	data r;
	std::string r_str;
	std::string show_str;
	std::string friends;
	int i = 1,j =1;
	int x =0,y=0;
	int fx=0,fy=0;
	winp->create_output();
	winp->create_friends_list();
	wrefresh(winp->output);
	wrefresh(winp->friends_list);
	while(1)
	{
		//读取数据,反序列化
		clientp->recv_msg(r_str);
		r.string_to_data(r_str);
		//判断是否为退出的客户端
		//构建输出语句和朋友列表信息
		show_str = r.nick_name;
		show_str += "- ";
		show_str += r.school;
		friends = show_str;
		show_str += "# ";
		show_str += r.msg;
		if(r.cmd == "QUIT")
		{
			del_user(friends);
		}
		else
		{
			add_user(friends);
		
			//输出到output窗口
			winp->put_str_to_win(winp->output,i++,1,show_str);
			wrefresh(winp->output);
			//判断是否输满
			getmaxyx(winp->output,y,x);
			if(i == y-1)
			{
				i=1;
				usleep(200000);
				
				winp->clear_win_line(winp->output,1,y-1);
				winp->create_output();
				wrefresh(winp->output);
			}
		}
		//显示好友列表
		std::vector<std::string>::iterator iter = fl.begin();
		for(;iter!= fl.end();++iter)
		{	
			winp->put_str_to_win(winp->friends_list,j++,1,*iter);
			wrefresh(winp->friends_list);
			getmaxyx(winp->output,fy,fx);
			if(j == fy-1)
			{
				j=1;
				winp->clear_win_line(winp->friends_list,1,fy-1);
				winp->create_friends_list();
				wrefresh(winp->friends_list);
			}
		}
		j=1;
		usleep(200000);
	}	
}
void* show_input(void *arg)
{
	net_window_p obj = (net_window_p)arg;

	window *winp = obj->wp;
	udp_client *clientp = obj->cp;
	
	std::string str = "Please Enter# ";
	std::string out;
	data w;
	w.nick_name = name;
	w.school = school;
	w.cmd = "";
	while(1)
	{
		winp->create_input();
		winp->put_str_to_win(winp->input,1,2,str);
		wrefresh(winp->input);
		winp->get_str(winp->input,w.msg);
		//序列化,发送
		w.data_to_string(out);
		clientp->send_msg(out);
		//清屏
		winp->clear_win_line(winp->input,1,1);
		winp->create_input();
		wrefresh(winp->input);
	}
}

int main(int argc,char*argv[])
{
	if(argc != 3)
	{
		usage(argv[0]);
		return -1;
	}

	std::cout<<"please enter nick_name:";
	std::cin>>name;
	std::cout<<"please enter school:";
	std::cin>>school;
	udp_client client(argv[1],atoi(argv[2]));
	client.init_client();
	window win;
	
	net_window_t nw={&client,&win};
	qclient = &client;

	// 客户端需要创建三个线程,完成每一模块的工作
    1. 头部header标题的功能是滚动的播放welcome

    2. 输出框又分为了两部分,分别是输出用户信息和在线成员,并且实现框满清屏的效果

    3. 输入框使用户用来输入消息,按回车键就发送出去
	pthread_t theader,toutput_fl,tinput;
	pthread_create(&theader,NULL,show_header,&nw);
	pthread_create(&toutput_fl,NULL,show_output_fl,&nw);
	pthread_create(&tinput,NULL,show_input,&nw);
	
	pthread_join(theader,NULL);
	pthread_join(toutput_fl,NULL);
	pthread_join(tinput,NULL);
	
	return 0;
}

comm模块:

//data.h文件
#ifndef _DATA_H_//使用条件编译,避免重复编译
#define _DATA_H_

#include <iostream>
#include <string>
#include "base_json.h"

//data类实现了将序列化和反序列化函数进行简单的封装
class data
{
public:
    //构造函数,析构函数
	data();
	~data();
	//数据的序列化value->string
	void data_to_string(std::string& out);
	//数据的反序列化string->value
	void string_to_data(std::string& in);
public:
    //私有数据成员:昵称,学校,消息内容,状态信息
	std::string nick_name;
	std::string school;
	std::string msg;
	std::string cmd;
};
#endif
//客户端将这些信息发送出去以后,在网络中会序列化为一个字符串,服务器接收到数据以后,再将字符串反序列化为用户信息,进行存储和处理
//data.cpp文件
#include "data.h"
//默认构造函数和析构函数
data::data(){}
data::~data(){}

//将data 序列化,value->string,
//void serialize(Json::Value& val,std::string& outString)
void data::data_to_string(std::string& out)
{   
	Json::Value val;
	val["nick_name"] = nick_name;
	val["school"] = school;
	val["msg"] = msg;
	val["cmd"] = cmd;
	serialize(val,out);

}
//反序列化 将序列化value转化为string
//void un_serialize(Json::Value& val,std::string& in)
void data::string_to_data(std::string& in)
{
	Json::Value val;
	un_serialize(val,in);
	nick_name = val["nick_name"].asString();
	school = val["school"].asString();
	msg = val["msg"].asString();
	cmd = val["cmd"].asString();
}

//int main()
//{
//	data d;
//	d.nick_name = "boy";
//	d.school = "bit";
//	d.msg = "hello";
//    d.cmd = "";
//	std::string out;
//	d.data_to_string(out);
//	std::cout <<"out:"<<out<<std::endl;
//
//	data r;
//	r.string_to_data(out);
//	std::cout <<r.nick_name<<"-"<<r.school<<"-"<<r.msg<<std::endl;
//	return 0;
//}
//base_json.h文件
#ifndef _BASE_JSON_H__
#define _BASE_JSON_H__


#include <iostream>
#include <string>
#include "json/json.h"

//序列化
void serialize(Json::Value& val,std::string &out);
//反序列化
void un_serialize(Json::Value& val,std::string &in);

#endif
//base_json.cpp
#include "base_json.h"
//序列化
void serialize(Json::Value& val,std::string &out)
{
    //以JSON格式输出值而不进行格式化,FastWriter类
	Json::FastWriter w; 
	out = w.write(val);
}
//反序列化
void un_serialize(Json::Value& val,std::string &in)
{
	Json::Reader read;
	read.parse(in,val,false);
}
//Json::Writer    与Json::Reader相反,将Json::Value转化成字符串流,Jsoncpp 的 Json::Writer 类是一个纯虚类,并不能直接使用。在此我们使用 Json::Writer 的子类:Json::FastWriter。
//例如: Json::FastWriter fast_writer;

 // std::cout << fast_writer.write(root) << std::endl;

server模块:

#ifndef _UDP_SERVER_H_
#define _UDP_SERVER_H_

#include <iostream>
#include <string.h>
#include <stdlib.h>
#include <map>
#include <sys/types.h>
#include <sys/socket.h>
#include <pthread.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <fcntl.h>
#include "log.h"
#include "pool.h"
#include "data.h"
//定义udp_server类
class udp_server
{
public:
    //构造函数
	udp_server(const std::string& _ip,int port);
    //初始化,成员函数声明
	int init_server();
    //接收消息,成员函数声明
	int recv_msg(std::string& out);
    //发送消息,成员函数声明
	int send_msg(const std::string& in,struct sockaddr_in& peer,\
			const socklen_t& len);
	//广播消息,声明
	int brocast_msg();
	//添加用户,删除用户
	int add_online_user(struct sockaddr_in *client);
	int del_online_user(struct sockaddr_in *client);
   //析构函数
	~udp_server();
private:
    //私有成员函数
	udp_server(const udp_server&);

private:
    //私有数据成员存放套接字的相关信息和套接字文件描述符
	std::string ip;
	int port;
	int sock;
	//用户数据表
    //map内部自建一颗红黑树(一种非严格意义上的平衡二叉树),这颗树具有对数据自动排序的功能,所以在map内部所有的数据都是有序的, map是STL的一个关联容器,它提供一对一(其中第一个可以称为关键字,每个关键字只能在map中出现一次,第二个可能称为该关键字的值)的数据处理能力,
//在线用户序号与套接字类型结构体之间是一一映射的
	std::map<int,struct sockaddr_in> online_user;
	pool data_pool;//数据池是一个vector<string>
};
#endif
//udp_server.cpp文件
#include "udp_server.h"

//默认构造函数的定义
udp_server::udp_server(const std::string& _ip,int _port):ip(_ip),port(_port),sock(-1),data_pool(256)
{}
//初始化成员函数定义,创建套接字文件。定义结构体,存放相关的套接字信息
int udp_server::init_server()
{
	sock = socket(AF_INET,SOCK_DGRAM,0);
	if(sock < 0)
	{
		write_log("socket error",FATAL);
		return -1;
	}
	struct sockaddr_in local;
	local.sin_family =AF_INET;
	local.sin_port = htons(port);
	local.sin_addr.s_addr = inet_addr(ip.c_str());
  //服务器端需要绑定套接字ip地址和端口,才能进行正常的通信
	if(bind(sock,(struct sockaddr*)&local,sizeof(local)) < 0)
	{
		write_log("bind sock error",FATAL);
		return -2;	
	}
	return 0;
}
//添加用户,ip标记用户(有问题NAT技术)
int udp_server::add_online_user(struct sockaddr_in *client)
{
    //pair是一种模板类型,包含两个数据值,两个数据的类型可以不同,也可以相同
    // pair<int ,double>p2(1,2.4)//用给定值初始化
	online_user.insert(std::pair<int,struct sockaddr_in>(client->sin_addr.s_addr,*client));
}
//删除用户
int udp_server::del_online_user(struct sockaddr_in *client)
{	//map函数实现一一映射,迭代器遍历在线用户
	std::map<int,struct sockaddr_in>::iterator iter = online_user.find(client->sin_addr.s_addr);
	if(iter != online_user.end())
		online_user.erase(iter);
}

//从客户端读取数据,然后写到data_pool里面
//out指从客户端输出的数据
int udp_server::recv_msg(std::string& out)
{
    //开辟缓冲区buf,用于存放读取到的消息
	char buf[1024];
	struct sockaddr_in peer;
	socklen_t len = sizeof(peer);
    //调用recv函数将读到的信息写到buf中,返回实际读取的字节数
	int ret = recvfrom(sock,buf,sizeof(buf)-1,0,\
			(struct sockaddr*)&peer,&len);
	if(ret > 0)
	{
		buf[ret] = 0;
		out = buf;//将buf的首地址赋给out
        //将收到的信息放入数据池中
		data_pool.put_data(out);		
		data d;
        //进行反序列化
		d.string_to_data(out);
		if(d.cmd == "QUIT")
		{
			del_online_user(&peer);
		}
		else
		{
			add_online_user(&peer);	
		}
		return 0;
	}
	return -1;
}
//将消息发送出去,in指从pool得到的数据
int udp_server::send_msg(const std::string& in,\
		struct sockaddr_in& peer,const socklen_t& len)
{   //调用sento函数发送消息
	int ret = sendto(sock,in.c_str(),in.size(),0,\
			(struct sockaddr*)&peer,len);
	if(ret < 0 )
	{
		write_log("server send_msg errror",WARNING);
		return -1;
	}
	return 0;
}
//从数据池里面取出消息,广播发送给每个用户。
int udp_server::brocast_msg()
{
	std::string msg ;
	data_pool.get_data(msg);
    //使用迭代器遍历在线用户,为每一位用户发送消息
	std::map <int, struct sockaddr_in >::iterator iter = online_user.begin();
	for(;iter != online_user.end();++iter)
	{
		send_msg(msg,iter->second,sizeof(iter->second));
	}
	return 0;
}

udp_server::~udp_server()
{
	if(sock >0)
		close(sock);
}
//chat_system.cpp
#include "udp_server.h"

void usage(const char* arg)
{
	std::cout<<"Usage: "<<arg<<"[server_ip] [server_port]" <<std::endl;
}
void* brocast(void *arg)
{
    //服务器端不停地广播消息
	udp_server* serverp = (udp_server*)arg;
	while(1)
	{
		serverp->brocast_msg();
	}
}
int main(int argc,char* argv[])
{
	if(argc != 3)
	{
		usage(argv[0]);
		return 1;
	}
    //定义构造函数
	udp_server server(argv[1],atoi(argv[2]));
    //初始化
	server.init_server();
	
	
	//创建线程,新线程给客户端发数据,主线程将从客户端读取的数据写入pool
	pthread_t id;
	pthread_create(&id,NULL,brocast,(void*)&server);

	std::string msg;
	while(1)
	{    //服务器端不停地接收来自客户端发来的消息
		server.recv_msg(msg);
	}

	return 0;
}

data_pool模块:

//pool.h
#ifndef _POOL_H_
#define _POOL_H_

#include <iostream>
#include <string>
#include <vector>
#include <semaphore.h>

//数据池的实现,是一个环形队列
class pool
{
public:
	pool(int);//构造函数
	//从数据池取出数据
	int get_data(std::string& out);
	//向数据池中放数据
	int put_data(const std::string& in);
	~pool();

private:
	pool(const pool&);
private:
    //私有数据成员
	sem_t c_sem;//消费者者可用资源
	sem_t p_sem;//生产者可用资源
	std::vector<std::string> data_pool;//用vector容器维护一个环形队列,存放数据(数据池)
	int c_step;//消费者的步数
	int p_step;// 生产者的步数
	int cap;//环形队列的容量,可以存放数据的总量
};


#endif
#include "pool.h"
//构造函数,信号量的数据类型为结构sem_t,函数sem_init()用来初始化一个信号量。
//extern int sem_init __P ((sem_t *__sem, int __pshared, unsigned int __value));  

//sem为指向信号量结构的一个指针

//pshared不为0时此信号量在进程间共享,为0时表示为当前进程的所有线程共享;

//value给出了信号量的初始值。 
pool::pool(int size)
	:data_pool(size)
	,c_step(0)
	,p_step(0)
	,cap(size)
{
	sem_init(&c_sem,0,0);//消费者可用资源数
	sem_init(&p_sem,0,size);//生产者可用资源数
}
int pool::get_data(std::string& out)
{   //从数据池中取数据,sem_wait函数是一个原子操作,它的作用是从信号量的值减去一个“1”,但它永远会先等待该信号量为一个非零值才开始做减法。也就是说,如果对一个值为2的信号量调用sem_wait(),线程将会继续执行,使信号量的值将减到1。如果对一个值为0的信号量调用sem_wait(),这个函数就会地等待直到有其它线程增加了这个值使它不再是0为止
	sem_wait(&c_sem);//消费者等待生产者生产一个数据后,自己再进行消费
	out = data_pool[c_step++];
	c_step %= cap;//环形队列的实现
//em_post函数的作用是给信号量的值加上一个“1”,它是一个“原子操作”---即同时对同一个信号量做加“1”操作的两个线程是不会冲突的;
	sem_post(&p_sem);//生产者的可用资源数加1
}
int pool::put_data(const std::string& in)
{   //向数据池中放数据
	sem_wait(&p_sem);//等到生产者可用资源数大于等于1时,开始放数据
	data_pool[p_step++] = in;//将数据放入数据池
	p_step %= cap;
	sem_post(&c_sem);//消费者可用资源数加1
}
pool::~pool()
{
	sem_destroy(&c_sem);
	sem_destroy(&p_sem);
}
//window.h
#ifndef _WINDOW_H_
#define _WINDOW_H_
//ncurses库,它提供了API,可以允许程序员编写独立于终端的基于文本的用户界面
#include <iostream>
#include <ncurses.h>
#include <string.h>
#include <string>
#include <unistd.h>
class window
{
public:
	window();
	~window();
	//清除窗口内消息
	void clear_win_line(WINDOW* w,int begin,int lines);
	//从窗口获取消息
	void get_str(WINDOW* win,std::string& out);
	//向窗口放置消息
	void put_str_to_win(WINDOW* w,int y,int x,std::string& msg);
	WINDOW* create_newwin(int _h,int _w,int _y,int _x); 
	void create_header();//绘制头部标题栏
	void create_output();//绘制输出栏
	void create_friends_list();//绘制好友列表栏
	void create_input();//绘制输入栏

public:
	WINDOW* header;//标题窗口句柄
	WINDOW* output;//输出窗口句柄
	WINDOW* friends_list;//好友列表窗口句柄
	WINDOW* input;//输入窗口句柄
};
#endif
windows.cpp文件
#include "window.h"

window::window()//构造函数
{   //WINDOW * initscr(void),initscr函数在一个程序中只能调用一次。如果成功,返回一个指向stdscr结构的指针;
	initscr();
   //这个函数用来设制光标是否可见。它的参数可以是:0(不可见),1(可见),2(完全可见)
	curs_set(0);
}

window::~window()
{   //析构函数,删除各个窗体
	delwin(header);
	delwin(input);
	delwin(friends_list);
	delwin(output);
	endwin();

}
//获取窗口内的消息
void window::get_str(WINDOW* win,std::string& out)
{
	char msg[1024];
	wgetnstr(win,msg,sizeof(msg));
	out = msg;
}

void window::put_str_to_win(WINDOW* w,int y,int x,std::string& msg)
{   //添加序列到窗口指定的位置
	mvwaddstr(w,y,x,msg.c_str());
}
void window::clear_win_line(WINDOW* w,int begin,int lines)
{   
	while(lines-- >0)
	{
		//移动光标
		wmove(w,begin++,0);
		wclrtoeol(w);
	}
}
WINDOW* window::create_newwin(int _h,int _w,int _y,int _x)
{   //创建一个新窗体
	WINDOW *win = newwin(_h,_w,_y,_x);
    //box(WINDOW *win,char1,char2);该函数用在linux程序的curses编程里,用来设计窗口的边框,win为窗口的指针,
	box(win,0,0);
	return win;
}
void window::create_header()
{
	int _y = 0;
	int _x = 0;
	int _h = LINES/5;
	int _w = COLS;
	header = create_newwin(_h,_w,_y,_x);
}
void window::create_output()
{
	int _y = LINES/5;
	int _x = 0;
	int _h = (LINES*3)/5;
	int _w = (COLS*3)/4;
	output = create_newwin(_h,_w,_y,_x);
}
void window::create_friends_list()
{
	int _y = LINES/5;
	int _x = (COLS*3)/4;
	int _h = (LINES*3)/5;
	int _w = COLS/4;
	friends_list = create_newwin(_h,_w,_y,_x);
}
void window::create_input()
{
	int _y =(LINES*4)/5;
	int _x = 0;
	int _h = LINES/5;
	int _w = COLS;
	input = create_newwin(_h,_w,_y,_x);
}


//int main()
//{
//	window win;
//	win.create_header();
//	sleep(1);
//	wrefresh(win.header);
//	win.create_output();
//	sleep(1);
//	wrefresh(win.output);
//	win.create_friends_list();
//	sleep(1);
//	wrefresh(win.friends_list);
//	win.create_input();
//	sleep(1);
//	wrefresh(win.input);
//		
//	//放置消息
//	std::string msg = "please Enter#";
//	mvwaddstr(win.input,1,2,msg.c_str());
//	wrefresh(win.input);
//	sleep(2);
//	int x=0,y=0;
//	getmaxyx(win.output,y,x);
//
//	int hx = 0,hy=0;
//	getmaxyx(win.header,hy,hx);
//	int i=1;
//	int j=1;
//	std::string running = "welcome to chat system";
//	while(1)
//	{
//		//header跑马灯实现
//		
//		mvwaddstr(win.header,hy/2,j++,running.c_str());
//		wrefresh(win.header);
//		usleep(200000);
//		win.clear_win_line(win.header,hy/2,1);
//		win.create_header();
//		wrefresh(win.header);
//		if(j == hx)
//		{
//			j=1;
//		}
//
//
//		//output 循环放出消息
//		//mvwaddstr(win.output,i,2,msg.c_str());
//		//wrefresh(win.output);
//		//usleep(200000);
//		//i++;
//		//i %=(y-1);
//		//if(i==0)
//		//{
//		//	i=1;
//		//	win.clear_win_line(win.output,1,y-1);
//		//	win.create_output();
//		//	wrefresh(win.output);
//		//}
//	}
//	return 0;

plugin模块(控制服务器):

//ctrl_server.sh,shell脚本
ROOT_PATH=/home/yinyunhong/chatroom/Chat_Master
BIN=$ROOT_PATH/chat_system
CONF=$ROOT_PATH/conf/server.conf
pid=''

porc=$(basename $0)

function usage()
{
	printf "%s %s\n" "$porc" "[start | -s] [stop | -t] [restart | -rt] [status | -a]"
}

check_status()
{
	name=$(basename $BIN)
	pid=$(pidof $name)
    //如果正在运行的进程的pid不为0
	if [ ! -z "$pid" ];then
		return 0 
	else
		return 1
	fi
}
server_start()
{   //正在运行的进程号不为0,代表服务器正在running
	if check_status ;then
		echo "server is ready running,pid : $pid"
	else
      
		ip=$(awk -F: '/^IP/{print $NF}' $CONF)
		port=$(awk -F: '/^PORT/{print $NF}' $CONF)
		$BIN $ip $port
		echo "server is start......done, "
	fi
}

server_stop()
{
	if check_status ;then
		kill -9 $pid
		echo "server stop......done"
	else
		echo "server is not running"
	fi
}

server_restart()
{
	server_stop
	server_start
}
server_status()
{
	if check_status ;then
		echo "server is running...,pid is $pid"
	else
		echo "server is not running ....."
	fi
}

if [ $# -ne 1 ] ;then
	usage
	exit 1
fi

case $1 in
	start | -s )
		server_start
		;;
	stop | -t )
		server_stop
		;;
	restart | -rt )
		server_restart
		;;
	status | -a )
		server_status
		;;
	*)
	usage
	exit 2
	;;
esac
check_status

结果截图:

项目总结:

(1)面对的首要问题:是如何识别客户端中哪个客户发来的消息。其实是通过序列化与反序列化解决该问题,通过序列化将用户发送的消息与用户信息进行绑定,之前没有了解过json的序列化和反序列化,后来多次尝试将其封装到data类中,实现了该操作。

(2)序列化和反序列化引入了json库,进行窗口化设计引入了ncurse库。但是引入这两个库后,编写makefile文件时,一直报错,提示有“未定义的引用”,修改了好久,才编写好正确的makefile文件,发现原来自己的文件路径上写的有问题。

       其次还有在编译链接库时,一些编译选项也有点儿忘了,-L指定的是库优先搜索的路径,-l指定的是搜索动态库文件libjson.so,还是应该多写makefile文件,对整个项目进行组织,有清晰的架构

(3)实现客户端的窗口界面对于我来说,也有些陌生,如:设置光标的可见性,创建删除新窗体等操作,在查阅了相关的资料以及相关函数的原型或源码后,对该部分内容有了更加深入的了解

当然最重要的是,对于库文件的路径问题一定要处理好,因为单单makefile的编写就占据了我很长的时间

猜你喜欢

转载自blog.csdn.net/qq_40170007/article/details/87890346