天天看點

Qt TCP/IP(多用戶端連接配接伺服器)多個用戶端同時登陸的聊天室示例

作者:QT進階進階

一、TCP和UDP的差別

這裡我會用一個表格來顯示這兩者的差別

比較項 TCP UDP
是否連接配接 面向連接配接 無連接配接
傳輸是否可靠 可靠 不可靠
流量控制 提供 不提供
工作方式 全雙工 可以是全雙工
應用場合 大量資料 少量資料
速度

二、incomingConnection函數

這個函數和之前講過的newConnection信号功能差不多,隻要有新的連接配接出現,就會自動調用這個函數。

然後我們隻需在這個函數中建立一個QTcpSocket對象,并且将這個套接字指定為這個函數的參數socketDescriptor,然後将這個套接字存放到套接字清單中就可以實作多個用戶端同時登陸了。

這裡我們簡單看一下這個函數裡的内容

1 void Server::incomingConnection(int socketDescriptor)
2 {
3     TcpClientSocket *tcpclientsocket = new TcpClientSocket(this);//隻要有新的連接配接就生成一個新的通信套接字
4     //将新建立的通信套接字描述符指定為參數socketdescriptor
5     tcpclientsocket->setSocketDescriptor(socketDescriptor);
6  
7     //将這個套接字加入用戶端套接字清單中
8     tcpclientsocketlist.append(tcpclientsocket);
9 }           

Server這個類是繼承于QTcpServer類的,是以我們需要在Server類中重寫incomingConnection函數。

【領QT開發教程學習資料,點選下方連結免費領取↓↓,先碼住不迷路~】

點選→領取「連結」

三、多個用戶端同時登陸的小聊天室示例。

首先說明一下這個小示例的功能,當有一個用戶端進入聊天室的時候,會向伺服器發送一個資訊,告訴伺服器這個賬号進入了聊天室, 然後伺服器會在界面中顯示哪個賬号進入了聊天室,并且伺服器會向所有的用戶端發送消息,告訴所有用戶端有哪個賬号進入了聊天室。某個賬号發送資訊的時候也是如此,伺服器會将收到的資訊發送給所有的用戶端。

如果需要實作網絡通信,就必須在pro檔案中加入 QT += network

伺服器端

在伺服器端需要添加三個類,一個類用來建立界面,一個類用來監聽,這個類的基類是QTcpServer,最後一個類用來通信,通信的類的基類是QTcpSocket類。

1、用來建立界面的Tcpserver類

1 #ifndef TCPSERVER_H
 2 #define TCPSERVER_H
 3  
 4 #include <QWidget>
 5 #include "server.h"
 6  
 7 namespace Ui {
 8 class TcpServer;
 9 }
10  
11 class TcpServer : public QWidget
12 {
13     Q_OBJECT
14  
15 public:
16     explicit TcpServer(QWidget *parent = 0);
17     ~TcpServer();
18  
19 private:
20     Ui::TcpServer *ui;
21     int port;
22     Server *server;
23  
24 protected slots:
25     void slotupdateserver(QString, int);//接收到server發過來的信号就更新界面的資訊
26  
27  
28 private slots:
29     void on_pushButtonCreateChattingRoom_clicked();
30 };
31  
32 #endif // TCPSERVER_H           
1 #include "tcpserver.h"
 2 #include "ui_tcpserver.h"
 3  
 4  
 5 TcpServer::TcpServer(QWidget *parent) :
 6     QWidget(parent),
 7     ui(new Ui::TcpServer)
 8 {
 9     ui->setupUi(this);
10     port = 8888;
11  
12 }
13  
14 TcpServer::~TcpServer()
15 {
16     delete ui;
17 }
18  
19 void TcpServer::on_pushButtonCreateChattingRoom_clicked()
20 {
21     server  = new Server(this, port);
22     connect(server, &Server::updateserver, this, &TcpServer::slotupdateserver);
23     ui->pushButtonCreateChattingRoom->setEnabled(false);
24 }
25  
26 void TcpServer::slotupdateserver(QString msg, int length)
27 {
28     ui->textEdit->append(msg);
29 }           

2、用來監聽的類Server

1 #ifndef SERVER_H
 2 #define SERVER_H
 3  
 4 #include <QTcpServer>
 5 #include <QObject>
 6 #include <QList>
 7 #include "tcpclientsocket.h"
 8  
 9 class Server : public QTcpServer
10 {
11     Q_OBJECT //為了實作信号和槽的通信
12 public:
13     Server(QObject *parent = 0, int port = 0);
14     QList<TcpClientSocket*> tcpclientsocketlist;
15 protected:
16     void incomingConnection(int socketDescriptor);//隻要出現一個新的連接配接,就會自動調用這個函數
17 protected slots:
18     void sliotupdateserver(QString, int);//用來處理tcpclient發過來的信号
19     void slotclientdisconnect(int);
20  
21 signals:
22     void updateserver(QString, int);//發送信号給界面,讓界面更新資訊
23  
24 };
25  
26 #endif // SERVER_H           
1 #include "server.h"
 2 #include <QHostAddress>
 3  
 4 Server::Server(QObject *parent, int port):QTcpServer(parent)
 5 {
 6     listen(QHostAddress::Any, port); //監聽
 7 }
 8  
 9 void Server::incomingConnection(int socketDescriptor)
10 {
11     TcpClientSocket *tcpclientsocket = new TcpClientSocket(this);//隻要有新的連接配接就生成一個新的通信套接字
12     //将新建立的通信套接字描述符指定為參數socketdescriptor
13     tcpclientsocket->setSocketDescriptor(socketDescriptor);
14  
15     //将這個套接字加入用戶端套接字清單中
16     tcpclientsocketlist.append(tcpclientsocket);
17  
18  
19     //接收到tcpclientsocket發送過來的更新界面的信号
20     connect(tcpclientsocket, &TcpClientSocket::updateserver, this, &Server::sliotupdateserver);
21     connect(tcpclientsocket, &TcpClientSocket::clientdisconnected, this, &Server::slotclientdisconnect);
22  
23 }
24  
25 void Server::sliotupdateserver(QString msg, int length)
26 {
27     //将這個信号發送給界面
28     emit updateserver(msg, length);
29  
30     //将收到的資訊發送給每個用戶端,從套接字清單中找到需要接收的套接字
31     for(int i = 0; i < tcpclientsocketlist.count(); i++)
32     {
33         QTcpSocket *item = tcpclientsocketlist.at(i);
34 //        if(item->write((char*)msg.toUtf8().data(), length) != length)
35 //        {
36 //            continue;
37 //        }
38         item->write(msg.toUtf8().data());
39     }
40  
41 }
42  
43 void Server::slotclientdisconnect(int descriptor)
44 {
45     for(int i = 0; i < tcpclientsocketlist.count(); i++)
46     {
47         QTcpSocket *item = tcpclientsocketlist.at(i);
48         if(item->socketDescriptor() == descriptor)
49         {
50             tcpclientsocketlist.removeAt(i);//如果有用戶端斷開連接配接, 就将清單中的套接字删除
51             return;
52         }
53     }
54     return;
55 }           

3、用來通信的類TcpClientSocket

【領QT開發教程學習資料,點選下方連結免費領取↓↓,先碼住不迷路~】

點選→領取「連結」

1 #ifndef TCPCLIENTSOCKET_H
 2 #define TCPCLIENTSOCKET_H
 3  
 4 #include <QTcpSocket>
 5  
 6 class TcpClientSocket : public QTcpSocket
 7 {
 8     Q_OBJECT //添加這個宏是為了實作信号和槽的通信
 9 public:
10     TcpClientSocket(QObject *parent = 0);
11 protected slots:
12     void receivedata();//處理readyRead信号讀取資料
13     void slotclientdisconnected();//用戶端斷開連接配接觸發disconnected信号,這個槽函數用來處理這個信号
14  
15 signals:
16     void updateserver(QString, int);//用來告訴tcpserver需要跟新界面的顯示
17     void clientdisconnected(int); //告訴server有用戶端斷開連接配接
18 };
19  
20 #endif // TCPCLIENTSOCKET_H           
1 #include "tcpclientsocket.h"
 2  
 3 TcpClientSocket::TcpClientSocket(QObject *parent)
 4 {
 5     //用戶端發送資料過來就會觸發readyRead信号
 6     connect(this, &TcpClientSocket::readyRead, this, &TcpClientSocket::receivedata);
 7     connect(this, &TcpClientSocket::disconnected, this, &TcpClientSocket::slotclientdisconnected);
 8 }
 9  
10 void TcpClientSocket::receivedata()
11 {
12 //    while(bytesAvailable() > 0)
13 //    {
14 //        int length = bytesAvailable();
15 //        char buf[1024]; //用來存放擷取的資料
16 //        read(buf, length);
17 //        QString msg = buf;
18 //        //發信号給界面,讓界面顯示登入者的資訊
19 //        emit updateserver(msg, length);
20 //    }
21     int length = 10;
22     QByteArray array = readAll();
23     QString msg = array;
24     emit updateserver(msg, length);
25 }
26  
27 void TcpClientSocket::slotclientdisconnected()
28 {
29     emit clientdisconnected(this->socketDescriptor());
30 }           

用戶端

用戶端隻需要一個類就行了,這個類我們隻需要建立一個通信套接字來和伺服器進行通信就可以了。

1 #ifndef TCPCLIENT_H
 2 #define TCPCLIENT_H
 3  
 4 #include <QWidget>
 5 #include <QTcpSocket>
 6  
 7 namespace Ui {
 8 class TcpClient;
 9 }
10  
11 class TcpClient : public QWidget
12 {
13     Q_OBJECT
14  
15 public:
16     explicit TcpClient(QWidget *parent = 0);
17     ~TcpClient();
18  
19 private slots:
20     void on_pushButtonEnter_clicked();
21     void slotconnectedsuccess();//用來處理連接配接成功的信号
22     void slotreceive();//接收伺服器傳過來的資訊
23     void on_pushButtonSend_clicked();
24     void slotdisconnected();//用來處理離開聊天室的信号
25  
26  
27 private:
28     Ui::TcpClient *ui;
29     bool status;//用來判斷是否進入了聊天室
30     int port;
31     QHostAddress *serverIP;
32     QString userName;
33     QTcpSocket *tcpsocket;
34 };
35  
36 #endif // TCPCLIENT_H           
1 #include "tcpclient.h"
  2 #include "ui_tcpclient.h"
  3 #include <QHostAddress>
  4 #include <QMessageBox>
  5  
  6 TcpClient::TcpClient(QWidget *parent) :
  7     QWidget(parent),
  8     ui(new Ui::TcpClient)
  9 {
 10     ui->setupUi(this);
 11     //将進入聊天室的标志位置為false
 12     status = false;
 13     //端口為8888
 14     port = 8888;
 15     ui->lineEditServerPort->setText(QString::number(port));//界面中端口預設顯示8888
 16  
 17     serverIP = new QHostAddress();
 18  
 19     //未進入聊天室内不能發送資訊
 20     ui->pushButtonSend->setEnabled(false);
 21 }
 22  
 23 TcpClient::~TcpClient()
 24 {
 25     delete ui;
 26 }
 27  
 28 //進入聊天室
 29 void TcpClient::on_pushButtonEnter_clicked()
 30 {
 31     //首先判斷這個使用者是不是在聊天室中
 32     if(status == false)
 33     {
 34         //不在聊天室中就和伺服器進行連接配接
 35         QString ip = ui->lineEditServerIp->text();//從界面擷取ip位址
 36         if(!serverIP->setAddress(ip))//用這個函數判斷IP位址是否可以被正确解析
 37         {
 38             //不能被正确解析就彈出一個警告視窗
 39             QMessageBox::warning(this, "錯誤", "IP位址不正确");
 40             return;
 41         }
 42         if(ui->lineEditUserName->text() == "")
 43         {
 44             //使用者名不能為空
 45             QMessageBox::warning(this, "錯誤", "使用者名不能為空");
 46             return;
 47         }
 48  
 49         //從界面擷取使用者名
 50         userName = ui->lineEditUserName->text();
 51         //建立一個通信套接字,用來和伺服器進行通信
 52         tcpsocket = new QTcpSocket(this);
 53  
 54         //和伺服器進行連接配接
 55         tcpsocket->connectToHost(*serverIP, port);
 56  
 57         //和伺服器連接配接成功能會觸發connected信号
 58         connect(tcpsocket, &QTcpSocket::connected, this, &TcpClient::slotconnectedsuccess);
 59         //接收到伺服器的資訊就會觸發readyRead信号
 60         connect(tcpsocket, &QTcpSocket::readyRead, this, &TcpClient::slotreceive);
 61  
 62  
 63  
 64         //将進入聊天室的标志位置為true;
 65         status = true;
 66     }
 67     else//已經進入了聊天室
 68     {
 69         int length = 0;
 70         QString msg = userName + ":Leave Chat Room";
 71 //        if((length = tcpsocket->write((char*)msg.toUtf8().data(), msg.length())) != msg.length())
 72 //        {
 73 //            return;
 74 //        }
 75         tcpsocket->write(msg.toUtf8().data());
 76         tcpsocket->disconnectFromHost();
 77         status = false;
 78         //離開聊天室就會觸發disconnected信号
 79         connect(tcpsocket, &QTcpSocket::disconnected, this, &TcpClient::slotdisconnected);
 80     }
 81 }
 82  
 83 //用來處理連接配接成功的信号
 84 void TcpClient::slotconnectedsuccess()
 85 {
 86     //進入聊天室可以發送資訊了
 87     ui->pushButtonSend->setEnabled(true);
 88     //将進入聊天的按鈕改為離開聊天室
 89     ui->pushButtonEnter->setText("離開聊天室");
 90  
 91     int length = 0;
 92     //将使用者名發送給伺服器
 93     QString msg= userName + " :Enter Chat Room";
 94  
 95 //    if((length = tcpsocket->write((char*)msg.toUtf8().data(), msg.length())) != msg.length())
 96 //    {
 97 //        return;
 98 //    }
 99     tcpsocket->write(msg.toUtf8().data());
100 }
101  
102  
103 void TcpClient::slotreceive()
104 {
105 //    while(tcpsocket->bytesAvailable() > 0 )
106 //    {
107 //        QByteArray datagram;
108 //        datagram.resize(tcpsocket->bytesAvailable());
109 //        tcpsocket->read(datagram.data(), datagram.size());
110 //        QString msg = datagram.data();
111 //        ui->textEdit->append(msg.left(datagram.size()));
112 //    }
113     QByteArray array = tcpsocket->readAll();
114     ui->textEdit->append(array);
115 }
116  
117 void TcpClient::on_pushButtonSend_clicked()
118 {
119     if(ui->lineEditSend->text() == "")
120     {
121         return;
122     }
123     QString msg = userName + ":" + ui->lineEditSend->text();
124    // tcpsocket->write((char*)msg.toUtf8().data(), msg.length());
125     tcpsocket->write(msg.toUtf8().data());
126     ui->lineEditSend->clear();
127 }
128  
129 void TcpClient::slotdisconnected()
130 {
131     ui->pushButtonSend->setEnabled(false);
132     ui->pushButtonEnter->setText("進入聊天室");
133 }           

編譯完之後運作的界面就是這樣的

Qt TCP/IP(多用戶端連接配接伺服器)多個用戶端同時登陸的聊天室示例

整個通信的步驟

首先我們點選伺服器的建立聊天室,TcpServer類的中的相應的槽函數會建立一個Server類的對象,然後調用構造函數會進行監聽。一旦有用戶端點選進入聊天室的按鈕,用戶端的tcpsocket就會和伺服器進行連接配接,隻要伺服器監聽到新的連接配接,Server類對象就會自動調用incomingConnection(int socketDescripter)這個函數。

接着我們隻需要在這個函數中建立一個通信套接字也就是TcpClientSocket對象,并且将這個套接字描述符設定為參數socketDescripter,然後将這個套接字加入套接字清單中。這個時候用戶端和伺服器連接配接成功了,用戶端就會觸發一個connected信号,需要我們寫一個槽函數來處理這個信号,這裡的處理就是将這個使用者名和“Enter Chat Room”發送給伺服器。

伺服器一旦收到資訊,就會觸發readyRead信号,這個時候我們需要在寫一個相應的槽函數來讀取套接字中的資訊。我們這裡是用readall來讀取套接字中的所有資訊。

當收到資料的時候,我們需要發一個信号給Server,将讀取的資訊再發送給所有在聊天室裡的使用者,是以在Server類中要添加一個相應的槽函數來将資料發送給所有的使用者。

因為同時需要在伺服器界面中顯示所有使用者發過來的消息,是以需要在剛剛那個槽函數中将自己定義的一個信号發送給TcpServer類,讓其将收到的消息顯示在界面中。用戶端收到消息之後也會觸發readyRead信号,用戶端隻需要将從套接字中讀取的消息顯示在見面中就行了。

用戶端發送消息的時候需要從QLineEdit對象中讀取内容,然後通過tcpsocket->write()函數将消息發送給伺服器就行了。伺服器同樣會将收到的資訊發送給所有的使用者,同時顯示在自己的界面中。最後用戶端退出聊天室,用戶端點選離開聊天室按鈕,進入相應的槽函數中調用disconnectFromHost()函數,就會和伺服器斷開連接配接,一旦有調用了這個函數,就會觸發disconnected信号,然後我們需要寫一個相應的槽函數來處理這個信号,我們所做的處理就是将這個使用者名和“Leave Chat Roo”發送給伺服器。伺服器那一端檢測到有用戶端斷開連接配接也會觸發disconnected信号,這個時候我們需要将套接字清單中将對應的套接字删除就可以了。