一、TCP工作原理
Tcp是面向连接的一种流式通信,能够提供可靠的通信连接,使一台客户机发出的字节流无差错地送达网络上的其他计算器。如果是对可靠性要求高的数据通信系统则需要使用Tcp传输数据,但是要发送数据,则必须首先建立连接。
二、TCP编程模型
1)首先启动服务器,成功后启动客户端;
2)客户端与服务器进行三次握手后建立连接;
3)客户端向服务端发送请求;
4)服务端发送给客户端响应;
5)多次循环,知道通讯结束,最后关闭网络连接。
三、TCP通信传输类型:
1)基于行的方式
基于行的数据通信协议用于一般用于纯文本数据的通信,每一行数据以换行符结束。
2)基于数据块方式
基于块的数据通信协议用于一般的二进制数据传输,需要自定义具体格式。
四、代码示例:
1、效果图
2、代码示例
1)TCP服务端
mainwindow.h :
#ifndef MAINWINDOW_H
#define MAINWINDOW_H#include <QMainWindow>
#include <QLabel>
#include <QTcpServer>
#include <QTcpSocket>
#include <QAbstractSocket>namespace Ui {
class MainWindow;
}class MainWindow : public QMainWindow
{
Q_OBJECTpublic:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
QString getLocalIP();private slots:
void onNewConnection();
void onSocketStateChange(QAbstractSocket::SocketState);
void onClientConnected();
void onClientDisconnected();
void onSocketReadyRead();
void on_connectBtn_clicked();
void on_disconnectBtn_clicked();
void on_sendBtn_clicked();
void on_clearBtn_clicked();private:
Ui::MainWindow *ui;
QLabel *labListen;
QLabel *labSocketState;
QTcpServer *tcpServer;
QTcpSocket *tcpSocket;
};#endif // MAINWINDOW_H
mainwindow.cpp:
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QHostInfo>MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
QString localIP = getLocalIP();
ui->ipLineEdit->setText(localIP);
ui->portLineEdit->setText("1000"); labListen = new QLabel(QString::fromLocal8Bit("监听状态:"));
labListen->setMinimumWidth(150);
ui->statusBar->addWidget(labListen); labSocketState = new QLabel(QString::fromLocal8Bit("Socket状态:"));
labSocketState->setMinimumWidth(200);
ui->statusBar->addWidget(labSocketState); tcpServer = new QTcpServer(this);
connect(tcpServer, SIGNAL(newConnection()), this, SLOT(onNewConnection()));
}MainWindow::~MainWindow()
{
delete ui;
}QString MainWindow::getLocalIP()
{
QString hostName = QHostInfo::localHostName(); //主机名
QHostInfo hostInfo = QHostInfo::fromName(hostName);
QString localIP = "";
QList<QHostAddress> addList = hostInfo.addresses(); if(!addList.isEmpty()){
for(int i = 0; i<addList.count(); i++)
{
QHostAddress hostAddr = addList.at(i);
if(QAbstractSocket::IPv4Protocol == hostAddr.protocol())
{
localIP = hostAddr.toString();
break;
}
}
}
return localIP;
}void MainWindow::onNewConnection()
{
tcpSocket = tcpServer->nextPendingConnection();
connect(tcpSocket, SIGNAL(connected()), this, SLOT(onClientConnected()));
onClientConnected();
connect(tcpSocket, SIGNAL(disconnected()), this, SLOT(onClientDisconnected()));
connect(tcpSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(onSocketStateChange(QAbstractSocket::SocketState)));
onSocketStateChange(tcpSocket->state());
connect(tcpSocket, SIGNAL(readyRead()), this, SLOT(onSocketReadyRead()));
}void MainWindow::onSocketStateChange(QAbstractSocket::SocketState socketState)
{
switch (socketState)
{
case QAbstractSocket::UnconnectedState:
labSocketState->setText(QString::fromLocal8Bit("socket 状态: UnconnectedState"));
break;
case QAbstractSocket::HostLookupState:
labSocketState->setText(QString::fromLocal8Bit("socket 状态: HostLookupState"));
break;
case QAbstractSocket::ConnectingState:
labSocketState->setText(QString::fromLocal8Bit("socket 状态: ConnectingState"));
break;
case QAbstractSocket::ConnectedState:
labSocketState->setText(QString::fromLocal8Bit("socket 状态: ConnectedState"));
break;
case QAbstractSocket::BoundState:
labSocketState->setText(QString::fromLocal8Bit("socket 状态: BoundState"));
break;
case QAbstractSocket::ClosingState:
labSocketState->setText(QString::fromLocal8Bit("socket 状态: ClosingState"));
break;
case QAbstractSocket::ListeningState:
labSocketState->setText(QString::fromLocal8Bit("socket 状态: ListeningState"));
break;
default:
break;
}
}void MainWindow::onClientConnected()
{
ui->plainTextEdit->appendPlainText("**client socket connected");
ui->plainTextEdit->appendPlainText("**peer address:" + tcpSocket->peerAddress().toString());
ui->plainTextEdit->appendPlainText("**peer port:" + QString::number(tcpServer->serverPort()));
ui->connectBtn->setEnabled(false);
ui->disconnectBtn->setEnabled(true);
}void MainWindow::onClientDisconnected()
{
ui->plainTextEdit->appendPlainText("**client socket disconnected");
tcpSocket->deleteLater();
}void MainWindow::onSocketReadyRead()
{
//一、基于行的网络传输
// while(tcpSocket->canReadLine())
//
// ui->plainTextEdit->appendPlainText("[client] " + tcpSocket->readLine());
// }
//二、基于块的网络传输
//1)方案一:
// while(tcpSocket->bytesAvailable() > 0)
// {
// ui->plainTextEdit->appendPlainText("[client] " + tcpSocket->readAll());
// }
//2)方案二:
// while (!tcpSocket->atEnd())
//{
// QByteArray data = tcpSocket->read(255);
// ui->plainTextEdit->appendPlainText("[client] " + data);
//} //3)方案三
while(tcpSocket->bytesAvailable() > 0)
{
int length = tcpSocket->bytesAvailable();
char buf[1024];
tcpSocket->read(buf, length);
QString msg = buf;
ui->plainTextEdit->appendPlainText("[client] " + msg);
}
}void MainWindow::on_connectBtn_clicked()
{
QString IP = ui->ipLineEdit->text();
quint16 port = ui->portLineEdit->text().toUInt(); if(!tcpServer->listen(QHostAddress(IP), port))
{
ui->plainTextEdit->appendPlainText(QString::fromLocal8Bit("服务器监听失败!"));
}
else
{
ui->plainTextEdit->appendPlainText(QString::fromLocal8Bit("服务器监听成功!"));
ui->connectBtn->setEnabled(false);
ui->disconnectBtn->setEnabled(true);
labListen->setText(QString::fromLocal8Bit("监听状态,正在监听"));
}
}void MainWindow::on_disconnectBtn_clicked()
{
if(tcpServer->isListening())
{
tcpServer->close();//停止监听
ui->connectBtn->setEnabled(true);
ui->disconnectBtn->setEnabled(false);
labListen->setText(QString::fromLocal8Bit("监听状态:已停止监听"));
}
}void MainWindow::on_sendBtn_clicked()
{
QString msg = ui->MsgLineEdit->text();
ui->plainTextEdit->appendPlainText("[server] " + msg);
ui->MsgLineEdit->clear();
ui->MsgLineEdit->setFocus();// QByteArray dataArray = msg.toUtf8();
// dataArray.append('\n');
// tcpSocket->write(dataArray);
QByteArray dataArray = msg.toUtf8();
tcpSocket->write(dataArray);
tcpSocket->flush();
}void MainWindow::on_clearBtn_clicked()
{
ui->MsgLineEdit->clear();
ui->plainTextEdit->clear();
}2)客户端
mainwindow.h:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H#include <QMainWindow>
#include <QTcpSocket>
#include <QLabel>
#include <QAbstractSocket>namespace Ui {
class MainWindow;
}class MainWindow : public QMainWindow
{
Q_OBJECTpublic:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
QString getLocalIP();
private slots:
void onConnected();
void onDisconnected();
void onSocketStateChange(QAbstractSocket::SocketState);
void onSocketReadyRead();
void on_connectBtn_clicked(); void on_disconnectBtn_clicked();
void on_sendBtn_clicked();
void on_clearBtn_clicked();
private:
Ui::MainWindow *ui;
QTcpSocket *tcpClient;
QLabel *labSocketState;};
#endif // MAINWINDOW_H
mainwindow.cpp:
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QHostInfo>MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
this->setWindowTitle(QString::fromLocal8Bit("客户端")); QString localIP = getLocalIP();
ui->ipLineEdit->setText(localIP);
ui->portLineEdit->setText("1000"); labSocketState = new QLabel(QString::fromLocal8Bit("Socket状态:"));
labSocketState->setMinimumWidth(250);
ui->statusBar->addWidget(labSocketState);
//tcpSocket
tcpClient = new QTcpSocket(this); connect(tcpClient, SIGNAL(connected()), this, SLOT(onConnected()));
connect(tcpClient, SIGNAL(disconnected()), this, SLOT(onDisconnected()));
connect(tcpClient, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(onSocketStateChange(QAbstractSocket::SocketState)));
connect(tcpClient, SIGNAL(readyRead()), this, SLOT(onSocketReadyRead()));
}MainWindow::~MainWindow()
{
delete ui;
}QString MainWindow::getLocalIP()
{
QString hostName = QHostInfo::localHostName(); //主机名
QHostInfo hostInfo = QHostInfo::fromName(hostName);
QString localIP = "";
QList<QHostAddress> addList = hostInfo.addresses(); if(!addList.isEmpty())
{
for(int i = 0; i<addList.count(); i++)
{
QHostAddress hostAddr = addList.at(i);
if(QAbstractSocket::IPv4Protocol == hostAddr.protocol())
{
localIP = hostAddr.toString();
break;
}
}
}
return localIP;
}void MainWindow::onConnected()
{
ui->plainTextEdit->appendPlainText(QString::fromLocal8Bit("**已连接到服务器"));
ui->plainTextEdit->appendPlainText("**peer address:" + tcpClient->peerAddress().toString());
ui->plainTextEdit->appendPlainText("**peer port:" + QString::number(tcpClient->peerPort()));
ui->connectBtn->setEnabled(false);
ui->disconnectBtn->setEnabled(true);
}void MainWindow::onDisconnected()
{
ui->plainTextEdit->appendPlainText(QString::fromLocal8Bit("**已断开与服务器连接"));
ui->connectBtn->setEnabled(true);
ui->disconnectBtn->setEnabled(false);
}void MainWindow::onSocketStateChange(QAbstractSocket::SocketState socketState)
{
switch (socketState)
{
case QAbstractSocket::UnconnectedState:
labSocketState->setText(QString::fromLocal8Bit("socket 状态: UnconnectedState"));
break;
case QAbstractSocket::HostLookupState:
labSocketState->setText(QString::fromLocal8Bit("socket 状态: HostLookupState"));
break;
case QAbstractSocket::ConnectingState:
labSocketState->setText(QString::fromLocal8Bit("socket 状态: ConnectingState"));
break;
case QAbstractSocket::ConnectedState:
labSocketState->setText(QString::fromLocal8Bit("socket 状态: ConnectedState"));
break;
case QAbstractSocket::BoundState:
labSocketState->setText(QString::fromLocal8Bit("socket 状态: BoundState"));
break;
case QAbstractSocket::ClosingState:
labSocketState->setText(QString::fromLocal8Bit("socket 状态: ClosingState"));
break;
case QAbstractSocket::ListeningState:
labSocketState->setText(QString::fromLocal8Bit("socket 状态: ListeningState"));
break;
default:
break;
}
}void MainWindow::onSocketReadyRead()
{
//一、基于行的网络传输
// while(tcpClient->canReadLine())
// {
// ui->plainTextEdit->appendPlainText("[Server] " + tcpClient->readLine());
// }
//二、基于块的网络传输
//1)方案一:
// while(tcpClient->bytesAvailable() > 0)
// {
// ui->plainTextEdit->appendPlainText("[Server] " + tcpClient->readAll());
// }
//2)方案二:
//while (!tcpClient->atEnd())
//{
// QByteArray data = tcpClient->read(255);
// ui->plainTextEdit->appendPlainText("[Server] " + data);
//}//3)方案三
while(tcpClient->bytesAvailable() > 0)
{
QByteArray datagram;
datagram.resize(tcpClient->bytesAvailable());
tcpClient->read(datagram.data(), datagram.size());
QString msg = datagram.data();
ui->plainTextEdit->appendPlainText("[Server] " + msg);
}
}void MainWindow::on_connectBtn_clicked()
{
QString addrIP = ui->ipLineEdit->text();
quint16 port = ui->portLineEdit->text().toUInt();
tcpClient->connectToHost(addrIP, port);
}void MainWindow::on_disconnectBtn_clicked()
{
if(tcpClient->state() == QAbstractSocket::ConnectedState)
{
tcpClient->disconnectFromHost();
}
}void MainWindow::on_sendBtn_clicked()
{
QString msg = ui->MsgLineEdit->text();
ui->plainTextEdit->appendPlainText("[Client] " + msg);
ui->MsgLineEdit->clear();
ui->MsgLineEdit->setFocus();// QByteArray dataArray = msg.toUtf8();
// dataArray.append('\n');
// tcpClient->write(dataArray); QByteArray dataArray = msg.toUtf8();
tcpClient->write(dataArray);
tcpClient->flush();
}void MainWindow::on_clearBtn_clicked()
{
ui->MsgLineEdit->clear();
ui->plainTextEdit->clear();
}
总结:Qt中tcp传输与接收方式各种各样,具体怎么使用还需要根据场景来看,当然了除了以上三种方式外还有一种是Qt4中提到的序列化数据包形式,如:
//发送
QByteArray dataArray;
QDataStream out(&dataArray, QIODevice::WriteOnly);
out.setVersion(QDatastream::Qt_4_7);
out<<quin16(0)<<quint8('S')<<currentData;
out.device()->seek(0);
out<< quint16(dataArray.size()- sizeof(quin16));
tcpSocket.write(dataArray);
一开始写入0值作为块的大小,以及其他数据。然后,对输入/输出设备,调用seek(0)以重新
移动到字节数组的开始处,同时利用数据块的实际尺寸值覆盖最初写入的0值。这个尺寸值是
通过由数据块的尺寸减去sizeof(quint16)计算得到的,也就是去掉前面容量字段所占空间,
最后发出这个块。
//接收(这里设置了缓存块quin16 nextBlockSize)
QDatastream in(&tcpSocket);
in.setVersion(QDatastream::Qt_4_7);if(nextBlockSize == 0)
{
if(bytesAvailable() < sizeof(quint16))
return;
in >> nextBlockSize;
}
if(bytesAvailable() < nextBlockSize)
return;quint8 requestType;
QString sData;in >> requestType >> sData;
此种方式使用特别注意数据缓存块nextBlockSize这个得使用,其次要理解占位的概念,以及内存数据的计算数据移动等。