BlackFeather'S Blog

首页 | |

mongoose魔改历程(C++封装http和ws服务、多线程、优化)-2021.07.23更新


2024.5.22更新

由于性能问题,ws里面还有一个小BUG未修改,此库放弃使用

推荐几个已经使用的http库:

A C++11 single-file header-only cross platform HTTP/HTTPS library.

https://github.com/yhirose/cpp-httplib


基于C++17/20的Http应用框架,使用Drogon可以方便的使用C++构建各种类型的Web应用服务端程序

https://github.com/drogonframework/drogon


Header only c++ network library, based on asio,support tcp,udp,http,websocket,rpc,ssl,icmp,serial_port.

https://github.com/zhllxt/asio2



背景需求

每个工作背景都不同,需要总结一下:

1.C++编写,跨平台(windows上用的vs2013,所以是C++11标准)

2.支持http和ws的server,支持多线程,支持uri映射(类似addhandler("/hello",onrequestcallback);),支持请求中相关字段的获取(get/post参数读取、header、cookie等读取)

3.不用性能超强,并发数也不大(几十并发)

4.依赖库少一点,不想为了一个小功能引入boost之类的大家伙

之前一直用的是libevent库提供的http服务作为一个接口服务器,后来有了新的需求要增加ws支持,官方的不支持ws,自己魔改libevent增加ws支持实力不够改的兼容性不好,框架网上有大把的框架,用现成的吧。


 


筛选

查找了很多库 https://github.com/fffaraz/awesome-cpp#networking

前后测试了两遍没有完全满足的,都要二次封装,那么就选一个库作为基石来鼓捣。

最后选择的是mongoose(具体过程不说了,完全是个人情况而定的)

mongoose一个很单纯的C编写的库,包含了client和server的功能,单线程业务处理,不过提供的方法倒是挺多的,非常方便使用。不过这个库不是性能优先,如果非常注重性能(几万并发起步)那还是不要用了,不过却满足我的当前需求。


 


建立http服务

mongoose简单示例(官方)

#include "mongoose.h"

//连接上所有事件的回调函数
static void ev_handler(struct mg_connection *nc, int ev, void *p) 
{
  
  //http请求
  if (ev == MG_EV_HTTP_REQUEST) 
  {
    //发送返回值,有多个函数可以用不同姿势发送返回值
    mg_printf(nc, "%s", "HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nOK");
  }

}

int main(void) 
{
  struct mg_mgr mgr;
  struct mg_connection *nc;

  mg_mgr_init(&mgr, NULL); //初始化连接管理器

  //绑定端口,设置回调函数
  nc = mg_bind(&mgr, "8000", ev_handler); 
  if (nc == NULL) {
    printf("Failed to create listener\n");
    return 1;
  }

  //允许http和websocket
  mg_set_protocol_http_websocket(nc);  

  //事件循环
  for (;;) {
    mg_mgr_poll(&mgr, 1000);
  }

  //释放连接管理器
  mg_mgr_free(&mgr);

  return 0;
}


先建立连接管理器(mg_mgr),然后所有的连接都在mg_mgr中以链表的形式管理。

然后在mg_mgr中bind一个本地端口,并设置该连接允许http和websocket(mg_set_protocol_http_websocket),然后处理回调消息就完事了,非常简单。

那么也简单了,按照这几个方法,很容易封装出来一个C++的版本,这里不多贴代码了。


 



多线程的使用

多线程的官方示例:

#include "mongoose.h"

static sig_atomic_t s_received_signal = 0;
static const char *s_http_port = "8000";
static const int s_num_worker_threads = 5;
static unsigned long s_next_id = 0;

static void signal_handler(int sig_num) {
  signal(sig_num, signal_handler);
  s_received_signal = sig_num;
}
static struct mg_serve_http_opts s_http_server_opts;
static sock_t sock[2];

// This info is passed to the worker thread
struct work_request {
  unsigned long conn_id;  // needed to identify the connection where to send the reply
  // optionally, more data that could be required by worker 
};

// This info is passed by the worker thread to mg_broadcast
struct work_result {
  unsigned long conn_id;
  int sleep_time;
};

static void on_work_complete(struct mg_connection *nc, int ev, void *ev_data) {
  (void) ev;
  char s[32];
  struct mg_connection *c;
  for (c = mg_next(nc->mgr, NULL); c != NULL; c = mg_next(nc->mgr, c)) {
    if (c->user_data != NULL) {
      struct work_result *res = (struct work_result *)ev_data;
      if ((unsigned long)c->user_data == res->conn_id) {
        sprintf(s, "conn_id:%lu sleep:%d", res->conn_id, res->sleep_time);
        mg_send_head(c, 200, strlen(s), "Content-Type: text/plain");
        mg_printf(c, "%s", s);
      }
    }
  }
}

void *worker_thread_proc(void *param) {
  struct mg_mgr *mgr = (struct mg_mgr *) param;
  struct work_request req = {0};
  
  while (s_received_signal == 0) {
    if (read(sock[1], &req, sizeof(req)) < 0)
      perror("Reading worker sock");
    int r = rand() % 10;
    sleep(r);
    struct work_result res = {req.conn_id, r};
    mg_broadcast(mgr, on_work_complete, (void *)&res, sizeof(res));
  }
  return NULL;
}

static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) {
  (void) nc;
  (void) ev_data;
  
  switch (ev) {
    case MG_EV_ACCEPT:
      nc->user_data = (void *)++s_next_id;
      break;
    case MG_EV_HTTP_REQUEST: {
      struct work_request req = {(unsigned long)nc->user_data};

      if (write(sock[0], &req, sizeof(req)) < 0)
        perror("Writing worker sock");
      break;
    }
    case MG_EV_CLOSE: {
      if (nc->user_data) nc->user_data = NULL;
    }
  }
}

int main(void) {
  struct mg_mgr mgr;
  struct mg_connection *nc;
  int i;

  if (mg_socketpair(sock, SOCK_STREAM) == 0) {
    perror("Opening socket pair");
    exit(1);
  }

  signal(SIGTERM, signal_handler);
  signal(SIGINT, signal_handler);

  mg_mgr_init(&mgr, NULL);

  nc = mg_bind(&mgr, s_http_port, ev_handler);
  if (nc == NULL) {
    printf("Failed to create listener\n");
    return 1;
  }

  mg_set_protocol_http_websocket(nc);
  s_http_server_opts.document_root = ".";  // Serve current directory
  s_http_server_opts.enable_directory_listing = "no";

  for (i = 0; i < s_num_worker_threads; i++) {
    mg_start_thread(worker_thread_proc, &mgr);
  }

  printf("Started on port %s\n", s_http_port);
  while (s_received_signal == 0) {
    mg_mgr_poll(&mgr, 200);
  }

  mg_mgr_free(&mgr);

  closesocket(sock[0]);
  closesocket(sock[1]);

  return 0;
}

开始看的我有点懵逼,建立了一个mg_socketpair来完成业务线程与工作线程的通信,在多线程的工作线程中要用mg_broadcast回调在回调里面真正发送数据,开始没有很好地理解写错了,崩溃了整整一宿(20:00到3:00)。后来仔细阅读了代码结合一些其他人的描述,才弄明白原理。

首先mongoose是业务线程是单线程,就是mg_mgr_poll那个循环就是业务线程,也就是说ev_handler这个回调和发送返回值操作都要在业务线程中做,不在业务线程中调用发送函数(例如mg_send_head)是错误的直接蹦。工作线程是可以多个线程来处理业务,处理完毕后要通知回业务线程。这个通知的方法就是mg_broadcast。

至于这个mg_socketpair可以认为是一个队列,用于业务线程接收到事件后,封装数据的发送到工作线程。补个图。

微信图片_20201030171508.jpg



 


多线程之间通信


ev_handler回调的数据发送到工作线程

官方示例中使用mg_socketpair通信,其实就是建立了一对socket一个作为发送者一个作为接收者,就是socket通信而已。实际上可以用队列比较方便。建议使用无锁队列效率比较高(写的第一版用的是加锁队列,效率差了十多倍),无锁队列库:https://github.com/cameron314/concurrentqueue  (个人比较喜欢的header only方式)

数据传输这里,开始我直接传递的event_data指针,发现完全不行,因为ev_handler返回后该数据会被销毁,所以要在回调中把event_data中所需要的数据都缓存下来。比如http的回调事件MG_EV_HTTP_REQUEST,event_data是http_message*数据,里面包含uri、mehtod、body、message、header等等信息,都要自己另存下来,否则回调一返回数据就会被销毁了。保存好这份数据后,将其插入队列等待工作线程取数据处理。这时候回调返回,业务线程可以继续loop去干其他事情。

			http_message *msg = (http_message *)event_data;

//保存回调里面的关键数据
MG_EVENT_DATA_PTR cbdata = new MG_EVENT_DATA;
cbdata->event_type = event_type;
cbdata->body.assign(msg->body.p, msg->body.len);
cbdata->method.assign(msg->method.p, msg->method.len);
cbdata->uri.assign(msg->uri.p, msg->uri.len);
cbdata->query.assign(msg->query_string.p, msg->query_string.len);
cbdata->proto.assign(msg->proto.p, msg->proto.len);
cbdata->contentlength = msg->content_length;
for (int i = 0; i < MG_MAX_HTTP_HEADERS; i++)
{
if (msg->header_names[i].len == 0)
break;
cbdata->headers.emplace_back(std::string(msg->header_names[i].p, msg->header_names[i].len), std::string(msg->header_values[i].p, msg->header_values[i].len));
}
//将cbdata插入队列


工作线程处理与返回


工作线程是多线程,例如4线程就用std::thread开启4个work_handler()。里面的逻辑就是先从队列里面取出来数据,然后取出来数据后回调给用户接口,用户接口干活,干完之后封装返回值,这些都不用多说就是个简单的callback而已。这里再次强调不能直接在工作线程中调用发送方法(例如mg_send_head),要用回调将数据返回给业务线程去处理。重点就是mg_broadcast这个坑,写的真尼玛晦涩难懂,还都是坑,怪不得网上很多人都说官方的多线程例子是坨屎。

首先说说mg_broadcast这个方法

//工作线程中调用
void mg_broadcast(struct mg_mgr *mgr, mg_event_handler_t cb, void *data, size_t len) {
  struct ctl_msg ctl_msg;

  /*
   * Mongoose manager has a socketpair, `struct mg_mgr::ctl`,
   * where `mg_broadcast()` pushes the message.
   * `mg_mgr_poll()` wakes up, reads a message from the socket pair, and calls
   * specified callback for each connection. Thus the callback function executes
   * in event manager thread.
   */
  if (mgr->ctl[0] != INVALID_SOCKET && data != NULL &&
      len < sizeof(ctl_msg.message)) {
    size_t dummy;

    ctl_msg.callback = cb;
    memcpy(ctl_msg.message, data, len);
    dummy = MG_SEND_FUNC(mgr->ctl[0], (char *) &ctl_msg,
                         offsetof(struct ctl_msg, message) + len, 0);
    dummy = MG_RECV_FUNC(mgr->ctl[0], (char *) &len, 1, 0);
    (void) dummy; /* https://gcc.gnu.org/bugzilla/show_bug.cgi?id=25509 */
  }
}


//业务线程中接收
static void mg_mgr_handle_ctl_sock(struct mg_mgr *mgr) {
  struct ctl_msg ctl_msg;
  int len = (int) MG_RECV_FUNC(mgr->ctl[1], (char *) &ctl_msg, sizeof(ctl_msg), 0);
  size_t dummy = MG_SEND_FUNC(mgr->ctl[1], ctl_msg.message, 1, 0);
  DBG(("read %d from ctl socket", len));
  (void) dummy; /* https://gcc.gnu.org/bugzilla/show_bug.cgi?id=25509 */
  if (len >= (int) sizeof(ctl_msg.callback) && ctl_msg.callback != NULL) {
    struct mg_connection *nc;
    for (nc = mg_next(mgr, NULL); nc != NULL; nc = mg_next(mgr, nc)) {
      //回调方法
      ctl_msg.callback(nc, MG_EV_POLL,
                       ctl_msg.message MG_UD_ARG(nc->user_data));
    }
  }
}


void mg_broadcast(struct mg_mgr *mgr, mg_event_handler_t cb, void *data, size_t len);

第二个参数是广播的回调方法,data和len是数据。工作线程中调用mg_broadcast来广播,触发的回调方法里面就是业务线程了(开始没理解,后来通过打印调用和回调的threadid才明白)。

内部很简单其实就是一个在内部建立了一个mg_socketpair(上面描述过),将要传递的数据封装到结构体里面,工作线程中调用send把结构体发送出去,业务线程里面recv到这个结构体,然后触发回调,很简单的逻辑。这里官方代码很坑,connection在连接后 ( MG_EV_ACCEPT 事件),初始化了一个唯一connid( s_next_id )给每个connection(存储到了user_data里面),广播会将每个connection都回调一遍(上面代码里面nc那个链表的遍历),然后在回调里面判断当前的connection是不是我要发送数据的connid,不是的话返回,是的话继续发送数据。官方示例里面在on_work_complete又遍历了一遍所有connection这里其实是错误的,一个大坑,因为回调已经是遍历所有connection回调了。这个也符合mg_broadcast这个名字,广播给所有connection回调。


这里还有一个大坑,因为mg_broadcast其实也是异步的,利用socket发送用户数据,那个结构体的定义为:

struct ctl_msg { 

    mg_event_handler_t callback; 

    char message[MG_CTL_MSG_MESSAGE_SIZE];  //默认是8192 

}; 

所以这里传递的数据不能是原始返回内容的buffer,太小了不合适呀,所以要new一个buffer传递进去,然后在回调里面去delete掉。但这里会引入另外一个问题,broadcast里面是遍历所有正常的connection,如果某个用户接口处理时间比较长比如要2秒,返回的时候connection已经断开了(多线程下是可能存在的),那么这个广播后就不能触发回调,导致这份buffer无法被删除造成内存泄露。


 


问题总结:

1.广播时connection断开了无法删除buffer

2.异步的,不知道当前connection的状态

3.工作线程与业务线程传递数据使用广播,意味着每次都要通知一遍所有的connection,这完全没有必要呀,我有connid那只回调一个connection就可以了哇。当然原本的broadcast到所有nc还是有必要的。



开始魔改mg_broadcast吧。。。改后的代码:

struct ctl_msg {
  mg_event_handler_t callback;
  uint32_t connid;  //connID,可以遍历指定的connection
  int syncflag;     //异步和同步执行标记
  void *data;       //传递的数据
};

//增加connid参数,可以指定connid来触发回调,如果connid为0那就是广播(给connection初始化connid的时候不能设置为0)
//data: 用户数据指针
//ret:返回值指针,NULL为异步,如果设置了就意味着同步发送 发送完毕后才返回。同步方式可以节省内存,异步发送需要新开辟内存
void mg_broadcast(struct mg_mgr *mgr, uint32_t connid, mg_event_handler_t cb, void *data, int *ret) {
  struct ctl_msg ctl_msg;
  /*
   * Mongoose manager has a socketpair, `struct mg_mgr::ctl`,
   * where `mg_broadcast()` pushes the message.
   * `mg_mgr_poll()` wakes up, reads a message from the socket pair, and calls
   * specified callback for each connection. Thus the callback function executes
   * in event manager thread.
   */
  if (mgr->ctl[0] != INVALID_SOCKET && data != NULL) {
    size_t dummy;

    ctl_msg.callback = cb;
ctl_msg.connid = connid;
if (ret)
ctl_msg.syncflag = 1;
ctl_msg.data = data;
dummy = MG_SEND_FUNC(mgr->ctl[0], (char *)&ctl_msg, sizeof(ctl_msg), 0);
dummy = MG_RECV_FUNC(mgr->ctl[0], (char *)&ctl_msg.syncflag, sizeof(int), 0);
    (void) dummy; /* https://gcc.gnu.org/bugzilla/show_bug.cgi?id=25509 */
if (ret)
*ret = ctl_msg.syncflag;
  }
}
static void mg_mgr_handle_ctl_sock(struct mg_mgr *mgr) {
struct ctl_msg ctl_msg;
size_t dummy;
int ret = 1;
int len = (int)MG_RECV_FUNC(mgr->ctl[1], (char *)&ctl_msg, sizeof(ctl_msg), 0);

//不是同步执行 就立刻返回
if (ctl_msg.syncflag == 0)
dummy = MG_SEND_FUNC(mgr->ctl[1], (char *)&ret, sizeof(int), 0);

DBG(("read %d from ctl socket", len));
(void)dummy; /* https://gcc.gnu.org/bugzilla/show_bug.cgi?id=25509 */
if (len >= (int) sizeof(ctl_msg.callback) && ctl_msg.callback != NULL) {
struct mg_connection *nc;
if (ctl_msg.connid == 0)
{
//先广播所有正常的连接
for (nc = mg_next(mgr, NULL); nc != NULL; nc = mg_next(mgr, nc)) {
ctl_msg.callback(nc, MG_EV_POLL,
ctl_msg.data MG_UD_ARG(nc->user_data));
}
//最后发送一个nc为NULL的回调 表示广播结束了 用于清理内存等等
ctl_msg.callback(NULL, MG_EV_POLL,
ctl_msg.data MG_UD_ARG(nc->user_data));
}
else
{
for (nc = mg_next(mgr, NULL); nc != NULL; nc = mg_next(mgr, nc)) {
if (nc->connid == ctl_msg.connid)
break;
}
if (nc == NULL)
ret = 0;
ctl_msg.callback(nc, MG_EV_POLL,
ctl_msg.data MG_UD_ARG(nc->user_data));
}
}

//同步执行的情况下 执行完毕了 返回ret
if (ctl_msg.syncflag)
dummy = MG_SEND_FUNC(mgr->ctl[1], (char *)&ret, sizeof(int), 0);
}

增加connid参数,可以指定connid来触发回调,如果connid为0那就是广播到所有connection(给connection初始化connid的时候不能设置为0)。

增加异步、同步控制变量(int *ret),传入NULL还为异步执行,如果设置了就意味着同步发送,发送完毕后才返回。同步方式可以节省内存,异步发送需要新开辟内存。

回调变动:

    指定connid:如果connection还活着,那么正常回调(一次),如果connection关闭了没了,那么回调的mg_connection*是个NULL,用于清理缓冲区

    未指定connid:广播到所有connection上,并且在所有connection回调完毕后,附加一次mg_connection*为NULL的广播,表示广播结束,用于清理缓冲区


 


小魔改:

上述保存connection的connid用的是mg_connection的user_data变量,但是我们实现业务肯定也有一堆数据要存储,而且不同类型的请求例如http和ws要保存的数据类型也不一样,区分处理很麻烦,随手魔改了mg_connection结构体,直接给他加上了connid这个变量。开始做第一版的时候,用了mg_connection结构体里面的sock这个变量作为connid,也就是socket的句柄,不过后来想想大并发下socket关闭后再建立新连接可能句柄是会重复的,那就失去唯一的作用了,所以新增了connid变量。



好啦,完事!


用压力测试跑了下几十个并发,对比发现低并发下跟其他库比如libevent差不多,满足要求。



=========================================================

2021.06.25更新

虽然上述的修改满足了要求,但是却修改了mongoose官方的代码,而且改动很细节,所以后来直接优化了一版,将上述的方法封装到了自己的C++代码中,mongoose直接引用官方6.18版本的代码即可,无需任何改动。

=========================================================

 

=========================================================

2021.07.23更新

有朋友指出官方的6.18版本无法编译通过,发现还真的是有个结构体声明有问题,当时开发我拿的是假的6.18么?

根据官方最后的6.18版本,重新调整。。。

增加静态文件服务器模式StartFileServer方法,该方法不能与StartServer同时使用,二选一。静态文件本地路径要求是"/"分割,不能用windows的"\"。

例如StartFileServer("127.0.0.1:8686", "D:/");

=========================================================


完整代码(2021.07.23更新):

需要依赖无锁队列库:https://github.com/cameron314/concurrentqueue

如果不开启ssl则无其他第三方依赖库。

C++11标准,编译器为vs2013。



FHttpServer.h

#pragma once

#include <string>
#include <chrono>
#include <vector>
#include <map>
#include <thread>
#include <mutex>
#include <functional>
#include "queue/blockingconcurrentqueue.h"


//#define MG_ENABLE_SSL 1
#include "mongoose.h"


typedef std::vector <std::pair<std::string, std::string>>  StringPairArray;


//每个连接的userdata
typedef struct
{
uint32_t connID;
std::string uri; //ws依赖 用于回调的map key
std::string wsname;//ws依赖 用于广播 类似组名 可重复
}MG_CONNECT_USERDATA;


//多线程任务队列
typedef struct
{
union socket_address saddr;
//uint32_t connFlags;
uint32_t connID;
int event_type;

std::string body; //如果是websocket 这里是data
std::string method;
std::string uri;
std::string query;
std::string proto;
StringPairArray headers;
int wsflags;  //ws frame事件中的flags
}MG_EVENT_DATA, *MG_EVENT_DATA_PTR;


class FHttpServer;


//http请求信息
class FHttpRequest
{
//不可复制
FHttpRequest(const FHttpRequest &) = delete;
FHttpRequest &operator=(const FHttpRequest &) = delete;

MG_EVENT_DATA_PTR m_pEventData;

public:
FHttpRequest(MG_EVENT_DATA_PTR eventdata) : m_pEventData(eventdata){};
~FHttpRequest(){};

uint32_t GetConnID();
std::string GetURI();
std::string GetMethod();
std::string GetRemoteIP();
//从url上面得到参数
std::string GetQueryParam(const char *name);
//从post的数据中得到参数
std::string GetPostParam(const char *name);
std::string GetHeader(const char *name);
std::string GetCookies();
size_t GetDataLen();
std::string GetData();

private:
std::string GetParam(mg_str *query_string, const char *name);
};

//http返回值操作
class FHttpResponse
{
//不可复制
FHttpResponse(const FHttpResponse &) = delete;
FHttpResponse &operator=(const FHttpResponse &) = delete;

FHttpServer *m_pServer;
uint32_t m_connID;
int m_nStatusCode;
std::string m_strStatus;
StringPairArray *m_pDefaultHeaders; //默认的返回headers
StringPairArray m_arrHeaders; //如果修改了,那么使用自己的副本

public:
FHttpResponse(uint32_t connID, FHttpServer *pServer, StringPairArray *defaultHeaders);
~FHttpResponse();
//是否已经发送过返回值了
bool m_bsenddata;

//设置http返回code和reason,要在Send之前调用
void SetHttpStatusCode(int nStatusCode, const char *reason);
//设置header 唯一header名 要在Send之前调用
void SetHttpHeader(const std::string &key, const std::string &value);
//追加header,类似多个SetCookie这种头部 要在Send之前调用
void AppendHttpHeader(const std::string &key, const std::string &value);

//发送320转向
void SetHttpRedirect(const std::string &rurl);

//发送数据 直接发送
void SendHttpContent(const std::string &data, const char *type = NULL);

//以chunk方式发送数据,调用SendChunkContent发送数据,不要直接用SendContent
void SendHttpChunkBegin();
//发送chunk数据,结束chunk发送一个空buf
void SendHttpChunkContent(const std::string &data);


private:
void _SendData(const std::string *content);
void _MakeResponseHeaders(std::string *data);
};

//websocket的消息信息
class fsWSMessage
{
MG_EVENT_DATA_PTR m_pEventData;

public:
fsWSMessage(MG_EVENT_DATA_PTR eventdata) : m_pEventData(eventdata){};
~fsWSMessage(){};

uint32_t GetConnID();
std::string GetMsg();
int GetFlag();
};

//websocket回话操作
class fsWSSession
{
FHttpServer *m_pServer;
uint32_t m_connID;
public:
fsWSSession(uint32_t connID, FHttpServer *pServer) :
m_connID(connID),
m_pServer(pServer){};
~fsWSSession(){};

//设置该ws连接的name
void SetName(const std::string &name);

//发送websocket数据
void SendFrameMsg(const std::string &data, int type = WEBSOCKET_OP_TEXT);

//关闭连接
void CloseConnect();
};

class FHttpServer
{
//静态文件服务器
struct mg_serve_http_opts m_mHttpServerOpts;
//mg_serve_http_opts傻逼结构体用的是指针,还要保存下来字符串变量
std::string m_strlocalpath;
bool m_bisfileserver;

struct mg_mgr m_mMgr;
bool m_exitflag;

//线程之间广播使用
typedef void(*BROADCAST_CALLBACK)(struct mg_connection *nc, void *ev_data);
typedef struct
{
BROADCAST_CALLBACK callback;
uint32_t connid;  //connID,可以遍历指定的connection
void *data;       //传递的数据
}MG_BROADCAST_MSG;
sock_t m_broadsender;

uint32_t m_nSeqConnid;

//默认的返回headers
StringPairArray m_defaultHeaders;

//工作线程和队列
tsBlockQuque<MG_EVENT_DATA_PTR> m_TaskQueue;
std::vector <std::thread> m_threadPool;

//http回调方法和映射表
typedef std::function<void(FHttpRequest &, FHttpResponse &)> funcHttpCallback;
std::map <std::string, funcHttpCallback> m_mapHttpRouter;
//websocket回调方法和映射表
typedef std::function<void(FHttpRequest &, fsWSSession &)> funcWSOnReadyCallback;
typedef std::function<void(fsWSMessage &, fsWSSession &)> funcWSOnMsgCallback;
typedef std::function<void(uint32_t)> funcWSOnCloseCallback;
struct stWebSocketCallback
{
funcWSOnReadyCallback onready;
funcWSOnMsgCallback onmessage;
funcWSOnCloseCallback onclose;
};
std::map <std::string, stWebSocketCallback> m_mapWSRouter;

public:
FHttpServer();
~FHttpServer();

bool StartFileServer(const std::string &addr, const std::string &localpath);
//启动服务 addr可以是"8888", ":8888", "127.0.0.1:8888"; nThread为工作线程数
bool StartServer(const std::string &addr, int nThread);
//停止服务
void StopServer();
//添加默认返回的http头,唯一值
void AddDefaultHeader(const std::string &key, const std::string &value);
//添加http回调接口
void AddHttpHandler(const std::string &uri, funcHttpCallback callback);
//添加websocket回调接口
void AddWSHandler(const std::string &uri, 
funcWSOnReadyCallback onready,
funcWSOnMsgCallback onmessage,
funcWSOnCloseCallback onclose);

//////////////////////////////////////////////////////////////////////////
//websocket使用的接口
//////////////////////////////////////////////////////////////////////////
//发送数据到connID这个ws连接上
void SendWSFrame(uint32_t connID, const std::string &data, int type = WEBSOCKET_OP_TEXT);
//关闭ws连接
void CloseWSConn(uint32_t connID);
//广播数据到name这个组
void BroadcastWS(const std::string &name, const std::string &data, int type = WEBSOCKET_OP_TEXT);
//////////////////////////////////////////////////////////////////////////


//内部使用的接口,外部不要调用
void _settaskquque(MG_EVENT_DATA_PTR eventdata);
bool _ishttphandler(const std::string &uri);
bool _iswshandler(const std::string &uri);
bool _isstop();
void _broadcast(uint32_t connID, void *data, BROADCAST_CALLBACK cb);

private:
static void onEventCallback(mg_connection *nc, int event_type, void *event_data);
void TaskQuqueRunner();
void WorkEventHandler(MG_EVENT_DATA_PTR eventdata);
};


FHttpServer.cpp

#include "stdafx.h"
#include "FHttpServer.h"


#define is_websocket(nc) (nc->flags & MG_F_IS_WEBSOCKET)
#define MG_REQUEST_MAXLENGTH 10 * 1024 * 1024   //10M

FHttpServer::FHttpServer()
{
mg_mgr_init(&m_mMgr, this);

m_bisfileserver = false;
m_nSeqConnid = 1;
m_exitflag = false;

//默认返回的http头
AddDefaultHeader("Content-Type", "text/plain; charset=GBK");
AddDefaultHeader("Connection", "Keep-Alive");
}


FHttpServer::~FHttpServer()
{
StopServer();
}

bool FHttpServer::StartFileServer(const std::string &addr, const std::string &localpath)
{
m_strlocalpath = localpath;
memset(&m_mHttpServerOpts, 0, sizeof(struct mg_serve_http_opts));
m_mHttpServerOpts.document_root = m_strlocalpath.c_str();
m_mHttpServerOpts.enable_directory_listing = "yes";

m_bisfileserver = true;
return StartServer(addr, 1);
}

bool FHttpServer::StartServer(const std::string &addr, int nThread)
{
//mg_connection *connection = mg_bind(&m_mMgr, addr.c_str(), FHttpServer::OnEventHandler, this);
mg_connection *connection = mg_bind(&m_mMgr, addr.c_str(), onEventCallback);
if (connection == NULL)
{
printf("listen server %s err.\n", addr.c_str());
return false;
}

//  mg_register_http_endpoint(connection, "/upload", [](struct mg_connection *nc, int ev, void *p){
//  struct mg_http_multipart_part *mp = (struct mg_http_multipart_part *) p;
//  printf("endpoint eventtype:%d \n", ev, mp->status);
//  });

//用于异步通知主线程干活 不修改mongoose源码模拟和扩展mg_broadcast方法
mg_connection *udprecv = mg_bind(&m_mMgr, "udp://127.0.0.1:0", [](mg_connection *nc, int event_type, void *event_data)
{
if (event_type == MG_EV_RECV)
{    
struct mbuf *io = &nc->recv_mbuf;
if (io->len == sizeof(MG_BROADCAST_MSG))
{
MG_BROADCAST_MSG *pctlmsg = (MG_BROADCAST_MSG *)io->buf;
if (pctlmsg->callback != NULL)
{

struct mg_mgr *mgr = nc->mgr;
struct mg_connection *nc;
if (pctlmsg->connid == 0)
{
//先广播所有正常的连接
for (nc = mg_next(mgr, NULL); nc != NULL; nc = mg_next(mgr, nc))
{
if (nc->user_data)
pctlmsg->callback(nc, pctlmsg->data);
}
//最后发送一个nc为NULL的回调 表示广播结束了 用于清理内存等等
pctlmsg->callback(NULL, pctlmsg->data);
}
else
{
for (nc = mg_next(mgr, NULL); nc != NULL; nc = mg_next(mgr, nc))
{
if (nc->user_data && ((MG_CONNECT_USERDATA *)nc->user_data)->connID == pctlmsg->connid)
break;
}
pctlmsg->callback(nc, pctlmsg->data);
}
}
}

// In case of UDP, Mongoose creates new virtual connection for
// incoming messages
// We can keep it (and it will be reused for another messages from
// the same address) or we can close it (this saves some memory, but
// decreases perfomance, because it forces creation of connection
// for every incoming dgram)
nc->flags |= MG_F_SEND_AND_CLOSE;
// Discard message from recv buffer
mbuf_remove(io, io->len);

}//eventhandler
});
if (udprecv == NULL)
{
printf("listen broadcase channel err.\n");
return false;
}
//建立发送者
union socket_address broadrecvsa;
socklen_t len = sizeof(broadrecvsa.sin);
getsockname(udprecv->sock, &broadrecvsa.sa, &len);
//建立发送者的udp socket
union socket_address udpsasender;
memset(&udpsasender, 0, sizeof(udpsasender));
len = sizeof(udpsasender.sin);
udpsasender.sin.sin_family = AF_INET;
udpsasender.sin.sin_addr.s_addr = htonl(0x7f000001); /* 127.0.0.1 */
m_broadsender = socket(AF_INET, SOCK_DGRAM, 0);
if (m_broadsender == INVALID_SOCKET)
{
printf("make broadcase channel err.\n");
return false;
}
if (bind(m_broadsender, &udpsasender.sa, len) != 0)
{
printf("bind broadcase channel err.\n");
return false;
}
if (connect(m_broadsender, &broadrecvsa.sa, len) != 0)
{
printf("connect broadcase channel err.\n");
return false;
}

//both http and websocket
mg_set_protocol_http_websocket(connection);

//初始化工作队列
for (int i = 0; i < nThread; i++)
m_threadPool.emplace_back(&FHttpServer::TaskQuqueRunner, this);

std::thread([this]{
// loop
while (!m_exitflag)
mg_mgr_poll(&m_mMgr, 200); // ms
}).detach();

printf("started server on: %s(tid:%d)\n", addr.c_str(), GetCurrentThreadId());

return true;
}

void FHttpServer::StopServer()
{
if (m_exitflag)
return;

printf("set stop flag.\n");
m_exitflag = true;

printf("free mgr.\n");
mg_mgr_free(&m_mMgr);

printf("wait for work thread finish...\n");
for (std::thread &th : m_threadPool)
{
if (th.joinable())
th.join();
}

printf("server stopped.");
}

void FHttpServer::AddDefaultHeader(const std::string &key, const std::string &value)
{
for (auto &header : m_defaultHeaders)
{
if (mg_ncasecmp(key.c_str(), header.first.c_str(), key.length()) == 0)
{
header.second = value;
return;
}
}

m_defaultHeaders.emplace_back(key, value);
}

void FHttpServer::AddHttpHandler(const std::string &uri, funcHttpCallback callback)
{
m_mapHttpRouter.emplace(uri, callback);
}

void FHttpServer::AddWSHandler(const std::string &uri,
funcWSOnReadyCallback onready,
funcWSOnMsgCallback onmessage,
funcWSOnCloseCallback onclose)
{
stWebSocketCallback cb = {onready, onmessage, onclose};
m_mapWSRouter.emplace(uri, cb);
}

void FHttpServer::SendWSFrame(uint32_t connID, const std::string &data, int type /*= WEBSOCKET_OP_TEXT*/)
{
return fsWSSession(connID, this).SendFrameMsg(data, type);
}

void FHttpServer::CloseWSConn(uint32_t connID)
{
fsWSSession(connID, this).CloseConnect();
}

void FHttpServer::BroadcastWS(const std::string &name, const std::string &data, int type /*= WEBSOCKET_OP_TEXT*/)
{
struct wsData
{
int type;
std::string name;
std::string buf;
};
wsData *param = new wsData{ type, name, data };

//传入connid为0,广播到所有nc上
_broadcast(0, param, [](struct mg_connection *nc, void *data){
if (nc)
{
if (is_websocket(nc))
{
wsData *p = (wsData *)data;
MG_CONNECT_USERDATA *pUserdata = (MG_CONNECT_USERDATA *)nc->user_data;
if (pUserdata->wsname == p->name)
mg_send_websocket_frame(nc, p->type, p->buf.c_str(), p->buf.length());
}
}
else
{
//nc为NULL就是广播完毕了 这类清理内存
delete ((wsData *)data);
}
});
}

void FHttpServer::_settaskquque(MG_EVENT_DATA_PTR eventdata)
{
m_TaskQueue.enqueue(eventdata);
}

bool FHttpServer::_ishttphandler(const std::string &uri)
{
if (m_mapHttpRouter.find(uri) == m_mapHttpRouter.end())
return false;
return true;
}

bool FHttpServer::_iswshandler(const std::string &uri)
{
if (m_mapWSRouter.find(uri) == m_mapWSRouter.end())
return false;
return true;
}

void FHttpServer::_broadcast(uint32_t connID, void *data, BROADCAST_CALLBACK cb)
{
MG_BROADCAST_MSG ctlmsg;
ctlmsg.connid = connID;
ctlmsg.callback = cb;
ctlmsg.data = data;

send(m_broadsender, (const char *)&ctlmsg, sizeof(ctlmsg), 0);
};

bool FHttpServer::_isstop()
{
return m_exitflag;
}

//单线程回调函数
void FHttpServer::onEventCallback(mg_connection *nc, int event_type, void *event_data)
{
FHttpServer *pServer = (FHttpServer *)nc->mgr->user_data;
MG_CONNECT_USERDATA *pUserData = (MG_CONNECT_USERDATA *)nc->user_data;

//debug
  //if (event_type != MG_EV_POLL)
// printf("EventCallback(%d): type:%d, sockid:%d\n", GetCurrentThreadId(), event_type, (pUserData ? pUserData->connID : 0));
//  if (event_type == MG_EV_RECV)
//  {
//  struct mbuf *io = &nc->recv_mbuf;
//  WriteBufferToFile("request.txt", io->buf, io->len, TRUE);
//  }

//连接上后 创建出来connid等信息
if (event_type == MG_EV_ACCEPT)
{
pUserData = new MG_CONNECT_USERDATA;
pUserData->connID = pServer->m_nSeqConnid;

nc->user_data = pUserData;
//数据包最大限制
//nc->recv_mbuf_limit = pServer->m_HttpMaxLength;

pServer->m_nSeqConnid++;
//connid使用0来标记 所以要+1
if (pServer->m_nSeqConnid == 0)
pServer->m_nSeqConnid++;

return;
}

//关闭事件 清理user_data
if (event_type == MG_EV_CLOSE)
{
if (is_websocket(nc))
{
//websocket的话要回调
MG_EVENT_DATA_PTR cbdata = new MG_EVENT_DATA;
cbdata->event_type = event_type;
cbdata->connID = pUserData->connID;
cbdata->uri = pUserData->uri;

pServer->_settaskquque(cbdata);
}

//删掉user_data
delete pUserData;
nc->user_data = NULL;

return;
}


//http请求和websocket握手完毕 要读取http request信息
if (event_type == MG_EV_HTTP_REQUEST || 
event_type == MG_EV_WEBSOCKET_HANDSHAKE_DONE ||
event_type == MG_EV_WEBSOCKET_FRAME)
{
if (event_type == MG_EV_HTTP_REQUEST)
{
if (pServer->m_bisfileserver)
{
mg_serve_http(nc, (http_message *)event_data, pServer->m_mHttpServerOpts);
return;
}

//处理映射表 不在映射表的就不加入队列了
std::string uri;
uri.assign(((http_message *)event_data)->uri.p, ((http_message *)event_data)->uri.len);
auto iter = pServer->m_mapHttpRouter.find(uri);
if (iter == pServer->m_mapHttpRouter.end())
{
//看看websocket
auto wsiter = pServer->m_mapWSRouter.find(uri);
if (wsiter == pServer->m_mapWSRouter.end())
{
//不支持的uri映射 返回403吧
mg_http_send_error(nc, 403, "Not Support.");
return;
}
}
}

MG_EVENT_DATA_PTR cbdata = new MG_EVENT_DATA;
cbdata->event_type = event_type;
cbdata->connID = pUserData->connID;

if (event_type == MG_EV_WEBSOCKET_FRAME)
{
websocket_message *wsmsg = (websocket_message *)event_data;
cbdata->body.assign((char *)wsmsg->data, wsmsg->size);
cbdata->wsflags = wsmsg->flags;
//从user_data里面读取uri
cbdata->uri = pUserData->uri;
}
else
{
http_message *httpmsg = (http_message *)event_data;

//用于读取远程IP
cbdata->saddr = nc->sa;

//http request信息
cbdata->body.assign(httpmsg->body.p, httpmsg->body.len);
cbdata->method.assign(httpmsg->method.p, httpmsg->method.len);
cbdata->uri.assign(httpmsg->uri.p, httpmsg->uri.len);
cbdata->query.assign(httpmsg->query_string.p, httpmsg->query_string.len);
cbdata->proto.assign(httpmsg->proto.p, httpmsg->proto.len);
for (int i = 0; i < MG_MAX_HTTP_HEADERS; i++)
{
if (httpmsg->header_names[i].len == 0)
break;
cbdata->headers.emplace_back(std::string(httpmsg->header_names[i].p, httpmsg->header_names[i].len), std::string(httpmsg->header_values[i].p, httpmsg->header_values[i].len));
}

//ws默认name就是uri 用于广播
if (event_type == MG_EV_WEBSOCKET_HANDSHAKE_DONE)
pUserData->wsname = pUserData->uri = cbdata->uri;
}

//printf("AddEvent Data(%d): %d\n", GetCurrentThreadId(), cbdata->event_type);
pServer->_settaskquque(cbdata);
}
}

void FHttpServer::TaskQuqueRunner()
{
while (true)
{
MG_EVENT_DATA_PTR eventdata;
if (m_TaskQueue.wait_dequeue_timed(eventdata, std::chrono::seconds(1)))
{
//printf("TaskQuque Get Data(%d): %d\n", GetCurrentThreadId(), eventdata->event_type);
WorkEventHandler(eventdata);

//用完了要删掉
delete eventdata;
}
else
{
//printf("Empty quque.\n");

//结束标记并且队列没有数据了 返回
if (m_exitflag)
break;
}
}

return;
}
 
void FHttpServer::WorkEventHandler(MG_EVENT_DATA_PTR eventdata)
 {
//printf("WorkHandler Data(%d): %d\n", GetCurrentThreadId(), eventdata->event_type);

  if (eventdata->event_type == MG_EV_HTTP_REQUEST)
  {
  //普通http请求
//OnHttpHandler(eventdata);

//准备变量
FHttpRequest request(eventdata);
FHttpResponse response(eventdata->connID, this, &m_defaultHeaders);

//回调
m_mapHttpRouter[eventdata->uri](request, response);
return;
  }

//剩下的都是ws的事件了
stWebSocketCallback wsCB = m_mapWSRouter[eventdata->uri];
if (eventdata->event_type == MG_EV_WEBSOCKET_HANDSHAKE_DONE && wsCB.onready)
wsCB.onready(FHttpRequest(eventdata), fsWSSession(eventdata->connID, this));
else if (eventdata->event_type == MG_EV_WEBSOCKET_FRAME && wsCB.onmessage)
wsCB.onmessage(fsWSMessage(eventdata), fsWSSession(eventdata->connID, this));
else if (eventdata->event_type == MG_EV_CLOSE && wsCB.onclose)
wsCB.onclose(eventdata->connID);
 }

uint32_t FHttpRequest::GetConnID()
{
return m_pEventData->connID;
}

std::string FHttpRequest::GetURI()
{
return m_pEventData->uri;
}

std::string FHttpRequest::GetMethod()
{
return m_pEventData->method;
}

std::string FHttpRequest::GetRemoteIP()
{
char addr[32];
mg_sock_addr_to_str(&m_pEventData->saddr, addr, sizeof(addr), MG_SOCK_STRINGIFY_IP);
return std::string(addr);
}

std::string FHttpRequest::GetParam(mg_str *query_string, const char *name)
{
char value[128];
int ret = mg_get_http_var(query_string, name, value, sizeof(value) - 1);
if (ret <= 0)
return std::string();
if (ret < sizeof(value))
return std::string(value, ret);

//数据过长了
std::unique_ptr<char> buf(new char[ret + 1]);
ret = mg_get_http_var(query_string, name, buf.get(), ret + 1);
return std::string(buf.get(), ret);
}

std::string FHttpRequest::GetQueryParam(const char *name)
{
mg_str query_string;
query_string.p = m_pEventData->query.c_str();
query_string.len = m_pEventData->query.length();
return GetParam(&query_string, name);
}

std::string FHttpRequest::GetPostParam(const char *name)
{
mg_str query_string;
query_string.p = m_pEventData->body.c_str();
query_string.len = m_pEventData->body.length();
return GetParam(&query_string, name);
}

std::string FHttpRequest::GetHeader(const char *name)
{
for (auto &header : m_pEventData->headers)
{
if (mg_ncasecmp(name, header.first.c_str(), header.first.length()) == 0)
return header.second;
}

return std::string();
}

std::string FHttpRequest::GetCookies()
{
return GetHeader("Cookie");
}

size_t FHttpRequest::GetDataLen()
{
return m_pEventData->body.length();
}

std::string FHttpRequest::GetData()
{
return m_pEventData->body;
}

FHttpResponse::FHttpResponse(uint32_t connID, FHttpServer *pServer, StringPairArray *defaultHeaders) :
m_connID(connID),
m_pServer(pServer),
m_nStatusCode(200),
m_strStatus("OK"),
m_pDefaultHeaders(defaultHeaders),
m_bsenddata(false)
{

}

FHttpResponse::~FHttpResponse()
{
if (!m_bsenddata)
SendHttpContent(std::string());
}

void FHttpResponse::SetHttpStatusCode(int nStatusCode, const char *reason)
{
m_nStatusCode = nStatusCode;
m_strStatus = reason;
}

void FHttpResponse::SetHttpHeader(const std::string &key, const std::string &value)
{
//修改了header 就建立自己的副本
if (m_pDefaultHeaders)
{
m_arrHeaders = *m_pDefaultHeaders;
m_pDefaultHeaders = NULL;
}

for (auto &header : m_arrHeaders)
{
if (mg_ncasecmp(key.c_str(), header.first.c_str(), key.length()) == 0)
{
header.second = value;
return;
}
}

m_arrHeaders.emplace_back(key, value);
}

void FHttpResponse::AppendHttpHeader(const std::string &key, const std::string &value)
{
//修改了header 就建立自己的副本
if (m_pDefaultHeaders)
{
m_arrHeaders = *m_pDefaultHeaders;
m_pDefaultHeaders = NULL;
}

m_arrHeaders.emplace_back(key, value);
}

void FHttpResponse::SetHttpRedirect(const std::string &rurl)
{
m_bsenddata = true;

m_pServer->_broadcast(m_connID, new std::string(rurl), [](struct mg_connection *nc, void *data){
std::string *rurl = (std::string *)data;
if (nc)
mg_http_send_redirect(nc, 302, mg_mk_str(rurl->c_str()), mg_mk_str(NULL));

delete rurl;
});
}

void FHttpResponse::SendHttpContent(const std::string &data, const char *type /*= NULL*/)
{
if (type)
SetHttpHeader("Content-Type", type);

_SendData(&data);
}

void FHttpResponse::SendHttpChunkBegin()
{
_SendData(NULL);
}

void FHttpResponse::SendHttpChunkContent(const std::string &data)
{
m_pServer->_broadcast(m_connID, new std::string(data), [](struct mg_connection *nc, void *data){
std::string *buf = (std::string *)data;
if (nc)
mg_send_http_chunk(nc, buf->c_str(), buf->length());

delete buf;
});
}

void FHttpResponse::_MakeResponseHeaders(std::string *data)
{
//拼接header
StringPairArray *headers = m_pDefaultHeaders;
if (headers == NULL)
headers = &m_arrHeaders;
size_t nHeanderSize = headers->size();
for (size_t i = 0; i < nHeanderSize; i++)
{
std::string str = headers->at(i).first + ": " + headers->at(i).second + "\r\n";
data->append(str);
}
}

void FHttpResponse::_SendData(const std::string *content)
{
//不要重复发送
if (m_bsenddata)
return;

std::string *pSendData = new std::string("HTTP/1.1 " + std::to_string(m_nStatusCode) + " " + m_strStatus + "\r\n");
_MakeResponseHeaders(pSendData);

if (content == NULL)
{
//开启了chunk模式
pSendData->append("Transfer-Encoding: chunked\r\n\r\n");
}
else
{
//有Content
pSendData->append("Content-Length: " + std::to_string(content->length()) + "\r\n\r\n");

//拼接数据
pSendData->append(*content);
}

//发送 要用mg_broadcast来完成回调 异步发送
m_pServer->_broadcast(m_connID, pSendData, [](struct mg_connection *nc, void *data){
std::string *buf = (std::string *)data;
if (nc)
mg_send(nc, buf->c_str(), buf->length());

delete buf;
});

m_bsenddata = true;
}

uint32_t fsWSMessage::GetConnID()
{
return m_pEventData->connID;
}

std::string fsWSMessage::GetMsg()
{
return m_pEventData->body;
}

int fsWSMessage::GetFlag()
{
return m_pEventData->wsflags;
}

void fsWSSession::SetName(const std::string &name)
{
m_pServer->_broadcast(m_connID, new std::string(name), [](struct mg_connection *nc, void *data){
std::string *name = (std::string *)data;
if (nc)
((MG_CONNECT_USERDATA *)nc->user_data)->wsname = *name;

delete name;
});
}

void fsWSSession::SendFrameMsg(const std::string &data, int type /*= WEBSOCKET_OP_TEXT*/)
{
int ret = 0;
struct wsData
{
int type;
std::string buf;
};
wsData *param = new wsData{ type, data };

//需要返回值 所以使用同步的方式发送
m_pServer->_broadcast(m_connID, param, [](struct mg_connection *nc, void *data){
wsData *p = (wsData *)data;
if (nc)
mg_send_websocket_frame(nc, p->type, p->buf.c_str(), p->buf.length());

delete p;
});
}

void fsWSSession::CloseConnect()
{
m_pServer->_broadcast(m_connID, NULL, [](struct mg_connection *nc, void *data){
if (nc)
mg_send_websocket_frame(nc, WEBSOCKET_OP_CLOSE, "bye", 3);
});
}


 


最后使用方法:

	FHttpServer server;
server.AddHttpHandler("/hello", [](fsRequest &req, fsResponse &response){
  printf("uri:%s\n", req.GetURI().c_str());
  printf("remote:%s\n", req.GetRemoteIP().c_str());
  printf("method:%s\n", req.GetMethod().c_str());
  printf("p:%s\n", req.GetQueryParam("p").c_str());
  printf("body(%d):%s\n", req.GetDataLen(), req.GetData().c_str());

response.SendHttpContent("hello word!");
});

uint32_t connID = -1;
server.AddWSHandler("/ws", 
[&](fsRequest &req, fsWSSession &session)
{
printf("ws on ready %s.\n", req.GetQueryParam("id").c_str());
session.SendFrame("ready.");
session.SetName(req.GetQueryParam("id"));
connID = req.GetConnID();
},  //onready
[](fsWSMessage &req, fsWSSession &session)
{
printf("body:%s\n", req.GetMsg().c_str());
session.SendFrame(req.GetMsg());
},
[](uint32_t connID)
{
printf("ws on close.\n");
} //onclose
);

server.StartServer("8881", 32);

while (true)
{
int x = getchar();
if (x == 'q')
break;

server.SendWSFrame(connID, "hello");
server.BroadcastWS("123", "broadcase msg.");
}

server.StopServer();





2020/10/30 | Tags:mongoose,Http,websocket,server | C/C++代码 | 查看评论(8)

相关文章:

Powered By Z-Blog  触屏版 | WAP版 | 电脑版