QT + Socket 客户端/服务端 公网通讯

项目需求:

有很多分布各地的客户端硬件(设备是小米pad),需要做一个服务器,支持客户端数据联网上传到服务器,在服务器端进行数据管理和可视化
实现步骤:
通讯:

公网通讯使用的还是QT Socket通讯那一套东西,唯一的区别是原来是在局域网进行通讯,现在是联网通讯,区别是公网IP的差别;
实现思路:

在有公网IP的基础上,建立一个Socket服务器,建立一个接收一定数量的客户端队列,使用共同的端口号,这个队列主要是用来接收设备端的数据;建立另一个客户端队列,使用另外的端口号,这个队列主要是用来查询、可视化、修改的,相当于是有一个专用的exe用来接收和转发数据,可视化软件专门用来查询和可视化

没办法的事,正常来说是需要找一个前端和java后端来做,但是没有这方面的人,还只会用QT C++,所以间接实现吧

关键技术:

主要是建立socket服务,其他可视化部分就是工作量的事情了

步骤1:

首先,先查询下当前的wifi是不是有公网ip,办公室的网络是移动网络,登录路由器查询后,发现拨号连接和DCHP都是10*和100 开头的,在百度搜索框中,检索【IP查询】,把IP地址输入,看下情况;

如下:办公室的wifi是局域网,也就是说入户的网线已经是被公网转发过的了,没有暴露公网ip

步骤2:

如果没有公网IP无法进行下一步,看怎么申请下来,我跳过这步,用了阿里云的云服务器(试用几天看看),先验证功能;要确保有公网IP,如果后续转实体服务器,也需要有公网IP才行

步骤3:

建立socket服务器:

1.界面端,开启一个子线程,用来初始化服务端监听

监听的时候,重点来了,正常来说,一个公网IP不可能只连一个电脑,如上图,除了公网IP外,还有一个主私网IP,这个IP也是局域网,所以监听的时候直接监听的IP是0.0.0.0,端口号指定,一旦有公网IP有用户连接,会直接转发给私网

2.我的服务端访问量不高,所以我只内置了10个客户端的队列,也就是说超过10个无法连接,服务端也可以主动踢人

3.通讯数据交互,我选择json字符串,通用解析,只要定好规则就行

4.需要注意的是 ,如果多个客户端在同一个wifi局域网中,实际上服务端响应的ip是同一个ip,只是端口号有区别,所以踢人的时候,需要指定对象的时候,一定是IP+端口号,或者给每个客户端指定一个别名

5.收发信息,要区别是哪个客户端发送来信息了?我要给哪个客户端做信息反馈?这就需要按照ip和端口号进行查找了,此外,如果发的包一次性没发完,还需要进行包合并啥的(因为我是json字符串,每次收到信息都要检查json字符串的完整性,看是不是分包了)

关键代码如下:

cpp 复制代码
	// 在子线程中创建QTcpServer对象
    m_tcpServer = new QTcpServer(this);
    // 确定监听地址
    QHostAddress listenAddress;
    if (m_isuserealip && !m_ip.isEmpty())
    {
        listenAddress = QHostAddress(m_ip);
    } else {
        listenAddress = QHostAddress::Any;
    }
    // 启动服务器
    if (m_tcpServer->listen(listenAddress, m_port))
    {
        m_running = true;
        PUSH_LOG(INFO_LOG_LEVEL,"服务端开始监听:ip:%s port:%d",m_ip.toStdString().c_str(),m_port);
    } else {
        QString errorMsg = QString("服务器启动失败:%1").arg(m_tcpServer->errorString());
        PUSH_LOG(ERROR_LOG_LEVEL,errorMsg.toStdString().c_str());
    }
    // 连接信号
    connect(m_tcpServer, &QTcpServer::newConnection,
            this, &ServerSocketCommunciateFunc::onNewConnection);
cpp 复制代码
//超过预设数量,不连接
if (m_clients.size() >= m_ClientCount) {
        QTcpSocket *clientSocket = m_tcpServer->nextPendingConnection();
        ......
        }
      // 接受新连接
    QTcpSocket *clientSocket = m_tcpServer->nextPendingConnection();
    if (!clientSocket) {
        PUSH_LOG(ERROR_LOG_LEVEL,"客户端连接失败");
        return;
    } 
     // 添加到客户端列表
    if (addClient(clientInfo))
    {
        PUSH_LOG(INFO_LOG_LEVEL,"新客户端已连入,ip: %s port: %d",clientInfo.peerAddress.toStdString().c_str(),clientInfo.peerPort);
//        emit clientConnected(clientInfo);
    }
cpp 复制代码
    // 客户端信息结构体
struct ClientInfo {
    qintptr socketDescriptor;  // 客户端socket描述符
    QTcpSocket *socket;        // 客户端socket指针
    QString clientName;        // 客户端名称
    QString peerAddress;       // 客户端地址
    int peerPort;          // 客户端端口
    QByteArray buffer;         // 数据缓冲区,用于处理分包
    ClientInfo(){
        socketDescriptor = -1;
        socket = nullptr;
        clientName = "";
        peerAddress = "";
        peerPort = 0;
        buffer.clear();
    }
};
    /**
     * @brief m_clients 所有已连接的客户端
     */
    QList<ClientInfo> m_clients;
cpp 复制代码
		//有信息发过来了,我们要看是哪个socket发过来的
    QTcpSocket *clientSocket = qobject_cast<QTcpSocket*>(sender());
    if (!clientSocket)
    {
        return;
    }
    //查看下socket的描述符
    qintptr socketDescriptor = clientSocket->socketDescriptor();
    QString ipip = clientSocket->peerAddress().toString();
    int port = clientSocket->peerPort();
    // 查找客户端信息
    int clientid_ = findClientIndexByDescriptor(ipip,port);
    if (m_clients[clientid_].socketDescriptor == -1)
    {
        PUSH_LOG(WARN_LOG_LEVEL,"接收到未知客户端的数据,描述符为:%d",socketDescriptor);
        return;
    }
    //开始解析数据
    QByteArray responseData = clientSocket->readAll();
    QString ss = QString::fromUtf8(responseData);//读取一次后,转为字符
    m_clients[clientid_].buffer.append(responseData);//把数据追加

	//如果是完整的json字符串,就需要开出任务响应了

贴一个截图吧,先把技术验证过了,后面再说工作量的事:

1.服务端简单一个日志窗口:

2.客户端1:window主机的调试助手

3.客户端2:手机【 码农宝】,Socket客户端工具

两个客户端都使用办公室wifi局域网,所以显示的ip都是一样的,就是端口号不一样,如果手机不连wifi,ip就不一样了

相关推荐
青梅橘子皮19 小时前
Linux---基本指令
linux·运维·服务器
汉克老师20 小时前
GESP2025年3月认证C++五级( 第三部分编程题(1、平均分配))
c++·算法·贪心算法·排序·gesp5级·gesp五级
cui_ruicheng21 小时前
Linux进程间通信(三):System V IPC与共享内存
linux·运维·服务器
运维全栈笔记21 小时前
Linux安装配置Tomcat保姆级教程:从部署到性能调优
linux·服务器·中间件·tomcat·apache·web
小短腿的代码世界21 小时前
Qt日志系统深度解析:从qDebug到企业级日志框架
开发语言·qt
dllmayday1 天前
Linux 上用终端连接 WiFi
linux·服务器·windows
ACP广源盛139246256731 天前
IX8024与科学大模型的碰撞@ACP#筑牢科研 AI 算力高速枢纽分享
运维·服务器·网络·数据库·人工智能·嵌入式硬件·电脑
峥无1 天前
Linux系统编程基石:静态库·动态库·ELF文件·进程地址空间全景图
linux·运维·服务器
智者知已应修善业1 天前
【51单片机2个按键控制流水灯运行与暂停】2023-9-6
c++·经验分享·笔记·算法·51单片机
云泽8081 天前
C++11 核心特性全解:列表初始化、右值引用与移动语义实战
开发语言·c++