IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 系统运维 -> Libevent学习记录(5) -> 正文阅读

[系统运维]Libevent学习记录(5)

TCP服务端和客户端开发基本流程(简易聊天窗口)

本节通过学习TCP服务端和客户端开发基本流程,对libevent库官方sample进行改写,完成一个简易聊天窗口的小项目,
功能:server启动后,client端启动并连接,在client中输入文字,server端收到后打印出来,在server中输入文字,client也能接收并打印。

服务端开发流程

1、创建event_base_new()创建框架上下文对象event base
2、evconnlistener_new_bind分配一个监听器对象,监听给定地址上的TCP连接,通知设置监听回调(当新连接到来时,框架会调用)。
备注:这个函数相当于完成系统调用socket()、bind()、listen(),并设置accept回调函数
(1)在监听回调中通过一个已存在的socket描述符创建socket bufferevent;
(2)设置bufferevent事件的回调函数;
(3)启用/禁用bufferevent相关写缓存区
3、启动事件调度循环event_base_dispatch()
4、根据业务逻辑进行读写操作bufferevent_read() /bufferevent_write()
5、结束则释放相关资源;

客户端开发流程

1、创建event_base_new()创建框架上下文对象event base
2、使用bufferevent socket new()创建和服务器通讯的bufferevent事件
3、设置bufferevent事件的事件回调函数bufferevent_setcb(),在事件回调体中
(1)判断events事件值为BEV_EVENT_CONNECTED,则进行连接成功的逻辑处理;
(2)判断events事件值为BEV_EVENT_ERROR,则进行连接发生错误的逻辑处理;
4、bufferevent_socket_connect()连接服务器
(1)若直接返回-1,则代表已经出错,进行出错逻辑处理;
(2)如果返回0,则代表几种可能,可能成功,也可能还在握手阶段,具体根据设置的事件回调中进行判断
5、启动事件调度循环event_base_dispatch()
6、根据业务逻辑进行读写操作bufferevent_read() /bufferevent_write()
7、结束则释放相关资源;

服务端代码:

注意:我们要求能读取终端输入的消息并互相发送给对方,这里我最初的想法是在conn_readcb回调函数中,当我们的读取到消息后,再使用gets()函数获取键盘输入,然后bufferevent_write给服务器,但是这样的话程序会一直阻塞等着我输入,不能正常的再次调用conn_readcb回调函数去读取;所以我们的conn_readcb回调函数中是不能使用gets这种输入的。
后来到网上看了一种方式解决,通过使用一个事件来单独检测键盘输入,当键盘有输入时,触发该事件进行回调,然后在对应回调函数中进行bufferevent_write;
综上所述,服务器的代码大致流程:
1、新建一个event_base,然后在其上绑定一个listener来监听特定端口(9995);
2、新建一个处理信号的事件signal_event,并与上一步中的event_base绑定;
3、调用event_base_dispatch来监控两个事件,包括指定的TCP连接及SIGINT信号:
??当监听到一个连接时,libevent会触发listen_callback,即为上面的listener_cb函数,该函数完成:
??(1)、首先会创建一个基于底层的socket的bufferevent和一个用来监听终端输入的event_cmd;将event_cmd加入到event_base中进行监听终端输入;
??(2)、然后设置bufferevent为可写可读,调用bufferevent_write函数向其中缓存区写。bufferevent_write函数写后会触发写回调函数conn_writecb,由于该例子中不需要其他操作,所以conn_writecb函数直接不进行任何操作。
??(3)、当终端有输入时,调用cmd_msg_cb回调函数read输入的消息,然后bufferevent_write将消息写入缓冲区;
4、当中断信号(Ctrl+C)出现时,libevent会触发signal_event的回调函数signal_cb,该函数会在2秒后停止event_base的监听,并退回到主函数;
5、主函数依次释放listener,signal_event和event_base的资源,结束。

补充:STDIN_FILENO
STDIN_FILENO属于系统API接口库,其声明为 int 型,是一个打开文件句柄,对应的函数主要包括 open/read/write/close 等系统级调用。
操作系统一级提供的文件API都是以文件描述符来表示文件。STDIN_FILENO就是标准输入设备(一般是键盘)的文件描述符。

#include <string.h>
#include <errno.h>
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.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>

static char getClientMsg[1024]={0};
static char sendClientMsg[1024]={0};
static int sendcnt = 1;
static const int PORT = 9995;
int clientCnt = 0;
static void listener_cb(struct evconnlistener *, evutil_socket_t,
    struct sockaddr *, int socklen, void *);
static void conn_readcb(struct bufferevent *, void *);
static void conn_writecb(struct bufferevent *, void *);
static void conn_eventcb(struct bufferevent *, short, void *);
static void signal_cb(evutil_socket_t, short, void *);
static void cmd_msg_cb(int , short , void* arg);
int
main(int argc, char **argv)
{
        struct event_base *base;        //定义event_base变量
        struct evconnlistener *listener;//定义一个evconnlistener来监测端口(9995)
        struct event *signal_event;     //定义一个处理信号的事件来处理ctrl+c的信
号

        struct sockaddr_in sin = {0};   //socket连接的协议地址结构体
#ifdef _WIN32
        WSADATA wsa_data;
        WSAStartup(0x0201, &wsa_data);
#endif

        base = event_base_new();
        if (!base) {
                fprintf(stderr, "Could not initialize libevent!\n");
                return 1;
        }
        //创建socket连接,设置sockaddr_in对应参数
        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, -1,
            (struct sockaddr*)&sin,
            sizeof(sin));

        if (!listener) {
                fprintf(stderr, "Could not create a listener!\n");
                return 1;
        }

        signal_event = evsignal_new(base, SIGINT, signal_cb, (void *)base);
        //判断signal_event是否创建成功,成功则并将其加入libevent事件链表上
        if (!signal_event || event_add(signal_event, NULL)<0) {
                fprintf(stderr, "Could not create/add a signal event!\n");
                return 1;
        }
        //进入主循环,检测事件是否发生,事件发生调用对应回调函数
        event_base_dispatch(base);//实际上是调用了event_base_loop();
        //依次释放listener,signal_event和event_base的资源,结束
        evconnlistener_free(listener);
        event_free(signal_event);
        event_base_free(base);

        printf("done\n");
        return 0;
}
static void
listener_cb(struct evconnlistener *listener, evutil_socket_t fd,
    struct sockaddr *sa, int socklen, void *user_data)
{
        struct event_base *base = user_data;
        struct bufferevent *bev;
        struct event *ev_cmd;
        bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
        if (!bev) {
                fprintf(stderr, "Error constructing bufferevent!");
                event_base_loopbreak(base);//结束事件主循环
                return;
        }
        ev_cmd = event_new(base, STDIN_FILENO,
                                      EV_READ | EV_PERSIST, cmd_msg_cb,
                                      (void*)bev);
        event_add(ev_cmd, NULL);
        bufferevent_setcb(bev, conn_readcb, conn_writecb, conn_eventcb, NULL);
        bufferevent_enable(bev, EV_WRITE);
        bufferevent_enable(bev, EV_READ);


        char Msg[64] = {0};
        sprintf(Msg, "welcome NO.%d client ", ++clientCnt);
        bufferevent_write(bev, Msg, strlen(Msg));
}

static void cmd_msg_cb(int fd, short events, void* arg)
{
        char msg[1024]={0};

        int ret = read(fd, msg, sizeof(msg));
        if( ret < 0 )
        {
                perror("read fail ");
                exit(1);
        }
        struct bufferevent* bev = (struct bufferevent*)arg;

        //把终端的消息发送给服务器端  
        bufferevent_write(bev, msg, ret);
}


static void
conn_readcb(struct bufferevent *bev, void *user_data)
{
        struct evbuffer *input = bufferevent_get_input(bev);
        size_t len = evbuffer_get_length(input);
        memset(getClientMsg,0,1024);
        bufferevent_read(bev, getClientMsg, len);
        getClientMsg[len] = '\0';
        printf("recv from NO.%d client =======>%s\n",clientCnt, getClientMsg);
}


static void
conn_writecb(struct bufferevent *bev, void *user_data)
{

}

static void
conn_eventcb(struct bufferevent *bev, short events, void *user_data)
{
      //BEV_EVENT_EOF遇到文件结束指示
        if (events & BEV_EVENT_EOF) {
                printf("Connection closed.\n");
        }
        //BEV_EVENT_ERROR操作时发生错误 
        else if (events & BEV_EVENT_ERROR) {
                printf("Got an error on the connection: %s\n",
                    strerror(errno));/*XXX win32*/
        }
        /* None of the other events can happen here, since we haven't enabled
         * timeouts */
        bufferevent_free(bev);
}

static void
signal_cb(evutil_socket_t sig, short events, void *user_data)
{
        struct event_base *base = user_data;
        struct timeval delay = { 2, 0 };//定义delay为2s

        printf("Caught an interrupt signal; exiting cleanly in two seconds.\n");

        event_base_loopexit(base, &delay);//2s后结束事件主循环
}

客户端代码

#include <event2/event.h>
#include <event2/bufferevent.h>
#include <event2/buffer.h>
#include <sys/socket.h>
#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>

static char getServerMsg[1024]={0};
static char sendServerMsg[1024]={0};


void eventcb(struct bufferevent *bev, short events, void *ptr)
{
        if (events & BEV_EVENT_CONNECTED) {
                printf("connect success\n");
                const char* Msg = "hello, server";
                bufferevent_write(bev, Msg, strlen(Msg));
                return;


        } else if (events & BEV_EVENT_ERROR) {
                /* An error occured while connecting. */
                printf("An error occured while connecting\n");
        }
        bufferevent_free(bev);
}

static void
conn_readcb(struct bufferevent *bev, void *user_data)
{

        struct evbuffer *input = bufferevent_get_input(bev);
        size_t len = evbuffer_get_length(input);
        memset(getServerMsg,0,1024);
        bufferevent_read(bev, getServerMsg, len);
        getServerMsg[len] = '\0';
        printf("recv from server =======> %s \n", getServerMsg);
static void
conn_writecb(struct bufferevent *bev, void *user_data)
{
        /*      
        memset(sendServerMsg,0,1024);
        printf("ser:>>");
        gets(sendServerMsg);
        //scanf("%s", g_szWriteMsg);    
        bufferevent_write(bev, sendServerMsg, strlen(sendServerMsg));
        */
}

static void cmd_msg_cb(int fd, short events, void* arg)
{
        char msg[1024]={0};

        int ret = read(fd, msg, sizeof(msg));
        if( ret < 0 )
        {
                perror("read fail ");
                exit(1);
        }
        struct bufferevent* bev = (struct bufferevent*)arg;

        //把终端的消息发送给服务器端  
        bufferevent_write(bev, msg, ret);
}

int main(int argc,char **argv)
{
        if(argc!=3){
                printf("请输入正确的ip地址和端口号\n");
                exit(0);
        }

        struct event_base *base;
        struct bufferevent *bev;
        struct event *ev_cmd;
        struct sockaddr_in sin;

        base = event_base_new();

        memset(&sin, 0, sizeof(sin));
        sin.sin_family = AF_INET;
        inet_aton(argv[1],&sin.sin_addr); /* 127.0.0.1 */
        sin.sin_port = htons(atoi(argv[2])); /* Port 9995 */

        bev = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE);
        //使用一个事件来检测键盘输入STDIN_FILENO为标准1输入设备(键盘)的文件描
述符     
        ev_cmd = event_new(base, STDIN_FILENO,
                                      EV_READ | EV_PERSIST, cmd_msg_cb,
                                      (void*)bev);
        event_add(ev_cmd, NULL);
        bufferevent_setcb(bev, conn_readcb,NULL, eventcb, NULL);

        if (bufferevent_socket_connect(bev,
                                (struct sockaddr *)&sin, sizeof(sin)) < 0) {

                /* Error starting connection */
                bufferevent_free(bev);
                return -1;
        }
        bufferevent_enable(bev, EV_WRITE|EV_PERSIST);
        bufferevent_enable(bev, EV_READ|EV_PERSIST);

        event_base_dispatch(base);
        bufferevent_free(bev);
        printf("finished\n");
        return 0;
}


运行结果

服务器:
在这里插入图片描述

客户端:
在这里插入图片描述

多个客户端连接也是没有问题的。

  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2022-03-03 16:53:58  更:2022-03-03 16:55:54 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/16 3:41:57-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码