网络库:NetLib 的封装

news2024/11/27 12:44:36

目录

一:前言

二:Socket的封装

1:成员变量

2:服务端的函数

3:客户端的函数

4:触发的事件回调

5:发送和接收

三:事件循环

1:定时器

2:Event

3:epoll_wait

4:检查任务

四:net_lib的封装


一:前言

        写过网络的小伙伴,一定写过长长的代码,来完成一个服务器的框架,长长的代码堆叠在一个main函数中,十分不美观,今天我们就将网络相关的代码全部封装成接口,使main函数中显示的代码十分简洁。

int main()
{
    int ret = netlib_listen(http_ip_, http_port, (callback_t)Http_callback, NULL);
    if(ret == -1)
    {
        printf("listen %s:%s failed\n",http_ip_,http_port_);
        return -1;
    }

    LogInfo("server start listen on:For http://{}:{}", http_ip_, http_port_);

    LogInfo("now enter the event loop...");

    netlib_eventloop(1);

}

        我们看上面的代码,发现我们只把服务端的监听给暴露出来了,以及事件循环的接口,其他一概不知,使代码十分简洁。下面我们开始进行封装。

二:Socket的封装

1:成员变量

        首先甭管服务端还是客户端,他俩的函数其实都差不多,只有listen,accept,和connect的区别,其他基本一样,所以我们创建一个基类,在这个基类中我们将服务端和客户端的代码全部加进去,方便我们进行封装操作。

        在下面的类中,我们看成员变量,包括最基本的远程本地的ip和port,还有我们存放任务的callback和callbackdata,以及表示当前的socket,和连接状态。对于将成员进行返回的函数,没啥好讲的,剩下的就是服务端和客户端的具体函数了

//关于网络连接的最基本类
class CbaseSocket : public CRefObject {
    public:
    CbaseSocket();
    virtual ~CbaseSocket();     //纯虚函数

    void SetSocket (SOCKET fd) {socket_ = fd;}
    void SetState (uint8_t state) {state_ = state;}
    void SetCallback (callback_t callback){callback_ = callback ;}
    void SetCallbackData (void *data) {callbackdata_ = data;}
    void SetRemoteIP (char *ip) {remote_ip_ = ip;}
    void SetRemotePort (uint16_t port) {remote_port_ = port;}
    void SetSendBufSize(uint32_t send_size);
    void SetRecvBufSize(uint32_t recv_size);

    SOCKET GetSocket() {return socket_;}
    const char *GetRemoteIP() { return remote_ip_.c_str(); }
    uint16_t GetRemotePort() { return remote_port_; }
    const char *GetLocalIP() { return local_ip_.c_str(); }
    uint16_t GetLocalPort() { return local_port_; }

    public:
    //监听地址和端口
    int Listen(const char * server_ip,uint16_t server_port,callback_t callback,void * callback_data);

    //客户端进行连接的函数
    int Connect(const char * server_ip,uint16_t server_port,callback_t callback,void * callback_data);

    //发送数据
    int Send(void * buf ,int len);

    //接收数据
    int Recv(void * buf ,int len);

    //关闭连接
    int Close();

    public:
    //三种触发的事件
    void OnRead();
    void OnWrite();
    void OnClose();

    private: //私有函数以_ 开头
    int _GetErrorCode();
    bool _IsBlock(int error_code);

    void _SetNonblock(SOCKET fd);
    void _SetReuseAddr(SOCKET fd);
    void _SetNoDelay(SOCKET fd);
    void _SetAddr(const char *ip, const uint16_t port, sockaddr_in *addr);

    //接收客户端连接的地方
    void _AcceptNewSocket(); 

    private:
    //最基本的一些变量,源和目的 的 IP和端口 ,以及要传入进来的回调函数和参数。
    std::string remote_ip_;
    uint16_t remote_port_;
    std::string local_ip_;
    uint16_t local_port_;

    callback_t callback_;
    void *callbackdata_;

    uint8_t state_;
    SOCKET socket_;     //int
};

2:服务端的函数

        服务端最重要的就是listen和accept函数,我们将关于他的代码全部放进去,封装起来,下面是listen和accept的函数。

//监听地址和端口,总的来说就是设置socket,bind地址,最后listen。
int CbaseSocket::Listen(const char * server_ip,uint16_t server_port,callback_t callback,void * callback_data)
{
    //这里的listen监听函数,其中包含了回调函数,是客户端要传进来的参数。当然服务器也是需要配置一个socket的
    local_ip_ = server_ip;
    local_port_ = server_port;
    callback_ = callback;
    callbackdata_ = callback_data;

    socket_ = socket(AF_INET,SOCK_STREAM,0 );
    if(socket_ == INVALID_SOCKET)
    {
        printf("socket failed, err_code=%d, server_ip=%s, port=%u",_GetErrorCode(), server_ip, server_port);
        return NETLIB_ERROR;
    }

    _SetReuseAddr(socket_);
    _SetNonblock(socket_);

    sockaddr_in serv_addr;
    _SetAddr(server_ip,server_port,&serv_addr);

    int ret = bind(socket_,(sockaddr *)&serv_addr,sizeof(serv_addr));
    if(ret == SOCKET_ERROR)
    {
        printf("bind failed, err_code=%d, server_ip=%s, port=%u", _GetErrorCode(), server_ip, server_port);
        closesocket(socket_);
        return NETLIB_ERROR;
    }

    ret = listen(socket_,64);
    if(ret == SOCKET_ERROR)
    {
        printf("listen failed, err_code=%d, server_ip=%s, port=%u", _GetErrorCode(), server_ip, server_port);
        closesocket(socket_);
        return NETLIB_ERROR;
    }

    state_ = SOCKET_STATE_LISTENING;

    printf("listen success, server_ip=%s, port=%u", server_ip, server_port);

    Addsocket(this);    //将服务端自己添加进去
    CEventDispatch::Instance()->AddEvent(socket_, SOCKET_READ | SOCKET_EXCEP);      //此处是进行分发事件
    return NETLIB_OK;

}

        我们accept是要一直阻塞等待的,所以实用while循环,当客户端链接上来,那就给他分配一个socket,然后就是添加到事件循环等等。最后我们看,这个客户端还执行了传入来的回调函数,这个回调函数里面还是重新设置回调函数,将业务代码添加进去。

//接收客户端连接的地方,服务端最开始只需要进行监听即可,在只读中开始接收客户端的连接。
void CbaseSocket::_AcceptNewSocket()
{
    SOCKET fd =0;
    sockaddr_in peer_addr;
    socklen_t addr_len = sizeof(sockaddr_in);
    char ip_str[64];
    while((fd = accept(socket_,(sockaddr *)&peer_addr,&addr_len))!=INVALID_SOCKET) 
    {   //连接上来了,我们分配一个socket给他
        CbaseSocket * psocket = new CbaseSocket();
        uint32_t ip = ntohl(peer_addr.sin_addr.s_addr);
        uint16_t port = ntohs(peer_addr.sin_port);
        snprintf(ip_str, sizeof(ip_str), "%d.%d.%d.%d", ip >> 24,(ip >> 16) & 0xFF, (ip >> 8) & 0xFF, ip & 0xFF);

        psocket->SetSocket(fd);
        psocket->SetRemoteIP(ip_str);
        psocket->SetRemotePort(port);
        psocket->SetCallback(callback_);
        psocket->SetCallbackData(callbackdata_);
        psocket->SetState(SOCKET_STATE_CONNECTED);

        _SetNoDelay(fd);
        _SetNonblock(fd);
        Addsocket(psocket);
        CEventDispatch::Instance()->AddEvent(fd,SOCKET_READ | SOCKET_EXCEP);

        //这里的回调函数也就是重新设置这个客户端的一些回调函数,以便后面使用业务相关的一些代码。
        //因为第一次只读状态肯定是客户端进行连接,而第二次只读状态就是业务相关的代码。
        callback_(callbackdata_, NETLIB_MSG_CONNECT, (net_handle_t)fd, NULL);
    } 
}

3:客户端的函数

        我们服务端进行接收的操作,那我们客户端就进行连接操作,和正常的连接操作没什么不同。

//客户端进行连接的函数
int CbaseSocket::Connect(const char * server_ip,uint16_t server_port,callback_t callback,void * callback_data)
{
    //这里是客户端的
    printf("");
    remote_ip_=server_ip;
    remote_port_=server_port;
    callback_=callback;
    callbackdata_=callback_data;

    socket_ = socket(AF_INET,SOCK_STREAM,0);
    if(socket_==INVALID_SOCKET)
    {
        printf("socket failed, err_code=%d, server_ip=%s, port=%u", _GetErrorCode(), server_ip, server_port);
        return NETLIB_ERROR;
    }

    _SetNonblock(socket_);
    _SetNoDelay(socket_);
    sockaddr_in serv_addr;
    _SetAddr(server_ip, server_port, &serv_addr);

    int ret = connect(socket_, (sockaddr *)&serv_addr, sizeof(serv_addr));
    if((ret==SOCKET_ERROR)&&(!_IsBlock(_GetErrorCode())))
    {
        printf("connect failed, err_code=%d, server_ip=%s, port=%u", _GetErrorCode(), server_ip, server_port);
        close(socket_);
        return NETLIB_INVALID_HANDLE;
    }

    state_ = SOCKET_STATE_CONNECTING;
    Addsocket(this);
    CEventDispatch::Instance()->AddEvent(socket_, SOCKET_READ | SOCKET_EXCEP);
    return (net_handle_t)socket_;

}

4:触发的事件回调

        首先在Onread中,我们看到有两个地方,首先就是触发了服务端的事件,表示客户端连接上来了,然后我们进行连接的操作。另一个就是客户端触发了读事件,执行自己的回调函数。对于只写和关闭,我们还是执行客户端的回调函数。

//三种触发的事件
void CbaseSocket::OnRead()
{
    //我们服务端的只读状态是当客户端连接上来的时候。
    if(state_==SOCKET_STATE_LISTENING)
    {
        _AcceptNewSocket();
    }
    else
    {
        //除了连接,就是客户端发来数据的只读状态。还有要关闭连接的时候。
        u_long avail = 0;
        int ret = ioctlsocket(socket_, FIONREAD, &avail);
        if((ret == SOCKET_ERROR)||(avail ==0))
        {
            callback_(callbackdata_,NETLIB_MSG_CLOSE,(net_handle_t)socket_,NULL);
        }
        else
        {
            callback_(callbackdata_,NETLIB_MSG_READ,(net_handle_t)socket_,NULL);
        }
    }
    
}

void CbaseSocket::OnWrite()
{
    if(state_ == SOCKET_STATE_CONNECTING)
    {
        callback_(callbackdata_,NETLIB_MSG_CLOSE,(net_handle_t)socket_,NULL);

    }else 
    {
        callback_(callbackdata_,NETLIB_MSG_WRITE,(net_handle_t)socket_,NULL);

    }
}

void CbaseSocket::OnClose()
{
    state_ = SOCKET_STATE_CLOSING;
    callback_(callbackdata_,NETLIB_MSG_CLOSE,(net_handle_t)socket_,NULL);
}

5:发送和接收

我们在把这些send和recv 也给封装起来。

//发送数据
int CbaseSocket::Send(void * buf ,int len)
{
    //发送数据之前,我们要保证,连接是连接状态
    if(state_ != SOCKET_STATE_CONNECTED)
        return NETLIB_ERROR;

    int ret = send(socket_,(char *) buf,len,0);
    if(ret == SOCKET_ERROR) 
    {
        int err_code = _GetErrorCode();
        printf("send failed, err_code=%d, len=%d", err_code, len);
    }

    return ret;
}

//接收数据
int CbaseSocket::Recv(void * buf ,int len)
{
    return recv(socket_,(char *) buf,len,0);
}

//关闭连接
int CbaseSocket::Close()
{
    CEventDispatch::Instance()->RemoveEvent(socket_, SOCKET_READ | SOCKET_EXCEP);   //移除这个事件
    RemoveSocket(this);//将自己进行删除
    closesocket(socket_);
    ReleaseRef();
    return 0;
}

三:事件循环

        这里的事件循环就是主要封装epoll的,并且还加入了我们制作的定时器。下面这个类中我们的成员变量就是epoll的相关fd,以及定时器存放的容器和事件触发的容器。

//我们使用单例模式
class CEventDispatch 
{
    public:
    virtual ~CEventDispatch();

    void AddEvent(SOCKET fd, uint8_t socket_event);
    void RemoveEvent(SOCKET fd , uint8_t socket_event);

    void AddTimer(callback_t callback,void * user_data,uint64_t interval);
    void RemoveTimer(callback_t callback,void * user_data);

    void Addloop(callback_t callback ,void * user_data);

    void StartDispatch(uint32_t wait_timeout = 100);        //开始循环等待
    void StopDispatch();

    static CEventDispatch * Instance();
    bool IsRunning() { return running_; }

    protected:
    CEventDispatch();

    private:
    void _CheckTimer();
    void _CheckLoop();

    typedef struct 
    {
        callback_t callback;
        void * user_data;
        uint64_t interval;
        uint64_t next_tick;
    }TimerItem;

    private:
    int epfd_;
    CLock lock_;
    list<TimerItem *> timer_list_;
    list<TimerItem *> loop_list_;

    static CEventDispatch *event_dispatch_;     //单例模式
    bool running_;
};

1:定时器

        这个定时器比较简单,并没有使用像时间轮那种复杂度很高的定时器,我们这个定时器方案,能够保证可以使用即可。我们将定时任务放入到定时器中,然后每次当触发的事件结束后,就进行检查操作。

void CEventDispatch::AddTimer(callback_t callback,void * user_data,uint64_t interval)
{
    list<TimerItem *>::iterator it;
    //如果有一样的回调函数
    for(it = timer_list_.begin();it!=timer_list_.end();it++)
    {
        TimerItem *ptimer=*it;
        if(ptimer->callback ==callback&&ptimer->user_data ==user_data)
        {
            ptimer->interval = interval;
            ptimer->next_tick = GetTickCount() + interval;
            return ;
        }
    }

    TimerItem * ptimer = new TimerItem;
    ptimer->callback = callback;
    ptimer->interval = interval;
    ptimer->user_data = user_data;
    ptimer->next_tick = GetTickCount() + interval;
    timer_list_.push_back(ptimer);

    return ;
}

void CEventDispatch::RemoveTimer(callback_t callback,void * user_data)
{
    list<TimerItem *>::iterator it;
    for(it = timer_list_.begin();it!=timer_list_.end();it++)
    {
        TimerItem *ptimer=*it;
        if(ptimer->callback ==callback&&ptimer->user_data ==user_data)
        {
            timer_list_.erase(it);
            delete ptimer;
            return ;
        }
    }
}

2:Event

简单的加入操作

void CEventDispatch::AddEvent(SOCKET fd, uint8_t socket_event)
{
    struct epoll_event ev;
    ev.events =EPOLLIN | EPOLLOUT | EPOLLET | EPOLLPRI | EPOLLERR | EPOLLHUP;
    ev.data.fd = fd;
    int ret = epoll_ctl(epfd_,EPOLL_CTL_ADD,fd,&ev);
    if(ret <0)
    {
        printf("EPOLL_CTL_ADD faild fd:%d \n",fd);
    }
}

void CEventDispatch::RemoveEvent(SOCKET fd , uint8_t socket_event)
{
    int ret = epoll_ctl(epfd_,EPOLL_CTL_DEL,fd,NULL);
    if(ret <0)
    {
        printf("EPOLL_CTL_DEL faild fd:%d \n",fd);
    }
}

3:epoll_wait

我们在这里等待事件的触发,当触发后,执行相应的代码,最后检查定时器和loop的任务。

void CEventDispatch::StartDispatch(uint32_t wait_timeout )
{
    if(running_)
        return;
    running_ = true;
    int ret =0;
    struct epoll_event events[1024];

    while(running_)
    {
        ret =epoll_wait(epfd_,events,1024,wait_timeout);
        for(int i=0;i<ret;i++)
        {
            //拿取这个fd的全部信息,包括一些回调函数
            int ret1=events[i].data.fd;
            CbaseSocket * basesocket = FindCbaseSocket(ret1);
            if(!basesocket)
                continue;

            if(events[i].events &EPOLLIN)
            {
                basesocket->OnRead();
            }

            if(events[i].events &EPOLLOUT)
            {
                basesocket->OnWrite();
            }

            if(events[i].events &EPOLLRDHUP)
            {
                basesocket->OnClose();
            }

            if (events[i].events & (EPOLLPRI | EPOLLERR | EPOLLHUP)) 
            {
                basesocket->OnClose();         
            }

            basesocket->ReleaseRef();
        }

        //当上述的全部信息执行完毕,我们开始将多线程的回复信息发送出去,以及定时器的触发。
        _CheckTimer();
        _CheckLoop();
    }
}

4:检查任务

        我们开始检查任务是否超时,以及将任务返回的消息发送出去。

void CEventDispatch::_CheckTimer()
{
    for(list<TimerItem*>::iterator it = loop_list_.begin();it!=loop_list_.end();)
    {
        TimerItem *ptimer = *it;
        it++;   //防止在迭代器内,将itdelete掉
        if(ptimer->next_tick <=GetTickCount() )
        {
            ptimer->next_tick +=ptimer->interval;       //因为这个定时器,并未被删除,所以要加入这个时间。
            ptimer->callback(ptimer->user_data,NETLIB_MSG_LOOP,0,NULL);
        }
    }
}

void CEventDispatch::_CheckLoop()
{
    list<TimerItem*>::iterator it;
    for(it = loop_list_.begin();it!=loop_list_.end();it++)
    {
        TimerItem *ptimer = *it;
        ptimer->callback(ptimer->user_data,NETLIB_MSG_LOOP,0,NULL);
    }
}

int GetTickCount1()
{
    struct timeval tval;
    uint64_t ret_tick;
    gettimeofday(&tval,NULL);
    ret_tick =tval.tv_sec * 1000L + tval.tv_usec /1000L;
    return ret_tick;
}

四:net_lib的封装

我们的net_lib封装就是在上面的两个类中,在进行封装一层,就是我们可以直接调用,不用再写多余的代码。

#ifdef __cplusplus
extern "C"
{
#endif
    int netlib_init();
	int netlib_destroy();
	int netlib_listen(const char *server_ip,uint16_t port,callback_t callback,void *callback_data);
	net_handle_t netlib_connect(const char *server_ip,uint16_t port,callback_t callback,void *callback_data);
	int netlib_send(net_handle_t handle, void *buf, int len);
	int netlib_recv(net_handle_t handle, void *buf, int len);
	int netlib_close(net_handle_t handle);
	int netlib_option(net_handle_t handle, int opt, void *optval);
	int netlib_register_timer(callback_t callback, void *user_data, uint64_t interval);
	int netlib_delete_timer(callback_t callback, void *user_data);
	int netlib_add_loop(callback_t callback, void *user_data);
	void netlib_eventloop(uint32_t wait_timeout = 100);
	void netlib_stop_event();
	bool netlib_is_running();

#ifdef __cplusplus
}
#endif

下面我直接给出全部代码,因为都是直接封装的另一层,并没有添加多余的代码,而且还很好理解。

#include "netlib.h"
#include "base_socket.h"
#include "event_dispatch.h"

int netlib_init()
{
    return 0;
}

int netlib_destroy()
{
    return 0;
}

int netlib_listen(const char *server_ip,uint16_t port,callback_t callback,void *callback_data)
{
    CbaseSocket * psocket = new CbaseSocket();
    if(!psocket)
        return -1;

    int ret = psocket->Listen(server_ip, port, callback, callback_data);
    if(ret == -1)
    {
        delete psocket;
        return -1;
    }
    return ret;
}

net_handle_t netlib_connect(const char *server_ip,uint16_t port,callback_t callback,void *callback_data)
{
    CbaseSocket * psocket = new CbaseSocket();
    if(!psocket)
        return -1;
    
    int ret = psocket->Connect(server_ip,port,callback,callback_data);
    if(ret == -1)
    {
        delete psocket;
        return -1;
    }
    return ret;
}

int netlib_send(net_handle_t handle, void *buf, int len)
{
    CbaseSocket * psocket = FindCbaseSocket(handle);
    if(!psocket)
        return -1;
    int ret = psocket->Send(buf, len);
    psocket->ReleaseRef();
    return ret;
}

int netlib_recv(net_handle_t handle, void *buf, int len)
{
    CbaseSocket * psocket = FindCbaseSocket(handle);
    if(!psocket)
        return -1;
    int ret = psocket->Recv(buf, len);
    psocket->ReleaseRef();
    return ret;
}

int netlib_close(net_handle_t handle)
{
    CbaseSocket * psocket = FindCbaseSocket(handle);
    if(!psocket)
        return -1;
    int ret = psocket->Close();
    psocket->ReleaseRef();
    return ret;
}


int netlib_register_timer(callback_t callback, void *user_data, uint64_t interval)
{
    CEventDispatch::Instance()->AddTimer(callback, user_data, interval);
    return 0;
}

int netlib_delete_timer(callback_t callback, void *user_data)
{
    CEventDispatch::Instance()->RemoveTimer(callback, user_data);
    return 0;
}

int netlib_add_loop(callback_t callback, void *user_data)
{
    CEventDispatch::Instance()->Addloop(callback, user_data);
    return 0;
}

void netlib_eventloop(uint32_t wait_timeout )
{
    CEventDispatch::Instance()->StartDispatch(wait_timeout);
}

void netlib_stop_event()
{
    CEventDispatch::Instance()->StopDispatch();
}

bool netlib_is_running()
{
    return CEventDispatch::Instance()->IsRunning();
}

今天讲解了net_lib的封装,十分简洁。https://github.com/0voice

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2248417.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

自动泊车“哐哐撞大墙”,小米SU7智驾功能bug缠身?

文/王俣祺 导语&#xff1a;小米SU7&#xff0c;自带热度与科技光环的“流量神车”&#xff0c;近日却以一种极为“狼狈”的方式闯入大众视野。多达70余辆小米SU7陷入“泊车魔咒”&#xff0c;瞬间在网络上炸开了锅。从“科技控”到“惹祸精”的背后&#xff0c;究竟藏着怎样的…

优维HAO案例:全球TOP15汽车零件供应商「IT运维自动化」创新工程

撰文&#xff1a;鹿小U / 制图&#xff1a;脾气超好 又是一家很厉害的客户。 YADT是全(hu)球(zhēn)领(ji)先(sh)的汽车座椅供应商&#xff0c;拥有从汽车座椅零部件到整椅的完整生产制造能力&#xff0c;为中国几乎所有的汽车制造商提供汽车整椅产品和服务。 YADT在国内拥…

Linux宝塔部署wordpress网站更换服务器IP后无法访问管理后台和打开网站页面显示错乱

一、背景&#xff1a; wordpress网站搬家&#xff0c;更换服务器IP后&#xff0c;如果没有域名时&#xff0c;使用服务器IP地址无法访问管理后台和打开网站页面显示错乱。 二、解决方法如下&#xff1a; 1.wordpress搬家后&#xff0c;在新服务器上&#xff0c;新建站点时&am…

MyBatis框架-动态SQL-XML中的常用标签+特殊字符在XML中的显示

一、if标签、where标签、trim标签、choose标签、set标签、foreach标签 1、问题引入&#xff1a;where关键字和and关键字在动态SQL里面应该如何添加&#xff1f; &#xff08;1&#xff09;if标签&#xff1a; test属性的值是判断条件 if标签里面的内容是条件成立时添加到SQ…

EXTI配置流程 含中断延时消抖点亮小灯

如图可知&#xff0c;配置流程分成以下一个部分 ①使能GPIO时钟 __HAL_RCC_GPIOA_CLK_ENABLE();// 打开时钟 ②初始化利用 HAL_GPIO_Init 一步到位&#xff0c;可以初始化外设GPIO的一切 4个参数 &#xff08;引脚 Pull 这里选择的模式是从下面这几个里面选 速度&#x…

数据库系列之GaussDB数据库高可用能力测试验证

数据库的高可用能力是数据库的基本能力&#xff0c;可靠性的设计和机制能够保证数据库节点异常时能够正常切换、减少业务的影响范围和时间&#xff0c;保证业务的可用性和连续性。本文主要介绍GaussDB数据库的高可用能力测试验证情况&#xff0c;通过模拟故障场景来验证GaussDB…

Android BottomNavigationView 底部导航栏使用详解

一、BottomNavigationView简介 BottomNavigationView是官方提供可以实现底部导航的组件&#xff0c;最多支持5个item&#xff0c;主要用于功能模块间的切换&#xff0c;默认会包含动画效果。 官方介绍地址&#xff1a;BottomNavigationView 二、使用BottomNavigationView a…

IIT开发自适应协作界面,通过Xsens动作捕捉系统实现安全人机交互

意大利理工学院(IIT)的研究人员正在利用自适应界面转变人机协作&#xff0c;实现实时机器人调整和安全、无缝的交互。 本文要点: l 协作的实时适应&#xff1a;IIT的研究员西林图纳和厄兹达马尔开发了一种自适应协作界面(ACI)&#xff0c;允许机器人根据人类的运动意图实时调…

一学就废|Python基础碎片,格式化F-string

Python 3.6 中引入了 f-string语法&#xff0c;提供了一种简洁直观的方法来将表达式和变量直接嵌入到字符串中进行字符串格式化&#xff0c;f -string背后的想法是使字符串插值更简单。 要创建 f -string&#xff0c;在字符串前加上字母 “f”即可&#xff0c;与字符串本身的格…

SVG无功补偿装置MATLAB仿真模型

“电气仔推送”获得资料&#xff08;专享优惠&#xff09; 模型简介 SVG&#xff08;又称ASVG 或STATCOM&#xff09;是Static Var Generator 的缩写&#xff0c;叫做静止无功发生器。也是做无功补偿的&#xff0c;比SVC 更加先进。其基本原理是将自换相桥式电路通过电抗器或…

MVC、EL、JSTL

1.MVC设计模式 三层&#xff1a; MVC&#xff1a; M&#xff08;Model&#xff09;模型&#xff1a;负责业务逻辑处理&#xff0c;数据库访问。 V&#xff08;View&#xff09;视图&#xff1a;负责与用户交互。 C&#xff08;Controller&#xff09;控制器&#xff1a;负责流程…

Web开发技术栈选择指南

互联网时代的蓬勃发展&#xff0c;让越来越多人投身软件开发领域。面对前端和后端的选择&#xff0c;很多初学者往往陷入迷茫。让我们一起深入了解这两个领域的特点&#xff0c;帮助你做出最适合自己的选择。 在互联网发展的早期&#xff0c;前端开发主要负责页面布局和简单的…

太通透了,Android 流程分析 蓝牙enable流程(应用层/Framework/Service层)

零. 前言 由于Bluedroid的介绍文档有限&#xff0c;以及对Android的一些基本的知识需要了(Android 四大组件/AIDL/Framework/Binder机制/JNI/HIDL等)&#xff0c;加上需要掌握的语言包括Java/C/C等&#xff0c;加上网络上其实没有一个完整的介绍Bluedroid系列的文档&#xff0…

R语言绘图过程中遇到图例的图块中出现字符“a“的解决方法

R语言绘图过程中遇到图例的图块中出现字符的解决方法 因为我遇到这个问题的时候没在网上找到合适的方法&#xff0c;找到个需要付费的&#xff0c;算了。也许是因为问的方式不同&#xff0c;问了半天AI也回答出来&#xff0c;莫名有些烦躁&#xff0c;打算对代码做个分析&…

【C语言】字符串左旋的三种解题方法详细分析

博客主页&#xff1a; [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: C语言 文章目录 &#x1f4af;前言&#x1f4af;题目描述&#x1f4af;方法一&#xff1a;逐字符移动法&#x1f4af;方法二&#xff1a;使用辅助空间法&#x1f4af;方法三&#xff1a;三次反转法&#x1f4af;方法对…

【346】Postgres内核 Startup Process 通过 signal 与 postmaster 交互实现 (5)

1. Startup Process 进程 postmaster 初始化过程中, 在进入 ServerLoop() 函数之前,会先通过调用 StartChildProcess() 函数来开启辅助进程,这些进程的目的主要用来完成数据库的 XLOG 相关处理。 如: 核实 pg_wal 和 pg_wal/archive_status 文件是否存在Postgres先前是否发…

大数据面试SQL题-笔记02【查询、连接、聚合函数】

大数据面试SQL题复习思路一网打尽&#xff01;(文档见评论区)_哔哩哔哩_bilibiliHive SQL 大厂必考常用窗口函数及相关面试题 大数据面试SQL题-笔记01【运算符、条件查询、语法顺序、表连接】大数据面试SQL题-笔记02【查询、连接、聚合函数】​​​​​​​ 目录 01、查询 01…

【Python中while循环】

一、深拷贝、浅拷贝 1、需求 1&#xff09;拷贝原列表产生一个新列表 2&#xff09;想让两个列表完全独立开&#xff08;针对改操作&#xff0c;读的操作不改变&#xff09; 要满足上述的条件&#xff0c;只能使用深拷贝 2、如何拷贝列表 1&#xff09;直接赋值 # 定义一个…

51单片机从入门到精通:理论与实践指南入门篇(二)

续51单片机从入门到精通&#xff1a;理论与实践指南&#xff08;一&#xff09;https://blog.csdn.net/speaking_me/article/details/144067372 第一篇总体给大家在&#xff08;全局&#xff09;总体上讲解了一下51单片机&#xff0c;那么接下来几天结束详细讲解&#xff0c;从…

【pyspark学习从入门到精通20】机器学习库_3

目录 使用 ML 预测婴儿生存几率 加载数据 创建转换器 创建估计器 创建管道 拟合模型 使用 ML 预测婴儿生存几率 在这一部分&#xff0c;我们将使用前一章中的数据集的一部分来介绍 PySpark ML 的概念。 在这一部分&#xff0c;我们将再次尝试预测婴儿的生存几率。 加载…