關於TCP協議的知識點
TCP協議是一種基於傳輸層的協議,具備可靠性,須要鏈接,工做方式爲全雙工,傳輸速度相較於UPD更慢的特色,通常用於傳輸大量的數據,傳輸過程不容許丟包的狀況.通常狀況下聊天類軟件均採用UDP協議,此處採用TCP是爲了瞭解TCP的特色,以及保證明驗過程不由於丟包影響實驗結果.服務器
實驗思路
我將服務器端分爲三個部分,每一個部分分別實現不一樣的功能.app
第一部分:服務器端的外形設計,服務器端應有一個對話框顯示客戶端的登入登出以及在登入期間所發送的信息,一個文本框顯示端口號,一個按鈕來開啓服務器socket
第二部分:服務器功能,服務器須要監聽一個固定的端口,當有客戶端接入該端口時,建立一個TCP套接字對象來爲該客戶服務(以便在服務端實現與客戶端的通訊),服務器還須要能存儲每個接入的客戶端的信息tcp
第三部分:一個TCP套接字對象的功能,該對象須要能讀取套接字中的數據,將其傳輸給服務器,函數
具體實現
如下代碼爲.cpp中的部分代碼,代碼參考<<Qt5開發及實例(第三版)>>,若內容有錯誤理解但願能提出指正佈局
第一部分:this
1 Tcpserver::Tcpserver(QWidget *parent,Qt::WindowFlags f)//構造函數 2 : QDialog(parent,f) 3 { 4 setWindowTitle(tr("TCP Server"));//更更名稱 5 ContentListWidget = new QListWidget;//初始化一個對話框,顯示相應的信息 6 PortLabel = new QLabel(tr("端口"));初始化一個標籤,讓其顯示相應的內容 7 PortLineEdit = new QLineEdit;//初始化一個對話框顯示固定的端口號 8 CreateBtn = new QPushButton(tr("create chat room"));//初始化一個按鈕,讓其顯示相應的內容 9 mainLayout = new QGridLayout(this);//初始化一個QGridLayout的佈局,並將全部部件塞進佈局 10 mainLayout->addWidget(ContentListWidget,0,0,1,2); 11 mainLayout->addWidget(PortLabel,1,0); 12 mainLayout->addWidget(PortLineEdit,1,1); 13 mainLayout->addWidget(CreateBtn,2,0,1,2); 14 port = 8010;//監聽8010端口 15 PortLineEdit->setText(QString::number(port));//將端口號顯示 16 connect(CreateBtn,SIGNAL(clicked()),this,SLOT(slotCreateServer()));//按下按鈕發送信號,觸發槽函數,該槽函數功能爲建立一個服務器 17 } 18 Tcpserver::~Tcpserver()//析構函數 19 { 20 } 21 void Tcpserver::slotCreateServer()//槽函數的實現 22 { 23 server = new Server(this,port);//建立一個Server對象,並將端口號傳給該對象,是其對該端口進行監聽 24 connect(server,SIGNAL(updateServer(QString,int)),this,SLOT(updateServer(QString,int)));//若server發送updateServer信號,觸發tcpserver的槽函數updateServer 25 //使其更新對話框內容 26 CreateBtn->setEnabled(false);//建立服務器以後 沒法再點擊該按鈕 27 } 28 void Tcpserver::updateServer(QString msg,int length)//更新內容的槽函數 29 { 30 ContentListWidget->addItem(msg.left(length)); 31 }
第二部分:
1 Server::Server(QObject *parent,int port) : QTcpServer(parent) 2 { 3 listen(QHostAddress::Any,port);//在指定的端口對任意地址監聽 4 } 5 void Server::incomingConnection(qintptr socketDescriptor)//當出現一個新的鏈接的時候,TcpServer便會觸發incomingConnection()函數,每有一個新的鏈接都會觸發一次,即都建立一個新的對象 6 { 7 TcpClientSocket *tcpClientSocket = new TcpClientSocket(this);//建立一個新的TcpClientSocket與客戶端通訊 8 //出現一個新鏈接纔會建立一個 TcpClientSocket 的對象,若是鏈接後有數據傳入,該對象將其讀下來 經過updateClient信號來發送 9 connect(tcpClientSocket,SIGNAL(updateClients(QString,int)),this,SLOT(updateClients(QString,int)));//鏈接TcpClientSocket的updateClients信號 10 //當有新數據傳入後,TcpClientSocket便會發出updateClients的信號,此時觸發Server中的updateClients的槽函數,將信號中的msg與length傳入該槽函數 11 connect(tcpClientSocket,SIGNAL(disconnected(int)),this,SLOT(slotDisconnected(int)));//鏈接TcpClientSocket中的disconnected信號 12 //當TcpClientSocket的對象斷開鏈接後,發出disconnected的信號,此時觸發Server中的slotDisconnected的槽函數 13 tcpClientSocket->setSocketDescriptor(socketDescriptor);//將新建立的TcpClientSocket的套接字描述符指定爲參數socketDescriptor 14 tcpClientSocketList.append(tcpClientSocket);//將tcpClientSocket這個對象添加入tcpClientSocketList這個列表中 15 } 16 void Server::updateClients(QString msg,int length) 17 { 18 emit updateServer(msg,length);//發出updateServer的信號,通知服務器對話框更新相應的顯示狀態 19 for(int i = 0;i<tcpClientSocketList.count();i++)//實現廣播,即將新發送到服務器中的數據進行廣播,發送到每個鏈接的對象,進行同步更新對話框 20 { 21 QTcpSocket *item = tcpClientSocketList.at(i);// 22 if(item->write(msg.toLatin1(),length)!=length) 23 { 24 continue; 25 } 26 } 27 } 28 void Server::slotDisconnected(int descriptor)//從tcpClientSocketList列表中將斷開鏈接的TcpClientSocket對象刪除 29 { 30 for (int i = 0;i<tcpClientSocketList.count();i++) { 31 QTcpSocket *item = tcpClientSocketList.at(i); 32 if(item->socketDescriptor()==descriptor) 33 { 34 tcpClientSocketList.removeAt(i); 35 return; 36 } 37 } 38 return; 39 }
第三部分:
1 TcpClientSocket::TcpClientSocket(QObject *parent) 2 { 3 connect(this,SIGNAL(readyRead()),this,SLOT(dateReceived()));//readyRead()是QIODevice的一個信號函數,由QTcpSocket繼承而來,在有數據來時發出信號 4 connect(this,SIGNAL(disconnected()),this,SLOT(slotDisconnected()));//disconnected()是QIODevice的一個信號函數,由QTcpSocket,斷開鏈接時發出信號 5 } 6 void TcpClientSocket::dateReceived() 7 { 8 while(bytesAvailable()>0)//當有數據來時,bytesAvailable()從套接字中檢測所來的數據,返回等待讀取的傳入字節數 9 { 10 int length = bytesAvailable(); 11 char buf[1024]; 12 read(buf,length);//將套接字中的數據讀取到buf中,讀取長度爲length 13 QString msg = buf; 14 emit updateClients(msg,length);//發出信號 15 } 16 } 17 void TcpClientSocket::slotDisconnected() 18 { 19 emit disconnected(this->socketDescriptor());//發出disconnected信號 20 }
部分代碼詳細解析
1.監聽
監聽主要依託listen()函數實現,listen()函數由QTcpServer的對象調用,接受兩個參數,第一個參數爲QHostAddress的枚舉,上文中的QHostAddress::Any爲任意地址的意思,包括IPv4和IPv6.而第二個參數爲整型的類型,用來表明端口,listen()函數返回一個布爾類型來表示監聽是否成功.spa
2.鏈接
當客戶端鏈接到端口時(如何鏈接將在客戶端實驗在闡述),因爲已經創建了監聽的關係,咱們即可以利用一個QTcpServer中一個虛函數--incomingConnection(),該函數當有服務器端連入時便會觸發,咱們能夠經過重寫該函數來完成客戶端接入的信息傳遞,該函數接受一個qintstr 類型的參數,該參數表明了接受鏈接的本機套接字描述符,該參數的做用將在讀取數據時體現設計
3.讀取數據
咱們想要接受客戶端發送的信息,除了監聽相應的端口,與之創建鏈接還須要一個服務器端的套接字來接受客戶端發送的數據,建立一個套接字咱們能夠使用QTcpSocket 的對象來使用一個setSocketDescriptor()函數,該函數的做用爲初始化套接字,接受一個qintstr的參數(就是在在上文中提到的那個)來指明該套接字接受哪一個的數據,當成功建立了一個套接字時,咱們即可以採用readyRead()函數來發出信號,該函數當有新的可讀數據在套接字時,即可以發出信號,爲之咱們創建相應的槽函數.在槽函數中能夠使用byteAvailable()函數來獲取可讀取數據的字節數,而正式讀取數據時使用read()函數即可以了,read()函數有兩個參數,第一個參數爲讀取數據的存儲位置,第二個參數爲讀取多少數據,咱們能夠使用byteAvailable()返回的值來完成所有讀取.code