对于阻塞式socket编程,libevent是一个不错的选择.而关于如何利用libevent来写自己的程序,网上的这方面的资料较少。
仅有几个例子:libevent-echosrv1.c,libevent-echosrv2.c,libevent-echosrv-buffered.c,看本文之前可先download下来看
一看.
1. 对于阻塞式,处理逻辑可放在一个函数里,很清楚.
foo()
{
recv(100); //1.读100个byte
handle(); //2.处理
send(); //3.将handle()产生的data全发走
done(); //4. happy.
}
{
recv(100); //1.读100个byte
handle(); //2.处理
send(); //3.将handle()产生的data全发走
done(); //4. happy.
}
而用libevent把这个函数写成非阻塞式,写法如下(psuedo-code):
foo()
{
event_set(EV_READ,myrecv);
event_add();
}
myrecv()
{
if (EV_TIMEOUT); //暂不考虑timeout
n=recv(); //1
if (n<0)
{
if (errno!=EAGAIN && errno!=EINTR)
err(); //系统错
else
event_add(); //还要读
}
else if (n==0)
err(); //对方close fd;
else
{
if (已读到100)
handle(); //2
else
event_add(); //还要读
}
}
handle()
{
real_handle(); //真正的处理函数
event_set(EV_WRITE,mysend);
event_add();
}
mysend()
{
n=send(); //3
if (n<0)
{
if (errno!=EAGAIN && errno!=EINTR)
err(); //系统错
else
event_add(); //还要写
}
else if (n==0)
err(); //对方close fd;
else
{
if (已发完)
done(); //4 终于到达终点,happy
else
event_add(); //还要写
}
}
{
event_set(EV_READ,myrecv);
event_add();
}
myrecv()
{
if (EV_TIMEOUT); //暂不考虑timeout
n=recv(); //1
if (n<0)
{
if (errno!=EAGAIN && errno!=EINTR)
err(); //系统错
else
event_add(); //还要读
}
else if (n==0)
err(); //对方close fd;
else
{
if (已读到100)
handle(); //2
else
event_add(); //还要读
}
}
handle()
{
real_handle(); //真正的处理函数
event_set(EV_WRITE,mysend);
event_add();
}
mysend()
{
n=send(); //3
if (n<0)
{
if (errno!=EAGAIN && errno!=EINTR)
err(); //系统错
else
event_add(); //还要写
}
else if (n==0)
err(); //对方close fd;
else
{
if (已发完)
done(); //4 终于到达终点,happy
else
event_add(); //还要写
}
}
可见在非阻塞式中逻辑是分布在各个函数里,形成一个可顺序执行的函数链.
2.考虑一个复杂点的情况
proxy()
{
recvP1(100); //1.读100个byte
handleP1(); //2.处理protocol-1
sendP2(); //3.将protocol-1转化成protocol-2后,发走
recvP2(100); //4.读100个byte
handleP2(); //5.处理protocol-2
sendP1(); //6.protocol-2转化成protocol-1后,发走
done(); //7. happy.
}
{
recvP1(100); //1.读100个byte
handleP1(); //2.处理protocol-1
sendP2(); //3.将protocol-1转化成protocol-2后,发走
recvP2(100); //4.读100个byte
handleP2(); //5.处理protocol-2
sendP1(); //6.protocol-2转化成protocol-1后,发走
done(); //7. happy.
}
如果直接将逻辑分布在函数链里,将会造成protocol-1与protocol-2绞在一起,不利于软件分层.
这时可用到callback(使用callback常常就是为了解决分层).
foo()
{
P1=malloc();
P1->callback = sendP2; // P1->callback表示收完P1包之后的动作.
event_set(EV_READ,recvP1,P1); // fd可读时调用recvP1;
event_add();
}
recvP1(fd,
,P1)
{
n=recv();
if (读结束) //为使用代码简洁,以下忽略n<0,n=0,TIMEOUT等情况
handleP1(); //处理protocol-1
else
event_add(); //还要读
}
sendP1(P1)
{
n=send();
if (已发完)
done(); // 结束,happy.
else
event_add();
}
handleP1(P1)
{
P2 =get_P2(P1); //真正的处理函数,p1包-->p2包
event_set(EV_WRITE,p1->callback,P2); //实际上是调用sendP2(),自此进入protocol-2这一层.
event_add();
}
recvP2(P2)
{
P2 = malloc();
P2->callback = sendP1; // 设定收完p2包之后的动作
n=recv();
if (读结束)
handleP2(); //处理protocol-1
else
event_add(); //还要读
}
sendP2(P2)
{
n=send();
if (已发完) {
event_set(EV_READ,recvP2,p2);
event_add();
}
else
event_add();
}
handleP2(P2)
{
P1 =get_P1(P2); //真正的处理函数,p2包-->P1包
event_set(EV_WRITE,p2->callback,P1); //实际上是调用sendP1(),自此返回protocol-1这一层.
event_add();
}
{
P1=malloc();
P1->callback = sendP2; // P1->callback表示收完P1包之后的动作.
event_set(EV_READ,recvP1,P1); // fd可读时调用recvP1;
event_add();
}
recvP1(fd,
,P1){
n=recv();
if (读结束) //为使用代码简洁,以下忽略n<0,n=0,TIMEOUT等情况
handleP1(); //处理protocol-1
else
event_add(); //还要读
}
sendP1(P1)
{
n=send();
if (已发完)
done(); // 结束,happy.
else
event_add();
}
handleP1(P1)
{
P2 =get_P2(P1); //真正的处理函数,p1包-->p2包
event_set(EV_WRITE,p1->callback,P2); //实际上是调用sendP2(),自此进入protocol-2这一层.
event_add();
}
recvP2(P2)
{
P2 = malloc();
P2->callback = sendP1; // 设定收完p2包之后的动作
n=recv();
if (读结束)
handleP2(); //处理protocol-1
else
event_add(); //还要读
}
sendP2(P2)
{
n=send();
if (已发完) {
event_set(EV_READ,recvP2,p2);
event_add();
}
else
event_add();
}
handleP2(P2)
{
P1 =get_P1(P2); //真正的处理函数,p2包-->P1包
event_set(EV_WRITE,p2->callback,P1); //实际上是调用sendP1(),自此返回protocol-1这一层.
event_add();
}
在实际应用时还要考虑以下几点
1.connect/recv/send这些函数失败后的处理,
2.请求块的malloc/free
3.TIMEOUT问题.
4.buffer管理
可见写非阻塞式程序要比阻塞式程序复杂得多。
对libevent有兴趣的,可加QQ群:64559783,一起交流.
浙公网安备 33010602011771号