Qt tcp/ip 通信

来源:互联网 发布:blingbling软件 编辑:程序博客网 时间:2024/04/29 05:26

折腾了很久TCP IP通信机制。

 

以前虽然看过bsd tcp/ip的so called 基础通信代码。什么bind, listen ,accept , receive, write, read,但是一直没真正理解。

 

这次由于公司需求,我狠狠地读了代码,并且搬出QT老本行,开始了QT For windows的编程。

 

这个大体构架是做一个聊天室软件。

 

每个client都可以给服务器TCP发消息,服务器通过TCP给各个客户端转发消息。

 

 

服务器端代码:

 

Server.cpp   继承 QTcpServer  主要用来listen to some port , 侦听到端口后, 继承重写了incomingConnection函数,来new 如下的一个代码

tcpClientSocket.cpp 这个继承QTcpSocket ,用来 server.cpp里被 New 出来,接受各种请求

它重写了函数dataReceived , 即各种客户端发来的请求数据,(注意,这个不是第一步的connect状态,这个是业务逻辑上得请求,比如我给server发送了“你好” ) 。

这一步处理好后,便开始给各个客户端分发同样的消息“你好” 。使用方法,很简单,QTcpSocket的write方法即可。

 

这里的细节重点是,在server.cpp里,每个new出来的TcpClientSocket的指针,我放到一个QLIST< TcpClientSocket * >模板里。这样,只要你不删去这个节点,这个TCP链接就一直存在,嘿嘿,神奇吧。

 

刚开始我看QT自带example ,fortuneclient and threaded fortune server;我试图着在example的基础上修改代码,一步步达到目的。结果发现他的业务逻辑,总是write后就自动disconnected, 我以为不disconnected,就能长链接,结果总是出错。

 

我一直纳闷,这是为什么呢?我用了个List保存了socket的descriptor,以为留着套接字的描述符,就可以下次再调出来用用。实际呢,必须创建链路的时刻,就保存指针。TCP链接,指针在,链路在。指针亡,链路亡。

 

这也验证了我的想法,所谓一个真正的通信链路SOCKET的创建,是这样执行下去的。在APP层,我们调用了connect,实际OS对网卡发送了连接对方的信号,这个电子,一路走过去,直到accept , 这一个链路创建了,在网卡开辟了区域了,在系统OS也开辟了内存,两方都为此一直保持着这段数据的存在,指针即维系一个网络TCP链路的关键。

 

这就意味着,客户端无需写什么侦听代码来接受服务器端的消息,直接保持那个链路,消息自然就可以发过来,触发dataReceived信号。

 

 

 

写完代码后,我测试了一下,3个客户端同时链接TCP服务器端的5566端口,全部成功。

 

曾经很纠结我的所谓端口只能被一个占用。看来,理论远不如实际来的直接。

 

最后,我还是贴个代码吧。我知道,当一个人寻找各类消息的时候,代码总是最先看得,谁喜欢看人家博客唠叨半天,不讲大道理啊!

 

 

服务器端:

 

代码结构直接看插图

 

chatserver.h

 

*********************

 

#ifndef CHATSERVER_H

#define CHATSERVER_H

 

#include <QTcpServer>

#include <QStringList>

 

#include "tcpclientsocket.h"

 

class ChatServer : public QTcpServer

{

    Q_OBJECT

public:

    ChatServer(QObject *parent = 0,int port=0);

    void PushMessage();

    QList<TcpClientSocket*> tcpClientSocketList;

signals:

    void updateServer(QString,int);

public slots:

    void updateClients(QString,int);

    void slotDisconnected(int);

protected:

    void incomingConnection(int socketDescriptor);

 

private:

    QStringList fortunes;

    int onlineDescriptor;

 

};

 

#endif // CHATSERVER_H

 

*********************

 

chatserver.cpp

 

*********************

 

#include "chatserver.h"

#include "chatthread.h"

 

#include <stdlib.h>

ChatServer::ChatServer(QObject *parent,int port)

        : QTcpServer(parent)

{

    fortunes << tr("Searching for people...")

             << tr("You've find a people. Try say hello!")

             << tr("You've disconnected.");

    listen(QHostAddress::Any,port);

}

 

void ChatServer::incomingConnection(int socketDescriptor)

{

    TcpClientSocket *tcpClientSocket=new TcpClientSocket(this);

    connect(tcpClientSocket,SIGNAL(updateClients(QString,int)),this,SLOT(updateClients(QString,int)));

    connect(tcpClientSocket,SIGNAL(disconnect(int)),this,SLOT(slotDisconnected(int)));

 

    tcpClientSocket->setSocketDescriptor(socketDescriptor);

    tcpClientSocketList.append(tcpClientSocket);

/*

    onlineDescriptor=socketDescriptor;

    QString fortune = fortunes.at(qrand() % fortunes.size());

//    QString fortune = fortunes.at(0);

    ChatThread *thread = new ChatThread(socketDescriptor, fortune, this);

    connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));

    thread->start();

*/

}

 

void ChatServer::updateClients(QString msg, int length)

{

 //   emit updateServer(msg,length);

    for(int i=0;i<tcpClientSocketList.count();i++)

    {

        QTcpSocket *item=tcpClientSocketList.at(i);

        if(item->write(msg.toLatin1(),length)!=length)

        {

            continue;

        }

    }

}

 

void ChatServer::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;

}

 

 

void ChatServer::PushMessage()

{

    QString testString;

    testString="fuck u";

    updateClients(testString,testString.length());

 /*

   ChatThread *thread = new ChatThread(onlineDescriptor, testString, this);

    connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));

    thread->start();

*/

 

}

 

*********************

 

chatui.h

 

*********************

 

#ifndef CHATUI_H

#define CHATUI_H

 

#include <QDialog>

#include "chatserver.h"

 

QT_BEGIN_NAMESPACE

class QLabel;

class QPushButton;

QT_END_NAMESPACE

 

class ChatUi : public QDialog

{

    Q_OBJECT

public:

    ChatUi(QWidget *parent = 0);

private slots:

    void Test();

private:

    QLabel *statusLabel;

    QPushButton *quitButton;

    QPushButton *testButton;

    ChatServer *server;

};

 

#endif // CHATUI_H

 

*********************

 

chatui.cpp

 

*********************

 

#include "chatui.h"

#include "chatserver.h"

#include <QtGui>

#include <QtNetwork>

 

#include <stdlib.h>

 

ChatUi::ChatUi(QWidget *parent)

    :QDialog(parent)

{

    statusLabel = new QLabel;

    quitButton=new QPushButton(tr("Quit"));

    quitButton->setAutoDefault(false);

    testButton=new QPushButton(tr("Test"));

    testButton->setAutoDefault(false);

 

    server=new ChatServer(this,5566);

    if (!server->isListening()) {

        QMessageBox::critical(this, tr("Chat Server"),

                              tr("Unable to start the server: %1.")

                              .arg(server->errorString()));

        close();

        return;

    }

 

    QString ipAddress;

    QList<QHostAddress> ipAddressesList = QNetworkInterface::allAddresses();

    for (int i = 0; i < ipAddressesList.size(); ++i) {

            if (ipAddressesList.at(i) != QHostAddress::LocalHost &&

                ipAddressesList.at(i).toIPv4Address()) {

                ipAddress = ipAddressesList.at(i).toString();

                break;

            }

        }

    if (ipAddress.isEmpty())

            ipAddress = QHostAddress(QHostAddress::LocalHost).toString();

    statusLabel->setText(tr("The server is running on/n/nIP: %1/nport: %2/n/n"

                                "Run the Fortune Client example now.")

                             .arg(ipAddress).arg(server->serverPort()));

 

    connect(quitButton,SIGNAL(clicked()),this,SLOT(close()));

    connect(testButton,SIGNAL(clicked()),this,SLOT(Test()));

 

    QHBoxLayout *buttonLayout = new QHBoxLayout;

    buttonLayout->addStretch(1);

    buttonLayout->addWidget(quitButton);

    buttonLayout->addStretch(1);

    buttonLayout->addWidget(testButton);

    buttonLayout->addStretch(1);

 

    QVBoxLayout *mainLayout = new QVBoxLayout;

    mainLayout->addWidget(statusLabel);

    mainLayout->addLayout(buttonLayout);

    setLayout(mainLayout);

 

    setWindowTitle(tr("Chat Server"));

 

 

}

 

void ChatUi::Test()

{

    server->PushMessage();

}

 

*********************

 

tcpclientsocket.h

 

*********************

 

#ifndef TCPCLIENTSOCKET_H

#define TCPCLIENTSOCKET_H

 

#include <QTcpSocket>

 

class TcpClientSocket : public QTcpSocket

{

    Q_OBJECT

public:

    TcpClientSocket(QObject* parent=0);

    ~TcpClientSocket();

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)

{

    connect(this,SIGNAL(readyRead()),this,SLOT(dataReceived()));

    connect(this,SIGNAL(disconnected()),this,SLOT(slotDisconnected()));

}

 

TcpClientSocket::~TcpClientSocket()

{

 

}

 

void TcpClientSocket::dataReceived()

{

    while(bytesAvailable()>0)

    {

        char buf[1024];

        int length=bytesAvailable();

        read(buf,length);

        QString msg=buf;

        emit updateClients(msg,length);

    }

}

 

 

 

*********************

 

下面是客户端代码

 

client.h

 

*********************

/****************************************************************************

**

** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).

** All rights reserved.

** Contact: Nokia Corporation (qt-info@nokia.com)

**

** This file is part of the examples of the Qt Toolkit.

**

** $QT_BEGIN_LICENSE:LGPL$

** Commercial Usage

** Licensees holding valid Qt Commercial licenses may use this file in

** accordance with the Qt Commercial License Agreement provided with the

** Software or, alternatively, in accordance with the terms contained in

** a written agreement between you and Nokia.

**

** GNU Lesser General Public License Usage

** Alternatively, this file may be used under the terms of the GNU Lesser

** General Public License version 2.1 as published by the Free Software

** Foundation and appearing in the file LICENSE.LGPL included in the

** packaging of this file.  Please review the following information to

** ensure the GNU Lesser General Public License version 2.1 requirements

** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.

**

** In addition, as a special exception, Nokia gives you certain additional

** rights.  These rights are described in the Nokia Qt LGPL Exception

** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.

**

** GNU General Public License Usage

** Alternatively, this file may be used under the terms of the GNU

** General Public License version 3.0 as published by the Free Software

** Foundation and appearing in the file LICENSE.GPL included in the

** packaging of this file.  Please review the following information to

** ensure the GNU General Public License version 3.0 requirements will be

** met: http://www.gnu.org/copyleft/gpl.html.

**

** If you have questions regarding the use of this file, please contact

** Nokia at qt-info@nokia.com.

** $QT_END_LICENSE$

**

****************************************************************************/

 

#ifndef CLIENT_H

#define CLIENT_H

 

#include <QDialog>

#include <QTcpSocket>

 

QT_BEGIN_NAMESPACE

class QDialogButtonBox;

class QLabel;

class QLineEdit;

class QPushButton;

class QTcpSocket;

QT_END_NAMESPACE

 

//! [0]

class Client : public QDialog

{

    Q_OBJECT

 

public:

    Client(QWidget *parent = 0);

 

private slots:

    void requestNewFortune();

    void readFortune();

    void displayError(QAbstractSocket::SocketError socketError);

    void enableGetFortuneButton();

    void slotConnected();

 

private:

    QLabel *hostLabel;

    QLabel *portLabel;

    QLineEdit *hostLineEdit;

    QLineEdit *portLineEdit;

    QLabel *statusLabel;

    QPushButton *getFortuneButton;

    QPushButton *quitButton;

    QDialogButtonBox *buttonBox;

 

    QTcpSocket *tcpSocket;

    QString currentFortune;

    quint16 blockSize;

#ifdef Q_OS_SYMBIAN

    bool isDefaultIapSet;

#endif

};

//! [0]

 

#endif

*********************

 

client.cpp

 

*********************

/****************************************************************************

**

** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).

** All rights reserved.

** Contact: Nokia Corporation (qt-info@nokia.com)

**

** This file is part of the examples of the Qt Toolkit.

**

** $QT_BEGIN_LICENSE:LGPL$

** Commercial Usage

** Licensees holding valid Qt Commercial licenses may use this file in

** accordance with the Qt Commercial License Agreement provided with the

** Software or, alternatively, in accordance with the terms contained in

** a written agreement between you and Nokia.

**

** GNU Lesser General Public License Usage

** Alternatively, this file may be used under the terms of the GNU Lesser

** General Public License version 2.1 as published by the Free Software

** Foundation and appearing in the file LICENSE.LGPL included in the

** packaging of this file.  Please review the following information to

** ensure the GNU Lesser General Public License version 2.1 requirements

** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.

**

** In addition, as a special exception, Nokia gives you certain additional

** rights.  These rights are described in the Nokia Qt LGPL Exception

** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.

**

** GNU General Public License Usage

** Alternatively, this file may be used under the terms of the GNU

** General Public License version 3.0 as published by the Free Software

** Foundation and appearing in the file LICENSE.GPL included in the

** packaging of this file.  Please review the following information to

** ensure the GNU General Public License version 3.0 requirements will be

** met: http://www.gnu.org/copyleft/gpl.html.

**

** If you have questions regarding the use of this file, please contact

** Nokia at qt-info@nokia.com.

** $QT_END_LICENSE$

**

****************************************************************************/

 

#include <QtGui>

#include <QtNetwork>

 

#include "client.h"

 

#ifdef Q_OS_SYMBIAN

#include "sym_iap_util.h"

#endif

 

//! [0]

Client::Client(QWidget *parent)

    : QDialog(parent)

{

//! [0]

    hostLabel = new QLabel(tr("&Server name:"));

    portLabel = new QLabel(tr("S&erver port:"));

 

    // find out which IP to connect to

    QString ipAddress;

    QList<QHostAddress> ipAddressesList = QNetworkInterface::allAddresses();

    // use the first non-localhost IPv4 address

    for (int i = 0; i < ipAddressesList.size(); ++i) {

        if (ipAddressesList.at(i) != QHostAddress::LocalHost &&

            ipAddressesList.at(i).toIPv4Address()) {

            ipAddress = ipAddressesList.at(i).toString();

            break;

        }

    }

    // if we did not find one, use IPv4 localhost

    if (ipAddress.isEmpty())

        ipAddress = QHostAddress(QHostAddress::LocalHost).toString();

 

    hostLineEdit = new QLineEdit(ipAddress);

    portLineEdit = new QLineEdit;

    portLineEdit->setValidator(new QIntValidator(1, 65535, this));

 

    hostLabel->setBuddy(hostLineEdit);

    portLabel->setBuddy(portLineEdit);

 

    statusLabel = new QLabel(tr("This examples requires that you run the "

                                "Fortune Server example as well."));

 

    getFortuneButton = new QPushButton(tr("Get Fortune"));

    getFortuneButton->setDefault(true);

    getFortuneButton->setEnabled(false);

 

    quitButton = new QPushButton(tr("Quit"));

 

    buttonBox = new QDialogButtonBox;

    buttonBox->addButton(getFortuneButton, QDialogButtonBox::ActionRole);

    buttonBox->addButton(quitButton, QDialogButtonBox::RejectRole);

 

//! [1]

    tcpSocket = new QTcpSocket(this);

//! [1]

 

    connect(hostLineEdit, SIGNAL(textChanged(QString)),

            this, SLOT(enableGetFortuneButton()));

    connect(portLineEdit, SIGNAL(textChanged(QString)),

            this, SLOT(enableGetFortuneButton()));

    connect(getFortuneButton, SIGNAL(clicked()),

            this, SLOT(requestNewFortune()));

    connect(quitButton, SIGNAL(clicked()), this, SLOT(close()));

//! [2] //! [3]

    connect(tcpSocket,SIGNAL(connected()),this,SLOT(slotConnected()));

    connect(tcpSocket, SIGNAL(readyRead()), this, SLOT(readFortune()));

//! [2] //! [4]

    connect(tcpSocket, SIGNAL(error(QAbstractSocket::SocketError)),

//! [3]

            this, SLOT(displayError(QAbstractSocket::SocketError)));

//! [4]

 

    QGridLayout *mainLayout = new QGridLayout;

    mainLayout->addWidget(hostLabel, 0, 0);

    mainLayout->addWidget(hostLineEdit, 0, 1);

    mainLayout->addWidget(portLabel, 1, 0);

    mainLayout->addWidget(portLineEdit, 1, 1);

    mainLayout->addWidget(statusLabel, 2, 0, 1, 2);

    mainLayout->addWidget(buttonBox, 3, 0, 1, 2);

    setLayout(mainLayout);

 

    setWindowTitle(tr("Fortune Client"));

    portLineEdit->setFocus();

 

#ifdef Q_OS_SYMBIAN

    isDefaultIapSet = false;

#endif

//! [5]

}

//! [5]

 

//! [6]

void Client::requestNewFortune()

{

    getFortuneButton->setEnabled(false);

#ifdef Q_OS_SYMBIAN

    if(!isDefaultIapSet) {

        qt_SetDefaultIap();

        isDefaultIapSet = true;

    }

#endif

    blockSize = 0;

    tcpSocket->abort();

//! [7]

    tcpSocket->connectToHost(hostLineEdit->text(),

                             portLineEdit->text().toInt());

//! [7]

}

//! [6]

 

//! [8]

void Client::readFortune()

{

    while(tcpSocket->bytesAvailable()>0)

    {

        QByteArray datagram;

        datagram.resize(tcpSocket->bytesAvailable());

        tcpSocket->read(datagram.data(),datagram.size());

        QString msg=datagram.data();

        statusLabel->setText(msg);

        getFortuneButton->setEnabled(true);

    }

    /*

//! [9]

    QDataStream in(tcpSocket);

    in.setVersion(QDataStream::Qt_4_0);

 

    if (blockSize == 0) {

        if (tcpSocket->bytesAvailable() < (int)sizeof(quint16))

            return;

        in >> blockSize;

    }

 

    if (tcpSocket->bytesAvailable() < blockSize)

        return;

 

    QString nextFortune;

    in >> nextFortune;

 

//! [12]

    currentFortune = nextFortune;

//! [9]

    statusLabel->setText(currentFortune);

    getFortuneButton->setEnabled(true);

*/

}

//! [12]

 

//! [13]

void Client::displayError(QAbstractSocket::SocketError socketError)

{

    switch (socketError) {

    case QAbstractSocket::RemoteHostClosedError:

        QMessageBox::information(this, tr("Fortune Client"),

                                 tr("Romote server closed illegally."));

        break;

    case QAbstractSocket::HostNotFoundError:

        QMessageBox::information(this, tr("Fortune Client"),

                                 tr("The host was not found. Please check the "

                                    "host name and port settings."));

        break;

    case QAbstractSocket::ConnectionRefusedError:

        QMessageBox::information(this, tr("Fortune Client"),

                                 tr("The connection was refused by the peer. "

                                    "Make sure the fortune server is running, "

                                    "and check that the host name and port "

                                    "settings are correct."));

        break;

    default:

        QMessageBox::information(this, tr("Fortune Client"),

                                 tr("The following error occurred: %1.")

                                 .arg(tcpSocket->errorString()));

    }

 

    getFortuneButton->setEnabled(true);

}

//! [13]

 

void Client::enableGetFortuneButton()

{

    getFortuneButton->setEnabled(!hostLineEdit->text().isEmpty()

                                 && !portLineEdit->text().isEmpty());

}

 

void Client::slotConnected()

{

    QString msg="hello,server";

    tcpSocket->write(msg.toLatin1(),msg.length());

    return;

}