QTcpSocket类 客户端/服务器模式

10 篇文章 1 订阅

TCP即Transmission Control Protocol,传输控制协议。与UDP不同,它是面向连接和数据流的可靠传输协议。也就是说,它能使一台计算机上的数据无差错的发往网络上的其他计算机,所以当要传输大量数据时,我们选用TCP协议。

TCP协议的程序使用的是客户端/服务器模式,在Qt中提供了QTcpSocket类来编写客户端程序,使用QTcpServer类编写服务器端程序。我们在服务器端进行端口的监听,一旦发现客户端的连接请求,就会发出newConnection()信号,我们可以关联这个信号到我们自己的槽函数,进行数据的发送。而在客户端,一旦有数据到来就会发出readyRead()信号,我们可以关联此信号,进行数据的接收。其实,在程序中最难理解的地方就是程序的发送和接收了,为了让大家更好的理解,我们在这一节只是讲述一个传输简单的字符串的例子,在下一节再进行扩展,实现任意文件的传输。

tcpClient
[cpp]
  1. private:  
  2.     QTcpSocket *tcpSocket;  
  3.     QString message;  //存放从服务器接收到的字符串   
  4.     quint16 blockSize;  //存放文件的大小信息   
  5.  QString str;  
  6. private slots:  
  7.     void newConnect(); //连接服务器   
  8.     void readMessage();  //接收数   
  9.  void displayError(QAbstractSocket::SocketError);  //显示错误  
private:
    QTcpSocket *tcpSocket;
    QString message;  //存放从服务器接收到的字符串
    quint16 blockSize;  //存放文件的大小信息
 QString str;
private slots:
    void newConnect(); //连接服务器
    void readMessage();  //接收数
 void displayError(QAbstractSocket::SocketError);  //显示错误

[cpp] 
  1. Widget::Widget(QWidget *parent) :  
  2.     QWidget(parent),  
  3.     ui(new Ui::Widget)  
  4. {  
  5.     ui->setupUi(this);  
  6.     tcpSocket = new QTcpSocket(this);  
  7.     connect(tcpSocket,SIGNAL(readyRead()),this,SLOT(readMessage()));  
  8.     connect(tcpSocket,SIGNAL(error(QAbstractSocket::SocketError)),this,SLOT(displayError(QAbstractSocket::SocketError)));  
  9.     //ui->hostLineEdit->setText("localhost");   
  10.     ui->hostLineEdit->setText("192.168.1.100");  
  11.     ui->portLineEdit->setText("6666");  
  12. }  
Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);
    tcpSocket = new QTcpSocket(this);
    connect(tcpSocket,SIGNAL(readyRead()),this,SLOT(readMessage()));
    connect(tcpSocket,SIGNAL(error(QAbstractSocket::SocketError)),this,SLOT(displayError(QAbstractSocket::SocketError)));
    //ui->hostLineEdit->setText("localhost");
    ui->hostLineEdit->setText("192.168.1.100");
    ui->portLineEdit->setText("6666");
}

[cpp]
  1. void Widget::newConnect()  
  2. {  
  3.     blockSize = 0; //初始化其为0   
  4.     tcpSocket->abort(); //取消已有的连   
  5.     //tcpSocket->connectToHost(ui->hostLineEdit->text(),ui->portLineEdit->text().toInt());   
  6.     //tcpSocket->connectToHost(QHostAddress("192.168.1.100"),ui->portLineEdit->text().toInt());   
  7.     //tcpSocket->connectToHost(QHostAddress::LocalHost,ui->portLineEdit->text().toInt());   
  8.     tcpSocket->connectToHost(QHostAddress(ui->hostLineEdit->text()),ui->portLineEdit->text().toInt());  
  9.     //连接到主机,这里从界面获取主机地址和端口号   
  10. }  
  11.   
  12. void Widget::on_pushButton_clicked() //连接按钮   
  13. {  
  14.    newConnect(); //请求连接   
  15. }  
void Widget::newConnect()
{
    blockSize = 0; //初始化其为0
    tcpSocket->abort(); //取消已有的连
    //tcpSocket->connectToHost(ui->hostLineEdit->text(),ui->portLineEdit->text().toInt());
    //tcpSocket->connectToHost(QHostAddress("192.168.1.100"),ui->portLineEdit->text().toInt());
    //tcpSocket->connectToHost(QHostAddress::LocalHost,ui->portLineEdit->text().toInt());
    tcpSocket->connectToHost(QHostAddress(ui->hostLineEdit->text()),ui->portLineEdit->text().toInt());
    //连接到主机,这里从界面获取主机地址和端口号
}

void Widget::on_pushButton_clicked() //连接按钮
{
   newConnect(); //请求连接
}

[cpp] 
  1. void Widget::readMessage()  
  2. {  
  3.     QDataStream in(tcpSocket);  
  4.     in.setVersion(QDataStream::Qt_4_6);  
  5.     //设置数据流版本,这里要和服务器端相同   
  6.     if(blockSize==0) //如果是刚开始接收数据   
  7.     {  
  8.         //判断接收的数据是否有两字节,也就是文件的大小信息   
  9.         //如果有则保存到blockSize变量中,没有则返回,继续接收数据   
  10.         if(tcpSocket->bytesAvailable() < (int)sizeof(quint16)) return;  
  11.         in >> blockSize;  
  12.     }  
  13.     if(tcpSocket->bytesAvailable() < blockSize) return;  
  14.     //如果没有得到全部的数据,则返回,继续接收数据   
  15.     in >> message;  
  16.     //将接收到的数据存放到变量中   
  17.     ui->messageLabel->setText(message);  
  18.     //显示接收到的数据   
  19. }  
void Widget::readMessage()
{
    QDataStream in(tcpSocket);
    in.setVersion(QDataStream::Qt_4_6);
    //设置数据流版本,这里要和服务器端相同
    if(blockSize==0) //如果是刚开始接收数据
    {
        //判断接收的数据是否有两字节,也就是文件的大小信息
        //如果有则保存到blockSize变量中,没有则返回,继续接收数据
        if(tcpSocket->bytesAvailable() < (int)sizeof(quint16)) return;
        in >> blockSize;
    }
    if(tcpSocket->bytesAvailable() < blockSize) return;
    //如果没有得到全部的数据,则返回,继续接收数据
    in >> message;
    //将接收到的数据存放到变量中
    ui->messageLabel->setText(message);
    //显示接收到的数据
}
[cpp] 
  1. void Widget::displayError(QAbstractSocket::SocketError)  
  2. {  
  3.     qDebug() << tcpSocket->errorString(); //输出错误信息   
  4. }  
void Widget::displayError(QAbstractSocket::SocketError)
{
    qDebug() << tcpSocket->errorString(); //输出错误信息
}
流程:
1创建tcpSocket ,tcpSocket = new QTcpSocket(this);
2.关联信号readyRead和槽函数readMessage,connect(tcpSocket,SIGNAL(readyRead()),this,SLOT( readMessage()));其中信号readyRead在有新的数据到达时发射
3.连接到某个ip的某个端口, tcpSocket->connectToHost(QHostAddress(ui->hostLineEdit->text()),ui->portLineEdit->text().toInt());
4.实现槽函数 readMessage,读取数据
 QDataStream in(tcpSocket);
 in >> message;

tcpServer
[cpp]
  1. private:  
  2.     QTcpServer *tcpServer;  
  3. private slots:  
  4.     void sendMessage();  
private:
    QTcpServer *tcpServer;
private slots:
    void sendMessage();
[cpp] 
  1. Widget::Widget(QWidget *parent) :  
  2.     QWidget(parent),  
  3.     ui(new Ui::Widget)  
  4. {  
  5.     ui->setupUi(this);  
  6.     tcpServer = new QTcpServer(this);  
  7.         //if(!tcpServer->listen(QHostAddress::LocalHost,6666))   
  8.         if(!tcpServer->listen(QHostAddress("192.168.1.100"),6666))  
  9.         {  //监听本地主机的6666端口,如果出错就输出错误信息,并关闭   
  10.   
  11.             qDebug() << tcpServer->errorString();  
  12.             close();  
  13.         }  
  14.      connect(tcpServer,SIGNAL(newConnection()),this,SLOT(sendMessage()));  
  15. }  
Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);
    tcpServer = new QTcpServer(this);
        //if(!tcpServer->listen(QHostAddress::LocalHost,6666))
        if(!tcpServer->listen(QHostAddress("192.168.1.100"),6666))
        {  //监听本地主机的6666端口,如果出错就输出错误信息,并关闭

            qDebug() << tcpServer->errorString();
            close();
        }
     connect(tcpServer,SIGNAL(newConnection()),this,SLOT(sendMessage()));
}
[cpp]
  1. void Widget::sendMessage()  
  2. {  
  3.     QByteArray block; //用于暂存我们要发送的数据   
  4.     QDataStream out(&block,QIODevice::WriteOnly);  
  5.     //使用数据流写入数据   
  6.     out.setVersion(QDataStream::Qt_4_6);  
  7.     //设置数据流的版本,客户端和服务器端使用的版本要相同   
  8.     out<<(quint16) 0;  
  9.     //要发送的数据放到out   
  10.     out<<tr("hello Tcp!!!");  
  11.     //要发送的数据放到out   
  12.     out.device()->seek(0);  
  13.     out<<(quint16)(block.size()-sizeof(quint16));  
  14.     //要发送的数据放到out   
  15.   
  16.     QTcpSocket *clientConnection = tcpServer->nextPendingConnection();  
  17.     //我们获取已经建立的连接的子套接字   
  18.     connect(clientConnection,SIGNAL(disconnected()),clientConnection,SLOT(deleteLater()));  
  19.     clientConnection->write(block);  
  20.   
  21.     clientConnection->disconnectFromHost();  
  22.     ui->statusLabel->setText("send message successful!!!");  
  23.     //发送数据成功后,显示提示   
  24. }  
void Widget::sendMessage()
{
    QByteArray block; //用于暂存我们要发送的数据
    QDataStream out(&block,QIODevice::WriteOnly);
    //使用数据流写入数据
    out.setVersion(QDataStream::Qt_4_6);
    //设置数据流的版本,客户端和服务器端使用的版本要相同
    out<<(quint16) 0;
    //要发送的数据放到out
    out<<tr("hello Tcp!!!");
    //要发送的数据放到out
    out.device()->seek(0);
    out<<(quint16)(block.size()-sizeof(quint16));
    //要发送的数据放到out

    QTcpSocket *clientConnection = tcpServer->nextPendingConnection();
    //我们获取已经建立的连接的子套接字
    connect(clientConnection,SIGNAL(disconnected()),clientConnection,SLOT(deleteLater()));
    clientConnection->write(block);

    clientConnection->disconnectFromHost();
    ui->statusLabel->setText("send message successful!!!");
    //发送数据成功后,显示提示
}
流程
1.创建tcpServer ,tcpServer = new QTcpServer(this);使之监听本机的某个端口,tcpServer->listen(QHostAddress("192.168.1.100"),6666)
2,关联信号newConnection和槽函数sendMessage, connect(tcpServer,SIGNAL(newConnection()),this,SLOT( sendMessage()));其中信号newConnection在有客户端的连接请求(即客户端执行tcpSocket->connectToHost)时发射
3.实现槽函数 sendMessage,在里面从tcpServer取得已经建立但挂起的QTcpSocket连接
QTcpSocket *clientConnection = tcpServer->nextPendingConnection();
用clientConnection 传输数据给客户
 QByteArray block;
 clientConnection->write(block);


用wireshark监听xp 192.168.1.100和虚拟机fedora 192.168.1.103之间的tcp数据包,如下
                                     
xp 192.168.1.100作为客户端发送连接请求,fedora 192.168.1.103 作为服务器端发送回数据 ,即上面代码所述
out<<(quint16)(block.size()-sizeof(quint16));
out<<tr("hello Tcp!!!");
可以看到用tcp发送一次数据,会有多个tcp Frame产生,下面显示是8个。而第4个Frame中才真正包含要传输的数据。这点和udp传输不一样,udp每次传输数据时仅有一个udp Frame产生。 这是由于tcp传输是面向连接的, 每次传输后对方要有一个ack信号返回。现还不清楚这个ack发送是否在tcp协议代码里实现的?


而每个tcp Frame和udp Frame类似,都是层层包装。
第1层EtherNet II包,记录源MAC和目的MAC等
第2层是IPv4包,记录源ip和目的ip等
第3层是tcp包,记录端口等
第4层才是真正的数据,"xxhello Tcp!!!"



http://download.csdn.net/detail/luck_good/3823903
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值