IM是一款使用开源websocket框架mongoose编写网页版本,http+
mongoose+session+mysql+jsoncpp的技术构成的群聊即时通信工具。
http && websocket协议
现在,很多网站为了实现推送技术,所用的技术都是轮询。在特定的时间间隔(如每一秒),由浏览器对服务器发起HTTP请求,然后由服务器返回数据给浏览器。由于HTTP协议是惰性的,只有客户端发起请求,服务器才会返回数据。轮询技术实现的前提条件同样是基于这种机制。而WebSocket属于服务端推送技术,本质是一种应用层协议,可以实现持久连接的全双工双向通信。
WebSocket是一种协议,与HTTP协议一样位于应用层,都是TCP/IP协议的子集。HTTP协议是单向通信协议,只有客户端发起HTTP请求,服务端才会返回数据。而WebSocket协议是双向通信协议,在建立连接之后,客户端和服务器都可以主动向对方发送或接受数据。WebSocket协议建立的前提需要借助HTTP协议,建立连接之后,持久连接的双向通信就与HTTP协议无关了。
mongoose
Mongoose是c语言写成的网络库。它为TCP、UDP、HTTP、WebSocket、CoAP、MQTT实现了事件驱动型的非阻塞api。
特性:
在运行和占用很小的内存,源代码符合ISO C 和ISO C++规范,使用时,仅需要包含 mongoose.c mongoose.h 到你的工程即可完成整合。Mongoose使嵌入式网络编程快速,健壮,轻松。
基本逻辑:
首先,设计基本框架
然后,先完成基本的聊天逻辑
在能够让我们访问数据库的基础上,访问我们之前所建立的表
最后,完成登录逻辑
struct session{
//登录用户的session信息数据结构,有部分内容是要写到客户端cookie中的
};
//M
class IM_MysqlClient{
//访问数据库客户端,使用C connect封装
};
//C
class IM_Controller{
//控制器
};
//V
class IM_Server{
//服务器功能
};
核心代码:
//ImServer.cc
#include "ImServer.hpp"
int main()
{
//MysqlClient *mc = new MysqlClient();
//mc->ConnectMysql();
//mc->InsertUser("赵六", "4321");
//delete mc;
ImServer *im = new ImServer();
im->InitServer();
im->Start();
return 0;
}
//ImServer.hpp
#pragma once
#include
#include
#include "Util.hpp"
#include "mongoose.h"
#include "mysql.h"
#define IM_DB "im_db"
#define MY_PORT 3306
struct mg_serve_http_opts s_http_server_opts;
//model
class MysqlClient{
private:
MYSQL *my;
bool ConnectMysql()
{
my = mysql_init(NULL);
mysql_set_character_set(my, "utf8");
if(!mysql_real_connect(my, "localhost",\
"root", "", IM_DB, MY_PORT, NULL, 0)){
cerr << "connect mysql error" << endl;
return false;
}
cout << "connect mysql success" << endl;
return true;
}
public:
MysqlClient(){
}
bool InsertUser(string name, string passwd)
{
ConnectMysql();
string sql = "INSERT INTO user (name, passwd) values(\"";
sql += name;
sql += "\",\"";
sql += passwd;
sql += "\")";
cout << sql << endl;
if(0 == mysql_query(my, sql.c_str())){
return true;
}
mysql_close(my);
return false;
}
bool SelectUser(string name, string passwd)
{
ConnectMysql();
string sql = "SELECT * FROM user WHERE name=\"";
sql += name;
sql += "\" AND passwd=\"";
sql += passwd;
sql += "\"";
cout << sql << endl;
if(0 != mysql_query(my, sql.c_str())){
//return false;
}
cout << "select done" << endl;
//judge result not empty
mysql_close(my);
return true;
}
~MysqlClient(){
}
};
//MVC
class ImServer{
private:
string port;
struct mg_mgr mgr;
struct mg_connection *nc;
volatile bool quit;
static MysqlClient mc;
public:
ImServer(string _port = "8080"):port(_port),quit(false)
{
}
static void Broadcast(struct mg_connection *nc, string msg)
{
struct mg_connection *c;
for (c = mg_next(nc->mgr, NULL); \
c != NULL; c = mg_next(nc->mgr, c)) {
//if (c == nc) continue; /* Don't send to the sender. */
mg_send_websocket_frame(c, WEBSOCKET_OP_TEXT,\
msg.c_str(), msg.size());
}
}
static void RegisterHandler(mg_connection *nc, int ev, void *data)
{
}
static void LoginHandler(mg_connection *nc, int ev, void *data)
{
struct http_message *hm = (struct http_message*)data;
string method = Util::mgStrToString(&(hm->method));
if(method == "POST"){
string body = Util::mgStrToString(&hm->body);
std::cout << "login handler: " << body << std::endl;
string name, passwd;
if(Util::GetNameAndPasswd(body, name, passwd)){
if(mc.SelectUser(name, passwd)){
string test = "{\"result\":12}";
nc->flags |= MG_F_SEND_AND_CLOSE; //相应完毕,完毕链接
mg_printf(nc, "HTTP/1.1 200 OK\r\n");
mg_printf(nc, "Content-Length: %d\r\n\r\n", test.size());
mg_printf(nc, test.c_str());
}
}
//mg_serve_http(nc, hm, s_http_server_opts);
}
else{
mg_serve_http(nc, hm, s_http_server_opts);
}
}
static void EventHandler(mg_connection *nc, int ev, void *data)
{
switch(ev){
case MG_EV_HTTP_REQUEST:{
struct http_message *hm = (struct http_message*)data;
mg_serve_http(nc, hm, s_http_server_opts);
//nc->flags |= MG_F_SEND_AND_CLOSE;
}
break;
case MG_EV_WEBSOCKET_HANDSHAKE_DONE:{
Broadcast(nc, "some body join...");
}
break;
case MG_EV_WEBSOCKET_FRAME:{
struct websocket_message *wm = (struct websocket_message*)data;
struct mg_str ms = {(const char*)wm->data, wm->size};
string msg = Util::mgStrToString(&ms);
Broadcast(nc, msg);
}
break;
case MG_EV_CLOSE:
std::cout << "close link" << std::endl;
break;
default:
cout << "other ev: " << ev << endl;
break;
}
}
void InitServer()
{
mg_mgr_init(&mgr, NULL);
nc = mg_bind(&mgr, port.c_str(), EventHandler);
mg_register_http_endpoint(nc, "/login", LoginHandler);
mg_register_http_endpoint(nc, "/register", RegisterHandler);
mg_set_protocol_http_websocket(nc);
s_http_server_opts.document_root = "web";
}
void Start()
{
int timeout = 1000000;
while(!quit){
mg_mgr_poll(&mgr, timeout);
cout << "time out ..." << endl;
}
}
~ImServer()
{
mg_mgr_free(&mgr);
}
};
MysqlClient ImServer::mc;
//Makefile
cc=g++
bin=ImServer
src=ImServer.cc mongoose/mongoose.c
include=-Imongoose/ -Imysql/include
lib=-Lmysql/lib
lib_name=-lmysqlclient -ljsoncpp #-lpthread -ldl -static
$(bin):$(src)
$(cc) -Wall -o $@ $^ -std=c++11 $(include) $(lib) $(lib_name)
.PHONY:clean
clean:
rm -f $(bin)