使用libevent进行多线程socket编程demo
最近要对一个用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的更多相关文章
- day08 多线程socket 编程,tcp粘包处理
复习下socket 编程的步骤: 服务端: 1 声明socket 实例 server = socket.socket() #括号里不写 默认地址簇使用AF_INET 即 IPv4 ...
- linux多线程socket编程一些心得
http://hi.baidu.com/netpet/blog/item/2cc79216d9012b54f2de32b9.html 前段时间将新的web模型办到linux上来,用epoll代替了IO ...
- Java 多线程Socket编程通讯--实现聊天室代码
1.创建服务器类 import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import ja ...
- 多线程socket编程示例
工程: 代码: package com.my.socket.business; /** * 业务实现类 * * @author ZY * */ public class CoreMisBusiness ...
- Qt中采用多线程实现Socket编程
Socket通常也称作"套接字",应用程序通常通过"套接字"向网络发出请求或者应答网络请求. 本文介绍的是Qt中采用多线程Socket编程,由于工作的需要,开始 ...
- 如何为可扩展系统进行Java Socket编程
从简单I/O到异步非阻塞channel的Java Socket模型演变之旅 上世纪九十年代后期,我在一家在线视频游戏工资工作,在哪里我主要的工作就是编写Unix Unix Berkley Socket ...
- 基于libevent, libuv和android Looper不断演进socket编程 - 走向架构师之路 - 博客频道 - CSDN.NET
基于libevent, libuv和android Looper不断演进socket编程 - 走向架构师之路 - 博客频道 - CSDN.NET 基于libevent, libuv和android L ...
- 为什么socket编程要用到多线程
不得不佩服计算机先驱的设计:socket编程为什么需要多线程.如果只有一个ServerSocket线程,那么如下代码: public void start() throws Exception { S ...
- Android应用开发提高篇(4)-----Socket编程(多线程、双向通信)
链接地址:http://www.cnblogs.com/lknlfy/archive/2012/03/04/2379628.html 一.概述 关于Socket编程的基本方法在基础篇里已经讲过,今天把 ...
随机推荐
- CDOJ 92 Journey(LCA&RMQ)
题目连接:http://acm.uestc.edu.cn/#/problem/show/92 题意:给定一棵树,最后给加一条边,给定Q次查询,每次查询加上最后一条边之后是否比不加这条边要近,如果近的话 ...
- codevs愚蠢的矿工(树形DP)
/* 树形DP 根节点一定有人 然后 剩下的人没到每个孩子去 因为孩子数可能很多 不好枚举 所以转二叉树 分两部分 O(sum)就可以了 当然 转二叉树候必须顾及原来树的一些性质 如不能只选左孩子 转 ...
- js实现图片上传及预览---------------------->>兼容ie6-8 火狐以及谷歌
<head runat="server"> <title>图片上传及预览(兼容ie6/7/8 firefox/chrome)</title> & ...
- div如何加滚动条
<div style="position:absolute; height:400px; overflow:auto"></div>div 设置滚动条显示: ...
- Xcode 的正确打开方式——Debugging(转)
转自CocoaChina http://www.cocoachina.com/ios/20150225/11190.html 程序员日常开发中有大量时间都会花费在 debug 上,从事 iOS 开发不 ...
- 了解HTML的代码注释
什么是代码注释?代码注释的作用是帮助程序员标注代码的用途,过一段时间后再看你所编写的代码,就能很快想起这段代码的用途. 代码注释不仅方便程序员自己回忆起以前代码的用途,还可以帮助其他程序员很快的读懂你 ...
- Swift - 28 - 内部参数名和外部参数名
//: Playground - noun: a place where people can play import UIKit // 外部参数的作用是为了让程序员调用代码的时候能清晰的看出所传参数 ...
- centos 用户切换
在系统的/etc/.bash_profile中已经配置了各种环境变量. 用账户a登陆,ldd xxx.so查看一切链接正常. 用账户root登陆,ldd xxx.so查看一切链接正常. 用账户a登陆, ...
- c/c++内存机制(一)(转)
转自:http://www.cnblogs.com/ComputerG/archive/2012/02/01/2334898.html 一:C语言中的内存机制 在C语言中,内存主要分为如下5个存储区: ...
- 【USACO 1.2.4】回文平方数
[题目描述] 回文数是指从左向右念和从右向左念都一样的数.如12321就是一个典型的回文数. 给定一个进制B(2<=B<=20,由十进制表示),输出所有的大于等于1小于等于300(十进制下 ...