引言
现今,网络聊天是我们生活常见的一种沟通方式,而且社交工具层出不穷。
前段时间也爆发了一场“三英战吕布的大戏”,“马桶MT”、“多闪”、“聊天宝”同日向“微信发起挑战”。这足以说明现今社交软件真的是竞争激烈。
既然它这么火热,我们是不是应该来深入了解这个聊天系统是什么?怎么实现?
PS:在这里所实现的并不能像日常见到的软件那样丰富多彩。没错,就是黑框框,但也足以让我们对聊天系统的技术有启蒙意义。
预期实现
用户注册
用户登陆
群聊
原理
注册&登陆
注册:
用户选择注册接口,向服务器提供用户信息;
服务器记录用户提供的信息,并返回给用户一个ID账号;
注册成功的同时,将用户插入到我们的ID->用户的映射表中。
登陆:
用户选择登陆接口,输入自己的账号和密码;
服务器通过查找映射表,匹对用户账号和密码;
匹对认证成功后,返回登陆成功,刷新出聊天界面。
聊天
作为聊天室,一定是有多个数据的接收和发送。
我们这里是通过创建一个数据池,如果我们数据池未满,服务器将接收到的数据放入到数据池中,数据池如果不为空再从数据池中去取数据,之后将数据发送给各个用户。
具体实现
注册&登陆
由于注册和登陆这里牵扯到用户的信息传输和认证,所以我们这里选择使用可靠的TCP协议实现。
为方便之后UDP的使用,封装一个Sock接口,通过传入类型选择是使用TCP还是UDP。
class SocketApi{
public:
static int Socket(int type){
int sock = socket(AF_INET, type, 0);
if(sock < 0){
LOG("Socket error!", ERROR);
exit(2);
}
return sock;
}
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("Bind 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 &net_ip, int &net_port){
struct sockaddr_in peer; // 远端信息,客户端通过accept后两个参数传信息
socklen_t len = sizeof(peer);
int sock = accept(listen_sock, (struct sockaddr*)&peer, &len);
if(sock < 0){
LOG("Accept error", WARING);
return -1;
}
net_ip = inet_ntoa(peer.sin_addr);
net_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!", WARING);
return false;
}
return true;
}
注册协议格式
为了规范用户信息,自定义一个注册协议,方便用户注册,也方便管理。
struct RegisterInfo{
char nick_name[32];
char school[64];
char passwd[32];
};
struct LoginInfo{
id_type id;
char passwd[32];
};
struct Reply{
int status;
id_type id;
};
通信协议
仿照HTTP协议自定义属于我们自定义的协议格式。
class Request{
public:
std::string method;
std::string content_length; // 正文长度
std::string blank;
std::string text;
Request()
:blank("\n")
{}
~Request()
{}
};
method方法中就放入我们所需的功能:注册,登陆,退出。
聊天协议格式
聊天的数据传输可以使用UDP协议提高我们信息传输效率。
客户端界面
通过引用ncurses库,编写客户端界面。
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 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);
const string tips = "Please Enter#:";
PutStringToWin(input, 2, 2, tips);
SafeWrefresh(input);
}
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);
}
效果展示
注册:
登陆:
聊天: