2019-03-24
LIBENENT
#cnclude<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet/h>
#include<errno.h>
#include<unistd.h>// 是 C 和 C++ 程序设计语言中提供对 POSIX 操作系统 API 的访问功能的头文件的名称。该头文件由 POSIX.1 标准(单一UNIX规范的基础)提出,故所有遵循该标准的操作系统和编译器均应提供该头文件(如 Unix 的所有官方版本,包括 Mac OS X、Linux 等)。
#include<stdio.h>
#include<string.h>
#inlcude<stdlib.h>
#include<event2/util.h>
type struct socketaddr SA;
int tcp_connect_to_server(const char * server_ip,int port )
{
int sockfd,save_error;
struct socketaddr_in,server_addr;
truct sockaddr_in
{
struct sockaddr_in
{
short sin_family;/*Address family一般来说AF_INET(地址族)PF_INET(协议族)*/
unsigned short sin_port;/*Port number(必须要采用网络数据格式,普通数字可以用htons()函数转换成网络数据格式的数字)*/
struct in_addr sin_addr;/*IP address in network byte order(Internet address)*/
unsigned char sin_zero[8];/*Same size as struct sockaddr没有实际意义,只是为了 跟SOCKADDR结构在内存中对齐*/
}
memset(&server_addr,0,sizeof(server_addr));
server_addr.sin_family=AF_INET;
server_addr.sin_port=htons(port);
status=inet_aton(server_ip,&server_addr.sin_addr);inet_aton是一个计算机函数,功能是将一个字符串IP地址转换为一个32位的网络序列IP地址。如果这个函数成功,函数的返回值非零,如果输入地址不正确则会返回零。使用这个函数并没有错误码存放在errno中,所以它的值会被忽略。
if( status == 0 ) //the server_ip is not valid value
{
errno = EINVAL;
return -1;
}
sockfd=::socket(PF_INET,SOCK_STREAM,0);//参数一表示地址类型,参数而表示面向连接还是面向无连接的套接字,参数三是表示TCP还是udp
if(sockfd==-1)
return sockfd;
其中流式套接字使用SOCKET_STREAM表示
SOCK_STREAM 是一种可靠的、双向的通信数据流,数据可以准确无误地到达另一台计算机,如果损坏或丢失,可以重新发送
流时套接字有自己的纠错机制
特点有
数据在传输过程中不会丢失,数据按照顺序传输的,数据的发送和接受不是同步的
只要传送带本身没有问题(不会断网),就能保证数据不丢失;同时,较晚传送的数据不会先到达,较早传送的数据不会晚到达,这就保证了数据是按照顺序传递的。
为什么流格式套接字可以达到高质量的数据传输呢?这是因为它使用了 TCP 协议(The Transmission Control Protocol,传输控制协议),TCP 协议会控制你的数据按照顺序到达并且没有错误。
你也许见过 TCP,是因为你经常听说“TCP/IP”。TCP 用来确保数据的正确性,IP(Internet Protocol,网络协议)用来控制数据如何从源头到达目的地,也就是常说的“路由”。
数据报格式套接字(Datagram Sockets)也叫“无连接的套接字”,在代码中使用 SOCK_DGRAM 表示。
计算机只管传输数据,不作数据校验,如果数据在传输中损坏,或者没有到达另一台计算机,是没有办法补救的。也就是说,数据错了就错了,无法重传。
因为数据报套接字所做的校验工作少,所以在传输效率方面比流格式套接字要高。
有了地址类型和数据传输方式,还不足以决定采用哪种协议吗?为什么还需要第三个参数呢?
正如大家所想,一般情况下有了 af 和 type 两个参数就可以创建套接字了,操作系统会自动推演出协议类型,除非遇到这样的情况:有两种不同的协议支持同一种地址类型和数据传输类型。如果我们不指明使用哪种协议,操作系统是没办法自动推演的。
status=::connect(sockfd,(SA*)&server_addr,sizeof(server_addr));
if( status == -1 )
{
save_errno = errno;
::close(sockfd);
errno = save_errno; //the close may be error
return -1;
}
evutil_make_socket_nonblocking(sockfd);
return sockfd;
}
void server_msg_cb(struct bufferevent* bev, void* arg)
{
char msg[1024];
msg[len];
size_t len=bufferevent_read(bev,msg,sizeof(msg));
msg[len]='\0';
printf("recv%s from server\n",msg);
}
void socket_read_cb(int fd, short events, void *arg)
{
char msg[1024];
//为了简单起见,不考虑读一半数据的情况
int len = read(fd, msg, sizeof(msg)-1);
if( len <= 0 )
{
perror("read fail ");
exit(1);
}
msg[len] = '\0';
printf("recv %s from server\n", msg);
}
int main()
{
if( argc < 3 )
{
printf("please input 2 parameter\n");
return -1;
}
//两个参数依次是服务器端口和IP地址
int sockfd=tcp_connect_server(argv[1], atoi(argv[2]));
if( sockfd == -1)
{
perror("tcp_connect error ");
return -1;
}
printf("connect to server successful\n");
struct event_base *base=event_base_new();
struct event *ev_sockfd=event_new(base,sockfd, EV_READ | EV_PERSIST,socket_read_cb, NULL);
event_add(ev_sockfd,NULL);
struct event *ev_cmd=event_new(base, STDIN_FILENO, EV_READ | EV_PERSIST, cmd_msg_cb, (void*)&sockfd);
//eventbase是libevent处理事物的框架负责事件的注册,删除,属于reactor模式中的reactor
//创建一个event_base event_base_new(void)
struct event_base *
event_base_new(void)
{
struct event_base *base = NULL;
struct event_config *cfg = event_config_new();
if (cfg) {
base = event_base_new_with_config(cfg);
event_config_free(cfg);
}
return base;
}
由上边程序知道,event_base创建的关键所在是event_base_new_config函数,而次函数有用过调用多路复用的机制来初始化event_base实例
event_new创建事件,涉及内存操作
event_add
event_del
event_free释放由new创建的内存
struct event_base *
event_init(void)
{
struct event_base *base = event_base_new(); //event_init()调用event_base_new()
if (base != NULL)
current_base = base;
return (base);
}
struct event_base *
event_base_new(void) //初始化libevent的event_base
{
int i;
struct event_base *base;
if ((base = calloc(1, sizeof(struct event_base))) == NULL) //在堆上分配内存存储event_base,所有字段初始化为0
event_err(1, "%s: calloc", __func__);
event_sigcb = NULL;
event_gotsig = 0;
detect_monotonic(); //设置use_monotonic变量
gettime(base, &base->event_tv); //base->tv_cache.tv_sec非0,则赋给base->event_tv
min_heap_ctor(&base->timeheap); //初始化定时事件的小根堆base->timeheap min_heap.h
TAILQ_INIT(&base->eventqueue); //初始化注册事件链表base->eventqueue sys/queue.h
base->sig.ev_signal_pair[0] = -1; //初始化信号base->sig
base->sig.ev_signal_pair[1] = -1;
base->evbase = NULL; //初始化I/O多路复用 base->evbase
//遍历全局数组eventops[],初始化libevent的I/O多路复用机制
for (i = 0; eventops[i] && !base->evbase; i++) { //以NULL标志数组结尾,只选取一个I/O多路复用机制
base->evsel = eventops[i]; //初始化base->evsel
base->evbase = base->evsel->init(base); //初始化base->evbase
}
if (base->evbase == NULL) //没有I/O多路复用
event_errx(1, "%s: no event mechanism available", __func__);
if (evutil_getenv("EVENT_SHOW_METHOD")) //调用getenv()获取环境变量EVENT_SHOW_METHOD evutil.c
event_msgx("libevent using: %s\n",
base->evsel->name);
/* allocate a single active event queue */
//event_base_new()内调用event_base_priority_init()
event_base_priority_init(base, 1); //设置优先级base->nactivequeues;分配数组base->activequeues。数组大小和优先级相同
return (base);
}
先是初始化event_base然后初始化event,通过初始化event_set()函数实现。
void
event_set(struct event *ev, int fd, short events,
void (*callback)(int, short, void *), void *arg)
{
/* Take the current base - caller needs to set the real base later */
ev->ev_base = current_base; //设置event属于当前base;current_base通过event_init()设置
ev->ev_callback = callback; //设置回调函数
ev->ev_arg = arg; //设置回调函数的3个参数
ev->ev_fd = fd;
ev->ev_events = events;
ev->ev_res = 0;
ev->ev_flags = EVLIST_INIT; //设置event状态
ev->ev_ncalls = 0;
ev->ev_pncalls = NULL;
min_heap_elem_init(ev); //初始化event在小根堆中索引为-1 min_heap.h
/* by default, we put new events into the middle priority */
if(current_base)
ev->ev_pri = current_base->nactivequeues/2; //设置event优先级
}
event_base()初始化event_base
event_set()初始化event
event_base_set()将event绑定到指定的event_base上
event_add()将event添加到事件链表上,注册事件
event_base_dispatch()循环、检测、分发事件
写一个测试程序
struct event ev;
struct timeval tv;
void time(int fd,short event,void *arg)
{
printf("amck");
event_add(&ev,&tv);
}
int main()
{
struct event_base *base=event_init();
tv.tv_sec=1;
tv.tv_usec=0;
event_set(&ev, -1, 0, timer_cb, NULL); //初始化event结构中成员
event_base_set(base, &ev);
event_add(&ev, &tv); //将event添加到events事件链表,注册事件
event_base_dispatch(base); //循环、分发事件
return 0;
}
服务端代码:
#include<stdio.h>
#include<string.h>
#include<errno.h>
#include<unistd.h>
#include<event.h>
void accept_cb(int fd, short events, void* arg)
{
evutil_socket_t sockfd;//int
struct sockaddr_in client;
socklen_t len=sizeof(client);
sockfd-::accept(fd,(struct sockaddr*&client,&len);
evutil_make_socket_nonblocking(sockfd);
printf("accept a client %d\n", sockfd);
struct event_base * base=(event_base*)arg;
bufferevent * bev=bufferevent_socket_new(base,sockfd BEV_OPT_CLOSE_ON_FREE);//
//bufferevent专门为封装成带有缓冲区的socket套接字。当有数据到来时,我们只需要在回调函数里面通过封装函数bufferevent_read读取数据即可,根本不需要自己处理一些细节,以及缓存的问题。
bufferevent其实也就是在event_base的基础上再进行一层封装,其本质还是离不开event和event_base,从bufferevent的结构体就可以看到这一点。
bufferevent_setcb(bev,socket_read_cb,NULL,event_cb,arg);
bufferevent_ebable(bev,EV_READ|EV_PERSIST);
}
void socket_read_cb(int fd, short events, void *arg);
typedef struct aockaddr SA
int tcp_server_init(int port, int listen_num)
{
int errno_save;
evutil_sock_t listener;
if(listener==-1)
return =-1;
evutil_make_listen_socket_reuseable(listen);//允许多次绑定同一地址
struct sockaddr_in sin;
sin.sin_family=AF_INET;
sin.sin_addr.s_addr=0;
sin.sin_port=htons(port);
if(::bind(listener,(SA)&sin,sizeof(sin))<0)
goto error;
if(::listen(listener,listen_num)<0)
goto err;
evutil_make_socket_nonblocking(listener);//跨平台统一接口,将套接字设置为非阻塞
return listener;
error:
errno_save = errno;
evutil_closesocket(listener);
errno = errno_save;
return -1;
}
int main(int argc,char** argv)
{
int listener=tcp_server_init(9999,10);
if( listener == -1 )
{
perror(" tcp_server_init error ");
return -1;
}
struct event_base *base=event_base_new();
struct event *ev_listen=event_new(base,listener,EV_READ|EV_PRESIST,accept__cb,base);
}