一、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函數。
三、多個客戶端同時登陸的小聊天室示例。
首先說明一下這個小示例的功能,當有一個客戶端進入聊天室的時候,會向服務器發送一個信息,告訴服務器這個賬號進入了聊天室, 然后服務器會在界面中顯示哪個賬號進入了聊天室,並且服務器會向所有的客戶端發送消息,告訴所有客戶端有哪個賬號進入了聊天室。某個賬號發送信息的時候也是如此,服務器會將收到的信息發送給所有的客戶端。
如果需要實現網絡通信,就必須在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
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 }
編譯完之后運行的界面就是這樣的
整個通信的步驟
首先我們點擊服務器的創建聊天室,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信號,這個時候我們需要將套接字列表中將對應的套接字刪除就可以了。