版权声明:若无来源注明,Techie亮博客文章均为原创。 转载请以链接形式标明本文标题和地址:
本文标题:QTcpServer实现多客户端连接     本文地址:https://www.techieliang.com/2017/12/760/
文章目录

1. 介绍

QTcpServer使用请见:QTcpSocket-Qt使用Tcp通讯实现服务端和客户端

QTcpServer类默认提供的只有无参数的newConnection的信号,这样虽然知道有人连接了,并且可以通过nextPendingConnection获取连接的socket,但并不便于管理,尤其是在连接断开以后无法判断具体那个断开了,因为QTcpSocket只提供了无参的disconnected信号。。。

这样就算在newConnection是存储一个list或者map,也无法在disconnected是知道具体是那一项断开连接,给不同的QTcpSocket的信号指向不同的槽。

实际上socket有自己的句柄,并通过下述函数在初步连接时就赋予了对应的socketDescriptor

  1. virtual void incomingConnection(qintptr socketDescriptor)

当有client连接时,首先是此方法被调用,可自行在此方法内建立QTcpSocket并将socketDescriptor值赋予socket,并在socket断开时告知此标识符

2. 范例

源码请见GitHub:QtOtherModuleExamples

tcp_server.h

  1. #ifndef TCP_SERVER_H
  2. #define TCP_SERVER_H
  3. #include <QTcpServer>
  4. namespace tcp_server_private {
  5. class TcpServerPrivate;
  6. }
  7. class QTcpSocket;
  8. /**
  9. * @brief Tcp多客户端服务器
  10. */
  11. class TcpServer : public QTcpServer {
  12. Q_OBJECT
  13. public:
  14. /**
  15. * @brief 构造函数
  16. * @param parent 父QObject
  17. */
  18. explicit TcpServer(QObject *parent = Q_NULLPTR);
  19. /**
  20. * @brief 析构函数
  21. * 非多线程模式行为:关闭所有连接后析构
  22. * 多线程模式行为:关闭所有连接及线程池后析构
  23. */
  24. ~TcpServer();
  25. signals:
  26. /**
  27. * @brief 客户端连入
  28. * @param 连接句柄
  29. * @param socket指针
  30. */
  31. void ClientConnected(qintptr, QTcpSocket*);//发送新用户连接信息
  32. /**
  33. * @brief socket已断开连接
  34. * 若需要在socket后析构后进行操作的可连接此信号
  35. * @param 连接句柄
  36. */
  37. void ClientDisconnected(qintptr);
  38. /**
  39. * @brief 主动断开连接信号
  40. * 若服务端想要主动断开与客户端连接将会发出此信号
  41. * 此信号发出这表明进行断开操作不表明断开成功,成功以SocketDisconnected信号为准
  42. * @param 连接句柄
  43. */
  44. void InitiativeDisConnectClient(qintptr);
  45. protected slots:
  46. /**
  47. * @brief 客户端已断开槽
  48. * 此槽与客户端的已断开信号连接
  49. * @param handle
  50. */
  51. void ClientDisconnectedSlot(qintptr handle);
  52. protected:
  53. /**
  54. * @brief 重写-有连接到来
  55. * 连接到来不一定连接,会根据maxPendingConnections决定是否连接
  56. * @param handle 连接句柄
  57. */
  58. virtual void incomingConnection(qintptr handle);
  59. private:
  60. tcp_server_private::TcpServerPrivate *private_;
  61. };
  62. #endif // TCP_SERVER_H

tcp_server.cpp

  1. #include "tcp_server.h"
  2. #include "tcp_server_private.h"
  3. //构造函数
  4. TcpServer::TcpServer(QObject *parent)
  5. : QTcpServer(parent),
  6. private_(new tcp_server_private::TcpServerPrivate) {
  7. }
  8. //析构函数
  9. TcpServer::~TcpServer() {
  10. for(tcp_server_private::TcpSocket *client : private_->clients.values()) {
  11. client->disconnectFromHost();
  12. auto handle = client->socketDescriptor();
  13. client->deleteLater();
  14. //告知其他调用者 当前socket断开,避免有需要在socket后执行的方法
  15. emit ClientDisconnected(handle);
  16. }
  17. if(private_)
  18. delete private_;
  19. this->close();
  20. }
  21. //重写-有连接到来
  22. void TcpServer::incomingConnection(qintptr handle) {
  23. //超出最大练级数量关闭连接
  24. if (private_->clients.size() > maxPendingConnections()) {
  25. QTcpSocket tcp;
  26. tcp.setSocketDescriptor(handle);
  27. tcp.disconnectFromHost();
  28. return;
  29. }
  30. auto client_socket = new tcp_server_private::TcpSocket(handle);
  31. Q_ASSERT(client_socket->socketDescriptor() == handle);
  32. //socket断开连接的信号与server槽连接
  33. connect(client_socket,
  34. &tcp_server_private::TcpSocket::ClientDisconnected,
  35. this,
  36. &TcpServer::ClientDisconnectedSlot);
  37. //主动断开连接的操作
  38. connect(this,
  39. &TcpServer::InitiativeDisConnectClient,
  40. client_socket,
  41. &tcp_server_private::TcpSocket::DisconnectSocket);
  42. //map记录
  43. private_->clients.insert(handle, client_socket);
  44. qDebug()<<handle<<"connected";
  45. emit ClientConnected(handle, client_socket);
  46. }
  47. //客户端已断开槽
  48. void TcpServer::ClientDisconnectedSlot(qintptr handle) {
  49. //map中移除
  50. private_->clients.remove(handle);
  51. qDebug()<<handle<<"disconnected";
  52. //发出信号
  53. emit ClientDisconnected(handle);
  54. }

private

  1. #ifndef TCP_SERVER_PRIVATE_H
  2. #define TCP_SERVER_PRIVATE_H
  3. #include <QTcpSocket>
  4. namespace tcp_server_private {
  5. class TcpSocket : public QTcpSocket {
  6. Q_OBJECT
  7. public:
  8. /**
  9. * @brief 构造函数
  10. * @param socketDescriptor 连接句柄
  11. * @param parent 父QObject
  12. */
  13. TcpSocket(qintptr handle, QObject *parent = 0);
  14. signals:
  15. /*
  16. * 已断开连接信号
  17. */
  18. void ClientDisconnected(qintptr);
  19. public slots:
  20. /**
  21. * @brief 断开连接
  22. * @param handle 连接句柄
  23. */
  24. void DisconnectSocket(qintptr handle);
  25. private:
  26. qintptr handle_;
  27. };
  28. /**
  29. * @brief Tcp多客户端服务器私有类
  30. */
  31. class TcpServerPrivate {
  32. public:
  33. QMap<int, TcpSocket *> clients; ///所有连接的map
  34. };
  35. }//tcp_server_private
  36. #endif // TCP_SERVER_PRIVATE_H
  37. //cpp
  38. #include "tcp_server_private.h"
  39. namespace tcp_server_private {
  40. //构造函数
  41. TcpSocket::TcpSocket(qintptr handle, QObject *parent)
  42. : QTcpSocket(parent), handle_(handle) {
  43. this->setSocketDescriptor(handle_);
  44. //断开连接消息
  45. connect(this,&TcpSocket::disconnected,
  46. [&](){
  47. this->deleteLater();
  48. emit this->ClientDisconnected(handle_);
  49. });
  50. }
  51. //主动断开连接槽
  52. void TcpSocket::DisconnectSocket(qintptr handle) {
  53. if (handle == handle_)
  54. disconnectFromHost();
  55. }
  56. }
  • incomingConnection首先判断是否超出最大连接数量,超出就断开新链接,最大连接数量在maxPendingConnections方法获取,而当前已连接client在自定义server的TcpServerPrivate::clients这个QMap记录,此map记录了socketDescriptor和socket指针的映射关系
  • 若未达最大连接数量,则连接创建新的tcpsocket,并将socketDescriptor赋值到socket对象,最后发出ClientConnected信号,此信号带有新链接的socket的指针,可以用于自定义收发信号槽。
  • socket对象的disconnected信号直接connect到了lambda表达式以发出新的信号,新信号ClientDisconnected,并带有socketDescriptor句柄标识符,从而避免了socket断开连接后不知道具体哪个断开
  • socket在创建时均吧ClientDisconnected信号与server的ClientDisconnectedSlot槽连接,当有连接断开时会动态维护map记录
  • server析构时以disconnectFromHost方法主动断开和所有客户端连接,若需要等客户端先断开可以用waitForDisconnected

上述范例将TcpSocket定义在tcp_server_private命名空间不对外可见,且TcpServer返回值也是QTcpSocket,若需要继承QTcpSocket做更多自定义需要将TcpSocket移出

转载请以链接形式标明本文标题和地址:Techie亮博客 » QTcpServer实现多客户端连接

QTcpServer实现多客户端连接的更多相关文章

  1. 高版本->低版本迁移,低版本客户端连接高版本数据库EXP导出报错EXP-00008,ORA-01455,EXP-00000

    生产环境: 源数据库:RHEL + Oracle 11.2.0.3 目标数据库:HP-UX + Oracle 10.2.0.4   需求:迁移部分表  11.2.0.3-->10.2.0.4,若 ...

  2. Redis客户端连接池

    使用场景 对于一些大对象,或者初始化过程较长的可复用的对象,我们如果每次都new对象出来,那么意味着会耗费大量的时间. 我们可以将这些对象缓存起来,当接口调用完毕后,不是销毁对象,当下次使用的时候,直 ...

  3. 【RabbitMQ】CentOS安装RabbitMQ,及简单的Java客户端连接

    在CentOS安装 因Rabbit MQ使用Erlang,所以需要先安装Erlang,安装过程中可能会遇到种种问题,可参考CentOS 6.5安装Erlang/OTP 17.0.然后就可以安装MQ了. ...

  4. redis客户端连接异常

    本文参考:http://mdba.cn/2015/04/02/redistwemproxy-%e5%ae%a2%e6%88%b7%e7%ab%af%e8%bf%9e%e6%8e%a5%e5%bc%82 ...

  5. Mysql从客户端连接服务器连不上的问题

    Mysql从客户端连接服务器连不上的问题   公司要用Mysql做一个测试,开始在自己的本地建一个Mysql数据库自己本地的程序再连上去,没有遇到过连接不上的问题.这次数据库在服务器上,从本地客户端连 ...

  6. Mark一下,一上午就这么过去了,关于客户端连接oracle10G的问题

    Mark一下,一上午就这么过去了,关于客户端连接oracle10G的问题 正常的客户端PLSQL和Navicat都可以正常连接Oracle(局域网内),但代码生成器和VS2015死活连不上,在网上找了 ...

  7. atitit.客户端连接oracle数据库的方式总结

    客户端连接oracle数据库的方式总结 目录 Java程序连接一般使用jar驱动连接..... 桌面GUI一般采取c语言驱动oci.dll 直接连接... 间接连接(需要配置tns及其envi var ...

  8. 今天遇到的一个问题(windows的ssh客户端连接不到虚拟机Ubuntu)

    今天比较郁闷,想用windows上的ssh客户端连接虚拟机中的Ubuntu. 但是死活连不上,之前是能脸上的,所以比较郁闷. 我首先在windows上ping Ubuntu的ip地址,竟然发不了数据包 ...

  9. Redis基础知识之————如何处理客户端连接

    redis 连接建立 Redis Redis 通过监听一个 TCP 端口或者 Unix socket 的方式来接收来自客户端的连接,当一个连接建立后,Redis 内部会进行以下一些操作: 首先,客户端 ...

随机推荐

  1. 基于OMAPL138的Linux字符驱动_GPIO驱动AD9833(一)之miscdevice和ioctl

    基于OMAPL138的Linux字符驱动_GPIO驱动AD9833(一)之miscdevice和ioctl 0. 导语 在嵌入式的道路上寻寻觅觅很久,进入嵌入式这个行业也有几年的时间了,从2011年后 ...

  2. Altiun designer问题汇总(不断更新)

    (1)元件库-引脚名称被矩形方框遮住 该问题可能是因为设置中文版而产生的错误,可以尝试在旁边再摆一个矩形,并且摆上引脚观察是否会被隐藏.如果还存在该现象,先将版本语言改为原版(英文版),再重新绘制即可

  3. 第一节 如何用Go实现单链表

    一.概念介绍 下面这副图是我们单链表运煤车队. 每节运煤车就是单链表里的元素,每节车厢里的煤炭就是元素中保存的数据.前后车通过锁链相连,作为单链表运煤车,从1号车厢开始,每节车厢都知道后面拉着哪一节车 ...

  4. 20155308 2016-2017-2《Java程序设计》课程总结

    20155308 2016-2017-2<Java程序设计>课程总结 每周作业链接汇总 预备作业1:回答"我专业吗.我的老师.师生关系.代码问题"四个问题. 预备作业2 ...

  5. 1071: [SCOI2007]组队

    1071: [SCOI2007]组队 https://lydsy.com/JudgeOnline/problem.php?id=1071 分析: dp+单调性. A*(hi–minH)+B*(si–m ...

  6. 使用iChecker的注意事项

    1. 要先引用jquery 2. ichecker分好多主题,每个主题带好几种颜色,在配置的时候最好指定一下. 比如引入了square主题的blue颜色演示,配置项中checkboxClass就写ic ...

  7. MySQL授权root

    1. 改表法. 可能是你的帐号不允许从远程登陆,只能在localhost.这个时候只要在localhost的那台电脑,登入mysql后,更改 "mysql" 数据库里的 " ...

  8. tp框架-------验证码

    验证码我们一般很常见,在注册或登录时,都可以用的到,下面我们就来看看它的代码和用法 加验证码是为了防止表单攻击的一种方式,为了我们的程序更加的安全 在tp框架中它自带了一个验证码的类,我们先来看一下 ...

  9. 在CentOS 7下编译安装Nginx+PHP+MySQL环境

    本文转载自:http://www.softeng.cn/?p=156,本文已获得作者授权,未经作者同意,不可转载. 1.前言 本文适合于已经对Linux操作系统具有基本操作经验,并且能够在Linux或 ...

  10. Spring Cloud Learning(一): 服务注册

    官网https://projects.spring.io/spring-cloud/,spring cloud官网各组件版本为: Component Edgware.SR4 Finchley.SR1 ...