用libevent实现的echo服务器及telnet客户端
以下代码在vs 2010编译通过,使用的libevent版本是:libevent-2.0.22,win7环境测试通过。
服务器实现:
1 流程图:
2 代码:
// my_telnet.cpp : Defines the entry point for the console application.
// #include "stdafx.h" #include <string.h>
#include <errno.h>
#include <stdio.h>
#include <signal.h> #ifndef WIN32
#include <netinet/in.h>
# ifdef _XOPEN_SOURCE_EXTENDED
# include <arpa/inet.h>
# endif
#include <sys/socket.h>
#endif #include "event2/bufferevent.h"
#include "event2/buffer.h"
#include "event2/listener.h"
#include "event2/util.h"
#include "event2/event.h"
#include "event2/util.h" #include <WinSock2.h>
//#include "MySimpleLog.h" static const int PORT = ; //读取一行输入并返回
static void conn_readcb(struct bufferevent *bev, void *user_data)
{
//MY_SIMPLE_LOG_DEBUG("conn_readcb!\n"); //只有读到一行完整行才进行处理 所以这里需要检查缓冲区是否存在换行符
//如果存在才往下走
struct evbuffer *input = bufferevent_get_input(bev);
struct evbuffer_ptr ptrInput;
if ((ptrInput = evbuffer_search(input , "\n", , NULL)).pos == -)
{
return;
} char line[] = {};
int nRead = ; //已读取字节数
int nExpect = ptrInput.pos + ; //期望读取的字节数 //把这一行读取出来(如果大于1024则需要分次读取)
while (nRead < nExpect)
{
int nLeft = nExpect - nRead;
int nReadOnce = min(nLeft, sizeof(line) - ); int n = bufferevent_read(bev, line, nReadOnce);
if (n <= )
{
//MY_SIMPLE_LOG_ERROR("expect to read %d bytes,but get %d.", nReadOnce, n);
break;
}
line[n] = '\0';
//把待发送数据添加到发送缓冲中,这里不会立即发送数据,要等conn_readcb返回后才会发送的
bufferevent_write(bev, line, n);
nRead += nReadOnce;
//MY_SIMPLE_LOG_DEBUG("n = %d nRead = %d nExpect = %d! line = [%s]", n, nRead, nExpect, line);
} //启动写事件,发送缓冲内容 //写事件是默认开启的,这里不需要额外设置
//bufferevent_enable(bev, EV_WRITE | EV_PERSIST); //为避免粘包,这里要判断缓冲里面是否还有剩余数据,如果有要特别处理
//这是因为libevent是边缘触发的,而不是水平触发
if (evbuffer_get_length(input) != )
{
//本来是想直接激活读事件
//但是发现要访问bufferevent未公开的成员,就不这样搞了
//event_active(&(bev->ev_read), EV_READ, 1);
conn_readcb(bev, user_data);
}
} static void conn_writecb(struct bufferevent *bev, void *user_data)
{
//MY_SIMPLE_LOG_DEBUG("conn_writecb!");
struct evbuffer *output = bufferevent_get_output(bev);
if (evbuffer_get_length(output) == )
{
//MY_SIMPLE_LOG_DEBUG("buffer writed\n");
//bufferevent_free(bev);
}
} static void conn_writecb_once(struct bufferevent *bev, void *user_data)
{
//MY_SIMPLE_LOG_DEBUG("conn_writecb_once!");
struct evbuffer *output = bufferevent_get_output(bev);
if (evbuffer_get_length(output) == )
{
//MY_SIMPLE_LOG_DEBUG("flushed free\n");
bufferevent_free(bev);
}
} static void conn_eventcb(struct bufferevent *bev, short events, void *user_data)
{
//MY_SIMPLE_LOG_DEBUG("conn_eventcb!\n"); //判断是因为什么原因调用了此函数
if (events & BEV_EVENT_EOF)
{
//MY_SIMPLE_LOG_DEBUG("Connection closed.\n");
}
else if (events & BEV_EVENT_ERROR)
{
//MY_SIMPLE_LOG_DEBUG("Got an error on the connection: %s\n",
// strerror(errno));/*XXX win32*/
}
else if ((events & BEV_EVENT_TIMEOUT) && (events & BEV_EVENT_READING))
{
//发生读超时 //超时事件发生时,会禁止相应读/写事件,需要重新注册
//这里由于业务逻辑上认为超时就要关闭socket,就没必要这样做了
//bufferevent_enable(bev, EV_READ | EV_PERSIST); //发送一个超时消息给客户端,然后就关闭bufferevent
const char time_out_msg[] = "time out!\n";
//MY_SIMPLE_LOG_INFOR(time_out_msg);
int n = bufferevent_write(bev, time_out_msg, sizeof(time_out_msg) - );
//MY_SIMPLE_LOG_DEBUG("bufferevent_write ret %d send %d", n, int(sizeof(time_out_msg) - 1));
// 据说这个版本bufferevent_flush没实现 实测总是返回0
// n = bufferevent_flush(bev, EV_WRITE, BEV_FINISHED);
// //MY_SIMPLE_LOG_DEBUG("bufferevent_flush ret %d", n); //等写完超时信息就退出
bufferevent_setcb(bev, NULL, conn_writecb_once, NULL, user_data);
return;
}
/* None of the other events can happen here, since we haven't enabled
* timeouts */
bufferevent_free(bev);
} //ctrl + c处理函数
static void signal_cb(evutil_socket_t sig, short events, void *user_data)
{
//MY_SIMPLE_LOG_DEBUG("signal_cb!\n"); printf("to exit in 2 seconds.\n");
struct event_base *base = (struct event_base *)user_data;
struct timeval delay = { , }; //MY_SIMPLE_LOG_DEBUG("Caught an interrupt signal; exiting cleanly in two seconds.\n"); event_base_loopexit(base, &delay);
} //新连接到来处理函数
static void listener_cb(struct evconnlistener *listener, evutil_socket_t fd,
struct sockaddr *sa, int socklen, void *user_data)
{
//MY_SIMPLE_LOG_DEBUG("listener_cb!\n");
struct event_base *base = (struct event_base *)user_data;
struct bufferevent *bev; //将该连接设置成非阻塞
evutil_make_socket_nonblocking(fd);
//BEV_OPT_CLOSE_ON_FREE参数让bufferevent被删除时会自动清理对应的socket
bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
if (!bev)
{
//MY_SIMPLE_LOG_ERROR("Error constructing bufferevent!");
event_base_loopbreak(base);
return;
}
//MY_SIMPLE_LOG_DEBUG("new connect!\n"); //开始设置各个事件的回调函数
//最后一个参数是各个回调函数的user_data 这里不需要用到,填NULL
//除了读写事件外,其它很多事件都是会回调conn_eventcb
bufferevent_setcb(bev, conn_readcb, conn_writecb, conn_eventcb, NULL); //EV_PERSIST不设置的话读事件只会触发一次
bufferevent_enable(bev, EV_READ | EV_PERSIST);
//默认情况下bufferevent自动监听可读事件,如有需要可以关闭
//关闭的情况下bufferevent_write调用后,不会真正往socket写,只保留在缓冲区
//bufferevent_disable(bev, EV_WRITE);
struct timeval delay = { , };
//读超时10秒 写超时无限
bufferevent_set_timeouts(bev, &delay, NULL);
} /*
基本流程:
1 新建event_base
2 生成监听socket
3 将
*/
int main(int argc, char **argv)
{
struct event_base *base;
struct evconnlistener *listener;
struct event *signal_event; struct sockaddr_in sin; #ifdef WIN32
WSADATA wsa_data;
WSAStartup(0x0201, &wsa_data);
#endif //MY_SIMPLE_LOG_INIT(//MY_SIMPLE_LOG_LEVEL_DEBUG); //初始化event_base 这个是全局唯一的
base = event_base_new();
if (!base)
{
//MY_SIMPLE_LOG_ERROR("Could not initialize libevent!\n");
return ;
}
//MY_SIMPLE_LOG_DEBUG("all inited"); //往event_base新增一个event,监听连接事件
memset(&sin, , sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(PORT); listener = evconnlistener_new_bind(base, listener_cb, (void *)base,
LEV_OPT_REUSEABLE|LEV_OPT_CLOSE_ON_FREE, -,
(struct sockaddr*)&sin,
sizeof(sin)); if (!listener)
{
//MY_SIMPLE_LOG_ERROR("Could not create a listener!\n");
event_base_free(base);
return ;
}
//MY_SIMPLE_LOG_INFOR("listener created"); //evutil_make_socket_nonblocking(listener); //监听socket不应该设置成非阻塞
//监听ctrl+c信号
signal_event = evsignal_new(base, SIGINT, signal_cb, (void *)base); if (!signal_event || event_add(signal_event, NULL)<)
{
//MY_SIMPLE_LOG_ERROR("Could not create/add a signal event!\n");
evconnlistener_free(listener);
event_base_free(base);
return ;
}
//MY_SIMPLE_LOG_INFOR("signal_event created"); //MY_SIMPLE_LOG_INFOR("beging to dispatch");
printf("beging to dispatch...\n");
//开始监听连接 这是一个内置循环的函数 event_base_loopexit函数调用才会返回
event_base_dispatch(base);
//MY_SIMPLE_LOG_INFOR("end dispatch\n"); //清理资源
evconnlistener_free(listener);
event_free(signal_event);
event_base_free(base); //MY_SIMPLE_LOG_DEBUG("done\n");
return ;
}
客户端实现:
客户端实现较简单,就不附流程图了,直接贴代码
// my_telnet.cpp : Defines the entry point for the console application.
// #include "stdafx.h" #include <string.h>
#include <errno.h>
#include <stdio.h>
#include <io.h>
#include <signal.h> #ifndef WIN32
#include <netinet/in.h>
# ifdef _XOPEN_SOURCE_EXTENDED
# include <arpa/inet.h>
# endif
#include <sys/socket.h>
#else
#include <Ws2tcpip.h>
#endif #include "event2/bufferevent.h"
#include "event2/buffer.h"
#include "event2/listener.h"
#include "event2/util.h"
#include "event2/event.h"
#include "event2/util.h"
#include <WinSock2.h>
#include <conio.h> // _kbhit(), _getch()
//#include "MySimpleLog.h"
#include <assert.h>
static const int PORT = ; int tcp_connect_server(const char* server_ip, int port); void cmd_msg_cb(int fd, short events, void* arg);
void server_msg_cb(struct bufferevent* bev, void* arg);
void event_cb(struct bufferevent *bev, short event, void *arg);
void signal_cb(evutil_socket_t sig, short events, void *user_data); typedef struct
{
struct bufferevent* bev;
struct evbuffer *buf;
}MyTelnetParam; int main(int argc, char** argv)
{
if( argc < )
{
//两个参数依次是服务器端的IP地址、端口号
fprintf(stderr, "please input 2 parameter\n");
return -;
} #ifdef WIN32
WSADATA wsa_data;
WSAStartup(0x0201, &wsa_data);
#endif //MY_SIMPLE_LOG_INIT(//MY_SIMPLE_LOG_LEVEL_DEBUG); struct event_base *base = event_base_new(); //创建bufferevent 暂时不设置对应的socket
struct bufferevent* bev = bufferevent_socket_new(base, -,
BEV_OPT_CLOSE_ON_FREE);
assert(bev != NULL);
/*
//监听终端输入事件 这是linux的做法 win上面不能这样玩
struct event* ev_cmd = event_new(base, _fileno(stdin),
EV_READ | EV_PERSIST,
cmd_msg_cb, (void*)bev); event_add(ev_cmd, NULL);
*/
struct evbuffer *buf = evbuffer_new();
assert(buf != NULL);
MyTelnetParam param = {bev, buf}; //监听终端输入事件 用超时事件 这里不用evtimer_new 因为那个事件只能触发一次
struct event *ev_cmd = event_new(base, -, EV_PERSIST, cmd_msg_cb, (void *)¶m);
assert(event_new != NULL);
//每100微秒检测一次键盘输入情况 如果有输入则读取输入并发送给服务器
//时间太短CPU占用太高,太长用户感觉有延迟
struct timeval wait_time = {, };
event_add(ev_cmd, &wait_time); //监听ctrl+c信号
struct event *signal_event = evsignal_new(base, SIGINT, signal_cb, (void *)base);
assert(signal_event != NULL);
event_add(signal_event, NULL); //服务器ip信息初始化
struct sockaddr_in server_addr;
memset(&server_addr, , sizeof(server_addr) );
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(atoi(argv[]));
//inet_aton(argv[1], &server_addr.sin_addr);
server_addr.sin_addr.s_addr = inet_addr(argv[]); //连接服务器,并将socket设置到bufferevent
bufferevent_socket_connect(bev, (struct sockaddr *)&server_addr,
sizeof(server_addr)); bufferevent_setcb(bev, server_msg_cb, NULL, event_cb, NULL);
bufferevent_enable(bev, EV_READ | EV_PERSIST); event_base_dispatch(base);
//这将自动close套接字和free读写缓冲区
bufferevent_free(bev);
event_free(ev_cmd);
event_free(signal_event);
evbuffer_free(buf);
event_base_free(base); #ifdef WIN32
WSACleanup();
#endif //MY_SIMPLE_LOG_DEBUG("finished \n");
return ;
} void cmd_msg_cb(int fd, short events, void* arg)
{
MyTelnetParam *param = (MyTelnetParam *)arg;
struct bufferevent *bev = param->bev;
struct evbuffer *buf = param->buf; //如果有键盘输入,则读取一个字符 如果不加这判断直接getch有可能会阻塞回调
if (_kbhit())
{
char cInput = EOF;
do
{
//读取键盘输入并回显 不用getchar 因为getchar要等用户按回车才能获取
int nInput = (char) _getch();
cInput = (char) nInput;
evbuffer_add(buf, &cInput, ); //这里最好用putch 不用putchar 因为有时是读取到非可视化字符,如方向键
putch(nInput); //读满一行就先返回 同时换行
if (cInput == '\r')
{
//实际测试发现,按回车时用getch只能读取到一次按键\r
cInput = '\n';
evbuffer_add(buf, &cInput, );
putch(cInput);
break;
}
} while (_kbhit()); //将buf整理成连续的内存
//size_t nLen = evbuffer_get_length(buf);
//evbuffer_pullup(buf, nLen); //往socket输出读取到的字节并移除buf现有字节
bufferevent_write_buffer(bev, buf);
evbuffer_drain(buf, evbuffer_get_length(buf));
}
} //ctrl + c处理函数
static void signal_cb(evutil_socket_t sig, short events, void *user_data)
{
//MY_SIMPLE_LOG_DEBUG("signal_cb!\n"); printf("to exit in 2 seconds.\n");
struct event_base *base = (struct event_base *)user_data;
struct timeval delay = { , }; //MY_SIMPLE_LOG_DEBUG("Caught an interrupt signal; exiting cleanly in two seconds.\n"); event_base_loopexit(base, &delay);
} void server_msg_cb(struct bufferevent* bev, void* arg)
{
char msg[ * ]; size_t len = bufferevent_read(bev, msg, sizeof(msg) - );
msg[len] = '\0'; printf("recv %d:[%s]\n", (int)len, msg);
} void event_cb(struct bufferevent *bev, short event, void *arg)
{
if (event & BEV_EVENT_EOF)
printf("connection closed\n");
else if (event & BEV_EVENT_ERROR)
printf("some other error\n");
else if( event & BEV_EVENT_CONNECTED)
{
printf("the client has connected to server\n");
return ;
} // 不等目前队列中所有回调事件完成,立即退出循环
// 如果等待回调事件完成再退出要用event_base_loopexit
// 这里不能用event_base_loopexit 不然如果用户操作太快,cmd_msg_cb会报错
event_base_loopbreak(bufferevent_get_base(bev));
}
用libevent实现的echo服务器及telnet客户端的更多相关文章
- 使用node新建一个socket服务器连接Telnet客户端并且进行输入的显示
最近在看node的socket,这个很有趣,这个可以很清晰的得到网络http请求的一个过程.首先我们需要一个Telnet的客户端,node(博主为8.0+版本) Telnet客户端的开启过程 有的系统 ...
- Linux下的echo服务器
epoll模式下的echo服务器,忘记从哪个网页上粘贴过来的了,学习一下 /* * main.cc * * Created on: 2009-11-30 * Author: liheyuan * De ...
- netty入坑第一步:了解netty和编写简单的Echo服务器和客户端
早期java API通过原生socket产生所谓的"blocking",大致过程是这样 这种的特点是每次只能处理一个请求,如果要实现多个请求并行,就还要分配一个新的线程来给每个客户 ...
- boost::asio实现一个echo服务器
以前使用ACE实现Server框架,但是觉得太笨重,决定采用boost.asio来写服务器程序: 1.服务器构建在linux上面:当然也可以在windows下运行 2.io部分采用非阻塞模式.业务逻辑 ...
- 域名可以解析(ping域名可以获取正确ip),服务器本地telnet 域名+端口 无法连接,通过建立本地虚拟域名指定的方法解决该问题
环境: 服务器A,网管已为A开通外网ip,且设置有映射域名:假如内网ip为172.16.2.6.外网ip为123.123.123.123.域名为test.sstest.com 现象: 服务器A,tel ...
- 网络编程-echo服务器
代码: #coding="utf-8" #name=echo服务器 from socket import * #1.创建套接字 udpSocket = socket(AF_INET ...
- Linux网络编程--多线程实现echo服务器与客户端“一对多”功能,是网络编程的“Hello World!”
在linux平台下,用多线程实现echo服务器与客户端“一对多”(即是一台服务器可以响应多个客户端的请求).本人写了个demo,和大家一起分享,有不足的地方,请多多指教,我是壮壮熊. 编译时,在后面加 ...
- 基于EPOLL模型的局域网聊天室和Echo服务器
一.EPOLL的优点 在Linux中,select/poll/epoll是I/O多路复用的三种方式,epoll是Linux系统上独有的高效率I/O多路复用方式,区别于select/poll.先说sel ...
- 关于telnet协议的研究以及用java进行封装实现自己的telnet客户端(转)
最近在做一个远程控制的模块,其中用到了telnet协议,开始用的是apache-net包的telnetclient,但发现问题不少,比较慢,还有就是判断是否read完毕的问题.后来经过讨论打算实现自己 ...
随机推荐
- 自定义属性之LinearLayout ImageView TextView模拟图片文字按钮
一.资源文件: 1.文字选择器: <?xml version="1.0" encoding="utf-8"?> <selector xmlns ...
- ARM v7-A 系列CPU的MMU隐射分析
ARM v7-A 系列CPU的MMU隐射分析 摘要:ARM v7-A系列的CPU加入了很多扩展,如多核处理器扩展.大物理地址扩展.TrustZone扩展.虚拟化扩展.若支持大的物理地址,则必须支持多核 ...
- 从C语言的volatile关键字,了解C#的volatile机制(转载)
C#中有一个关键字volatile,一直不太明白到底什么时候才用它,只知道在多线程操作同一个变量的时候要使用volatile关键字,下面看到了一篇C语言关于volatile关键字的介绍,写的很不错,其 ...
- 用js写三个数,让三个数从小到大排列
console.log('请输入三个数:'); let num1 = readline.question() - 0; let num2 = readline.question() - 0; let ...
- java项目如何使用ajax来减少页面的刷新
之前写项目,总是用重定向或请求转发,导致每做一步动作就会刷新页面,客户体验不好,而且效率低下,这种问题可以使用ajax来有效的解决此类问题的发生. 我使用的框架:Spring boot 数据库:mys ...
- iOS视频倒放
iOS视频倒放 视频的倒放就是视频从后往前播放,这个只适应于视频图像,对声音来说倒放只是噪音,没什么意义,所以倒放的时候声音都是去除的. 倒放实现 一般对H264编码的视频进行解码,都是从头至尾进行的 ...
- 开发机器上利用vs2013调试远程IIS上的c#程序
当远程IIS上的C#程序出现问题,怎么排错,一般我们通过看日志排查错误的方法,这种方法在程序异常日志都打印出来的情况下是可以解决的,但如果程序日志不详细,或者从日志看不出有用的内容的时候怎么排错? 本 ...
- 2. HTML常用标签
相信大家常常会打开浏览器搜索一些内容或者浏览一些网站,在浏览器的页面上会呈现很多内容,但是具体的形式无非就是图片.文字以及链接(可以点击进入另一个页面的特殊文字),其中文字承载着巨大的作用,传递着各种 ...
- php比较两个数组的差异array_diff()函数
下面简单介绍php比较两个数组的差异array_diff()函数. 原文地址:小时刻个人技术博客 > http://small.aiweimeng.top/index.php/archives/ ...
- 大数据学习--day08(hnapp 后台系统开发、面向对象)
hnapp 后台系统开发.面向对象 利用前面所学的知识,写一个控制台登陆注册后台界面 package sy180918.hnapp.array; import java.util.Arrays; im ...