转载自http://mobile.51cto.com/symbian-270781_all.htm
Qt TCP协议 传输简单字符串实例是本文要介绍的内容。TCP即Transmission Control Protocol,传输控制协议。与UDP不同,它是面向连接和数据流的可靠传输协议。也就是说,它能使一台计算机上的数据无差错的发往网络上的其他计算机,所以当要传输大量数据时,我们选用TCP协议。
TCP协议的程序使用的是客户端/服务器模式,在Qt中提供了QTcpSocket类来编写客户端程序,使用QTcpServer类编写服务器端程序。我们在服务器端进行端口的监听,一旦发现客户端的连接请求,就会发出newConnection()信号,我们可以关联这个信号到我们自己的槽函数,进行数据的发送。而在客户端,一旦有数据到来就会发出readyRead()信号,我们可以关联此信号,进行数据的接收。其实,在程序中最难理解的地方就是程序的发送和接收了,为了让大家更好的理解,我们在这一节只是讲述一个传输简单的字符串的例子,在下一节再进行扩展,实现任意文件的传输。
一、服务器端。
在服务器端的程序中,我们监听本地主机的一个端口,这里使用6666,然后我们关联newConnection()信号与自己写的sendMessage()槽函数。就是说一旦有客户端的连接请求,就会执行sendMessage()函数,在这个函数里我们发送一个简单的字符串。
1.我们新建Qt4 Gui Application,工程名为“tcpServer”,选中QtNetwork模块,Base class选择QWidget。(说明:如果一些Qt Creator版本没有添加模块一项,我们就需要在工程文件tcpServer.pro中添加一行代码:QT += network)
2.我们在widget.ui的设计区添加一个Label,更改其objectName为statusLabel,用于显示一些状态信息。如下:
3.在widget.h文件中做以下更改。
添加头文件:#include <QtNetWork>
添加private对象:QTcpServer *tcpServer;
添加私有槽函数:
- private slots:
- void sendMessage();
4.在widget.cpp文件中进行更改。在其构造函数中添加代码:
- tcpServer = new QTcpServer(this);
- if(!tcpServer->listen(QHostAddress::LocalHost,6666))
- { //监听本地主机的6666端口,如果出错就输出错误信息,并关闭
- qDebug() << tcpServer->errorString();
- close();
- }
- connect(tcpServer,SIGNAL(newConnection()),this,SLOT(sendMessage()));
- //连接信号和相应槽函数
我们在构造函数中使用tcpServer的listen()函数进行监听,然后关联了newConnection()和我们自己的sendMessage()函数。下面我们实现sendMessage()函数。
- void Widget::sendMessage()
- {
- QByteArray block; //用于暂存我们要发送的数据
- QDataStream out(&block,QIODevice::WriteOnly);
- //使用数据流写入数据
- out.setVersion(QDataStream::Qt_4_6);
- //设置数据流的版本,客户端和服务器端使用的版本要相同
- out<<(quint16) 0;
- out<<tr(“hello Tcp!!!”);
- out.device()->seek(0);
- out<<(quint16) (block.size() – sizeof(quint16));
- QTcpSocket *clientConnection = tcpServer->nextPendingConnection();
- //我们获取已经建立的连接的子套接字
- connect(clientConnection,SIGNAL(disconnected()),clientConnection,
- SLOT(deleteLater()));
- clientConnection->write(block);
- clientConnection->disconnectFromHost();
- ui->statusLabel->setText(“send message successful!!!”);
- //发送数据成功后,显示提示
- }
这个是数据发送函数,我们主要介绍两点:
(1)为了保证在客户端能接收到完整的文件,我们都在数据流的最开始写入完整文件的大小信息,这样客户端就可以根据大小信息来判断是否接受到了完整的文件。而在服务器端,我们在发送数据时就要首先发送实际文件的大小信息,但是,文件的大小一开始是无法预知的,所以我们先使用了out<<(quint16) 0;在block的开始添加了一个quint16大小的空间,也就是两字节的空间,它用于后面放置文件的大小信息。然后out<<tr(“hello Tcp!!!”);输入实际的文件,这里是字符串。当文件输入完成后我们在使用out.device()->seek(0);返回到block的开始,加入实际的文件大小信息,也就是后面的代码,它是实际文件的大小:out<<(quint16) (block.size() – sizeof(quint16));
(2)在服务器端我们可以使用tcpServer的nextPendingConnection()函数来获取已经建立的连接的Tcp套接字,使用它来完成数据的发送和其它操作。比如这里,我们关联了disconnected()信号和deleteLater()槽函数,然后我们发送数据
- clientConnection->write(block);
然后是clientConnection->disconnectFromHost();它表示当发送完成时就会断开连接,这时就会发出disconnected()信号,而最后调用deleteLater()函数保证在关闭连接后删除该套接字clientConnection。
5.这样服务器的程序就完成了,我们先运行一下程序。
二、客户端。
我们在客户端程序中向服务器发送连接请求,当连接成功时接收服务器发送的数据。
1.我们新建Qt4 Gui Application,工程名为“tcpClient”,选中QtNetwork模块,Base class选择QWidget。
2,我们在widget.ui中添加几个标签Label和两个Line Edit以及一个按钮Push Button。
其中“主机”后的Line Edit的objectName为hostLineEdit,“端口号”后的为portLineEdit。“收到的信息”标签的objectName为messageLabel 。
3.在widget.h文件中做更改。
添加头文件:#include <QtNetwork>
添加private变量:
- QTcpSocket *tcpSocket;
- QString message; //存放从服务器接收到的字符串
- quint16 blockSize; //存放文件的大小信息
添加私有槽函数:
- private slots:
- void newConnect(); //连接服务器
- void readMessage(); //接收数据
- void displayError(QAbstractSocket::SocketError); //显示错误
4.在widget.cpp文件中做更改。
(1)在构造函数中添加代码:
- tcpSocket = new QTcpSocket(this);
- connect(tcpSocket,SIGNAL(readyRead()),this,SLOT(readMessage()));
- connect(tcpSocket,SIGNAL(error(QAbstractSocket::SocketError)),
- this,SLOT(displayError(QAbstractSocket::SocketError)));
这里关联了tcpSocket的两个信号,当有数据到来时发出readyRead()信号,我们执行读取数据的readMessage()函数。当出现错误时发出error()信号,我们执行displayError()槽函数。
(2)实现newConnect()函数。
- void Widget::newConnect()
- {
- blockSize = 0; //初始化其为0
- tcpSocket->abort(); //取消已有的连接
- tcpSocket->connectToHost(ui->hostLineEdit->text(),
- ui->portLineEdit->text().toInt());
- //连接到主机,这里从界面获取主机地址和端口号
- }
这个函数实现了连接到服务器,下面会在“连接”按钮的单击事件槽函数中调用这个函数。
(3)实现readMessage()函数。
- 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);
- //显示接收到的数据
- }
这个函数实现了数据的接收,它与服务器端的发送函数相对应。首先我们要获取文件的大小信息,
- void Widget::displayError(QAbstractSocket::SocketError)
- {
- qDebug() << tcpSocket->errorString(); //输出错误信息
- }
然后根据文件的大小来判断是否接收到了完整的文件。
(4)实现displayError()函数。
这里简单的实现了错误信息的输出。
(5)我们在widget.ui中进入“连接”按钮的单击事件槽函数,然后更改如下。
- void Widget::on_pushButton_clicked() //连接按钮
- {
- newConnect(); //请求连接
- }
这里直接调用了newConnect()函数。
5.我们运行程序,同时运行服务器程序,然后在“主机”后填入“localhost”,在“端口号”后填入“6666”,点击“连接”按钮,效果如下。
可以看到我们正确地接收到了数据。因为服务器端和客户端是在同一台机子上运行的,所以我这里填写了“主机”为“localhost”,如果你在不同的机子上运行,需要在“主机”后填写其正确的IP地址。
这个我亲自试了一下,还是可以的,但是有点问题就这反映了tcp的哪些规则呢:现做如下整理转载自http://blog.163.com/ykn_2010/blog/static/142033336201412311014489/
首先了解一下TCP/IP协议中的端口指的是什么呢?如果把IP地址比作一间房子 ,端口就是出入这间房子的门。真正的房子只有几个门,但是一个IP地址的端口 可以有65536(即:256×256)个之多!端口是通过端口号来标记的,端口号只有整数,范围是从0 到65535(256×256-1)。 在Internet上,各主机间通过TCP/IP协议发送和接收数据包,各个数据包根据其目的主机的ip地址来进行互联网络中的路由选择。可见,把数据包顺利的传送到目的主机是没有问题的。问题出在哪里呢?我们知道大多数操作系统都支持多程序(进程)同时运行,那么目的主机应该把接收到的数据包传送给众多同时运行的进程中的哪一个呢?显然这个问题有待解决,端口机制便由此被引入进来。 本地操作系统会给那些有需求的进程分配协议端口(protocol port,即我们常说的端口),每个协议端口由一个正整数标识,如:80,139,445,等等。当目的主机接收到数据包后,将根据报文首部的目的端口号,把数据发送到相应端口,而与此端口相对应的那个进程将会领取数据并等待下一组数据的到来。说到这里,端口的概念似乎仍然抽象,那么继续跟我来,别走开。 端口其实就是队,操作系统为各个进程分配了不同的队,数据包按照目的端口被推入相应的队中,等待被进程取用,在极特殊的情况下,这个队也是有可能溢出的,不过操作系统允许各进程指定和调整自己的队的大小。 不光接受数据包的进程需要开启它自己的端口,发送数据包的进程也需要开启端口,这样,数据包中将会标识有源端口,以便接受方能顺利的回传数据包到这个端口。 一个数据包包括了文件,ip,和端口号,ip是为了服务器可以找到你的主机,端口号是你接受数据包的门户, 而所谓的端口监听,是指主机网络进程接受到IP数据包后,察看其的目标端口是不是自己的端口号,如果是的话就接受该数据包进行处理。进行网络通讯的主机,既要发送数据,也要接受数据,所以就要开启相应的端口以接受数据。一个网络上的主机有可能开启多个网络进程(如即浏览网页又上QQ),也就是监听了多个端口。开始→控制面板→管理工具→本地安全策略→ip策略 在本地计算机→右键,创建ip安全策略→“激活 默认响应规则”去掉→“编辑属性”去掉→要把“使用添加向导”去掉→添加→新ip筛选列表→添加:源地 址“任何IP地址”→目标地址“我的ip地址”→协议“TCP协议”→选“从任意端口” 选“到此端口”→确 关闭需要关闭的端口 每一项服务都对应相应的端口,比如众如周知的WWW服务的端口是80,smtp是25,ftp是21,win2000安装中默认的都是这些服务开启的。对于个人用户来说确实没有必要,关掉端口也就是关闭无用的服务。 “控制面板”的“管理工具”中的“服务”中来配置。 1、关闭7.9等等端口:关闭Simple TCP/IP Service,支持以下TCP/IP服务:Character Generator,Daytime, Discard, Echo, 以及 Quote of the Day。 2、关闭80口:关掉WWW服务。在“服务”中显示名称为"World Wide Web Publishing Service",通过 Internet 信息服务的管理单元提供 Web 连接和管理。 3、关掉25端口:关闭Simple Mail Transport Protocol (SMTP)服务,它提供的功能是跨网传送电子邮件。 4、关掉21端口:关闭FTP Publishing Service,它提供的服务是通过 Internet 信息服务的管理单元提供 FTP 连接和管理。 5、关掉23端口:关闭Telnet服务,它允许远程用户登录到系统并且使用命令行运行控制台程序。 6、还有一个很重要的就是关闭server服务,此服务提供RPC支持、文件、打印以及命名管道共享。关掉它就关掉了win2k的默认共享,比如ipc$、c$、admin$等等,此服务关闭不影响您的其他操作。 7、还有一个就是139端口,139端口是NetBIOS>>Session端口,用来文件和打印共享,注意的是运行samba的unix机器也开放了139端口,功能一样。以前流光2000用来判断对方主机类型不太准确,估计就是139端口开放既认为是NT机,现在好了。 关闭139口听方法是在“网络和拨号连接”中“本地连接”中选?gt;>癐nternet协议(TCP/IP)”属性,进入“高级TCP/IP设置”“WINS设置”里面有一项“禁用TCP/IP的NETBIOS”,打勾就关闭了139端口。 对于个人用户来说,可以在各项服务属性设置中设为“禁用”,以免下次重启服务也重新启动,端口也开放了。 第一步,点击“开始”菜单/设置/控制面板/管理工具,双击打开“本地安全策略”,选中“IP 安全策略,在本地计算机”,在右边窗格的空白位置右击鼠标,弹出快捷菜单,选择“创建 IP 安全策略”(如右图),于是弹出一个向导。在向导中点击“下一步”按钮,为新的安全策略命名;再按“下一步”,则显示“安全通信请求”画面,在画面上把“激活默认相应规则”左边的钩去掉,点击“完成”按钮就创建了一个新的IP 安全策略。 第二步,右击该IP安全策略,在“属性”对话框中,把“使用添加向导”左边的钩去掉,然后单击“添加”按钮添加新的规则,随后弹出“新规则属性”对话框,在画面上点击“添加”按钮,弹出IP筛选器列表窗口;在列表中,首先把“使用添加向导”左边的钩去掉,然后再点击右边的“添加”按钮添加新的筛选器。 第三步,进入“筛选器属性”对话框,首先看到的是寻址,源地址选“任何 IP 地址”,目标地址选“我的 IP 地址”;点击“协议”选项卡,在“选择协议类型”的下拉列表中选择“TCP”,然后在“到此端口”下的文本框中输入“135”,点击“确定”按钮(如左图),这样就添加了一个屏蔽 TCP 135(RPC)端口的筛选器,它可以防止外界通过135端口连上你的电脑。 点击“确定”后回到筛选器列表的对话框,可以看到已经添加了一条策略,重复以上步骤继续添加 TCP 137、139、445、593 端口和 UDP 135、139、445 端口,为它们建立相应的筛选器。 重复以上步骤添加TCP 1025、2745、3127、6129、3389 端口的屏蔽策略,建立好上述端口的筛选器,最后点击“确定”按钮。 第四步,在“新规则属性”对话框中,选择“新 IP 筛选器列表”,然后点击其左边的圆圈上加一个点,表示已经激活,最后点击“筛选器操作”选项卡。在“筛选器操作”选项卡中,把“使用添加向导”左边的钩去掉,点击“添加”按钮,添加“阻止”操作(右图):在“新筛选器操作属性”的“安全措施”选项卡中,选择“阻止”,然后点击“确定”按钮。 第五步、进入“新规则属性”对话框,点击“新筛选器操作”,其左边的圆圈会加了一个点,表示已经激活,点击“关闭”按钮,关闭对话框;最后回到“新IP安全策略属性”对话框,在“新的IP筛选器列表”左边打钩,按“确定”按钮关闭对话框。在“本地安全策略”窗口,用鼠标右击新添加的 IP 安全策略,然后选择“指派”。 于是重新启动后,电脑中上述网络端口就被关闭了,病毒和黑客再也不能连上这些端口,从而保护了你的电脑。目前还没听说有补丁下载。
小结:Qt TCP协议传输简单字符串实例,到这里我们最简单的TCP应用程序就完成了,在下一节我们将会对它进行扩展,实现任意文件的传输。