libevent

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

sockaddr是地址,不是套接字

套接字(socket)是一个抽象层,应用程序可以通过它发送或接收数据,可对其进行像对文件一样的打开、读写和关闭等操作。套接字允许应用程序将I/O插入到网络中,并与网络中的其他应用程序进行通信。网络套接字是IP地址与端口的组合。

BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB

truct event *event_new(struct event_base *, evutil_socket_t, short, event_callback_fn, void *);

分配并初始化一个新的event结构体,准备被添加。

参数1:base表示event base。

参数2:fd,文件描述符或者socket,或者-1

参数3:events,需要监控的事件:EV_READ, EV_WRITE,

      EV_SIGNAL, EV_PERSIST, EV_ET的位域。

参数4:callback,回调函数

参数5:callback_arg, 回调函数的参数

返回值:一个新分配的时间被创建,需要用event_free释放。
 

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

event_base 介绍

一个event_base就是一个Reactor框架。我们在调用任何Libevent的函数前,我们都是需要先申请 event_base 结构体。对于一个event_base结构来说,它会保存一系列的event事件并且以轮训的方式去关注每一个事件,去查看哪一个事件是就绪的。(事件就绪可以从unp第6章 I/O模型中有讲到)
如果一个 event_base设置了使用lock,那么它在多线程之间访问就是安全的。虽然event_base 可以在多线程中使用,但是一个loop仅仅只能在一个线程内。如果我们想多个线程都去轮训,那么我们只能每一个线程去申请一个event_base。
Libevent是基于Reactor模式的一个网络库,也是基于多路复用的一个网络库。所以每一个event_base都有一个对应的多路复用的method,event_base支持的所有method如下:

    select
    poll
    epoll
    kqueue
    devpoll
    evport
    win32

我们可以取消(disable)指定的method通过环境变量,比如设置 EVENT_NOQUEUE 环境变量。也可以通过调用event_config_avoid_method函数来关闭相应的method。(下面有这个函数介绍)
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD

 

 

sockaddr和sockaddr_in详解

2018年03月29日 08:18:50 青竹雨仙 阅读数 9005

struct sockaddr和struct sockaddr_in这两个结构体用来处理网络通信的地址。

一、sockaddr

sockaddr在头文件#include <sys/socket.h>中定义,sockaddr的缺陷是:sa_data把目标地址和端口信息混在一起了,如下:

 
  1. struct sockaddr {

  2. sa_family_t sin_family;//地址族

  3.    char sa_data[14]; //14字节,包含套接字中的目标地址和端口信息

  4.    };

  • 1
  • 2
  • 3
  • 4

二、sockaddr_in

sockaddr_in在头文件#include<netinet/in.h>或#include <arpa/inet.h>中定义,该结构体解决了sockaddr的缺陷,把port和addr 分开储存在两个变量中,如下: 
这里写图片描述 
sin_port和sin_addr都必须是网络字节序(NBO),一般可视化的数字都是主机字节序(HBO)。

三、总结

二者长度一样,都是16个字节,即占用的内存大小是一致的,因此可以互相转化。二者是并列结构,指向sockaddr_in结构的指针也可以指向sockaddr。

sockaddr常用于bind、connect、recvfrom、sendto等函数的参数,指明地址信息,是一种通用的套接字地址。 
sockaddr_in 是internet环境下套接字的地址形式。所以在网络编程中我们会对sockaddr_in结构体进行操作,使用sockaddr_in来建立所需的信息,最后使用类型转化就可以了。一般先把sockaddr_in变量赋值后,强制类型转换后传入用sockaddr做参数的函数:sockaddr_in用于socket定义和赋值;sockaddr用于函数参数。

例子如下:

 
  1. #include <stdio.h>

  2. #include <stdlib.h>

  3. #include <sys/socket.h>

  4. #include <netinet/in.h>

  5.  
  6. int main(int argc,char **argv)

  7. {

  8. int sockfd;

  9. struct sockaddr_in mysock;

  10.  
  11. sockfd = socket(AF_INET,SOCK_STREAM,0); //获得fd

  12.  
  13. bzero(&mysock,sizeof(mysock)); //初始化结构体

  14. mysock.sin_family = AF_INET; //设置地址家族

  15. mysock.sin_port = htons(800); //设置端口

  16. mysock.sin_addr.s_addr = inet_addr("192.168.1.0"); //设置地址

  17. bind(sockfd,(struct sockaddr *)&mysock,sizeof(struct sockaddr); /* bind的时候进行转化 */

  18. ... ...

  19. return 0;

  20. }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

题外话,两个函数 htons() 和 inet_addr()。

htons()作用是将端口号由主机字节序转换为网络字节序的整数值。(host to net)

inet_addr()作用是将一个IP字符串转化为一个网络字节序的整数值,用于sockaddr_in.sin_addr.s_addr。

inet_ntoa()作用是将一个sin_addr结构体输出成IP字符串(network to ascii)。比如:

printf("%s",inet_ntoa(mysock.sin_addr));
  • 1

htonl()作用和htons()一样,不过它针对的是32位的(long),而htons()针对的是两个字节,16位的(short)。

与htonl()和htons()作用相反的两个函数是:ntohl()和ntohs()。 
这里写图片描述

参考: 
《TCP/IP网络编程》 
http://www.it165.net/pro/html/201211/4066.html 
http://www.cnblogs.com/huqian23456/archive/2011/02/22/1961822.html

==================================================================================================================================================================

 

socket编程与libevent2的一些归纳总结

 

PS: 更全面的总结可以从相关的文档中获取,为了叙述方便这里特指linux环境下

1. 涉及的一些背景知识

1.1. nonblock socket

描述

对应block,如果一个socket设置为nonblock,那么其相关的操作将变为非阻塞的。这里所说的非阻塞,并不是说异步回调什么的,例如,调用recv()函数:

#include <sys/types.h>
#include <sys/socket.h>

ssize_t recv(int sockfd, void *buf, size_t len, int flags);

read = recv(sock, buf, len, 0);

如果是默认的block情形,这个函数将一直等待直到获取到数据,或者报错。在高并发中,这显然是悲剧的。
如果设置为noblock,同样的调用将直接返回。
下边详细描述一下的recv的情形:

  1. 连接失败
    block:立即返回,返回值-1,同时设置errno := ENOTCONN
    nonblock: 同上

  2. 缓冲区中有数据:
    block: 立即返回,将缓冲区的数据写入buf,最多写入len字节,返回值为写入的字节数
    nonblock: 同上

  3. 缓冲区无数据:
    block:将阻塞等待缓冲区有数据
    nonblock:立即返回,返回值-1,同时设置errno := EAGAIN

类似的,对于send(), connect(), bind(), accept(),均有类似一样的区别

设置

有如下方式设置nonblock

  1. 新建 socket 时设置
    在传入 socket type 时,同时置SOCK_NONBLOCK位为1

    sock = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
  2. 使用fcntl()设置

    int flag = fcntl(sock, F_GETFL);
    fcntl(sock, F_SETFL, flag | O_NONBLOCK); 
  3. 使用even2设置

    #inlcude <event2/util.h>
    
    int evutil_make_socket_nonblocking(evutil_socket_t sock);

1.2. reuseable socket

描述

一个socket在系统中的表示如下

{<protocol>, <src addr>, <src port>, <dest addr>, <dest port>}

如果指定src addr0.0.0.0,将不再表示某一个具体的地址,而是表示本地的所有的可用地址。

reuse有三个级别:

  1. non-reuse: src addrsrc port不能冲突(同一个protocol下), 0.0.0.0和其他IP视为冲突

  2. reuse-addr: src addrsrc port不能冲突(同一个protocol下), 0.0.0.0和其他IP视为不冲突

  3. reuse-port: src addrsrc port可以冲突

下边仍然举例说明reuse的特性
系统有两个网口,分别是192.168.0.10110.0.0.101

  • 情形1:
    sock1绑定了192.168.0.101:8080,sock2尝试绑定10.0.0.101:8080
    non-reuse - 可以绑定成功,虽然端口一样,但是addr不同
    reuse - 同上

  • 情形2
    sock1绑定了0.0.0.0:8080, sock2尝试绑定192.168.0.101:8080
    non-reuse - 不能绑定成功,系统认为0.0.0.0包含了所有的本地ip,发生冲突
    reuse - 可以绑定成功,系统认为0.0.0.0192.168.0.101不是一样的地址

  • 情形3
    sock1绑定了192.168.0.101:8080,sock2尝试绑定0.0.0.0:8080
    non-reuse - 不能绑定成功,系统认为0.0.0.0包含了所有的本地ip,发生冲突
    reuse - 可以绑定成功,系统认为0.0.0.0192.168.0.101不是一样的地址

  • 情形4
    sock1绑定了0.0.0.0:8080,sock2尝试绑定0.0.0.0:8080
    non-reuse - 不能绑定成功,系统认为0.0.0.0包含了所有的本地ip,发生冲突
    reuse-addr - 不能绑定成功,系统认为0.0.0.0包含了所有的本地ip,发生冲突
    reuse-port - 可以绑定成功

设置reuse

  1. 使用setsockopt()
    必须设置所有相关的sock。
    设置reuse-addr:

    setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &(int){1}, sizeof(int));

    设置reuse-port:

    setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, &(int){1}, sizeof(int));
  2. 使用event2设置

    #inlcude <event2/util.h>
    
    int evutil_make_listen_socket_reuseable(evutil_socket_t sock);

2. 常用的系统API接口

新建一个socket

#include <sys/socket.h>

int socket(int domain, int type, int protocol);

domain 一般设置为:

  • AF_UNIX - 本地socket

  • AF_INET - ipv4

  • AF_INET6 - ipv6

type 一般设置为:

  • SOCK_STREAM - TCP

  • SOCK_DGRAM - UDP

连接到远程端口

#include <sys/socket.h>

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

对于不同协议,addr的类型不同,长度也不同,这里需要把不同的类型强转为struct sockaddr *,在强转中,addr的类型信息丢失,所以需要在addrlen中指定原有类型的长度。

绑定到本地端口

#include <sys/socket.h>

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

addr类似connect(),这个函数常用语服务器端,但是实际上客户端也是可以使用的(然并卵一般没啥意义)

读写数据

#include <sys/types.h>
#include <sys/socket.h>

ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t send(int sockfd, const void *buf, size_t len, int flags);

读写数据涉及的问题较多,第一是失败时候返回-1而不是0,如果是0表示socket关闭。第二就是读写不一定100%完成,计划读写512字节,但是读到256字节的时候发生了中断或者没有数据/空闲缓冲区都是是可能的,返回值表示实际读入和写出的字节数。

监听数据

#include <sys/socket.h>

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
int listen(int sockfd, int backlog);
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

和主动发起连接不同,被动接收连接分为三个阶段,bind()用来设置本地端口,listen()表示socket开始接收到来的连接,而不会建立连接,要真正建立连接,使用accept()

关闭连接

#include <unistd.h>

int close(int fd);

关闭即可,没啥说的

3. 常用的event2的接口

旧版libevent中,一般只能操作一个全局的event_base,而在新版libevent中,event_base交由用户来管理,用户可以创建删除event_base,也可以把event注册到不同的event_base上。

新建一个 event_base

#include <event2/event.h>

struct event_base *event_base_new(void);

释放一个event_base

#include <event2/event.h>

void event_base_free(struct event_base *eb);

event的生命周期

event的生命周期与相关的函数关系密切

event生命周期

用户自己创建的event是uninitialized的,需要使用event_assign()进行初始化,或者直接使用event_new()从无到有创建一个新的初始化了的event。在初始化时,完成了回调函数的绑定。
event的初始状态是non-pending,表示这个event不会被触发。

新建(并初始化)一个 event

struct event *event_new(struct event_base *base, evutil_socket_t fd, short events,
                        event_callback_fn callback, void *callback_arg);

新建event需要给定event_base, evutil_socket_t与系统相兼容,在linux下实际就是int,与socket()返回的类型一致

#ifdef WIN32
#define evutil_socket_t intptr_t
#else
#define evutil_socket_t int
#endif

events是一组flag,用于表示要监视的事件类型,还会影响event的一些行为,包括:

  • EV_TIMEOUT - 监视超时的事件
    需要说明的是,在调用event_new()时,这个flag是不用设置的,如果event发生超时,则必然会触发,无论设置与否

  • EV_READ - 监视可读的事件

  • EV_WRITE - 监视可写的事件

  • EV_SIGNAL - 监视信号量

  • EV_PERSIST - 永久生效,否则触发一次后就失效了

  • EV_ET - 设置边缘触发(edge-triggered)
    callback和callback_arg是回调操作所需的,不再详述
    新建的event是non-pending状态的

初始化一个event

int event_assign(struct event *ev,
                 struct event_base *base, evutil_socket_t fd, short events, 
                 event_callback_fn callback, void *callback_arg);

这个不会申请内存,其他同event_new()

释放一个event

void event_free(struct event *ev);

判断event是否初始化/被释放

int event_initialized(const struct event *ev);

将event置为pending状态

int event_add(struct event *ev, const struct timeval *timeout);

其中timeout可以指定超时时间,超时和EV_TIMEOUT配合使用。如果timeout如果为NULL,则表示永不超时,struct timeval的结构为:

struct timeval {
    time_t      tv_sec;     /* seconds */
    suseconds_t tv_usec;    /* microseconds */
};

额外说句,操作当前时间对应的timeval可以用

#include <sys/time.h>

int gettimeofday(struct timeval *tv, struct timezone *tz);
int settimeofday(const struct timeval *tv, const struct timezone *tz);

将event置为non-pending状态

int event_del(struct event *ev);

检查event是否为pending状态

int event_pending(const struct event *ev, short events, struct timeval *tv);

需要注意的是,不需要查询event是否为active状态,因为在active时,线程正在执行回调函数,其他函数需要等到回调执行完毕,而此时已经退出了active状态

将event置为active状态

void event_active(struct event *ev, int res, short/* deprecated */);

res是要手动指派的flag

 

===========================================================================================================================================================================================================================================================================

 

libevent源码分析:event_assign、event_new

在libevent中,获取event类型对象的方法有两种,event_assign、event_new

1、event_assign()

复制代码

 1 /**
 2   Prepare a new, already-allocated event structure to be added.
 3 
 4   The function event_assign() prepares the event structure ev to be used
 5   in future calls to event_add() and event_del().  Unlike event_new(), it
 6   doesn't allocate memory itself: it requires that you have already
 7   allocated a struct event, probably on the heap.  Doing this will
 8   typically make your code depend on the size of the event structure, and
 9   thereby create incompatibility with future versions of Libevent.
10 
11   The easiest way to avoid this problem is just to use event_new() and
12   event_free() instead.
13 
14   A slightly harder way to future-proof your code is to use
15   event_get_struct_event_size() to determine the required size of an event
16   at runtime.
17 
18   Note that it is NOT safe to call this function on an event that is
19   active or pending.  Doing so WILL corrupt internal data structures in
20   Libevent, and lead to strange, hard-to-diagnose bugs.  You _can_ use
21   event_assign to change an existing event, but only if it is not active
22   or pending!
23 
24   The arguments for this function, and the behavior of the events that it
25   makes, are as for event_new().
26 
27   @param ev an event struct to be modified
28   @param base the event base to which ev should be attached.
29   @param fd the file descriptor to be monitored
30   @param events desired events to monitor; can be EV_READ and/or EV_WRITE
31   @param callback callback function to be invoked when the event occurs
32   @param callback_arg an argument to be passed to the callback function
33 
34   @return 0 if success, or -1 on invalid arguments.
35 
36   @see event_new(), event_add(), event_del(), event_base_once(),
37     event_get_struct_event_size()
38   */
39 EVENT2_EXPORT_SYMBOL
40 int event_assign(struct event *, struct event_base *, evutil_socket_t, short, event_callback_fn, void *);

复制代码

实现:

其实event_assign的作用就是把给定的event类型对象的每一个成员赋予一个指定的值。

复制代码

 1 int
 2 event_assign(struct event *ev, struct event_base *base, evutil_socket_t fd, short events, void (*callback)(evutil_socket_t, short, void *), void *arg)
 3 {
 4     if (!base)
 5         base = current_base;
 6     if (arg == &event_self_cbarg_ptr_)
 7         arg = ev;
 8 
 9     event_debug_assert_not_added_(ev);
10 
11     ev->ev_base = base;
12 
13     ev->ev_callback = callback;
14     ev->ev_arg = arg;
15     ev->ev_fd = fd;
16     ev->ev_events = events;
17     ev->ev_res = 0;
18     ev->ev_flags = EVLIST_INIT;
19     ev->ev_ncalls = 0;
20     ev->ev_pncalls = NULL;
21 
22     if (events & EV_SIGNAL) {
23         if ((events & (EV_READ|EV_WRITE|EV_CLOSED)) != 0) {
24             event_warnx("%s: EV_SIGNAL is not compatible with "
25                 "EV_READ, EV_WRITE or EV_CLOSED", __func__);
26             return -1;
27         }
28         ev->ev_closure = EV_CLOSURE_EVENT_SIGNAL;
29     } else {
30         if (events & EV_PERSIST) {
31             evutil_timerclear(&ev->ev_io_timeout);
32             ev->ev_closure = EV_CLOSURE_EVENT_PERSIST;
33         } else {
34             ev->ev_closure = EV_CLOSURE_EVENT;
35         }
36     }
37 
38     min_heap_elem_init_(ev);
39 
40     if (base != NULL) {
41         /* by default, we put new events into the middle priority */
42         ev->ev_pri = base->nactivequeues / 2;
43     }
44 
45     event_debug_note_setup_(ev);
46 
47     return 0;
48 }

复制代码

2、event_new()

复制代码

 1 /**
 2   Allocate and asssign a new event structure, ready to be added.
 3 
 4   The function event_new() returns a new event that can be used in
 5   future calls to event_add() and event_del().  The fd and events
 6   arguments determine which conditions will trigger the event; the
 7   callback and callback_arg arguments tell Libevent what to do when the
 8   event becomes active.
 9 
10   If events contains one of EV_READ, EV_WRITE, or EV_READ|EV_WRITE, then
11   fd is a file descriptor or socket that should get monitored for
12   readiness to read, readiness to write, or readiness for either operation
13   (respectively).  If events contains EV_SIGNAL, then fd is a signal
14   number to wait for.  If events contains none of those flags, then the
15   event can be triggered only by a timeout or by manual activation with
16   event_active(): In this case, fd must be -1.
17 
18   The EV_PERSIST flag can also be passed in the events argument: it makes
19   event_add() persistent until event_del() is called.
20 
21   The EV_ET flag is compatible with EV_READ and EV_WRITE, and supported
22   only by certain backends.  It tells Libevent to use edge-triggered
23   events.
24 
25   The EV_TIMEOUT flag has no effect here.
26 
27   It is okay to have multiple events all listening on the same fds; but
28   they must either all be edge-triggered, or all not be edge triggerd.
29 
30   When the event becomes active, the event loop will run the provided
31   callbuck function, with three arguments.  The first will be the provided
32   fd value.  The second will be a bitfield of the events that triggered:
33   EV_READ, EV_WRITE, or EV_SIGNAL.  Here the EV_TIMEOUT flag indicates
34   that a timeout occurred, and EV_ET indicates that an edge-triggered
35   event occurred.  The third event will be the callback_arg pointer that
36   you provide.
37 
38   @param base the event base to which the event should be attached.
39   @param fd the file descriptor or signal to be monitored, or -1.
40   @param events desired events to monitor: bitfield of EV_READ, EV_WRITE,
41       EV_SIGNAL, EV_PERSIST, EV_ET.
42   @param callback callback function to be invoked when the event occurs
43   @param callback_arg an argument to be passed to the callback function
44 
45   @return a newly allocated struct event that must later be freed with
46     event_free().
47   @see event_free(), event_add(), event_del(), event_assign()
48  */
49 EVENT2_EXPORT_SYMBOL
50 struct event *event_new(struct event_base *, evutil_socket_t, short, event_callback_fn, void *);

复制代码

实现:

event_new的实现其实是间接的调用的event_assign,首先调用mm_malloc分配一块内存,然后调用event_assign来给event类型的对象各个成员赋值。

复制代码

 1 struct event *
 2 event_new(struct event_base *base, evutil_socket_t fd, short events, void (*cb)(evutil_socket_t, short, void *), void *arg)
 3 {
 4     struct event *ev;
 5     ev = mm_malloc(sizeof(struct event));
 6     if (ev == NULL)
 7         return (NULL);
 8     if (event_assign(ev, base, fd, events, cb, arg) < 0) {
 9         mm_free(ev);
10         return (NULL);
11     }
12 
13     return (ev);
14 }

复制代码

 

3、区别

这两种方式的区别就是,event_assign是在栈上分配一个对象,然后给成员赋值;而event_new是在堆上分配一个对象,然后给成员赋值。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值