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就不一样了

相关推荐
网络小江1 小时前
组网第27课:ARP 的能与不能——免费 ARP、代理 ARP 与 ARP 欺骗
服务器·网络·网络安全·负载均衡·免费arp
云计算练习生1 小时前
什么是文件权限?Linux 中 rwx 与数字权限的对应关系
linux·运维·服务器·操作系统·文件权限管理
饿了吃洗衣凝珠1 小时前
red hat day2
linux·运维·服务器
上海云盾-高防顾问2 小时前
边缘验签是什么
运维·服务器·网络
布莱克6052 小时前
A* 算法详解:从原理到实现
开发语言·c++·算法
多弗朗皮卡丘2 小时前
C++ 智能指针
开发语言·c++·c++11·智能指针
进化中的刀盾兽2 小时前
从零实现 C++ AI 大模型接入 SDK(十):SessionManager 会话管理与多轮对话实现
c++
小成202303202652 小时前
C++~02运算符重载
开发语言·c++
Source.Liu2 小时前
【A11】服务器数据库结构笔记
服务器·数据库·笔记
企业解惑小能手-域智盾系统2 小时前
终端安全管理软件怎么选?终端安全管理不是“装个客户端”:从底层逻辑看企业怎么选
服务器·网络·安全·域智盾软件