首先介绍一下TCP:(Transmission Control Protocol 传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议。相比而言UDP,就是开放式、无连接、不可靠的传输层通信协议。
下面,我一次进行客户端和服务器端的QT实现。我的开发环境是:QT Creator 5.7。

先看下效果图:

一:客户端编程

QT提供了QTcpSocket类,可以直接实例化一个客户端,可在help中索引如下:

The QTcpSocket class provides a TCP socket. More...
Header      #include <QTcpSocket>
qmake       QT += network
Inherits:   QAbstractSocket
Inherited By:   QSslSocket
从这里,我们可以看到,必须要在.pro文件中添加QT += network才可以进行网络编程,否则是访问不到<QTcpSocket>头文件的。客户端读写相对简单,我们看一下代码头文件:
#ifndef MYTCPCLIENT_H
#define MYTCPCLIENT_H#include <QMainWindow>
#include <QTcpSocket>
#include <QHostAddress>
#include <QMessageBox>
namespace Ui {
class MyTcpClient;
}class MyTcpClient : public QMainWindow
{Q_OBJECTpublic:explicit MyTcpClient(QWidget *parent = 0);~MyTcpClient();private:Ui::MyTcpClient *ui;QTcpSocket *tcpClient;private slots://客户端槽函数void ReadData();void ReadError(QAbstractSocket::SocketError);void on_btnConnect_clicked();void on_btnSend_clicked();void on_pushButton_clicked();
};#endif // MYTCPCLIENT_H
我们在窗口类中,定义了一个私有成员QTcpSoket *tcpClient。

1) 初始化QTcpSocket
在构造函数中,我们需要先对其进行实例化,并连接信号与槽函数:

    //初始化TCP客户端tcpClient = new QTcpSocket(this);   //实例化tcpClienttcpClient->abort();                 //取消原有连接connect(tcpClient, SIGNAL(readyRead()), this, SLOT(ReadData()));connect(tcpClient, SIGNAL(error(QAbstractSocket::SocketError)), \this, SLOT(ReadError(QAbstractSocket::SocketError)));

2)建立连接 和 断开连接

    tcpClient->connectToHost(ui->edtIP->text(), ui->edtPort->text().toInt());if (tcpClient->waitForConnected(1000))  // 连接成功则进入if{}{ui->btnConnect->setText("断开");ui->btnSend->setEnabled(true);}
a)建立TCP连接的函数:void connectToHost(const QHostAddress &address, quint16 port, OpenMode openMode = ReadWrite)是从QAbstractSocket继承下来的public function,同时它又是一个virtual function。作用为:Attempts to make a connection to address on port port。
b)等待TCP连接成功的函数:bool waitForConnected(int msecs = 30000)同样是从QAbstractSocket继承下来的public function,同时它又是一个virtual function。作用为:Waits until the socket is connected, up to msecs milliseconds. If the connection has been established, this function returns true; otherwise it returns false. In the case where it returns false, you can call error() to determine the cause of the error.上述代码中,edtIP, edtPort是ui上的两个lineEditor,用来填写服务器IP和端口号。btnConnect是“连接/断开”复用按钮,btnSend是向服务器发送数据的按钮,只有连接建立之后,才将其setEnabled。
        tcpClient->disconnectFromHost();if (tcpClient->state() == QAbstractSocket::UnconnectedState \|| tcpClient->waitForDisconnected(1000))  //已断开连接则进入if{}{ui->btnConnect->setText("连接");ui->btnSend->setEnabled(false);}
a)断开TCP连接的函数:void disconnectFromHost()是从QAbstractSocket继承的public function,同时它又是一个virtual function。作用为:Attempts to close the socket. If there is pending data waiting to be written, QAbstractSocket will enter ClosingState and wait until all data has been written. Eventually, it will enter UnconnectedState and emit the disconnected() signal.
b)等待TCP断开连接函数:bool waitForDisconnected(int msecs = 30000),同样是从QAbstractSocket继承下来的public function,同时它又是一个virtual function。作用为:Waits until the socket has disconnected, up to msecs milliseconds. If the connection has been disconnected, this function returns true; otherwise it returns false. In the case where it returns false, you can call error() to determine the cause of the error.

3)读取服务器发送过来的数据
readyRead()是QTcpSocket从父类QIODevice中继承下来的信号:This signal is emitted once every time new data is available for reading from the device’s current read channel。
readyRead()对应的槽函数为:

void MyTcpClient::ReadData()
{QByteArray buffer = tcpClient->readAll();if(!buffer.isEmpty()){ui->edtRecv->append(buffer);}
}
readAll()是QTcpSocket从QIODevice继承的public function,直接调用就可以读取从服务器发过来的数据了。我这里面把数据显示在textEditor控件上(ui>edtRecv)。由此完成了读操作。
error(QAbstractSocket::SocketError)是QTcpSocket从QAbstractSocket继承的signal, This signal is emitted after an error occurred. The socketError parameter describes the type of error that occurred.连接到的槽函数定义为:
void MyTcpClient::ReadError(QAbstractSocket::SocketError)
{tcpClient->disconnectFromHost();ui->btnConnect->setText(tr("连接"));QMessageBox msgBox;msgBox.setText(tr("failed to connect server because %1").arg(tcpClient->errorString()));
xec();
}
这段函数的作用是:当错误发生时,首先断开TCP连接,再用QMessageBox提示出errorString,即错误原因。

4)向服务器发送数据

    QString data = ui->edtSend->toPlainText();if(data != ""){tcpClient->write(data.toLatin1()); //qt5去除了.toAscii()}
定义一个QString变量,从textEditor(edtSend)中获取带发送数据,write()是QTcpSocket从QIODevice继承的public function,直接调用就可以向服务器发送数据了。这里需要注意的是:toAscii()到qt5就没有了,这里要写成toLatin1()。

至此,通过4步,我们就完成了TCP Client的程序开发,源码下载地址:客户端qt程序源码

二:服务器端编程
服务器段编程相比于客户端要繁琐一些,因为对于客户端来说,只能连接一个服务器。而对于服务器来说,它是面向多连接的,如何协调处理多客户端连接就显得尤为重要。
前言:编程过程中遇到的问题 和 解决的方法
遇到的问题:每个新加入的客户端,服务器给其分配一个SocketDescriptor后,就会emit newConnection()信号,但分配好的SocketDecriptor并没有通过newConnection()信号传递,所以用户得不到这个客户端标识SocketDescriptor。同样的,每当服务器收到新的消息时,客户端会emit readReady()信号,然而readReady()信号也没有传递SocketDescriptor, 这样的话,服务器端即使接收到消息,也不知道这个消息是从哪个客户端发出的。

解决的方法:
1. 通过重写[virtual protected] void QTcpServer::incomingConnection(qintptr socketDescriptor),获取soketDescriptor。自定义TcpClient类继承QTcpSocket,并将获得的soketDescriptor作为类成员。 这个方法的优点是:可以获取到soketDescriptor,灵活性高。缺点是:需要重写函数、自定义类。
2. 在newConnection()信号对应的槽函数中,通过QTcpSocket *QTcpServer::nextPendingConnection()函数获取 新连接的客户端:Returns the next pending connection as a connected QTcpSocket object. 虽然仍然得不到soketDescriptor,但可以通过QTcpSocket类的peerAddress()和peerPort()成员函数获取客户端的IP和端口号,同样是唯一标识。 优点:无需重写函数和自定义类,代码简洁。缺点:无法获得SocketDecriptor,灵活性差。

本文介绍第二种方法:

QT提供了QTcpServer类,可以直接实例化一个客户端,可在help中索引如下:

The QTcpServer class provides a TCP-based server. More...
Header:     #include <QTcpServer>
qmake:      QT += network
Inherits:       QObject
从这里,我们可以看到,必须要在.pro文件中添加QT += network才可以进行网络编程,否则是访问不到<QTcpServer>头文件的。我们看一下代码头文件:
#ifndef MYTCPSERVER_H
#define MYTCPSERVER_H#include <QMainWindow>
#include <QTcpServer>
#include <QTcpSocket>
#include <QNetworkInterface>
#include <QMessageBox>
namespace Ui {
class MyTcpServer;
}class MyTcpServer : public QMainWindow
{Q_OBJECTpublic:explicit MyTcpServer(QWidget *parent = 0);~MyTcpServer();private:Ui::MyTcpServer *ui;QTcpServer *tcpServer;QList<QTcpSocket*> tcpClient;QTcpSocket *currentClient;private slots:void NewConnectionSlot();void disconnectedSlot();void ReadData();void on_btnConnect_clicked();void on_btnSend_clicked();void on_btnClear_clicked();
};#endif // MYTCPSERVER_H
值得注意的是,在服务端编写时,需要同时定义服务器端变量QTcpServer *tcpServer和客户端变量 QList<QTcpSocket*> tcpClient。tcpSocket QList存储了连接到服务器的所有客户端。因为QTcpServer并不是QIODevice的子类,所以在QTcpServer中并没有任何有关读写操作的成员函数,读写数据的操作全权交由QTcpSocket处理。

1)初始化QTcpServer

    tcpServer = new QTcpServer(this);ui->edtIP->setText(QNetworkInterface().allAddresses().at(1).toString());   //获取本地IPui->btnConnect->setEnabled(true);ui->btnSend->setEnabled(false);connect(tcpServer, SIGNAL(newConnection()), this, SLOT(NewConnectionSlot()));
通过QNetworkInterface().allAddresses().at(1)获取到本机IP显示在lineEditor上(edtIP)。介绍如下:
[static] QList<QHostAddress> QNetworkInterface::allAddresses()
This convenience function returns all IP addresses found on the host machine. It is equivalent to calling addressEntries() on all the objects returned by allInterfaces() to obtain lists of QHostAddress objects then calling QHostAddress::ip() on each of these.:每当新的客户端连接到服务器时,newConncetion()信号触发,NewConnectionSlot()是用户的槽函数,定义如下:
void MyTcpServer::NewConnectionSlot()
{currentClient = tcpServer->nextPendingConnection();tcpClient.append(currentClient);ui->cbxConnection->addItem(tr("%1:%2").arg(currentClient->peerAddress().toString().split("::ffff:")[1])\.arg(currentClient->peerPort()));connect(currentClient, SIGNAL(readyRead()), this, SLOT(ReadData()));connect(currentClient, SIGNAL(disconnected()), this, SLOT(disconnectedSlot()));
}
通过nextPendingConnection()获得连接过来的客户端信息,取到peerAddress和peerPort后显示在comboBox(cbxConnection)上,并将客户端的readyRead()信号连接到服务器端自定义的读数据槽函数ReadData()上。将客户端的disconnected()信号连接到服务器端自定义的槽函数disconnectedSlot()上。

2)监听端口 与 取消监听

     bool ok = tcpServer->listen(QHostAddress::Any, ui->edtPort->text().toInt());if(ok){ui->btnConnect->setText("断开");ui->btnSend->setEnabled(true);}
a)监听端口的函数:bool QTcpServer::listen(const QHostAddress &*address* = QHostAddress::Any, quint16 *port* = 0),该函数的作用为:Tells the server to listen for incoming connections on address *address* and port *port*. If port is 0, a port is chosen automatically. If address is QHostAddress::Any, the server will listen on all network interfaces.
Returns true on success; otherwise returns false.
     for(int i=0; i<tcpClient.length(); i++)//断开所有连接{tcpClient[i]->disconnectFromHost();bool ok = tcpClient[i]->waitForDisconnected(1000);if(!ok){// 处理异常}tcpClient.removeAt(i);  //从保存的客户端列表中取去除}tcpServer->close();     //不再监听端口
b)断开客户端与服务器连接的函数:disconnectFromHost()和waitForDisconnected()上文已述。断开连接之后,要将其从QList tcpClient中移除。服务器取消监听的函数:tcpServer->close()。
    //由于disconnected信号并未提供SocketDescriptor,所以需要遍历寻找for(int i=0; i<tcpClient.length(); i++){if(tcpClient[i]->state() == QAbstractSocket::UnconnectedState){// 删除存储在combox中的客户端信息ui->cbxConnection->removeItem(ui->cbxConnection->findText(tr("%1:%2")\.arg(tcpClient[i]->peerAddress().toString().split("::ffff:")[1])\.arg(tcpClient[i]->peerPort())));// 删除存储在tcpClient列表中的客户端信息tcpClient[i]->destroyed();tcpClient.removeAt(i);}}
c)若某个客户端断开了其与服务器的连接,disconnected()信号被触发,但并未传递参数。所以用户需要遍历tcpClient list来查询每个tcpClient的state(),若是未连接状态(UnconnectedState),则删除combox中的该客户端,删除tcpClient列表中的该客户端,并destroy()。

3)读取客户端发送过来的数据

    // 客户端数据可读信号,对应的读数据槽函数void MyTcpServer::ReadData(){// 由于readyRead信号并未提供SocketDecriptor,所以需要遍历所有客户端for(int i=0; i<tcpClient.length(); i++){QByteArray buffer = tcpClient[i]->readAll();if(buffer.isEmpty())    continue;static QString IP_Port, IP_Port_Pre;IP_Port = tr("[%1:%2]:").arg(tcpClient[i]->peerAddress().toString().split("::ffff:")[1])\.arg(tcpClient[i]->peerPort());// 若此次消息的地址与上次不同,则需显示此次消息的客户端地址if(IP_Port != IP_Port_Pre)ui->edtRecv->append(IP_Port);ui->edtRecv->append(buffer);//更新ip_portIP_Port_Pre = IP_Port;}}
这里需要注意的是,虽然tcpClient产生了readReady()信号,但readReady()信号并没有传递任何参数,当面向多连接客户端时,tcpServer并不知道是哪一个tcpClient是数据源,所以这里遍历tcpClient列表来读取数据(略耗时,上述的解决方法1则不必如此)。
读操作由tcpClient变量处理:tcpClient[i]->readAll();

4)向客户端发送数据

    //全部连接if(ui->cbxConnection->currentIndex() == 0){for(int i=0; i<tcpClient.length(); i++)tcpClient[i]->write(data.toLatin1()); //qt5除去了.toAscii()}
a)向当前连接的所有客户端发数据,遍历即可。
    //指定连接QString clientIP = ui->cbxConnection->currentText().split(":")[0];int clientPort = ui->cbxConnection->currentText().split(":")[1].toInt();for(int i=0; i<tcpClient.length(); i++){if(tcpClient[i]->peerAddress().toString().split("::ffff:")[1]==clientIP\&& tcpClient[i]->peerPort()==clientPort){tcpClient[i]->write(data.toLatin1());return; //ip:port唯一,无需继续检索}}
b)在comboBox(cbxConnction)中选择指定连接发送数据:通过peerAddress和peerPort匹配客户端,并发送。写操作由tcpClient变量处理:tcpClient[i]->write()。

至此,通过4步,我们就完成了TCP Server的程序开发,源码下载地址:服务器端qt程序源码

QT TCP网络编程相关推荐

  1. 迈入JavaWeb第一步,Java网络编程基础,TCP网络编程URL网络编程等

    文章目录 网络编程概述 网络通信要素 要素一IP和端口号 要素二网络协议 TCP网络编程 UDP网络编程 URL网络编程 Java网络编程基础 网络编程概述 Java是Internet上的语言,它从语 ...

  2. TCP网络编程的基本流程

    TCP网络编程的基本流程 对于服务端,通常为以下流程: 调用socket函数创建socket 调用bind函数将socket绑定到某个IP和端口上 调用listen开始监听 当有客户端请求连接上来时, ...

  3. 【计算机网络】Linux环境中的TCP网络编程

    文章目录 前言 一.TCP Socket API 1. socket 2. bind 3. listen 4. accept 5. connect 二.封装TCPSocket 三.服务端的实现 1. ...

  4. muduo学习笔记:net部分之实现TCP网络编程库-Buffer

    文章目录 为什么采用non-blocking网络编程中应用层buffer是必需的? Buffer 设计 Buffer::readFd() 线程安全 Muduo Buffer 的数据结构 Muduo B ...

  5. TCP网络编程 [Java]

    TCP网络编程 这里我们通过一个例子来了解什么是TCP网络编程: eg: 问题: -> 从客户端发送文件到服务端,服务端将接受到的客户端发来的文件存储到本地之后并返回一个发送成功给客户端,并关闭 ...

  6. 套接字编程-TCP网络编程

    文章目录 套接字地址结构 通用套接字地址数据结构 以太网协议的套接字地址数据结构 Netlink协议套接字地址结构 TCP网络编程 套接字初始化socket() domain type protoco ...

  7. 粤嵌GEC6818板子TCP网络编程发送命令控制音视频

    TCP网络编程 (1)gec6818网络编程前期准备工作 (1).开发板和Ubuntu系统都处于教室局域网内 1.开发板和电脑(Ubuntu)都需要连接上教室的网线 2.开发板设置ip地址 在开发板终 ...

  8. tcp网络编程客户端和服务端及listen和tcp允许最大连接数

    tcp网络编程 tcp网络编程步骤: 由于tcp传输特点是可靠有连接,那么就有 1.客户端向服务端发送连接请求(SYN), 2.服务端接受请求并向客户端发送(SYN+ACK); 3.客户端向服务端回复 ...

  9. Qt之网络编程错误总结

    来自http://blog.csdn.net/qianguozheng/article/details/6849293 最近在做Qt网络编程,主要是项目需要,学习下Qt下网络编程的多线程实现. 1.Q ...

  10. 基于数据库及TCP网络编程实现的电子词典

    目录 一.前言 二.项目介绍 三.功能实现 3.1. 用户注册 3.1.1 功能演示 3.1.2 功能函数实现 3.2. 用户登录 3.2.1 功能演示 3.2.2 功能函数实现 3.3. 查询单词 ...

最新文章

  1. JS页面加载完成触发事件
  2. android 编译 oserror,Android-4.4.2 编译出错 OSError: [Errno 2] No such file or directory
  3. Java面试题之一 (转)
  4. 网络营销——网络营销专员到底是教你如何选择网站页面制作
  5. PAL算法原理及代码实现
  6. 4.从单应矩阵中分离得到内参和外参(需要拍摄n=3张标定图片)
  7. STM32 TIMER初始化步骤
  8. 深入mysql ON DUPLICATE KEY UPDATE 语法的分析
  9. odata协议里filter操作自带的函数 - endswith
  10. [DP/单调队列]BZOJ 2059 [Usaco2010 Nov]Buying Feed 购买饲料
  11. 王道计算机网络 计算机网络体系结构整理 超详细版
  12. Java 和 Python 并列第二、Julia 下滑,揭晓 RedMonk 最新编程语言榜单!
  13. php密送,向多个收件人发送电子邮件 – 抄送:和密送:在PHP中
  14. 计算机中缺失freeimage.dll,FreeImage.dll
  15. java象棋人机_Java版中国象棋人机对战源代码,作者将AI算法用得淋漓尽致JAVA游戏源码下载...
  16. ldap 统一认证 java_LDAP统一用户认证
  17. windows8没有剪贴板查看器clipbrd怎么办?
  18. R 语言计算卡方检验的P值
  19. 中关村标协智能物联分技术委员会成立,小米张明当选第一届主任委员
  20. html怎么在表格中加虚线,html设置虚线边框的方法详细介绍

热门文章

  1. 全年CCF级别会议列表
  2. 算法设计与分析(1)——基础知识
  3. java毕设查重,已经毕业的给毕设查重的人一点建议
  4. 啊哈C语言--20220823练习
  5. python win32api教程_winapi-如何在python中使用win32 API?
  6. 软件测试报告的项目背景,软件测试报告范本
  7. 用Python实现简单的人脸识别,10分钟搞定!(附源码)
  8. 前端视频播放初探总结,video标签-视频插件jwplayer
  9. 电子工程师私藏的一个网站
  10. EPLAN p8 安装失败解决办法