Libevent 服务器

目录

Note

利用Libevent搭建TCP服务器,可以接受TCP客户端的连接,并可进行数据读写

服务端代码

注意:编译的时候要加libevent库, gcc Libevent服务器.c -o Libevent服务器 -levent

/**********************************************************************************************
常规创建服务器:
1、创建socket
2、bind 绑定socket
3、listen 监听
4、accept 连接
------------------------------------------------
Libevent创建服务器:
1、evconnlistener_new_bind()函数封装了创建、绑定、监听、连接等功能:
   **
    Allocate a new evconnlistener object to listen for incoming TCP connections
    on a given address. //创建监听对象,在指定的地址上监听接下来的TCP连接

    @param base The event base to associate the listener with.
     @param cb A callback to be invoked when a new connection arrives. If the
        callback is NULL, the listener will be treated as disabled until the
        callback is set.
     @param ptr A user-supplied pointer to give to the callback.
     @param flags Any number of LEV_OPT_* flags
     @param backlog Passed to the listen() call to determine the length of the
        acceptable connection backlog.  Set to -1 for a reasonable default.
     @param addr The address to listen for connections on.
     @param socklen The length of the address.
   **
   EVENT2_EXPORT_SYMBOL struct evconnlistener *evconnlistener_new_bind(struct event_base *base,
      evconnlistener_cb cb, void *ptr, unsigned flags, int backlog,
      const struct sockaddr *sa, int socklen);

2、unsigned flags参数:
 ** Flag: Indicates that we should not make incoming sockets nonblocking
 * before passing them to the callback. *
#define LEV_OPT_LEAVE_SOCKETS_BLOCKING	(1u<<0)  //阻塞,读不到卡住
 ** Flag: Indicates that freeing the listener should close the underlying
 * socket. *
#define LEV_OPT_CLOSE_ON_FREE		(1u<<1)  //关闭的时候释放监听对象
 ** Flag: Indicates that we should set the close-on-exec flag, if possible *
#define LEV_OPT_CLOSE_ON_EXEC		(1u<<2)
 ** Flag: Indicates that we should disable the timeout (if any) between when
 * this socket is closed and when we can listen again on the same port. *
#define LEV_OPT_REUSEABLE		(1u<<3)  //端口可以重复使用
 ** Flag: Indicates that the listener should be locked so it's safe to use
 * from multiple threadcs at once. *
#define LEV_OPT_THREADSAFE		(1u<<4)
 ** Flag: Indicates that the listener should be created in disabled
 * state. Use evconnlistener_enable() to enable it later. *
#define LEV_OPT_DISABLED		(1u<<5)
 ** Flag: Indicates that the listener should defer accept() until data is
 * available, if possible.  Ignored on platforms that do not support this.
 *
 * This option can help performance for protocols where the client transmits
 * immediately after connecting.  Do not use this option if your protocol
 * _doesn't_ start out with the client transmitting data, since in that case
 * this option will sometimes cause the kernel to never tell you about the
 * connection.
 *
 * This option is only supported by evconnlistener_new_bind(): it can't
 * work with evconnlistener_new_fd(), since the listener needs to be told
 * to use the option before it is actually bound.
 *
#define LEV_OPT_DEFERRED_ACCEPT		(1u<<6)
 ** Flag: Indicates that we ask to allow multiple servers (processes or
 * threads) to bind to the same port if they each set the option.
 *
 * SO_REUSEPORT is what most people would expect SO_REUSEADDR to be, however
 * SO_REUSEPORT does not imply SO_REUSEADDR.
 *
 * This is only available on Linux and kernel 3.9+
 *
#define LEV_OPT_REUSEABLE_PORT		(1u<<7)
 ** Flag: Indicates that the listener wants to work only in IPv6 socket.
 *
 * According to RFC3493 and most Linux distributions, default value is to
 * work in IPv4-mapped mode. If there is a requirement to bind same port
 * on same ip addresses but different handlers for both IPv4 and IPv6,
 * it is required to set IPV6_V6ONLY socket option to be sure that the
 * code works as expected without affected by bindv6only sysctl setting in
 * system.
 *
 * This socket option also supported by Windows.
 *
#define LEV_OPT_BIND_IPV6ONLY		(1u<<8)

3、回调函数原型:
 **
   A callback that we invoke when a listener has a new connection.

   @param listener The evconnlistener
   @param fd The new file descriptor
   @param addr The source address of the connection
   @param socklen The length of addr
   @param user_arg the pointer passed to evconnlistener_new()
 *
typedef void (*evconnlistener_cb)(struct evconnlistener *, evutil_socket_t, struct sockaddr *, int socklen, void *);

4、bufferevent_socket_new函数
  **
  Create a new socket bufferevent over an existing socket.

  @param base the event base to associate with the new bufferevent.
  @param fd the file descriptor from which data is read and written to.
	    This file descriptor is not allowed to be a pipe(2).
	    It is safe to set the fd to -1, so long as you later
	    set it with bufferevent_setfd or bufferevent_socket_connect().
  @param options Zero or more BEV_OPT_* flags
  @return a pointer to a newly allocated bufferevent struct, or NULL if an
	  error occurred
  @see bufferevent_free()
  *
EVENT2_EXPORT_SYMBOL
struct bufferevent *bufferevent_socket_new(struct event_base *base, evutil_socket_t fd, int options);

//属性设置
  ** Options that can be specified when creating a bufferevent *
  enum bufferevent_options {
	 ** If set, we close the underlying file
	 * descriptor/bufferevent/whatever when this bufferevent is freed. *
	BEV_OPT_CLOSE_ON_FREE = (1<<0),

	 ** If set, and threading is enabled, operations on this bufferevent
	 * are protected by a lock *
	BEV_OPT_THREADSAFE = (1<<1),

	 ** If set, callbacks are run deferred in the event loop. *
	BEV_OPT_DEFER_CALLBACKS = (1<<2),

	** If set, callbacks are executed without locks being held on the
	* bufferevent.  This option currently requires that
	* BEV_OPT_DEFER_CALLBACKS also be set; a future version of Libevent
	* might remove the requirement.*
	BEV_OPT_UNLOCK_CALLBACKS = (1<<3)
  };

5、给bufferevent设置回调函数 bufferevent_setcb
  **
  Changes the callbacks for a bufferevent.

  @param bufev the bufferevent object for which to change callbacks
  @param readcb callback to invoke when there is data to be read, or NULL if
	 no callback is desired
  @param writecb callback to invoke when the file descriptor is ready for
	 writing, or NULL if no callback is desired
  @param eventcb callback to invoke when there is an event on the file
	 descriptor
  @param cbarg an argument that will be supplied to each of the callbacks
	 (readcb, writecb, and errorcb)
  @see bufferevent_new()
  *
EVENT2_EXPORT_SYMBOL
void bufferevent_setcb(struct bufferevent *bufev,
    bufferevent_data_cb readcb, bufferevent_data_cb writecb,
    bufferevent_event_cb eventcb, void *cbarg);

6、使能bufferevent对象bufferevent_enable函数
  **
  Enable a bufferevent.

  @param bufev the bufferevent to be enabled
  @param event any combination of EV_READ | EV_WRITE.
  @return 0 if successful, or -1 if an error occurred
  @see bufferevent_disable()
  *
  EVENT2_EXPORT_SYMBOL int bufferevent_enable(struct bufferevent *bufev, short event);

7、回调函数read_cb原型:
  **
   A read or write callback for a bufferevent.

   The read callback is triggered when new data arrives in the input
   buffer and the amount of readable data exceed the low watermark
   which is 0 by default.

   The write callback is triggered if the write buffer has been
   exhausted or fell below its low watermark.

   @param bev the bufferevent that triggered the callback
   @param ctx the user-specified context for this bufferevent
  *
typedef void (*bufferevent_data_cb)(struct bufferevent *bev, void *ctx);

8、bufferevent_read函数原型:从bufferevent对象中读取数据
  **
  Read data from a bufferevent buffer.

  The bufferevent_read() function is used to read data from the input buffer.

  @param bufev the bufferevent to be read from
  @param data pointer to a buffer that will store the data
  @param size the size of the data buffer, in bytes
  @return the amount of data read, in bytes.
  *
  EVENT2_EXPORT_SYMBOL
  size_t bufferevent_read(struct bufferevent *bufev, void *data, size_t size);

9、回调函数bufferevent_event_cb原型:如果不是读\写事件之外的事件
  **
   An event/error callback for a bufferevent.

   The event callback is triggered if either an EOF condition or another
   unrecoverable error was encountered.

   For bufferevents with deferred callbacks, this is a bitwise OR of all errors
   that have happened on the bufferevent since the last callback invocation.

   @param bev the bufferevent for which the error condition was reached
   @param what a conjunction of flags: BEV_EVENT_READING or BEV_EVENT_WRITING
	  to indicate if the error was encountered on the read or write path,
	  and one of the following flags: BEV_EVENT_EOF, BEV_EVENT_ERROR,
	  BEV_EVENT_TIMEOUT, BEV_EVENT_CONNECTED.

   @param ctx the user-specified context for this bufferevent
  *
typedef void (*bufferevent_event_cb)(struct bufferevent *bev, short what, void *ctx);

************************************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <event.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <event2/listener.h>
#include <string.h>

//bufferevent对象读\写之外的事件,
void event_cb(struct bufferevent *bev, short what, void *ctx)
{
    printf("异常发生!%x \n", what);
    if(what & BEV_EVENT_EOF)  //以 ctrl+C 为例,当客户端断开,便会触发该回调类型
    {
        //printf("客户端 %d 下线\n", *(int*)ctx);  //容易传局部参数,不建议传

        printf("客户端下线\n");
        //释放bufferevent对象
        bufferevent_free(bev);
    }
    else
    {
        printf("未知错误!\n");
    }
}

//回调函数read_cb实现,读取数据。参数:bufferevent对象、回调函数入参(可将fd传入,知道当前的连接fd是谁)
void read_cb(struct bufferevent *bev, void *ctx)
{
    //int fd = *(int*)ctx;  //容易传局部参数,不建议
    //读取数据
    char buf[128] = {0};
    size_t ret =  bufferevent_read(bev, buf, sizeof(buf));
    if(0 > ret)
    {
        printf("bufferevent_read error\n");
    }
    else
    {
        //printf("read from %d %s \n", fd, buf);

        printf("read %s \n", buf);
    }
}

void listener_cb(struct evconnlistener *listener, evutil_socket_t fd, struct sockaddr *addr, int socklen, void *arg)
{
    printf("服务器接收 %d 的连接\n", fd);

    struct event_base *base = arg;
    //针对已经存在的socket创建bufferevent对象。实际上是将TCP连接socket与bufferevent绑定起来建立连接
    //参数:事件集合(从主函数传递过来)、fd(代表TCP连接,一个客户端一个连接,fd不同)、属性设置BEV_OPT_CLOSE_ON_FREE(如果释放bufferevent对象则关闭连接)
    struct bufferevent *bev =  bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
    if(NULL == bev)
    {
	printf("bufferevent_socket_new error!\n");
	exit(1);
    }

    //给bufferevent设置回调函数
    //参数:bufferevent对象、读事件回调函数、写事件回调函数、其他事件回调函数、回调函数入参(无参数用NULL,将fd地址传给回调函数)
    bufferevent_setcb(bev, read_cb, NULL, event_cb, &fd);

    //使能bufferevent对象,使bufferevent对象生效,读功能。
    bufferevent_enable(bev, EV_READ);


}

int main()
{
    //创建一个事件集合
    struct event_base *base = event_base_new();
    if(NULL == base)
    {
        printf("event_base_new error! \n");
        exit(1);
    }

    struct sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = 8000;
    server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");     

    //创建socket、绑定、监听、接受连接;创建监听对象,在指定的地址上监听接下来的TCP连接
    //参数:监听集合、回调函数(连接到来会触发调用)、回调参数、属性设置(释放监听对象关闭socket|端口重复使用、接收队列长度、绑定接收地址、地址长度
    struct evconnlistener *listener =  evconnlistener_new_bind(base, listener_cb, base, LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, 10, (struct sockaddr *)&server_addr, sizeof(server_addr));
    if(NULL == listener)
    {
	printf("evconnlistener_new_bind error! \n");
	exit(1);
    }

    //监听集合中的事件
    event_base_dispatch(base);

    //释放两个对象,base对象和监听对象
    evconnlistener_free(listener);
    event_base_free(base);



    return 0;
 }

测试客户端

NOTE: 运行客户端显示“connect error!
Connection refused ” 待解决。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <errno.h>

#define IP    "127.0.0.1"
#define PORT  8000

int main()
{
    int ret = -1;
    char buf[128] = {0};

    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if(-1 == fd)
    {
        printf("create socket error! \n");
        exit(1);
    }

    struct sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;            //设置tcp协议族
    server_addr.sin_port   = htons(PORT);        //设置端口号
    server_addr.sin_addr.s_addr = inet_addr(IP); //设置连接服务器ip地址

    ret = connect(fd, (struct sockaddr *)&server_addr, sizeof(server_addr));
    if(-1 == ret)
    {
        perror("connect\n");
       // printf("connect error! ret = %d \n",ret);
        close(fd);
        exit(1);
    }

    while(1)
    {
        printf("Input: \n");
        scanf("%s", buf);

        ret = send(fd, buf, sizeof(buf), 0);
        if(-1 == ret)
        {
	    printf("send error! \n");
            close(fd);
            exit(1);
        }
    }

    close(fd);

    return 0;
}

打赏一个呗

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦