版权声明:若无来源注明,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. Python学习 :多线程

    多线程 什么是线程? - 能独立运行的基本单位——线程(Threads). - 线程是操作系统能够进行运算调度的最小单位.它被包含在进程之中,是进程中的实际运作单位. - 一条线程指的是进程中一个单一 ...

  2. Mysql Explain的简单使用

    Mysql Explain 主要重要的字段有上面红色方框圈出来的那几个. type: 连接类型,一个好的SQL语句至少要达到range级别,杜绝出现all级别. key: 使用到的索引名,如果没有选择 ...

  3. 笔记(assert 断言)

    并发:在同一个时间段交替执行多个任务并行:在同一个时间点同时执行多个任务串行:同时执行的多个任务按顺序执行(换句话说就是一个任务执行完后才能执行下一个任务) #mysql limit用法: selec ...

  4. linux通过命令查找大文件

    一:如果linux根分区使用量达到100%,会造成如下现象: root不能登录 系统不能正常启动 二:通过命令查找根分区内的大文件 1.du -sh /* 2>/dev/null | sort ...

  5. Ambari搭建hadoop错误记录

    1.ResourceManager启动失败 错误如下 2019-03-24 19:57:00,607 - Error while executing command 'start': Tracebac ...

  6. bmob关联表

    var DDB_User = Bmob.Object.createWithoutData("DDB_User", "b2fd2fe68f"); // var T ...

  7. 与Linux的第一次遭遇

    我的与linux首次遭遇战 虚拟机安装 安装虚拟机我遇到的问题个数可以缩减到1--我几乎没遇到安装虚拟机的问题!我严格按照老师给的链接去下载那个VirtualBox,尽管那个网页是全英文的,但是我像看 ...

  8. 20155233 《Java程序设计》实验五 Java网络编程及安全

    实验内容 1.掌握Socket程序的编写: 2.掌握密码技术的使用: 3.设计安全传输系统. 实验1 两人一组结对编程: 0. 参考http://www.cnblogs.com/rocedu/p/67 ...

  9. MySQL优化Explain命令简介(二)

    type列 MySQL手册上注明type列用于描述join type,不过我们认为把这一列视为对access type--即MySQL决定如何在表中寻找数据的方式的描述,更加合适一些,以下所示从最坏情 ...

  10. day7 RHCE

    6.配置本地邮件服务 在系统server0和desktop0上配置邮件服务,满足以下要求:这些系统不接收外部发送来的邮件这些系统上本地发送的任何邮件都会自动路由到 classroom.example. ...