基于ZLMediaKit实现同步发送接收数据的TCP Client

24 篇文章 2 订阅
该文章介绍了一个在遇到QTcpSocket在多线程环境下使用问题时,如何转向使用ZLMediaKit库创建TCP客户端的解决方案。代码示例展示了如何利用ZLMediaKit进行连接、断开、数据收发以及线程安全的管理。
摘要由CSDN通过智能技术生成

本来是个Qt项目,按理说使用QTcpSocket岂不是更好,但是,QTcpSocket在多线程方式下使用时,各种报错或警告,原因是非多线程安全,如果改造成多线程安全,各种信号槽,又不怎么优雅。所以,基于ZLMediaKit写了一个,代码,贴在此处,大家帮忙参谋一下,存在哪些问题?

TcpClient.h

#pragma once

#include <QString>
#include <QObject>
#include <atomic>
#include <mutex>
#include <condition_variable>

extern "C" {
#include "mk_mediakit.h"
}

struct tcp_client_user_data 
{
    int count = 0;

    std::condition_variable conncectCv;
    std::mutex connectMtx;
    bool connected = false;

    std::condition_variable cv;
    std::mutex bufMtx;
    QByteArray buffer;

    void Reset()
    {
        count = 0;

        {
            std::lock_guard locker(connectMtx);
            connected = false;
        }

        {
            std::lock_guard locker(bufMtx);
            buffer.clear();
        }
    }
};

class TcpClient : public QObject
{
    Q_OBJECT

public:
    TcpClient(bool bAsync = true);
    ~TcpClient();

    bool Connect(QString ip, std::uint16_t port);
    void DisConnect();
    QByteArray Request(const QByteArray& msg);

private:
    mk_tcp_client m_client;
    tcp_client_user_data* m_userData;

    bool m_bAsync;
};

TcpClient.cpp


#include "TcpClient.h"
#include <chrono>


#define LOG_LEV 4
//修改此宏,可以选择协议类型
#define TCP_TYPE mk_type_tcp

/**
 * tcp客户端连接服务器成功或失败回调
 * @param client tcp客户端
 * @param code 0为连接成功,否则为失败原因
 * @param msg 连接失败错误提示
 */
void API_CALL on_mk_tcp_client_connect(mk_tcp_client client, int code, const char* msg)
{
    log_printf(LOG_LEV, "connect result:%d %s", code, msg);
    tcp_client_user_data* user_data = (tcp_client_user_data*)mk_tcp_client_get_user_data(client);
    if (code == 0)
    {
        //连接上后我们发送一个hello world测试数据
        //mk_tcp_client_send(client, "hello world", 0);
        std::lock_guard locker(user_data->connectMtx);
        user_data->connected = true;
        user_data->conncectCv.notify_all();
    }
    else
    {
        mk_tcp_client_release(client);
    }
}

/**
 * tcp客户端与tcp服务器之间断开回调
 * 一般是eof事件导致
 * @param client tcp客户端
 * @param code 错误代码
 * @param msg 错误提示
 */
void API_CALL on_mk_tcp_client_disconnect(mk_tcp_client client, int code, const char* msg)
{
    log_printf(LOG_LEV, "disconnect:%d %s", code, msg);
    tcp_client_user_data* user_data = (tcp_client_user_data*)mk_tcp_client_get_user_data(client);
    mk_tcp_client_release(client);

    std::lock_guard locker(user_data->connectMtx);
    user_data->connected = false;
}

/**
 * 收到tcp服务器发来的数据
 * @param client tcp客户端
 * @param data 数据指针
 * @param len 数据长度
 */
void API_CALL on_mk_tcp_client_data(mk_tcp_client client, mk_buffer buffer) 
{
    log_printf(LOG_LEV, "data[%d]:%s", mk_buffer_get_size(buffer), mk_buffer_get_data(buffer));
    size_t incomingSize = mk_buffer_get_size(buffer);

    tcp_client_user_data* user_data = (tcp_client_user_data*)mk_tcp_client_get_user_data(client);
    {
        std::lock_guard locker(user_data->bufMtx);
        user_data->buffer.append(mk_buffer_get_data(buffer), incomingSize);
        user_data->cv.notify_all();
    }
}

/**
 * 每隔2秒的定时器,用于管理超时等任务
 * @param client tcp客户端
 */
void API_CALL on_mk_tcp_client_manager(mk_tcp_client client)
{
    tcp_client_user_data* user_data = (tcp_client_user_data*)mk_tcp_client_get_user_data(client);
    //printf("on_mk_tcp_client_manager:%d\n", user_data->count++);
}



TcpClient::TcpClient(bool bAsync):
    m_client(nullptr),
    m_bAsync(bAsync)
{
    m_userData = new tcp_client_user_data();
}

TcpClient::~TcpClient()
{
    bool conncected = false;
    {
        std::lock_guard locker(m_userData->connectMtx);
        conncected = m_userData->connected;
    }

    if (conncected)
    {
        DisConnect();
    }
}

bool TcpClient::Connect(QString ip, std::uint16_t port)
{
    char* ini_path = mk_util_get_exe_dir("c_api.ini");
    char* ssl_path = mk_util_get_exe_dir("ssl.p12");

    mk_config config;
    {
        config.ini = ini_path;
        config.ini_is_path = 1;
        config.log_level = 0;
        config.log_mask = LOG_CONSOLE;
        config.ssl = ssl_path;
        config.ssl_is_path = 1;
        config.ssl_pwd = NULL;
        config.thread_num = 0;
    };
    mk_env_init(&config);
    free(ini_path);
    free(ssl_path);

    mk_tcp_client_events events_clent;
    {
        events_clent.on_mk_tcp_client_connect = on_mk_tcp_client_connect;
        events_clent.on_mk_tcp_client_data = on_mk_tcp_client_data;
        events_clent.on_mk_tcp_client_disconnect = on_mk_tcp_client_disconnect;
        events_clent.on_mk_tcp_client_manager = on_mk_tcp_client_manager;
    };
    m_client = mk_tcp_client_create(&events_clent, TCP_TYPE);

    m_userData->Reset();
    mk_tcp_client_set_user_data(m_client, m_userData);

    std::unique_lock locker(m_userData->connectMtx);
    mk_tcp_client_connect(m_client, ip.toStdString().c_str(), port, 3);
    m_userData->conncectCv.wait_for(locker, std::chrono::milliseconds(3000));

    return m_userData->connected;
}

void TcpClient::DisConnect()
{
    mk_tcp_client_release(m_client);

    std::lock_guard locker(m_userData->connectMtx);
    m_userData->connected = false;
}

QByteArray TcpClient::Request(const QByteArray& msg)
{
    std::unique_lock locker(m_userData->bufMtx);
    if (m_bAsync) m_userData->buffer.clear();

    mk_tcp_client_send(m_client, msg.data(), msg.size());

    m_userData->cv.wait_for(locker, std::chrono::milliseconds(30000));

    return std::move(m_userData->buffer);
}

main.cpp

#include <iostream>
#include <TcpClient.h>
#include <QDebug>

int main(int argc, char *argv[])
{
    TcpClient client;
    bool ret = client.Connect("127.0.0.1", 60000);
    std::cout << ret << std::endl;

    printf("enter any key to exit\n");
    std::string input;
    while (std::cin >> input)
    {
        if (input == "exit") break;
        else
        {
            qDebug() << client.Request(input.c_str());
        }
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值