最近要对一个用libevent写的C/C++项目进行修改,要改成多线程的,故做了一些学习和研究。

libevent是一个用C语言写的开源的一个库。它对socket编程里的epoll/select等功能进行了封装,并且使用了一些设计模式(比如反应堆模式),用事件机制来简化了socket编程。libevent的好处网上有很多,但是初学者往往都看不懂。我打个比方吧,1)假设有N个客户端同时往服务端通过socket写数据,用了libevent之后,你的server程序里就不用再使用epoll或是select来判断都哪些socket的缓冲区里已经收到了客户端写来的数据。当某个socket的缓冲区里有可读数据时,libevent会自动触发一个“读事件”,通过这个“读事件”来调用相应的代码来读取socket缓冲区里的数据即可。换句话说,libevent自己调用select()或是epoll的函数来判断哪个缓冲区可读了,只要可读了,就自动调用相应的处理程序。2)对于“写事件”,libevent会监控某个socket的缓冲区是否可写(一般情况下,只要缓冲区没满就可写),只要可写,就会触发“写事件”,通过“写事件”来调用相应的函数,将数据写到socket里。

以上两个例子分别从“读”和“写”两方面简介了一下,可能不十分准确(但十分准确的描述往往会让人看不懂)。

以下两个链接关于libevent的剖析比较详细,想学习libevent最好看一下。

  1)sparkliang的专栏        2)鱼思故渊的专栏

=========关于libevent使用多线程的讨论=========================

网上很多资料说libevent不支持多线程,也有很多人说libevent可以支持多线程。究竟值不支持呢?我的答案是:得看你的多线程是怎么写的,如何跟libevent结合的。

1)可以肯定的是,libevent的信号事件是不支持多线程的(因为源码里用了个全局变量)。可以看这篇文章(http://blog.csdn.net/sparkliang/article/details/5306809)。(注:libevent里有“超时事件”,“IO事件”,“信号事件”。)

2)对于不同的线程,使用不同的base,是可以的。

3)如果不同的线程使用相同的base呢?——如果在不同的线程里的事件都注册到同一个base上,会有问题吗?

  (http://www.cnblogs.com/zzyoucan/p/3970578.html)这篇博客里提到说,不行!即使加锁也不行。我最近稍微看了部分源码,我的答案是:不加锁会有并发问题,但如果对每个event_add(),event_del()等这些操作event的动作都用同一个临界变量来加锁,应该是没问题的。——貌似也有点问题,如果某个事件没有用event_set()设置为EV_PERSIST,当事件发生时,会被自动删除。有可能线程a在删除事件的时候,线程b却在添加事件,这样还是会出现并发问题。最后的结论是——不行!

========本次实验代码逻辑的说明==========================

我采取的方案是对于不同的线程,使用不同的base。——即每个线程对应一个base,将线程里的事件注册到线程的base上,而不是所有线程里的事件都用同一个base。

一 实验需求描述:

  1)写一个client和server程序。多个client可以同时连接一个server;

  2)client接收用户在标准输入的字符,发往server端;

  3)server端收到后,再把收到的数据处理一下,返回给client;

  4)client收到server返回的数据后,将其打印在终端上。

二 设计方案:

1. client:

  1)  client采用两个线程,主线程接收用户在终端上的输入,并通过socket将用户的输入发往server。

  2)  派生一个子线程,接收server返回来的数据,如果收到数据,就打印出来。

2. server:

  在主线程里监听client有没有连接连过来,如果有,立马accept出一个socket,并创建一个子线程,在子线程里接收client传过来的数据,并对数据进行一些修改,然后将修改后的数据写回到client端。

三 代码实现

1. client代码如下:

 #include <iostream>
#include <sys/select.h>
#include <sys/socket.h>
#include <unistd.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string>
#include <string.h>
#include <event.h>
using namespace std; #define BUF_SIZE 1024 /**
* 连接到server端,如果成功,返回fd,如果失败返回-1
*/
int connectServer(char* ip, int port){
int fd = socket( AF_INET, SOCK_STREAM, );
cout<<"fd= "<<fd<<endl;
if(- == fd){
cout<<"Error, connectServer() quit"<<endl;
return -;
}
struct sockaddr_in remote_addr; //服务器端网络地址结构体
memset(&remote_addr,,sizeof(remote_addr)); //数据初始化--清零
remote_addr.sin_family=AF_INET; //设置为IP通信
remote_addr.sin_addr.s_addr=inet_addr(ip);//服务器IP地址
remote_addr.sin_port=htons(port); //服务器端口号
int con_result = connect(fd, (struct sockaddr*) &remote_addr, sizeof(struct sockaddr));
if(con_result < ){
cout<<"Connect Error!"<<endl;
close(fd);
return -;
}
cout<<"con_result="<<con_result<<endl;
return fd;
} void on_read(int sock, short event, void* arg)
{
char* buffer = new char[BUF_SIZE];
memset(buffer, , sizeof(char)*BUF_SIZE);
//--本来应该用while一直循环,但由于用了libevent,只在可以读的时候才触发on_read(),故不必用while了
int size = read(sock, buffer, BUF_SIZE);
if( == size){//说明socket关闭
cout<<"read size is 0 for socket:"<<sock<<endl;
struct event* read_ev = (struct event*)arg;
if(NULL != read_ev){
event_del(read_ev);
free(read_ev);
}
close(sock);
return;
}
cout<<"Received from server---"<<buffer<<endl;
delete[]buffer;
} void* init_read_event(void* arg){
long long_sock = (long)arg;
int sock = (int)long_sock;
//-----初始化libevent,设置回调函数on_read()------------
struct event_base* base = event_base_new();
struct event* read_ev = (struct event*)malloc(sizeof(struct event));//发生读事件后,从socket中取出数据
event_set(read_ev, sock, EV_READ|EV_PERSIST, on_read, read_ev);
event_base_set(base, read_ev);
event_add(read_ev, NULL);
event_base_dispatch(base);
//--------------
event_base_free(base);
}
/**
* 创建一个新线程,在新线程里初始化libevent读事件的相关设置,并开启event_base_dispatch
*/
void init_read_event_thread(int sock){
pthread_t thread;
pthread_create(&thread,NULL,init_read_event,(void*)sock);
pthread_detach(thread);
}
int main() {
cout << "main started" << endl; // prints Hello World!!!
cout << "Please input server IP:"<<endl;
char ip[];
cin >> ip;
cout << "Please input port:"<<endl;
int port;
cin >> port;
cout << "ServerIP is "<<ip<<" ,port="<<port<<endl;
int socket_fd = connectServer(ip, port);
cout << "socket_fd="<<socket_fd<<endl;
init_read_event_thread(socket_fd);
//--------------------------
char buffer[BUF_SIZE];
bool isBreak = false;
while(!isBreak){
cout << "Input your data to server(\'q\' or \"quit\" to exit)"<<endl;
cin >> buffer;
if(strcmp("q", buffer)== || strcmp("quit", buffer)==){
isBreak=true;
close(socket_fd);
break;
}
cout << "Your input is "<<buffer<<endl;
int write_num = write(socket_fd, buffer, strlen(buffer));
cout << write_num <<" characters written"<<endl;
sleep();
}
cout<<"main finished"<<endl;
return ;
}

client端的代码

  1)在main()里先调用init_read_event_thread()来生成一个子线程,子线程里调用init_read_event()来将socket的读事件注册到libevent的base上,并调用libevent的event_base_dispatch()不断地进行轮询。一旦socket可读,libevent就调用“读事件”上绑定的on_read()函数来读取数据。

  2)在main()的主线程里,通过一个while循环来接收用户从终端的输入,并通过socket将用户的输入写到server端。

-------------------------------------------------------------

2. server端代码如下:

 #include <iostream>
#include <sys/select.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <stdio.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string>
#include <string.h>
#include <event.h>
#include <stdlib.h>
using namespace std; #define SERVER_IP "127.0.0.1"
#define SERVER_PORT 9090
#define BUF_SIZE 1024 struct sock_ev_write{//用户写事件完成后的销毁,在on_write()中执行
struct event* write_ev;
char* buffer;
};
struct sock_ev {//用于读事件终止(socket断开)后的销毁
struct event_base* base;//因为socket断掉后,读事件的loop要终止,所以要有base指针
struct event* read_ev;
}; /**
* 销毁写事件用到的结构体
*/
void destroy_sock_ev_write(struct sock_ev_write* sock_ev_write_struct){
if(NULL != sock_ev_write_struct){
// event_del(sock_ev_write_struct->write_ev);//因为写事件没用EV_PERSIST,故不用event_del
if(NULL != sock_ev_write_struct->write_ev){
free(sock_ev_write_struct->write_ev);
}
if(NULL != sock_ev_write_struct->buffer){
delete[]sock_ev_write_struct->buffer;
}
free(sock_ev_write_struct);
}
} /**
* 读事件结束后,用于销毁相应的资源
*/
void destroy_sock_ev(struct sock_ev* sock_ev_struct){
if(NULL == sock_ev_struct){
return;
}
event_del(sock_ev_struct->read_ev);
event_base_loopexit(sock_ev_struct->base, NULL);//停止loop循环
if(NULL != sock_ev_struct->read_ev){
free(sock_ev_struct->read_ev);
}
event_base_free(sock_ev_struct->base);
// destroy_sock_ev_write(sock_ev_struct->sock_ev_write_struct);
free(sock_ev_struct);
}
int getSocket(){
int fd =socket( AF_INET, SOCK_STREAM, );
if(- == fd){
cout<<"Error, fd is -1"<<endl;
}
return fd;
} void on_write(int sock, short event, void* arg)
{
cout<<"on_write() called, sock="<<sock<<endl;
if(NULL == arg){
cout<<"Error! void* arg is NULL in on_write()"<<endl;
return;
}
struct sock_ev_write* sock_ev_write_struct = (struct sock_ev_write*)arg; char buffer[BUF_SIZE];
sprintf(buffer, "fd=%d, received[%s]", sock, sock_ev_write_struct->buffer);
// int write_num0 = write(sock, sock_ev_write_struct->buffer, strlen(sock_ev_write_struct->buffer));
// int write_num = write(sock, sock_ev_write_struct->buffer, strlen(sock_ev_write_struct->buffer));
int write_num = write(sock, buffer, strlen(buffer));
destroy_sock_ev_write(sock_ev_write_struct);
cout<<"on_write() finished, sock="<<sock<<endl;
} void on_read(int sock, short event, void* arg)
{
cout<<"on_read() called, sock="<<sock<<endl;
if(NULL == arg){
return;
}
struct sock_ev* event_struct = (struct sock_ev*) arg;//获取传进来的参数
char* buffer = new char[BUF_SIZE];
memset(buffer, , sizeof(char)*BUF_SIZE);
//--本来应该用while一直循环,但由于用了libevent,只在可以读的时候才触发on_read(),故不必用while了
int size = read(sock, buffer, BUF_SIZE);
if( == size){//说明socket关闭
cout<<"read size is 0 for socket:"<<sock<<endl;
destroy_sock_ev(event_struct);
close(sock);
return;
}
struct sock_ev_write* sock_ev_write_struct = (struct sock_ev_write*)malloc(sizeof(struct sock_ev_write));
sock_ev_write_struct->buffer = buffer;
struct event* write_ev = (struct event*)malloc(sizeof(struct event));//发生写事件(也就是只要socket缓冲区可写)时,就将反馈数据通过socket写回客户端
sock_ev_write_struct->write_ev = write_ev;
event_set(write_ev, sock, EV_WRITE, on_write, sock_ev_write_struct);
event_base_set(event_struct->base, write_ev);
event_add(write_ev, NULL);
cout<<"on_read() finished, sock="<<sock<<endl;
} /**
* main执行accept()得到新socket_fd的时候,执行这个方法
* 创建一个新线程,在新线程里反馈给client收到的信息
*/
void* process_in_new_thread_when_accepted(void* arg){
long long_fd = (long)arg;
int fd = (int)long_fd;
if(fd<){
cout<<"process_in_new_thread_when_accepted() quit!"<<endl;
return ;
}
//-------初始化base,写事件和读事件--------
struct event_base* base = event_base_new();
struct event* read_ev = (struct event*)malloc(sizeof(struct event));//发生读事件后,从socket中取出数据 //-------将base,read_ev,write_ev封装到一个event_struct对象里,便于销毁---------
struct sock_ev* event_struct = (struct sock_ev*)malloc(sizeof(struct sock_ev));
event_struct->base = base;
event_struct->read_ev = read_ev;
//-----对读事件进行相应的设置------------
event_set(read_ev, fd, EV_READ|EV_PERSIST, on_read, event_struct);
event_base_set(base, read_ev);
event_add(read_ev, NULL);
//--------开始libevent的loop循环-----------
event_base_dispatch(base);
cout<<"event_base_dispatch() stopped for sock("<<fd<<")"<<" in process_in_new_thread_when_accepted()"<<endl;
return ;
} /**
* 每当accept出一个新的socket_fd时,调用这个方法。
* 创建一个新线程,在新线程里与client做交互
*/
void accept_new_thread(int sock){
pthread_t thread;
pthread_create(&thread,NULL,process_in_new_thread_when_accepted,(void*)sock);
pthread_detach(thread);
} /**
* 每当有新连接连到server时,就通过libevent调用此函数。
* 每个连接对应一个新线程
*/
void on_accept(int sock, short event, void* arg)
{
struct sockaddr_in remote_addr;
int sin_size=sizeof(struct sockaddr_in);
int new_fd = accept(sock, (struct sockaddr*) &remote_addr, (socklen_t*)&sin_size);
if(new_fd < ){
cout<<"Accept error in on_accept()"<<endl;
return;
}
cout<<"new_fd accepted is "<<new_fd<<endl;
accept_new_thread(new_fd);
cout<<"on_accept() finished for fd="<<new_fd<<endl;
} int main(){
int fd = getSocket();
if(fd<){
cout<<"Error in main(), fd<0"<<endl;
}
cout<<"main() fd="<<fd<<endl;
//----为服务器主线程绑定ip和port------------------------------
struct sockaddr_in local_addr; //服务器端网络地址结构体
memset(&local_addr,,sizeof(local_addr)); //数据初始化--清零
local_addr.sin_family=AF_INET; //设置为IP通信
local_addr.sin_addr.s_addr=inet_addr(SERVER_IP);//服务器IP地址
local_addr.sin_port=htons(SERVER_PORT); //服务器端口号
int bind_result = bind(fd, (struct sockaddr*) &local_addr, sizeof(struct sockaddr));
if(bind_result < ){
cout<<"Bind Error in main()"<<endl;
return -;
}
cout<<"bind_result="<<bind_result<<endl;
listen(fd, );
//-----设置libevent事件,每当socket出现可读事件,就调用on_accept()------------
struct event_base* base = event_base_new();
struct event listen_ev;
event_set(&listen_ev, fd, EV_READ|EV_PERSIST, on_accept, NULL);
event_base_set(base, &listen_ev);
event_add(&listen_ev, NULL);
event_base_dispatch(base);
//------以下语句理论上是不会走到的---------------------------
cout<<"event_base_dispatch() in main() finished"<<endl;
//----销毁资源-------------
event_del(&listen_ev);
event_base_free(base);
cout<<"main() finished"<<endl;
}

server端的代码

  1)在main()里(运行在主线程中),先设置服务端的socket,然后为主线程生成一个libevent的base,并将一个“读事件”注册到base上。“读事件”绑定了一个on_accept(),每当client有新连接连过来时,就会触发这个“读事件”,进而调用on_accept()方法。

  2)在on_accept()里(运行在主线程中),每当有新连接连过来时,就会accept出一个新的new_fd,并调用accept_new_thread()来创建一个新的子线程。子线程里会调用process_in_new_thread_when_accepted()方法。

  3)process_in_new_thread_when_accepted()方法里(运行在子线程中),创建一个子线程的base,并创建一个“读事件”,注册到“子线程的base”上。并调用event_base_dispatch(base)进入libevent的loop中。当发现new_fd的socket缓冲区中有数据可读时,就触发了这个“读事件”,继而调用on_read()方法。

  4)on_read()方法里(运行在子线程中),从socket缓冲区里读取数据。读完数据之后,将一个“写事件”注册到“子线程的base”上。一旦socket可写,就调用on_write()函数。

  5)on_write()方法(运行在子线程中),对数据进行修改,然后通过socket写回到client端。

  注:其实可以不用注册“写事件”——在on_read()方法中直接修改数据,然后写回到client端也是可以的——但这有个问题。就是如果socket的写缓冲区是满的,那么这时候 write(sock, buffer, strlen(buffer))会阻塞的。这会导致整个on_read()方法阻塞掉,而无法读到接下来client传过来的数据了。而用了libevent的”写事件“之后,虽然 write(sock, buffer, strlen(buffer))仍然会阻塞,但是只要socket缓冲区不可以写就不会触发这个“写事件”,所以程序就不会阻塞,也就不会影响on_read()函数里的流程了。

使用libevent进行多线程socket编程demo的更多相关文章

  1. day08 多线程socket 编程,tcp粘包处理

    复习下socket 编程的步骤: 服务端:   1 声明socket 实例 server = socket.socket()  #括号里不写  默认地址簇使用AF_INET  即 IPv4       ...

  2. linux多线程socket编程一些心得

    http://hi.baidu.com/netpet/blog/item/2cc79216d9012b54f2de32b9.html 前段时间将新的web模型办到linux上来,用epoll代替了IO ...

  3. Java 多线程Socket编程通讯--实现聊天室代码

    1.创建服务器类 import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import ja ...

  4. 多线程socket编程示例

    工程: 代码: package com.my.socket.business; /** * 业务实现类 * * @author ZY * */ public class CoreMisBusiness ...

  5. Qt中采用多线程实现Socket编程

    Socket通常也称作"套接字",应用程序通常通过"套接字"向网络发出请求或者应答网络请求. 本文介绍的是Qt中采用多线程Socket编程,由于工作的需要,开始 ...

  6. 如何为可扩展系统进行Java Socket编程

    从简单I/O到异步非阻塞channel的Java Socket模型演变之旅 上世纪九十年代后期,我在一家在线视频游戏工资工作,在哪里我主要的工作就是编写Unix Unix Berkley Socket ...

  7. 基于libevent, libuv和android Looper不断演进socket编程 - 走向架构师之路 - 博客频道 - CSDN.NET

    基于libevent, libuv和android Looper不断演进socket编程 - 走向架构师之路 - 博客频道 - CSDN.NET 基于libevent, libuv和android L ...

  8. 为什么socket编程要用到多线程

    不得不佩服计算机先驱的设计:socket编程为什么需要多线程.如果只有一个ServerSocket线程,那么如下代码: public void start() throws Exception { S ...

  9. Android应用开发提高篇(4)-----Socket编程(多线程、双向通信)

    链接地址:http://www.cnblogs.com/lknlfy/archive/2012/03/04/2379628.html 一.概述 关于Socket编程的基本方法在基础篇里已经讲过,今天把 ...

随机推荐

  1. Android蓝牙操作笔记(转)

    蓝牙是一种支持设备短距离传输数据的无线技术.android在2.0以后提供了这方面的支持. 从查找蓝牙设备到能够相互通信要经过几个基本步骤(本机做为服务器): 1.设置权限 在manifest中配置 ...

  2. Notice : Soft open files now is 1024, We recommend greater than 10000

    在研究 workerman 时, 报了这个错误, 感觉只是个notice级别的, 就一直给忽略掉了, 今天有时间, 就查了一下. 其实本质就是 ulimit 这个命令 打开一个命令行, 输入 ulim ...

  3. ASP.NET 中的定时执行任务

    在一个网站中,设定一些任务能够在后台定时执行. public static void AddTask(int seconds, Action todo) { HttpRuntime.Cache.Ins ...

  4. mssql sql高效关联子查询的update 批量更新

    /* 使用带关联子查询的Update更新     --1.创建测试表 create TABLE Table1     (     a varchar(10),     b varchar(10),   ...

  5. 【转】Auto Layout 进阶

    原文:http://blog.csdn.net/ysy441088327/article/details/12558097   引言: Auto Layout是iOS6发布后引入的一个全新的布局特性, ...

  6. 使用<br>标签分行显示文本

    对于上一小节的例子,我们想让那首诗显示得更美观些,如显示下面效果: 怎么可以让每一句诗词后面加入一个折行呢?那就可以用到<br />标签了,在需要加回车换行的地方加入<br /> ...

  7. js 验证表单 js提交验证类

    附加:js验证radio是否选择 <script language="javascript">function checkform(obj){for(i=0;i< ...

  8. EBS与FMW集成工作流管理器的检查

    工作流管理器的检查点(DB层面): --1:数据库job aq参数设置,建议设置job_queue_processes>=10 select p.NAME,p.DESCRIPTION,p.VAL ...

  9. 你好,C++(10)这次的C++考试你过了没有?C++中表示逻辑判断的布尔数据类型

    3.4  布尔类型 在日常生活中,我们除了需要使用int类型的变量表示216路公交车:需要使用float类型的变量表示西红柿3.5元一斤,有时候还需要表示一种数据,那就是逻辑状态: “这次的C++考试 ...

  10. 推荐一个有趣的软件"Process Monitor"

    同事给的,用起来感觉很不错,官网地址:http://technet.microsoft.com/en-us/sysinternals/bb896645.aspx 以下为官网介绍: Introducti ...