TCP協議部分,還真讓我迷糊了好大一會。原因是剛弄完UDP,UDP的本地端和遠程端代碼是完全一樣的。只是初始化在建構函式裡,綁定的本地Ip地址不同,這點有函數getIp()自動完成。因此本地和遠程完全一樣。但Tcp部分不是這樣,我這裡總結一下(本人水平很低級的,屬菜鳥未入門級。總結下只是防自己忘):
一,伺服器端:
整個工作流程是:如:
程式的核心部分在tcpNewConnect()這個函數裡,當伺服器收到串連時會觸發這個槽函數。這個函數實現為:
//伺服器監聽到用戶端串連時,伺服器的響應槽函數
void Widget::tcpNewConnect()
{
QMessageBox box;
box.setText("tcp用戶端,請求串連....");
box.exec();
tcpsocketServer = tcpServer->nextPendingConnection();
connect(tcpsocketServer, SIGNAL(readyRead()), this, SLOT(tcpReadmesg()));
connect(ui->tcpSendButton, SIGNAL(clicked()), this, SLOT(tcpSendmesg()));
ui->tcpSendButton->setEnabled(true);
}
這個時候tcpsocketServer被執行個體化了,tcpsocketServer是一個QTcpSocket類型的,我為了區別用戶端的tcpSocket給加了尾碼server。 也就是說tcpServer的作用僅僅是監聽,監聽到了就執行個體化一個QTcpSocket,剩下的工作由這個QTcpSocket來完成,包括接收資訊和發送資訊。
//TCP端的接收資訊並顯示槽函數
void Widget::tcpReadmesg()
{
QTextCodec *tc=QTextCodec::codecForName("UTF-8");
QByteArray data = tcpsocketServer->readAll();
QString str = tc->toUnicode(data);
QDateTime time;
QString timeStr = time.currentDateTime().toString("yyyy-MM-dd hh:mm:ss ddd");
autoScroll();
ui->getTextEdit->setTextColor(QColor("red"));
ui->getTextEdit->insertPlainText("遠程" + remoteIpStr+": "+ timeStr +"--TCP--\n" );
ui->getTextEdit->setTextColor(QColor("black"));
ui->getTextEdit->insertPlainText(str + "\n" );
autoScroll();
}
//TCP端的發送文本槽函數
void Widget::tcpSendmesg()
{
autoScroll();
QString str = ui->sendTextEdit->toPlainText();
if(str.length()==0)
{
QMessageBox box;
box.setText("請輸入內容後再發送!");
box.exec();
}
else
{
QByteArray data = str.toAscii();
// qDebug()<<str;
tcpsocketServer->write(data);
QDateTime time;
QString timeStr = time.currentDateTime().toString("yyyy-MM-dd hh:mm:ss ddd");
ui->getTextEdit->setTextColor(QColor("red"));
ui->getTextEdit->insertPlainText("本機" + localIpStr + ": " + timeStr + "--TCP--\n");
ui->getTextEdit->setTextColor(QColor("black"));
ui->getTextEdit->insertPlainText(str +"\n");
ui->sendTextEdit->clear(); //點擊發送後,發送編輯框內清零
ui->sendTextEdit->setFocus(); //焦點停留在發送編輯框
autoScroll();
}
}
有了UDp的經驗,這兩個函數還算好些,至少編碼解碼上不會出問題了。伺服器端和接收端的顯示發送函數都是一樣的。接收函數用的QByteArray data = tcpsocketServer->readAll();發送函數寫資料用的 tcpsocketServer->write(data);著和UDP的發送接收還是有區別的。當然了只是發送簡單的文本可以這麼搞,如果發送大檔案,估計沒這麼簡單了。
二、Tcp用戶端
用戶端只有一個QTcpSocket *tcpSocket,在建構函式裡tcpSocket = new QTcpSocket(this),我們希望按下“串連”按鍵,用戶端發出請求串連。這個按鍵的槽函數是:
//用戶端,按下啟動TCP按鍵的槽函數
void Widget::on_startTcpBtn_clicked()
{
remoteIpStr = ui->ipEdit->text();
port = ui->portEdit->text();
tcpSocket->abort(); //取消原來的串連
tcpSocket->connectToHost(remoteIpStr, port.toInt());
}
核心的一句話是tcpSocket->connectToHost(remoteIpStr, port.toInt());注意這裡remoteIpStr是個QString類型的變數,您也可以寫成tcpSocket->connectToHost(“192.168.2.211”,
6665)這樣來測試下是沒問題的。也可以寫成 remoteHostAddr = new QHostAddress(remoteIpStr); tcpSocket->connectToHost(*remoteHostAddr, port.toInt());這樣也是沒有問題的!看來qt還是很靈活的。
另外,在用戶端的建構函式裡還應該串連四個槽函數:
connect(tcpSocket, SIGNAL(connected()), this,SLOT(tcpIsConnect()));
//已經串連上時的槽函數
connect(tcpSocket, SIGNAL(disconnected()), this, SLOT(tcpFailConnect()));
//中斷連線時的槽函數
connect(ui->tcpSendButton, SIGNAL(clicked()), this, SLOT(tcpSendmesg())); //發送訊息
connect(tcpSocket, SIGNAL(readyRead()), this, SLOT(tcpReadmesg())); //收到訊息
發送訊息、接收訊息的和上面一樣,前兩個槽函數只是彈出個提示就行啦,如下:
bool Widget::tcpIsConnect()
{
ui->tcpSendButton->setEnabled(true);
QMessageBox box;
box.setText("恭喜您,TCP串連已建立!");
box.exec();
return true;
}
void Widget::tcpFailConnect()
{
ui->tcpSendButton->setEnabled(false);
QMessageBox box;
box.setText("TCP串連失敗,請重新串連或使用UDP發送!");
box.exec();
}
最後,總結下讓我一開始雲裡霧裡的基於Qt的TCP協議實現:
只有兩個類用到,QTcpServer和QTcpSocket,伺服器端用到一個QTcpServer和一個QTcpSocket;用戶端只用一個QTcpSocket。QTcpServer的作用是監聽對方的串連,因此開始時要設定監聽的連接埠號碼或者監聽具體的Ip地址。當有用戶端串連時,QTcpServer調用 tcpsocketServer
= tcpServer->nextPendingConnection();來執行個體化伺服器端的QTcpSocket,然後剩下的發送和接收都由這個QTcpSocket來擔當!用戶端用到QTcpSocket,讓他發送串連請求,當串連成功後又會觸發一個函數。
一句話:資料的發送、讀取永遠由QTcpSocket來擔當!!!QTcpSocket還可以發送串連請求,QTcpServer進行監聽,並得到發送請求的QTcpSocket執行個體。
核心的代碼,紅色加底線的部分。
疑惑:伺服器端在偵聽到串連請求時,如果不想串連用戶端,拒絕怎麼搞?估計應該再伺服器端加個按鍵,按鍵槽函數裡用QTcpServer的一些方法進行拒絕。繼續研究啊還得!