基于libuv库实现tcpserver和tcpclient

项目中使用网络实现高速收发文件,经过验证,决定采用libuv库搭建tcpserver和tcpclient。libuv库是第三方库,其大量采用回调实现。同时,其官方关于server端以及client的demo都是十分简单,无法满足实际需求,因此在采用libuv的基础上,自行封装成类作为中间件,方便应用层调用。

一、封装svuv类

svuv类做为最顶层的类,该类将包含tcpclient以及tcpserver两个子功能。
svuv类允许添加一个服务端以及N个客户端;添加服务端后,会自动在库中申请200M发送缓存区和200M的接收缓存区;每个客户端同样会申请200M的发送缓存区以及200M的接收缓存区。(缓存区的大小根据实际清空修改一个宏即可
其对外接口如下所述:

api参数功能
svuv()svuv类的构造函数
~svuv()svuv类的析构函数
void gStartLoop()启动uv库
int32_t gaddTcpClient(std::string server_ip, uint16_t server_port, std::string local_ip, uint16_t local_port)server_ip:服务器IP;server_port:服务器端口;local_ip:本地IP;local_port:本地端口添加client端
int32_t gTcpClientSend(std::string local_ip, uint16_t local_port, uint8_t *sdata, int32_t sdata_len)local_ip:本地IP;local_port:本地端口;sdata:发送数据;sdata_len:数据长度client端发送函数
int32_t gTcpClientRecv(std::string local_ip, uint16_t local_port, uint8_t *buff, int32_t buff_len)local_ip:本地IP;local_port:本地端口;buff:接收区;buff_len:接收区长度client接收函数
bool gTcpClientGetConnectStatus(std::string local_ip, uint16_t local_port)local_ip:本地IP;local_port:本地端口获取客户端的连接状态
int32_t gaddTcpServer(std::string server_ip, uint16_t server_port)server_ip:服务器IP;server_port:服务器端口添加服务器
int32_t gTcpServerRecv(std::string client_ip, uint16_t client_port, uint8_t *buff, int32_t buff_len)client_ip:客户端IP;client_port:客户端端口;buff:接收区;buff_len:接收区长度获取客户端数据
int32_t gTcpServerSend(std::string client_ip, uint16_t client_port, uint8_t *sdata, int32_t sdata_len);client_ip:客户端IP;client_port:客户端端口;sdata:发送数据;sdata_len:数据长度向客户端发送数据
int32_t gTcpServerGetRemainSpace(std::string client_ip, uint16_t client_port)client_ip:客户端IP;client_port:客户端端口获取客户端接收缓存区的剩余空间

整个svuv的使用流程将会比原来的libuv库大大简化:
svuv做服务端时:
1、创建svuv类的对象;
2、调用gaddTcpServer添加服务端
3、调用gStartLoop启动libuv
4、在线程中调用gTcpServerRecv或者gTcpServerSend
svuv做客户端时:
1、创建svuv类的对象;
2、调用gaddTcpClient添加客户端
3、调用gStartLoop启动libuv
4、在线程中调用gTcpClientRecv或者gTcpClientSend
详细的使用demo见后面链接。

二、封装tcpclient

tcpclient类用于生成一个客户端对象;每一个client都拥有各自的发送缓存区sbuff和接收缓存区rbuff,都具备断线重连功能。利用构造函数生成client时,只需要传入所要连接的server端地址以及本地的地址即可。client生成后,会在底层自动创建200M发送缓存区和200M接收缓存区,同时创建两个定时器reconnectTimer以及sendTimer,定时器sendTimer用于将发送缓存区的数据经libuv发送出去,定时器reconnectTimer则会定时检查与server端的连接状态。
tcpclient的主要功能如下表所述:

API参数功能
tcpclient(uv_loop_t *ploop, std::string server_ip, uint16_t server_port, std::string local_ip, uint16_t local_port)server_ip:服务器IP;server_port:服务器端口;local_ip:本地IP;local_port:本地端口申请发送缓存区和接收缓存区;调用libuv进行tcp初始化配置;申请reconnectTimer以及sendT imer
int32_t tcSendData(uint8_t *sdata, int32_t sdata_len)sdata:发送数据;sdata_len:数据长度将数据存入发送缓存区(保证不溢出)
int32_t tcRecvData(uint8_t *buff, int32_t buff_len)buff:接收缓存区;buff_len:缓存区长度对数据进行初步解析,满足帧格式才将整帧数据从底层缓存区取出传给调用者

整个tcpclient的实现过程如下所述:
1、调用tcpclient进行初始化配置,包括申请发送和接收缓存区;配置定时器;绑定本地IP地址;绑定连接回调函数sTCAfterConnect;启动断线重连机制;

tcpclient::tcpclient(uv_loop_t *ploop, std::string server_ip, uint16_t server_port, std::string local_ip, uint16_t local_port)
{
    int32_t iret = 0;

    this->serverIP = server_ip;
    this->serverPort = server_port;
    this->localIP = local_ip;
    this->localPort = local_port;
    this->loop = ploop;

    this->uvRbuf.base = new char [TCP_RECV_UVBUFF_SIZE];
    this->uvRbuf.len = TCP_RECV_UVBUFF_SIZE;
    this->rbuff = new uint8_t [TCP_RECV_BUFF_SIZE];
    this->rbuffIn = 0;
    this->rbuffOut = 0;

    this->sbuff = new uint8_t [TCP_SEND_BUFF_SIZE];
    this->sbuffIn = 0;
    this->sbuffOut = 0;
    this->repeatSendTime = 1;

    this->connectStatus = false;
    this->isReconnect = true;
    this->isClosed = false;

    do
    {
        iret = uv_mutex_init(&this->rMutex);
        if (iret)
        {
            PlayerInfo("addTcpClient uv_mutex_init rMutex", iret);
            break;
        }

        iret = uv_mutex_init(&this->sMutex);
        if (iret)
        {
            PlayerInfo("addTcpClient uv_mutex_init sMutex", iret);
            break;
        }

        iret = uv_timer_init(this->loop, &this->sendTimer);						// 发送定时器
        if (iret)
        {
            PlayerInfo("addTcpClient uv_timer_init sendTimer", iret);
            break;
        }

        iret = uv_async_init(this->loop, &this->sendAsync, sTCSendAsync);
        if (iret)
        {
            PlayerInfo("addTcpClient uv_async_init sendAsync", iret);
            break;
        }

        iret = uv_timer_init(this->loop, &this->reconnectTimer);			// 重连定时器
        if (iret)
        {
            PlayerInfo("addTcpClient uv_timer_init reconnectTimer", iret);
            break;
        }

        iret = uv_tcp_init(this->loop, &this->clientHandle);
        if (iret)
        {
            PlayerInfo("addTcpClient uv_tcp_init", iret);
            break;
        }

        iret = uv_tcp_nodelay(&this->clientHandle, 1);
        if (iret)
        {
            PlayerInfo("addTcpClient uv_tcp_nodelay", iret);
            break;
        }

        printf("uv_tcp_nodelay\n");

        iret = uv_tcp_keepalive(&this->clientHandle, 1, 60);
        if (iret)
        {
            PlayerInfo("addTcpClient uv_tcp_keepalive", iret);
            break;
        }

        printf("uv_tcp_keepalive\n");

        iret = uv_ip4_addr(server_ip.c_str(), server_port, &this->serverAddr);
        if (iret)
        {
            PlayerInfo("addTcpClient uv_ip4_addr serverAddr", iret);
            break;
        }

        iret = uv_ip4_addr(local_ip.c_str(), local_port, &this->localAddr);	// 绑定本地IP和端口,这样server端才能获取到固定的IP和端口
        if (iret)
        {
            PlayerInfo("addTcpClient uv_ip4_addr localAddr", iret);
            break;
        }

        iret = uv_tcp_bind(&this->clientHandle, (const struct sockaddr*)&this->localAddr, 0);
        if (iret)
        {
            PlayerInfo("addTcpClient uv_tcp_bind localAddr", iret);
            break;
        }

        iret = uv_tcp_connect(&this->connectReq, &this->clientHandle, (const sockaddr*)&this->serverAddr, sTCAfterConnect);
        if (iret)
        {
            PlayerInfo("addTcpClient uv_tcp_connect", iret);
            break;
        }

        this->sendTimer.data = this;
        this->sendAsync.data = this;

        this->clientHandle.data = this;
        this->connectReq.data = this;
        this->reconnectTimer.data = this;
    } while (0);
    
}

2、client连接成功后,启动接收回调函数sTCAfterRecv,当有数据进入,会自动存入接收缓存区rbuff(保证不溢出);

void tcpclient::sTCAfterConnect(uv_connect_t* handle, int status)
{
    class tcpclient *tmpClient = (class tcpclient *)handle->data;
    int32_t iret = 0;

    if (status)
    {
        tmpClient->connectStatus = false;
        PlayerInfo("sTCAfterConnect", status);
        tmpClient->TCStopSend();
        tmpClient->TCStartReconnect();
        uv_close((uv_handle_t *)&tmpClient->clientHandle, tcpclient::sTCAfterClientClose);
        return;
    }

    iret = uv_read_start(handle->handle, sTCAllocBufferForRecv, sTCAfterRecv);	// 正常连接,开始接收数据
    if (iret)
    {
        PlayerInfo("sTCAfterConnect uv_read_start", iret);
    }
    else
    {
        tmpClient->connectStatus = true;
        tmpClient->TCStartSend();
    }

    if (tmpClient->isReconnect)
    {
        tmpClient->TCStopReconnect();
    }
}

void tcpclient::sTCAllocBufferForRecv(uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf)
{
    class tcpclient *tmpClient = (class tcpclient *)handle->data;
    assert(tmpClient);
    *buf = tmpClient->uvRbuf;
}

void tcpclient::sTCAfterRecv(uv_stream_t* handle, ssize_t nread, const uv_buf_t* buf)
{
    class tcpclient *tmpClient = (class tcpclient *)handle->data;

    if (nread > 0)
    {
        uv_mutex_lock(&tmpClient->rMutex);
        if ((tmpClient->rbuffIn + nread) <= TCP_RECV_BUFF_SIZE)	// 在不溢出的清空下,将数据存入接收缓存区
        {
            memset(tmpClient->rbuff + tmpClient->rbuffIn, 0x00, nread);
            memcpy(tmpClient->rbuff + tmpClient->rbuffIn, buf->base, nread);
            tmpClient->rbuffIn += nread;
        }
        uv_mutex_unlock(&tmpClient->rMutex);
    }
    else if (nread < 0)
    {
        PlayerInfo("sTCAfterRecv ", nread);
        tmpClient->TCStopSend();
        tmpClient->TCStartReconnect();
        uv_close((uv_handle_t *)handle, tcpclient::sTCAfterClientClose);
    }
}

同时启动定时发送机制,每隔10ms将发送缓存区的数据sbuff经libuv库发送出去;

void tcpclient::sTCSendTimer(uv_timer_t* handle)
{
    class tcpclient *tmpClient = (class tcpclient *)handle->data;

    uv_async_send(&tmpClient->sendAsync);		// 触发发送函数
}

void tcpclient::sTCSendAsync(uv_async_t *handle)
{
    class tcpclient *tmpClient = (class tcpclient *)handle->data;
    uv_buf_t bufs[CHUNKS_PER_WRITE];
    uv_write_t *write_reqs = &tmpClient->write_reqs;
    int32_t i = 0;

    char *ptr = (char *)tmpClient->sbuff + tmpClient->sbuffOut;
    uint32_t len = 0;

    uv_mutex_lock(&tmpClient->sMutex);
    if ((tmpClient->connectStatus) && (tmpClient->sbuffIn != tmpClient->sbuffOut))
    {
        for (i = 0; i < CHUNKS_PER_WRITE; )			// 按照libuv的格式组包uv_buf_t,然后调用uv_write发送数据
        {
            if ((tmpClient->sbuffOut + TCP_SEND_UVBUFF_SIZE) <= tmpClient->sbuffIn)
            {
                len = TCP_SEND_UVBUFF_SIZE;
            }
            else
            {
                len = tmpClient->sbuffIn - tmpClient->sbuffOut;
            }

            bufs[i] = uv_buf_init(ptr, len);
            i++;

            tmpClient->sbuffOut += len;
            ptr += len;
            if (tmpClient->sbuffOut == tmpClient->sbuffIn)
            {
                tmpClient->sbuffOut = 0;
                tmpClient->sbuffIn = 0;
                break;
            }
        }

        if (i > 0)
        {
            write_reqs->data = tmpClient;
            uv_write(write_reqs, (uv_stream_t *)&tmpClient->clientHandle, bufs, i, tcpclient::sTCAfterSend);
        }
    }
    else if ((tmpClient->connectStatus) && (tmpClient->sbuffIn == tmpClient->sbuffOut))
    {
        tmpClient->TCStartSend();
    }
    uv_mutex_unlock(&tmpClient->sMutex);
}

void tcpclient::sTCAfterSend(uv_write_t* req, int status)
{
    class tcpclient *tmpClient = (class tcpclient *)req->data;

    if (status == 0)			// 数据发送成功后,再次触发定时器
    {
        tmpClient->TCStartSend();
    }
    else
    {
        PlayerInfo("sTCAfterSend ", status);
        tmpClient->TCStopSend();
    }
}

3、客户端重连机制

void tcpclient::sTCReconnectTimer(uv_timer_t* handle)
{
    class tcpclient *tmpClient = (class tcpclient *)handle->data;
    int32_t iret;

    if (!tmpClient->isReconnect)
        return;

    fprintf(stdout, "ip:%s port:%u start reconnect\n", tmpClient->localIP.c_str(), tmpClient->localPort);

    do
    {
        iret = uv_tcp_init(tmpClient->loop, &tmpClient->clientHandle);
        if (iret)
        {
            PlayerInfo("sTCReconnectTimer uv_tcp_init", iret);
            break;
        }

        iret = uv_tcp_nodelay(&tmpClient->clientHandle, 1);
        if (iret)
        {
            PlayerInfo("sTCReconnectTimer uv_tcp_nodelay", iret);
            break;
        }

        iret = uv_tcp_keepalive(&tmpClient->clientHandle, 1, 60);
        if (iret)
        {
            PlayerInfo("sTCReconnectTimer uv_tcp_keepalive", iret);
            break;
        }

        iret = uv_ip4_addr(tmpClient->serverIP.c_str(), tmpClient->serverPort, &tmpClient->serverAddr);
        if (iret)
        {
            PlayerInfo("sTCReconnectTimer uv_ip4_addr serverAddr", iret);
            break;
        }
        
        iret = uv_ip4_addr(tmpClient->localIP.c_str(), tmpClient->localPort, &tmpClient->localAddr);
        if (iret)
        {
            PlayerInfo("sTCReconnectTimer uv_ip4_addr localAddr", iret);
            break;
        }

        iret = uv_tcp_bind(&tmpClient->clientHandle, (const struct sockaddr*)&tmpClient->localAddr, 0);
        if (iret)
        {
            PlayerInfo("addTcpClient uv_tcp_bind localAddr", iret);
            break;
        }

        iret = uv_tcp_connect(&tmpClient->connectReq, &tmpClient->clientHandle, (const sockaddr*)&tmpClient->serverAddr, sTCAfterConnect);
        if (iret)
        {
            PlayerInfo("sTCReconnectTimer uv_tcp_connect", iret);
            break;
        }

        tmpClient->clientHandle.data = tmpClient;
        tmpClient->connectReq.data = tmpClient;
        tmpClient->reconnectTimer.data = tmpClient;
        return;
    } while (0);
    

    //reconnect failure, restart timer to trigger reconnect.
    // uv_timer_stop(handle);
    // tmpClient->repeatTime *= 2;
    // uv_timer_start(handle, tcpclient::sTCReconnectTimer, tmpClient->repeatTime, tmpClient->repeatTime);
    tmpClient->TCStopSend();
    tmpClient->TCStartReconnect();
    uv_close((uv_handle_t *)handle, tcpclient::sTCAfterClientClose);
}
void tcpclient::TCStartReconnect()
{
    isReconnect = true;
    this->clientHandle.data = this;
    this->repeatTime = 1e3;
}

void tcpclient::TCStopReconnect()
{
    this->isReconnect = false;
    this->clientHandle.data = this;
    this->repeatTime = 1e3;
    uv_timer_stop(&this->reconnectTimer);
}

4、应用层调用tcSendData,将数据存入发送缓存区sbuff;

int32_t tcpclient::tcSendData(uint8_t *sdata, int32_t sdata_len)
{
    int32_t ret = 0;
    assert(sdata);
    assert(sdata_len > 0);

    if (this->connectStatus)
    {
        uv_mutex_lock(&this->sMutex);
        if ((this->sbuffIn + sdata_len) <= TCP_SEND_BUFF_SIZE)			// 确保不溢出
        {
            memcpy(this->sbuff + this->sbuffIn, sdata, sdata_len);
            this->sbuffIn += sdata_len;

            ret = sdata_len;
        }
        uv_mutex_unlock(&this->sMutex);
    }

    return ret;
}

5、应用层主动调用tcRecvData,及时将数据从接收缓存区rbuff取走(应用层必须尽快将数据取走,否则可能导致缓存区满而丢帧)

int32_t tcpclient::tcRecvData(uint8_t *buff, int32_t buff_len)
{
    uint8_t *ptr = this->rbuff + this->rbuffOut;
    uint16_t frameLen = 0;

    assert(buff);
    assert(buff_len > 0);

    uv_mutex_lock(&this->rMutex);
    if (this->rbuffIn != this->rbuffOut)
    {
        if (((ptr[0] == 0xD2) && (ptr[1] == 0x8C)) ||		// 这里要更改为实际的帧格式(或者直接将数据取出)
            ((ptr[0] == 0x4D) && (ptr[1] == 0x73)))
        {
            frameLen = htons(*(uint16_t *)(ptr + FRAME_OFFSET_DATALEN)) + 18;
            if ((this->rbuffOut + frameLen) <= this->rbuffIn)
            {
                memcpy(buff, ptr, frameLen);
                this->rbuffOut += frameLen;
                if (this->rbuffOut == this->rbuffIn)
                {
                    this->rbuffIn = 0;
                    this->rbuffOut = 0;
                }
            }
        }
        else
        {
            this->rbuffOut++;
        }
    }
    uv_mutex_unlock(&this->rMutex);
    return frameLen;
}

三、封装tcpserver

tcpserver类用于生成一个服务端对象,初始化时调用libuv库进行server端的配置,然后启动监听服务(监听128个客户端);当有新的客户端接入,便创建一个client对象;同时为client分配发送缓存区sbuff和接收缓存区rbuff,每个client端也都具备定时发送的功能。
tcpserver的主要功能如下表所述:

API参数功能
tcpserver(uv_loop_t *ploop, std::string server_ip, uint16_t server_port)server_ip:服务器IP;server_port:服务器端口初始化server,启动监听服务
int32_t tsGetAcceptClientRecvData(std::string accept_ip, uint16_t accept_port, uint8_t *buff, int32_t buff_len)accept_ip:接入的客户端IP;accept_port:接入的客户端端口;buff:接收区;buff_len:接收区长度对数据初步解析,满足帧格式后将整帧数据从底层缓存区取走传给调用者
int32_t tsSetAcceptClientSendData(std::string accept_ip, uint16_t accept_port, uint8_t *sdata, int32_t sdata_len)accept_ip:接入的客户端IP;accept_port:接入的客户端端口;sdata:发送数据;sdata_len:数据长度将数据存入对应客户端的发送缓存区

整个tcpserver的实现过程如下所述:
1、调用tcpserver进行初始化配置,包括绑定server地址,启动server监听服务AcceptConnection;

tcpserver::tcpserver(uv_loop_t *ploop, std::string server_ip, uint16_t server_port)
{
    int32_t iret = 0;

    this->loop = ploop;
    this->serverIP = server_ip;
    this->serverPort = server_port;

    do
    {
        iret = uv_mutex_init(&this->acceptMutex);
        if (iret)
        {
            PlayerInfo("tcpserver uv_mutex_init", iret);
            break;
        }

        iret = uv_tcp_init(this->loop, &this->serverHandle);
        if (iret)
        {
            PlayerInfo("tcpserver uv_tcp_init", iret);
            break;
        }

        iret = uv_tcp_nodelay(&this->serverHandle, 1);
        if (iret)
        {
            PlayerInfo("tcpserver uv_tcp_nodelay", iret);
            break;
        }

        iret = uv_tcp_keepalive(&this->serverHandle, 1, 60);
        if (iret)
        {
            PlayerInfo("tcpserver uv_tcp_keepalive", iret);
            break;
        }

        iret = uv_ip4_addr(server_ip.c_str(), server_port, &this->addr);
        if (iret)
        {
            PlayerInfo("tcpserver uv_ip4_addr", iret);
            break;
        }

        iret = uv_tcp_bind(&this->serverHandle, (const struct sockaddr *)&this->addr, 0);	// 绑定server地址
        if (iret)
        {
            PlayerInfo("tcpserver uv_tcp_bind", iret);
            break;
        }

        iret = uv_listen((uv_stream_t *)&this->serverHandle, 128, AcceptConnection);	// 启动监听服务
        if (iret)
        {
            PlayerInfo("tcpserver uv_listen", iret);
        }

        this->serverHandle.data = this;
        return;
    } while (0);
}

2、有新的客户端接入,则创建acceptclient;为每一个acceptclient分配发送缓存区sbuff和接收缓存区rbuff;对每个acceptclient配置发送定时器;

void tcpserver::AcceptConnection(uv_stream_t *server, int status)
{
    int32_t iret = 0;

    class tcpserver *tmpServer = (class tcpserver *)server->data;
    assert(tmpServer);

    if (status)
    {
        PlayerInfo("AcceptConnection ", status);
        return;
    }

    class acceptclient *tmpAccept = new acceptclient(tmpServer->loop, server, tmpServer->serverIP, tmpServer->serverPort);		// 有新的客户端接入,创建一个acceptclient
    assert(tmpAccept);

    uv_mutex_lock(&tmpServer->acceptMutex);
    tmpServer->acceptclientVec.push_back(tmpAccept);	// 将新接入的client加入到vector中
    uv_mutex_unlock(&tmpServer->acceptMutex);
    return;
}

3、acceptclient接入成功后,启动接收回调函数sAcceptAfterRecv;当有新的数据进入,自动将数据存入接收缓存区rbuff;

acceptclient::acceptclient(uv_loop_t *ploop, uv_stream_t *server, std::string server_ip, uint16_t server_port)
{
    int32_t iret = 0;

    this->loop = ploop;

    // this->rbuff = new uint8_t[TCP_RECV_BUFF_SIZE];
    this->rbuff = (uint8_t *)malloc(TCP_RECV_BUFF_SIZE);
    this->rbuffIn = 0;
    this->rbuffOut = 0;

    // this->uvRbuf.base = new char[TCP_RECV_UVBUFF_SIZE];
    this->uvRbuf.base = (char *)malloc(TCP_RECV_UVBUFF_SIZE);
    this->uvRbuf.len = TCP_RECV_UVBUFF_SIZE;

    // this->sbuff = new uint8_t[TCP_SEND_BUFF_SIZE];
    this->sbuff = (uint8_t *)malloc(TCP_SEND_BUFF_SIZE);
    this->sbuffIn = 0;
    this->sbuffOut = 0;

    this->serverIP = server_ip;
    this->serverPort = server_port;

    do
    {
        iret = uv_mutex_init(&this->rMutex);
        if (iret)
        {
            PlayerInfo("acceptclient uv_mutex_init rMutex", iret);
            break;
        }

        iret = uv_mutex_init(&this->sMutex);
        if (iret)
        {
            PlayerInfo("acceptclient uv_mutex_init sMutex", iret);
            break;
        }


        iret = uv_timer_init(this->loop, &this->sendTimer);
        if (iret)
        {
            PlayerInfo("acceptclient uv_timer_init sendTimer", iret);
            break;
        }

        iret = uv_async_init(this->loop, &this->sendAsync, sAcceptSendAsync);
        if (iret)
        {
            PlayerInfo("acceptclient uv_async_init sendAsync", iret);
            break;
        }

        iret = uv_tcp_init(this->loop, &this->acceptHandle);
        if (iret)
        {
            PlayerInfo("acceptclient uv_tcp_init", iret);
            break;
        }

        iret = uv_tcp_nodelay(&this->acceptHandle, 1);
        if (iret)
        {
            PlayerInfo("addTcpClient uv_tcp_nodelay", iret);
            break;
        }

        iret = uv_tcp_keepalive(&this->acceptHandle, 1, 60);
        if (iret)
        {
            PlayerInfo("addTcpClient uv_tcp_keepalive", iret);
            break;
        }

        iret = uv_accept((uv_stream_t *)server, (uv_stream_t *)&this->acceptHandle);	// 客户端接入成功
        if (iret)
        {
            PlayerInfo("acceptclient uv_accept", iret);
            break;
        }

        struct sockaddr peername;
        int namelen = sizeof(peername);
        struct sockaddr_in compare_addr;
        char client_ip[17];
        uint16_t client_port;
        uv_tcp_getpeername(&this->acceptHandle, &peername, &namelen);		// 获取客户端IP和端口
        check_sockname(&peername, server_ip.c_str(), server_port, client_ip, &client_port);
        this->acceptIP = client_ip;
        this->acceptPort = client_port;
        fprintf(stdout, "newAcceptClient ip = %s, port = %d\n", client_ip, client_port);

        iret = uv_read_start((uv_stream_t *)&this->acceptHandle, acceptclient::sAcceptAllocBufferForRecv, acceptclient::sAcceptAfterRecv);		// 从客户端接收数据
        if (iret)
        {
            PlayerInfo("AcceptConnection uv_read_start", iret);
            break;
        }

        this->isConnect = true;
        this->acceptHandle.data = this;
        this->sendTimer.data = this;
        this->sendAsync.data = this;
        this->parent = server->data;

        this->acceptStartSend();
    } while (0);
}
void acceptclient::sAcceptAfterRecv(uv_stream_t *client, ssize_t nread, const uv_buf_t *buf)
{
    class acceptclient *tmpAccept = (class acceptclient *)client->data;
    class tcpserver *tmpServer = (class tcpserver *)tmpAccept->parent;
    unsigned long long now = 0;

    if (nread > 0)
    {
        // sysGetCurPts(&now);
        uv_mutex_lock(&tmpAccept->rMutex);
        if ((tmpAccept->rbuffIn + nread) <= TCP_RECV_BUFF_SIZE)		// 数据存入接收缓存区
        {
            memset(tmpAccept->rbuff + tmpAccept->rbuffIn, 0x00, nread);
            memcpy(tmpAccept->rbuff + tmpAccept->rbuffIn, buf->base, nread);
            tmpAccept->rbuffIn += nread;
        }
        // printf("time %lld us, recv %ld, all %u\n", now, nread, tmpAccept->rbuffIn);
        uv_mutex_unlock(&tmpAccept->rMutex);
    }
    else if (nread < 0)
    {
        printf("close and delete acceptClient\n");
        tmpAccept->acceptStopSend();
        uv_close((uv_handle_t *)client, sAcceptAfterClose);
        // tmpServer->tsDeleteAcceptClient(client, tmpAccept->acceptIP, tmpAccept->acceptPort);     // 这个函数似乎会导致服务端程序崩溃
    }
}

4、应用层调用tsSetAcceptClientSendData将要发送的数据存入缓存区,底层自行发送;

void acceptclient::sAcceptSendTimer(uv_timer_t *handle)
{
    class acceptclient *tmpAccept = (class acceptclient *)handle->data;

    if (tmpAccept->isConnect)
        uv_async_send(&tmpAccept->sendAsync);			// 触发发送
}

void acceptclient::sAcceptSendAsync(uv_async_t *handle)
{
    class acceptclient *tmpAccept = (class acceptclient *)handle->data;

    uv_buf_t bufs[CHUNKS_PER_WRITE];
    uv_write_t *write_reqs = &tmpAccept->write_reqs;
    int32_t i = 0;

    char *ptr = (char *)tmpAccept->sbuff + tmpAccept->sbuffOut;
    uint32_t len = 0;

    uv_mutex_lock(&tmpAccept->sMutex);
    if ((tmpAccept->isConnect) && (tmpAccept->sbuffIn != tmpAccept->sbuffOut))
    {
        for (i = 0; i < CHUNKS_PER_WRITE; )		// 按照kibuv的格式,组包发送
        {
            if ((tmpAccept->sbuffOut + TCP_SEND_UVBUFF_SIZE) <= tmpAccept->sbuffIn)
            {
                len = TCP_SEND_UVBUFF_SIZE;
            }
            else
            {
                len = tmpAccept->sbuffIn - tmpAccept->sbuffOut;
            }

            bufs[i] = uv_buf_init(ptr, len);
            i++;

            tmpAccept->sbuffOut += len;
            ptr += len;
            if (tmpAccept->sbuffOut == tmpAccept->sbuffIn)
            {
                tmpAccept->sbuffOut = 0;
                tmpAccept->sbuffIn = 0;
                break;
            }
        }

        if (i > 0)
        {
            write_reqs->data = tmpAccept;
            uv_write(write_reqs, (uv_stream_t *)&tmpAccept->acceptHandle, bufs, i, acceptclient::sAcceptAfterSend);
        }
    }
    else if  ((tmpAccept->isConnect) && (tmpAccept->sbuffIn == tmpAccept->sbuffOut))	// 连接状态下,即使没有数据需要发送,也要触发定时器
    {
        tmpAccept->acceptStartSend();
    }
    uv_mutex_unlock(&tmpAccept->sMutex);
}

void acceptclient::sAcceptAfterSend(uv_write_t *req, int status)
{
    class acceptclient *tmpAccept = (class acceptclient *)req->data;

    if (status == 0)
    {
        tmpAccept->acceptStartSend();		// 发送成功后,再次触发定时器,开始下一轮的发送周期
    }
    else
    {
        PlayerInfo("sAcceptAfterSend ", status);
        tmpAccept->acceptStopSend();
    }
}

void acceptclient::acceptStartSend(void)
{
    this->repeatSendTime = 10;
    uv_timer_start(&this->sendTimer, acceptclient::sAcceptSendTimer, this->repeatSendTime, 0);
}

void acceptclient::acceptStopSend(void)
{
    this->repeatSendTime = 10;
    uv_timer_stop(&this->sendTimer);
}

int32_t acceptclient::acceptSendData(uint8_t *sdata, int32_t sdata_len)
{
    int32_t ret = 0;

    assert(sdata);
    assert(sdata_len > 0);

    if (this->isConnect)
    {
        uv_mutex_lock(&this->sMutex);
        if ((this->sbuffIn + sdata_len) <= TCP_SEND_BUFF_SIZE)	// 应用层数据存入发送缓存区
        {
            memcpy(this->sbuff + this->sbuffIn, sdata, sdata_len);
            this->sbuffIn += sdata_len;

            ret = sdata_len;
        }
        uv_mutex_unlock(&this->sMutex);
    }

    return ret;
}

5、应用层调用tsGetAcceptClientRecvData将数据从底层取走(应用层必须尽快将数据取走,否则导致接收缓存区满而丢帧)

int32_t acceptclient::acceptRecvData(uint8_t *buff, int32_t buff_len)
{
    uint8_t *ptr = this->rbuff + this->rbuffOut;
    uint16_t frameLen = 0;

    assert(buff);
    assert(buff_len > 0);

    uv_mutex_lock(&this->rMutex);
    if (this->rbuffIn != this->rbuffOut)
    {
        if (((ptr[0] == 0xD2) && (ptr[1] == 0x8C)) ||	// 这里根据实际清空更改协议(或者直接将数据取走)
            ((ptr[0] == 0x4D) && (ptr[1] == 0x73)))
        {
            frameLen = htons(*(uint16_t *)(ptr + FRAME_OFFSET_DATALEN)) + 18;
            if ((this->rbuffOut + frameLen) <= this->rbuffIn)
            {
                memcpy(buff, ptr, frameLen);
                this->rbuffOut += frameLen;
                memset(ptr, 0x00, frameLen);
                if (this->rbuffOut == this->rbuffIn)
                {
                    this->rbuffIn = 0;
                    this->rbuffOut = 0;
                }
            }
            else
                frameLen = 0;
        }
        else
        {
            this->rbuffOut++;
        }
    }
    uv_mutex_unlock(&this->rMutex);
    return frameLen;
}

四、测试用例

测试用例:
1、创建svuv对象;
2、添加客户端或者服务端;
3、创建线程进行收发。

整个demo可以见链接:gitee链接地址

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值