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

相关推荐
我是一只小青蛙8881 小时前
位图与布隆过滤器:高效数据结构解析
开发语言·c++·算法
xiaoye-duck2 小时前
吃透C++类和对象(下):初始化列表深度解析
c++
曼巴UE52 小时前
UE5 C++ GameInstanceSubsystem 在学习
c++·ue5·ue
HarmonLTS2 小时前
Python Socket网络通信详解
服务器·python·网络安全
sun0077002 小时前
androd和qnx判断实网卡还是虚网卡
运维·服务器·网络
Ethan Wilson2 小时前
VS2019 C++20 模块相关 C1001: 内部编译器错误
开发语言·c++·c++20
郝学胜-神的一滴2 小时前
Python数据封装与私有属性:保护你的数据安全
linux·服务器·开发语言·python·程序人生
m0_748252382 小时前
Bootstrap 5 加载效果实现方法
c++
口嗨农民工3 小时前
live555 sample基本解读
运维·服务器