该聊天室相对于之前的聊天室进行了相应的升级:
之前的聊天室进程中只有一个线程,因此该聊天室程序对于监听描述符上的连接请求以及多个聊天室成员的连接描述符上的读写操作只能完成一个;
该聊天室进程中采取了多线程模式,类似于线程池的思想,一个聊天室进程中可以并发的利用主线程处理监听描述符上的连接请求,同时对于每一个聊天室成员的连接描述符分配一个工作线程,完成与该聊天室成员的读写操作.
具体的思想是(创建多个线程,每个线程运行一个独立的loop循环变量):
1.利用hloop循环中自带的一个eventfd管道,loop循环变量初始化时,就已经完成了对于eventfd[0]调用hio_read()函数注册读操作;
2.我们只需要创建一个hevent变量,绑定一个工作线程的loop循环变量,该连接描述符的io事件变量以及新连接的回调函数,然后再调用hloop_post_event()函数传入hevent变量和工作线程loop变量;
3.该函数内部会将hevent变量加入工作线程loop变量下面的custom_events链表中,然后向eventfd[1]中写入事件个数,从而触发eventfd[0]绑定的io事件变量read回调函数,内部会将custom_events链表中的hevent变量取出,然后作为参数调用该变量中绑定的新连接回调函数;
4.新连接回调函数是由用户定义的一个回调函数:将hevent变量中的连接描述符绑定的io事件变量加入到工作线程所对应的loop循环变量中的I/O事件数组中,然后再调用hio_read()函数以及hio_setcb_read()函数绑定用户定义的读回调函数,从而使得该工作线程实现对于一个聊天室成员的连接描述符的读写操作.
#include "hv/hthread.h"
#include "hv/hloop.h"
#include "hv/hsocket.h"
#include "hv/hbase.h"
#include "list.h"
hloop_t* accept_loop;
hloop_t** worker_loops;
int thread_num = 4;
int port = 1234;
const char *ip = "0.0.0.0";
struct chatroom_s {
hloop_t* loop;//事件循环结构体指针
hio_t* listenio;//监听io结构体
int roomid;//房间号
struct list_head conns;//链表存储房间中的连接
};
struct chatroom_s chatroom;
struct connection_s {
hio_t* connio;// 连接io结构体指针
char addr[SOCKADDR_STRLEN];// 套接字地址
struct list_node node;// 链表结点
};
hloop_t* get_next_loop()
{
static int cur_index = 0;
if(cur_index == thread_num)
cur_index = 0;
return worker_loops[cur_index];
}
void boardcast_talk(struct chatroom_s *room, char *msg, int msglen, hio_t* talk_io)
{
struct list_node* node;
struct connection_s* cur;
list_for_each (node, &room->conns) {
cur = list_entry(node, struct connection_s, node);
if(cur->connio != talk_io)
hio_write(cur->connio, msg, msglen);
}
}
void boardcast(struct chatroom_s *room, char *msg, int msglen)
{
struct list_node* node;
struct connection_s* cur;
list_for_each (node, &room->conns) {
cur = list_entry(node, struct connection_s, node);
hio_write(cur->connio, msg, msglen);
}
}
void on_recv(hio_t *io, void *buf, int readbytes)
{
char localaddrstr[SOCKADDR_STRLEN] = {0};
char peeraddrstr[SOCKADDR_STRLEN] = {0};
printf("[%s] <=> [%s]\n",
SOCKADDR_STR(hio_localaddr(io), localaddrstr),
SOCKADDR_STR(hio_peeraddr(io), peeraddrstr));
printf("< %.*s", readbytes, (char*)buf);
//广播成员说的话
char msg[256] = {0};
int msglen = 0;
struct connection_s* conn = (struct connection_s*)hevent_userdata(io);
msglen = snprintf(msg, sizeof(msg), "成员[%s]:%.*s", conn->addr, readbytes, (char*)buf);
boardcast_talk(&chatroom, msg, msglen, conn->connio);
}
void leave(struct chatroom_s* room, struct connection_s* conn) {
// 从链表里删除
list_del(&conn->node);
// 广播有成员离开聊天室
char msg[256] = {0};
int msglen = snprintf(msg, sizeof(msg), "成员[%s]离开房间[%d]\r\n", conn->addr, room->roomid);
boardcast(room, msg, msglen);
}
void on_close(hio_t* io) {
printf("关闭描述符%d,错误=%d\n", hio_fd(io), hio_error(io));
struct connection_s* conn = (struct connection_s*)hevent_userdata(io);
if (conn) {
hevent_set_userdata(io, NULL);
// 断连离开聊天室
leave(&chatroom, conn);
HV_FREE(conn);
}
}
void new_conn_cb(hevent_t *ev)
{
hio_t *io = (hio_t*)hevent_userdata(ev);
hloop_t *worker_loop = ev->loop;
hio_attach(worker_loop, io);//将该连接io绑定工作线程
//获取服务器和客户端IP地址以及端口
char localaddrstr[SOCKADDR_STRLEN] = {0};//服务器IP:端口号
char peeraddrstr[SOCKADDR_STRLEN] = {0};//客户端IP:端口号
printf("建立连接(连接描述符:%d):%s=====>%s\r\n\r\n",hio_fd(io) , SOCKADDR_STR(hio_localaddr(io), localaddrstr), SOCKADDR_STR(hio_peeraddr(io), peeraddrstr));
char peerport[SOCKADDR_STRLEN] = {0};
int i = 0, j = -1;
while(peeraddrstr[i])//获取端口
{
if(j >= 0) peerport[j++] = peeraddrstr[i];
if(peeraddrstr[i++] == ':') j = 0;
}
//将新连接加入聊天室链表
struct connection_s *conn = NULL;
HV_ALLOC_SIZEOF(conn);
conn->connio = io;
strcpy(conn->addr, peerport);
hevent_set_userdata(io, conn);
list_add(&conn->node, &chatroom.conns);
//设置io读和关闭事件
hio_setcb_close(io, on_close);
hio_setcb_read(io, on_recv);
hio_read(io);
//向新来的成员发送当前聊天室成员列表
char msg[256] = {0};
int msglen = 0;
struct list_node* node;
struct connection_s* cur;
msglen = snprintf(msg, sizeof(msg), "房间[%d] 成员列表:\r\n", chatroom.roomid);
hio_write(io, msg, msglen);
list_for_each (node, &chatroom.conns) {
cur = list_entry(node, struct connection_s, node);
msglen = snprintf(msg, sizeof(msg), "%s\r\n", cur->addr);
hio_write(io, msg, msglen);
}
hio_write(io, "\r\n", 2);
//向全体成员广播新成员加入
msglen = snprintf(msg, sizeof(msg), "新成员加入房间[%d]:%s\r\n\r\n",chatroom.roomid, conn->addr);
boardcast(&chatroom, msg, msglen);
}
void on_accept(hio_t *io)
{
hio_detach(io);//从主线程的loop循环变量中去除连接io
hloop_t* worker_loop = get_next_loop();//获取下一个工作线程的loop循环变量
hevent_t ev;
memset(&ev, 0, sizeof(ev));
ev.loop = worker_loop;
ev.cb = new_conn_cb;
ev.userdata = io;
hloop_post_event(worker_loop, &ev);
}
HTHREAD_RETTYPE worker_thread(void *userdata)
{
hloop_t *worker_loop = (hloop_t*)userdata;
hloop_run(worker_loop);
return 0;
}
void accept_thread()
{
//创建按事件循环
hloop_t* loop = hloop_new(0);
//创建TCP服务器,设置accept回调函数
hio_t* listenio = hloop_create_tcp_server(loop, ip, port, on_accept);//监听所有IP地址,端口号为8080
chatroom.loop = loop;
chatroom.listenio = listenio;
chatroom.roomid = 1;
list_init(&chatroom.conns);
//循环事件体
hloop_run(loop);
//释放事件体
hloop_free(&loop);
}
int main()
{
//创建工作者线程
worker_loops = (hloop_t**) malloc(sizeof(hloop_t*) * thread_num);
for(int i = 0;i < thread_num;i++)
{
worker_loops[i] = hloop_new(HLOOP_FLAG_AUTO_FREE);
hthread_create(worker_thread, worker_loops[i]);
}
accept_thread();//运行主线程
return 0;
}
客户端通讯回显:
服务器事件记录: