共享单车(七):网络接口

在这里插入图片描述

消息传输格式定义

发送数据前,给数据附加两字节的长度:

4字节2个字节4个字节
FBEB事件ID数据长度N数据内容
  1. 包标识(FBEB): 包头部的特殊标识,用来标识包的开始
  2. 事件类型:事件ID, 固定两个字节表示
  3. 数据长度:数据包的大小, 固定长度4字节。
  4. 数据内容:数据内容, 长度为数据头定义的长度大小。
// 用于应用程序协议
#define MESSAGE_HEADER_LEN  10
#define MESSAGE_HEADER_ID   "FBEB"

a)发送端:先发送包标识、时间类型和长度,再发送数据内容。
b)接收端:先解析本次数据包的时间ID、大小N,再读取N个字节,这N个字节就是一个完整的数据内容。

服务器端在接收到第一个数据后会先取出前四个字节判断是否是"FBEB" 。如果不是,则数据出错,断开该连接。如果是,则继续下面的处理,取出事件ID,保存事件ID之后根据事件ID做出具体的响应操作。取出数据长度,循化等待接收数据,直至接收到的数据长度等于数据长度。

封装

class NetworkInterface
{
public:
	NetworkInterface();
	~NetworkInterface();

	bool start(int port);
	void close();

	static void listener_cb(struct evconnlistener* listener, evutil_socket_t fd,
		struct sockaddr* sock, int socklen, void* arg);

	static void handle_request(struct bufferevent* bev, void* arg);	//读请求回调
	static void handle_response(struct bufferevent* bev, void* arg);	//发送响应回调
	static void handle_error(struct bufferevent* bev, short event, void* arg);	//处理错误回调

	void network_event_dispatch();

	// 发送线程池的响应结果
	void send_response_message(ConnectSession* cs);

private:
	struct evconnlistener* listener_;
	struct event_base* base_;

};

监听事件

void  NetworkInterface::listener_cb(struct evconnlistener* listener, evutil_socket_t fd,
    struct sockaddr* sock, int socklen, void* arg) {
    struct event_base* base = (struct event_base*)arg;  // libevent的事务处理框架,负责事件注册、删除等

    LOG_DEBUG("accept a client %d\n", fd);

    // 为这个客户端分配一个bufferevent  
    // if fd = -1 则表示客户端需要用bufferevent_socket_connect连接服务器
    struct bufferevent* bev = bufferevent_socket_new(base, fd,
        BEV_OPT_CLOSE_ON_FREE); 

    ConnectSession* cs = session_init(fd, bev);
    cs->session_stat = SESSION_STATUS::SS_REQUEST;  // 可以开始接收数据
    cs->req_stat = MESSAGE_STATUS::MS_READ_HEADER;  // 数据头部

    strcpy(cs->remote_ip, inet_ntoa(((sockaddr_in*)sock)->sin_addr));   // 复制IP到当前会话,‘\0’是停止拷贝的终止条件,同时也会将 '\0' 也复制到目标空间
    LOG_DEBUG("remote ip : %s\n", cs->remote_ip);

    // 设定bufferevent接收数据回调函数
    // readcb、writecb和eventcb函数将分别在已经读取足够的数据 、已经写入足够的数据 ,或者发生错误时被调用 
    bufferevent_setcb(bev, handle_request, handle_response, handle_error, cs); 

    // 默认情况下,新创建的 bufferevent 的写入是启用的,但是读取没有启用
    // 其实是调用了event_add 将相应读写事件加入事件监听队列poll
    bufferevent_enable(bev, EV_READ | EV_PERSIST);  //将 bev 时间添加到epoll  EV_PERSIST:事件是“持久的”


    // //设置读、写事件的超时时间
    bufferevent_settimeout(bev, 10, 10);//超时值应设置在配置文件
}

读请求

/*****************************************************
*            4字节         2个字节         4个字节
*  请求格式:FBEB	       事件ID	      数据长度N	    数据内容

*  1.包标识:  包头部的特殊标识, 用来标识包的开始
*  2.事件类型:事件ID, 固定两个字节表示
*  3.数据长度:数据包的大小, 固定长度4字节。
*  4.数据内容:数据内容, 长度为数据头定义的长度大小。
*
* ****************************************************/
void NetworkInterface::handle_request(struct bufferevent* bev, void* arg)
{
    ConnectSession* cs = (ConnectSession*)arg;

    if (cs->session_stat != SESSION_STATUS::SS_REQUEST) // 是否可以接收
    {
        LOG_WARN("NetworkInterface::handle_request - wrong session state[%d].", cs->session_stat);
        return;
    }

    if (cs->req_stat == MESSAGE_STATUS::MS_READ_HEADER)
    {
        // 从底层的套接字缓冲区中读取数据,并将其复制到应用层的缓冲区中
        i32 len = bufferevent_read(bev, cs->header + cs->read_header_len, MESSAGE_HEADER_LEN - cs->read_header_len);
        cs->read_header_len += len;

        cs->header[cs->read_header_len] = '\0';
        LOG_DEBUG("recv from client<<<< %s\n", cs->header);

        if (cs->read_header_len == MESSAGE_HEADER_LEN)
        {
            if (strncmp(cs->header, MESSAGE_HEADER_ID, strlen(MESSAGE_HEADER_ID)) == 0) // 判断是否等于FBEB
            {
                cs->eid = *((u16*)(cs->header + 4));    // 读取事件 ID
                cs->message_len = *((i32*)(cs->header + 6));    // 读取消息长度

                LOG_DEBUG("NetworkInterface::handle_request - read  %d bytes in header, message len: %d\n", cs->read_header_len, cs->message_len);

                if (cs->message_len<1 || cs->message_len > MAX_MESSAGE_LEN) {
                    LOG_ERROR("NetworkInterface::handle_request wrong message, len: %u\n", cs->message_len);
                    bufferevent_free(bev);
                    session_free(cs);
                    return;
                }

                cs->read_buf = new char[cs->message_len];
                cs->req_stat = MESSAGE_STATUS::MS_READ_MESSAGE;
                cs->read_message_len = 0;

            }
            else
            {
                LOG_ERROR("NetworkInterface::handle_request - Invalid request from %s\n", cs->remote_ip);
                //直接关闭请求,不给予任何响应,防止客户端恶意试探
                bufferevent_free(bev);
                session_free(cs);
                return;
            }
        }
    }

    if (cs->req_stat == MESSAGE_STATUS::MS_READ_MESSAGE && evbuffer_get_length(bufferevent_get_input(bev)) > 0)    //取出输入缓冲区,开始读数据
    {
        i32 len = bufferevent_read(bev, cs->read_buf + cs->read_message_len, cs->message_len - cs->read_message_len);
        cs->read_message_len += len;
        LOG_DEBUG("NetworkInterface::handle_request - bufferevent_read: %d bytes, message len: %d read len: %d\n", len, cs->message_len, cs->read_message_len);

        if (cs->message_len == cs->read_message_len)    //接收数据完成
        {
            cs->session_stat = SESSION_STATUS::SS_RESPONSE; 
            iEvent* ev = DispatchMsgService::getInstance()->parseEvent(cs->read_buf, cs->read_message_len, cs->eid);    // 解析事件

            delete[] cs->read_buf;
            cs->read_buf = nullptr;
            cs->read_message_len = 0;

            if (ev)
            {
                ev->set_args(cs);
                DispatchMsgService::getInstance()->enqueue(ev); // 投递到队列中
            }
            else
            {
                LOG_ERROR("NetworkInterface::handle_request - ev is null. remote ip: %s, eid: %d\n", cs->remote_ip, cs->eid);
                //直接关闭请求,不给予任何响应,防止客户端恶意试探
                bufferevent_free(bev);
                session_free(cs);
                return;
            }
        }
    }
}

处理错误

//超时、连接关闭、读写出错等异常情况指定的回调函数
void NetworkInterface::handle_error(struct bufferevent* bev, short event, void* arg)
{
    ConnectSession* cs = (ConnectSession*)arg;

    LOG_DEBUG("NetworkInterface::handle_error ...\n");

    //连接关闭
    if (event & BEV_EVENT_EOF)
    {
        LOG_DEBUG("connection closed\n");
    }
    else if ((event & BEV_EVENT_TIMEOUT) && (event & BEV_EVENT_READING))
    {
        LOG_WARN("NetworkInterface::reading timeout ...\n");
    }
    else if ((event & BEV_EVENT_TIMEOUT) && (event & BEV_EVENT_WRITING))
    {
        LOG_WARN("NetworkInterface::writting timeout ...\n");
    }
    else if (event & BEV_EVENT_ERROR)
    {
        LOG_ERROR("NetworkInterface:: some other error ...\n");
    }


    //这将自动close套接字和free读写缓冲区
    bufferevent_free(bev);
    session_free(cs);
}

轮询

void NetworkInterface::network_event_dispatch()
{
    // 执行循环的时候,函数重复地检查是否有任何已经注册的事件被触发(比如说,读事件的文件描述符已经就绪,可以读取了;
    // 或者超时事件的超时时间即将到达)。如果有事件被触发,函数标记被触发的事件为“激活的”,并且执行这些事件。
    // EVLOOP_NONBLOCK,循环不会等待事件被触发:循环将仅仅检测是否有事件已经就绪,可以立即触发,如果有,则执行事件的回调。
    event_base_loop(base_, EVLOOP_NONBLOCK);
    //处理响应事件,回复响应消息 - 未完待续
    DispatchMsgService::getInstance()->handleAllResponseEvent(this);

}

发送响应

void NetworkInterface::send_response_message(ConnectSession* cs)
{
    if (cs->response == nullptr)
    {
        bufferevent_free(cs->bev);
        if (cs->request)
        {
            delete cs->request;
        }

        session_free(cs);
    }
    else
    {
        // 将内存中从 data 处开始的 size 字节数据添加到输出缓冲区的末尾 。
        bufferevent_write(cs->bev, cs->write_buf, cs->message_len + MESSAGE_HEADER_LEN);
        session_reset(cs);
    }
}

相关

在内核中有读缓冲区和写缓冲区,减少用户态和内核态的切换。

  • 用户态读缓冲区的存在是为了处理粘包的问题,因为网络协议栈是不知道用户界定数据包的格式,没法确定一个完整的数据包。
  • 用户态写缓冲区的存在是因为用户根本不清楚内核写缓存区的状态,需要把没有写出去的数据缓存起来等待下次写事件时把数据写出去。

buffer的设计有三种类型:
(1)固定数组,固定长度。 限定了处理数据包的能力,没有动态伸缩的能力;需要频繁挪动数据。
(2)ring buffer。可伸缩性差。
(3)chain buffer。 解决可伸缩性差的问题,避免频繁挪动数据;同时也引进了新的问题,一个数据可能在多个buffer中都有,即数据分割,这会导致多次系统调用,从而引起中断上下文的切换。解决办法是使用readv()将内核中连续的buffer读到用户态不连续的buffer中,writev()把用户态不连续的buffer写到内核连续的buffer中;从而减少系统调用。

libevent是一个事件通知库;封装了reactor。
libevent API 提供了一种机制,用于在文件描述符上发生特定事件或达到超时后执行回调函数。此外,libevent还支持由于信号或常规超时而导致的回调。
libevent 旨在替换在事件驱动的网络服务器中找到的事件循环。应用程序只需要调用event_dispatch(),然后动态添加或删除事件,而无需更改事件循环。

IO函数使用与网络原理
(1)有了libevent可以不使用IO函数。因为如果使用IO函数,既需要知道这些IO函数里面的系统调用返回值的含义。
(2)有了libevent就可以不清楚数据拷贝原理。
(3)有了libevent就可以不清楚网络原理以及网络编程流程。
(4)有了libevent只需要知道事件处理,IO操作完全交由libevent处理。

https://blog.csdn.net/u010710458/article/details/80067885
https://aceld.gitbooks.io/libevent/content/chapter1.html
https://blog.csdn.net/Long_xu/article/details/127142798

  • 14
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Python是一种强大的编程语言,广泛应用于数据分析和科学计算。共享单车数据分析是指通过对共享单车系统中的数据进行统计、分析和挖掘,以从中提取有价值的信息和洞察力。在技术方面,Python提供了许多用于数据处理和分析的库和工具,例如NumPy、Pandas和Matplotlib等。 Python在共享单车数据分析中的应用非常广泛。首先,Python具有简单易学的语法,使得初学者和专业人士都能够快速上手。其次,Python拥有丰富的数据处理和分析库,可以方便地进行数据清洗、筛选和转换。而且,Python还提供了强大的可视化工具,使得数据分析结果可以以图表的形式直观展示。 在共享单车数据分析中,通常需要进行以下几个步骤。首先,需要从共享单车系统中获取原始数据,包括骑行时间、地点、骑行距离等信息。其次,可以使用Python的Pandas库对数据进行清洗和预处理,比如去除异常值、处理缺失值等。然后,可以使用Python的统计分析库,如Scipy和Statsmodels,进行数据分析和模型建立。最后,可以使用Matplotlib进行数据可视化,生成各种图表和报表,以便更好地理解数据,发现潜在的模式和规律。 总之,Python在共享单车数据分析中具备良好的适用性和实用性,可以帮助我们更好地理解共享单车系统的运行情况,提供决策支持和优化方案。CSDN是一个开发者社区网站,上面有很多关于Python共享单车数据分析的教程和案例分享,可以供初学者和专业人士参考学习。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

飞大圣

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值