天天看点

Qt 网络编程-TCP1、 TCP 协议工作原理和编程模型2、 TCP 服务器3、TCP 客户端4、运行5、 QTcpSocket 常用函数

简述:

TCP (Transmission Control Protocol ),传输控制协议,是一种可靠、面向连接、面向数据流的传输协议,许多高层应用协议(包括HTTP、FTP等)都是以它为基础,TCP协议非常适合数据的连续传输。

TCP 协议与 UDP 协议的差别见下表:

Qt 网络编程-TCP1、 TCP 协议工作原理和编程模型2、 TCP 服务器3、TCP 客户端4、运行5、 QTcpSocket 常用函数

注意:在.pro文件中要添加 QT += network,否则无法使用Qt的网络功能。

Qt 中通过 QTcpSocket 类 和 QTcpServer 类实现TCP 协议的编程。

QTcpServer的基本操作:

A. 调用listen监听端口

B. 连接信号newConnection,在槽函数里调用nextPendingConnection获取连接进来的socket。

QTcpSocket的基本操作:

A. 调用connectToHost连接服务器

B. 调用waitForConnected判断是否连接成功

C. 连接信号readyRead槽函数,异步读取数据

D. 调用waitForReadyRead,阻塞读取数据

本文介绍一个基于TCP 协议的网络聊天室应用,由客户端和服务器两部分组成。

操作系统:windows 7

Qt构建套件:qt-opensource-windows-x86-mingw530-5.7.0.exe

Qt Creator版本:4.0.2

1、 TCP 协议工作原理和编程模型

1 > TCP 工作原理

TCP协议能够为应用程序提供可靠的通信连接,使一台计算机发出的字节流无差错地送达网络上的其他计算机。

因此,对可靠性要求高的数据通信系统往往使用TCP协议传输数据,但在正式收发数据前,通信双方必须先建立连接。

Qt 网络编程-TCP1、 TCP 协议工作原理和编程模型2、 TCP 服务器3、TCP 客户端4、运行5、 QTcpSocket 常用函数

2 > TCP 编程模型

基于TCP协议的经典编程模型,程序编写的通用流程如下:

Qt 网络编程-TCP1、 TCP 协议工作原理和编程模型2、 TCP 服务器3、TCP 客户端4、运行5、 QTcpSocket 常用函数

2、 TCP 服务器

1 > tcpserver.h ,声明了需要的各个控件,TcpServer 继承自 QDialog ,实现了服务器端的对话框的显示与控制 。
#ifndef TCPSERVER_H
#define TCPSERVER_H

#include <QDialog>
#include <QListWidget>
#include <QLabel>
#include <QLineEdit>
#include <QPushButton>
#include <QGridLayout>
#include "server.h"

class TcpServer : public QDialog
{
    Q_OBJECT
    
public:
    TcpServer(QWidget *parent = 0,Qt::WindowFlags f=0);
    ~TcpServer();    
private:
    QListWidget *ContentListWidget;
    QLabel *PortLabel;
    QLineEdit *PortLineEdit;
    QPushButton *CreateBtn;
    QGridLayout *mainLayout;
    int port;
    Server *server;
public slots:
    void slotCreateServer();
    void updateServer(QString,int);
};

#endif // TCPSERVER_H
           
2 > tcpserver.cpp , TcpServer 类的构造函数主要实现窗体各控件的创建、布局等。
#include "tcpserver.h"

TcpServer::TcpServer(QWidget *parent,Qt::WindowFlags f)
    : QDialog(parent,f)
{
    setWindowTitle(tr("TCP Server")); //设置窗体的标题

    //初始化各个控件
    ContentListWidget = new QListWidget;
    PortLabel = new QLabel(tr("端口:"));
    PortLineEdit = new QLineEdit;
    CreateBtn = new QPushButton(tr("创建聊天室"));

    //设置布局
    mainLayout = new QGridLayout(this);
    mainLayout->addWidget(ContentListWidget,0,0,1,2);
    mainLayout->addWidget(PortLabel,1,0);
    mainLayout->addWidget(PortLineEdit,1,1);
    mainLayout->addWidget(CreateBtn,2,0,1,2);

    port=8010; //初始化TCP端口号
    PortLineEdit->setText(QString::number(port));

    connect(CreateBtn,SIGNAL(clicked()),this,SLOT(slotCreateServer()));
}

TcpServer::~TcpServer()
{
    
}

//slotCreateServer():创建一个TCP 服务器
void TcpServer::slotCreateServer()
{
    //创建一个Server对象
    server = new Server(this,port);
    //将Server 对象的updateServer()信号与相应的槽函数进行连接
    connect(server,SIGNAL(updateServer(QString,int)),this,SLOT(updateServer(QString,int)));

    CreateBtn->setEnabled(false);
}

//updateServer()用于更新服务器上的信息显示
void TcpServer::updateServer(QString msg,int length)
{
    ContentListWidget->addItem(msg.left(length));
}
           
3 >  tcpclientsocket.h 及  tcpclientsocket.cpp , TcpClientSocket 继承自 QTcpSocket ,创建一个 TCP 套接字,以便在服务器端实现与客户端程序的通信 。
tcpclientsocket.h :
#ifndef TCPCLIENTSOCKET_H
#define TCPCLIENTSOCKET_H

#include <QTcpSocket>
#include <QObject>

class TcpClientSocket : public QTcpSocket
{
    Q_OBJECT   //添加宏(Q_OBJECT)是为了实现信号与槽的通信
public:
    TcpClientSocket(QObject *parent=0);
signals:
    void updateClients(QString,int);
    void disconnected(int);
protected slots:
    void dataReceived();
    void slotDisconnected();
};

#endif // TCPCLIENTSOCKET_H
           
tcpclientsocket.cpp :
#include "tcpclientsocket.h"

TcpClientSocket::TcpClientSocket(QObject *parent)
{
    //指定了信号与槽的连接关系
    //readyRead()是QIODevice的signal,由QTcpSocket继承而来
    //在QT中,QTcpSocket被看成一个QIODevice,readyRead()信号在有数据到来时发出。
    connect(this,SIGNAL(readyRead()),this,SLOT(dataReceived()));
    //disconnected()信号在断开连接时发出
    connect(this,SIGNAL(disconnected()),this,SLOT(slotDisconnected()));
}

//当有数据到来时,触发dataReceived()
void TcpClientSocket::dataReceived()
{
    /*bytesAvailable()表示有效数据*/
    while(bytesAvailable()>0)
    {
        int length = bytesAvailable();
        char buf[1024];
        read(buf,length);

        QString msg=buf;
        //updateClients()信号是通知服务器向聊天室内所有成员广播信息
        emit updateClients(msg,length);
    }
}

void TcpClientSocket::slotDisconnected()
{
    emit disconnected(this->socketDescriptor());
}
           
4 >  server.h 及 server.cpp,Server 类继承自QTcpServer ,实现一个TCP 协议的服务器。利用QTcpServer ,可以监听到指定的端口的TCP连接 。
server.h :
#ifndef SERVER_H
#define SERVER_H

#include <QTcpServer>
#include <QObject>
#include "tcpclientsocket.h"  //包含TCP的套接字

class Server : public QTcpServer
{
    Q_OBJECT  //添加宏(Q_OBJECT)是为了实现信号与槽的通信
public:
    Server(QObject *parent=0,int port=0);
    //保存与每一个客户端连接的TcpClientSocket
    QList<TcpClientSocket*> tcpClientSocketList;
signals:
    void updateServer(QString,int);
public slots:
    void updateClients(QString,int);
    void slotDisconnected(int);
protected:
    void incomingConnection(int socketDescriptor);
};

#endif // SERVER_H
           
server.cpp :
#include "server.h"

Server::Server(QObject *parent,int port)
    :QTcpServer(parent)
{
    //在指定的端口对任意地址进行监听
    listen(QHostAddress::Any,port);
    /*******************************
     * QHostAddress::Null 表示一个空地址,
     * QHostAddress::LocalHost 表示IPv4的本地地址127.0.0.1,
     * QHostAddress::LocalHostIPv6 表示IPv6的本地地址,
     * QHostAddress::Broadcast 表示广播地址255.255.255.255,
     * QHostAddress::Any 表示IPv4的任意地址0.0.0.0,
     * QHostAddress::AnyIPv6 表示IPv6的任意地址
    *******************************/
}

/*******************************
 * 当出现一个新的连接时,QTcpServer 触发incomingConnection()函数
 * 参数socketDescriptor 指定了连接Socket描述符
*******************************/
void Server::incomingConnection(int socketDescriptor)
{
    //创建一个新的TcpClientSocket与客户端通信
    TcpClientSocket *tcpClientSocket=new TcpClientSocket(this);
    //连接TcpClientSocket的updateClients()信号
    connect(tcpClientSocket,SIGNAL(updateClients(QString,int)),
            this,SLOT(updateClients(QString,int)));

    //连接TcpClientSocket的disconnected()信号
    connect(tcpClientSocket,SIGNAL(disconnected(int)),
            this,SLOT(slotDisconnected(int)));
    //将新创建的TcpClientSocket的套接字描述符指定为参数socketDescriptor
    tcpClientSocket->setSocketDescriptor(socketDescriptor);
    //将tcpClientSocket加入客户端套接字列表以便管理
    tcpClientSocketList.append(tcpClientSocket);
}

/*******************************
 * updateClients()函数将任意客户端发来的信息进行广播,
 * 保证聊天室的所有用户均能看到其他人看发言
*******************************/
void Server::updateClients(QString msg,int length)
{
    //发出updateServer()信号,用来通知服务器对话框更新相应的显示状态
    emit updateServer(msg,length);

    /*******************************
     * 实现信息的广播,
     * tcpClientSocketList中保存了所有与服务器相连的TcpClientSocket对象
    *******************************/
    for(int i=0;i<tcpClientSocketList.count();i++)
    {
        QTcpSocket *item = tcpClientSocketList.at(i);
        if(item->write(msg.toLatin1(),length)!=length)
        {
            continue;
        }
    }
}

/*******************************
 * slotDisconnected()函数
 * 实现从tcpClientSocketList列表中将断开连接的TcpClientSocket对象删除的功能
*******************************/
void Server::slotDisconnected(int descriptor)
{
    for(int i=0;i<tcpClientSocketList.count();i++)
    {
        QTcpSocket *item = tcpClientSocketList.at(i);
        if(item->socketDescriptor()==descriptor)
        {
            tcpClientSocketList.removeAt(i);
            return;
        }
    }
    return;
}
           

3、TCP 客户端

1 > tcpclient.h ,TcpClient 类继承自QDialog 类,声明了需要的各种的控件。
#ifndef TCPCLIENT_H
#define TCPCLIENT_H

#include <QDialog>
#include <QListWidget>
#include <QLineEdit>
#include <QPushButton>
#include <QLabel>
#include <QGridLayout>
#include <QHostAddress>
#include <QTcpSocket>

class TcpClient : public QDialog
{
    Q_OBJECT
    
public:
    TcpClient(QWidget *parent = 0,Qt::WindowFlags f=0);
    ~TcpClient();
private:
    QListWidget *contentListWidget;
    QLineEdit *sendLineEdit;
    QPushButton *sendBtn;
    QLabel *userNameLabel;
    QLineEdit *userNameLineEdit;
    QLabel *serverIPLabel;
    QLineEdit *serverIPLineEdit;
    QLabel *portLabel;
    QLineEdit *portLineEdit;
    QPushButton *enterBtn;
    QGridLayout *mainLayout;
    bool status;
    int port;
    QHostAddress *serverIP;
    QString userName;
    QTcpSocket *tcpSocket;
public slots:
    void slotEnter();
    void slotConnected();
    void slotDisconnected();
    void dataReceived();
    void slotSend();
};

#endif // TCPCLIENT_H
           
2 >  tcpclient.cpp
#include "tcpclient.h"
#include <QMessageBox>
#include <QHostInfo>

TcpClient::TcpClient(QWidget *parent,Qt::WindowFlags f)
    : QDialog(parent,f)
{
    setWindowTitle(tr("TCP Client"));//设置窗体的标题

    //初始化各个控件
    contentListWidget = new QListWidget;
    sendLineEdit = new QLineEdit;
    sendBtn = new QPushButton(tr("发送"));
    userNameLabel = new QLabel(tr("用户名:"));
    userNameLineEdit = new QLineEdit;
    serverIPLabel = new QLabel(tr("服务器地址:"));
    serverIPLineEdit = new QLineEdit;
    portLabel = new QLabel(tr("端口:"));
    portLineEdit = new QLineEdit;
    enterBtn= new QPushButton(tr("进入聊天室"));

    //设置布局
    mainLayout = new QGridLayout(this);
    mainLayout->addWidget(contentListWidget,0,0,1,2);
    mainLayout->addWidget(sendLineEdit,1,0);
    mainLayout->addWidget(sendBtn,1,1);
    mainLayout->addWidget(userNameLabel,2,0);
    mainLayout->addWidget(userNameLineEdit,2,1);
    mainLayout->addWidget(serverIPLabel,3,0);
    mainLayout->addWidget(serverIPLineEdit,3,1);
    mainLayout->addWidget(portLabel,4,0);
    mainLayout->addWidget(portLineEdit,4,1);
    mainLayout->addWidget(enterBtn,5,0,1,2);

    status = false;

    //初始化TCP端口号
    port = 8010;
    portLineEdit->setText(QString::number(port));

    serverIP =new QHostAddress();

    connect(enterBtn,SIGNAL(clicked()),this,SLOT(slotEnter()));
    connect(sendBtn,SIGNAL(clicked()),this,SLOT(slotSend()));

    sendBtn->setEnabled(false);
}

TcpClient::~TcpClient()
{
    
}

void TcpClient::slotEnter()
{
    //status表示当前的状态
    if(!status)
    {
        //完成输入合法性检验
        QString ip = serverIPLineEdit->text();
        if(!serverIP->setAddress(ip)) //判断给定的IP是否能够被正确解析
        {
            QMessageBox::information(this,tr("error"),tr("server ip address error!"));
            return;
        }

        if(userNameLineEdit->text()=="")
        {
            QMessageBox::information(this,tr("error"),tr("User name error!"));
            return;
        }

        userName=userNameLineEdit->text();

        //初始化TCP客户端:创建一个QTcpSocket类对象,并将信号/槽连接起来
        tcpSocket = new QTcpSocket(this); //实例化tcpSocket
        tcpSocket->abort(); //取消原有连接
        connect(tcpSocket,SIGNAL(connected()),this,SLOT(slotConnected()));
        connect(tcpSocket,SIGNAL(disconnected()),this,SLOT(slotDisconnected()));
        connect(tcpSocket,SIGNAL(readyRead()),this,SLOT(dataReceived()));

        //与TCP服务器连接,连接成功后发出conneted()信号
        tcpSocket->connectToHost(*serverIP,port);

        status=true;
    }
    else
    {
        int length=0;
        QString msg=userName+tr(":Leave Chat Room"); //构建一条离开聊天室的消息
        //通知服务器端以上构建的信息
        if((length=tcpSocket->write(msg.toLatin1(),msg.length()))!=msg. length())
        {
            return;
        }

        //与服务器断开连接,断开连接后发出disconnected()信号
        tcpSocket->disconnectFromHost();

        status=false;
    }
}


/*******************************
 * slotConnected()为connected()信号的响应槽,
 * 当与服务器连接成功后,客户端构造一条进入聊天室的信息,并通知服务器
*******************************/
void TcpClient::slotConnected()
{
    sendBtn->setEnabled(true);
    enterBtn->setText(tr("离开"));

    int length=0;
    QString msg=userName+tr(":Enter Chat Room");
    if((length=tcpSocket->write(msg.toLatin1(),msg.length()))!=msg.length())
    {
        return;
    }
}

void TcpClient::slotSend()
{
    if(sendLineEdit->text()=="")
    {
        return ;
    }

    QString msg=userName+":"+sendLineEdit->text();

    //write()发送数据
    tcpSocket->write(msg.toLatin1(),msg.length());
    sendLineEdit->clear();
}

void TcpClient::slotDisconnected()
{
    sendBtn->setEnabled(false);
    enterBtn->setText(tr("进入聊天室"));
}

/*******************************
 * dataReceived()函数,当有数据到来时,触发此函数,
 * 从套接字中将有效数据取出并显示
*******************************/
void TcpClient::dataReceived()
{
    /*bytesAvailable()表示有效数据*/
    while(tcpSocket->bytesAvailable()>0)
    {
        QByteArray datagram;
        datagram.resize(tcpSocket->bytesAvailable());

        //read()读数据
        tcpSocket->read(datagram.data(),datagram.size());

        QString msg=datagram.data();
        contentListWidget->addItem(msg.left(datagram.size()));
    }
}
           

4、运行

Qt 网络编程-TCP1、 TCP 协议工作原理和编程模型2、 TCP 服务器3、TCP 客户端4、运行5、 QTcpSocket 常用函数

5、 QTcpSocket 常用函数

 QTcpSocket  继承于QAbstractSocket继承于QIODevice 。
// ### Qt6: make the first one virtual
    bool bind(const QHostAddress &address, quint16 port = 0, BindMode mode = DefaultForPlatform);
    bool bind(quint16 port = 0, BindMode mode = DefaultForPlatform);
    //与服务器连接或断开连接
    // ### Qt6: de-virtualize connectToHost(QHostAddress) overload
    virtual void connectToHost(const QString &hostName, quint16 port, OpenMode mode = ReadWrite, NetworkLayerProtocol protocol = AnyIPProtocol);
    virtual void connectToHost(const QHostAddress &address, quint16 port, OpenMode mode = ReadWrite);
    virtual void disconnectFromHost();

    bool isValid() const;

    qint64 bytesAvailable() const Q_DECL_OVERRIDE;//表示有效数据
    qint64 bytesToWrite() const Q_DECL_OVERRIDE;

    bool canReadLine() const Q_DECL_OVERRIDE; // ### Qt6: remove me

    quint16 localPort() const;
    QHostAddress localAddress() const;
    quint16 peerPort() const;
    QHostAddress peerAddress() const;
    QString peerName() const;

    qint64 readBufferSize() const;
    virtual void setReadBufferSize(qint64 size);

    void abort();//取消原有连接

    virtual qintptr socketDescriptor() const;
    virtual bool setSocketDescriptor(qintptr socketDescriptor, SocketState state = ConnectedState,
                             OpenMode openMode = ReadWrite);

    virtual void setSocketOption(QAbstractSocket::SocketOption option, const QVariant &value);
    virtual QVariant socketOption(QAbstractSocket::SocketOption option);

    SocketType socketType() const;
    SocketState state() const;
    SocketError error() const;

    // from QIODevice
    void close() Q_DECL_OVERRIDE;
    bool isSequential() const Q_DECL_OVERRIDE;
    bool atEnd() const Q_DECL_OVERRIDE; // ### Qt6: remove me
    bool flush();

    // for synchronous access
    virtual bool waitForConnected(int msecs = 30000);
    bool waitForReadyRead(int msecs = 30000) Q_DECL_OVERRIDE;
    bool waitForBytesWritten(int msecs = 30000) Q_DECL_OVERRIDE;
    virtual bool waitForDisconnected(int msecs = 30000);

#ifndef QT_NO_NETWORKPROXY
    void setProxy(const QNetworkProxy &networkProxy);
    QNetworkProxy proxy() const;
#endif

Q_SIGNALS:
    void hostFound();
    void connected();
    void disconnected();
    void stateChanged(QAbstractSocket::SocketState);
    void error(QAbstractSocket::SocketError);
#ifndef QT_NO_NETWORKPROXY
    void proxyAuthenticationRequired(const QNetworkProxy &proxy, QAuthenticator *authenticator);
#endif

protected:
    qint64 readData(char *data, qint64 maxlen) Q_DECL_OVERRIDE;
    qint64 readLineData(char *data, qint64 maxlen) Q_DECL_OVERRIDE;
    qint64 writeData(const char *data, qint64 len) Q_DECL_OVERRIDE;

    void setSocketState(SocketState state);
    void setSocketError(SocketError socketError);
    void setLocalPort(quint16 port);
    void setLocalAddress(const QHostAddress &address);
    void setPeerPort(quint16 port);
    void setPeerAddress(const QHostAddress &address);
    void setPeerName(const QString &name);
           
故 QTcpSocket 提供的几种接收和发送数据方法如下:
write ( const char *, qint64 ) : qint64
write ( const char * ) : qint64
write ( const QByteArray & ) : qint64
writeData ( const char*, qint64 ) : qint64
read ( char * data, qint64 maxSize): qint64 
read ( qint64 maxSize):QByteArray
readAll ():QByteArray
readLine ( char *data, qint64 maxSize ):qint64
readLine ( qint64 maxSize =0 ):QByteArray