QT编写多线程TCP文件接收服务器
2017-03-24 22:38
351 查看
目标:
用户端:linux(arm平台),完成文件的传输
服务器:windows ,使用多线程的文件的接收
实现无线的文件传输功能
用户端程序,用标准的socket完成文件传输的功能,代码如下:
服务器端代码列表:
具体代码如下:
“tcpserver.h”
TCPSERVER继承QTcpServer,主要完成TCP服务器的建立,类中最主要的成员函数为虚函数incomingConnection(int socketDescriptor)的定义。
“tcpserver.cpp”
极其简单的构造函数,在incomingConnection()中,定义一个线程TcpThread,并将socketDescriptor传递给其构造函数,完成线程的创建,并且调用QThread的start函数,开始执行线程的虚函数run()。
“tcpthread.h”
继承自QThread类,在此线程中完成TCPSOCKET的建立,和文件的接收。
“tcpthread.cpp”
代码中已经有很详细的注释,需要再说明的一点就是在多线程的编写中,信号/槽的连接方式一定要根据实际情况来进行选择!
“widget.h”
简单的widget类。
“widget.cpp”
完成服务器的监听,和进度条的更新。
点击开始后,处于监听状态。
传输文件时:
用户端:linux(arm平台),完成文件的传输
服务器:windows ,使用多线程的文件的接收
实现无线的文件传输功能
用户端程序,用标准的socket完成文件传输的功能,代码如下:
1: // Linux下网络编程,客户端程序代码
2: //程序运行参数:
3:// ./client IPADDRESS PORTNUMBER
4:// (其中IPADDRESS是服务端IP地址,PORTNUMBER是服务端用于监听的端口)
5://
6:
7:#include <stdio.h>
8:#include <stdlib.h>
9: #include <errno.h>
10:#include <string.h>
11:#include <netdb.h>
12: #include <ctype.h>
13:#include <unistd.h>
14:#include <sys/types.h>
15:#include <sys/socket.h>
16:#include <netinet/in.h>
17:#include <sys/time.h>
18:
19:
20://用这个my_read()函数代替本来的read()函数原因有以下几点:
21://
22://ssize_t read(int fd,void *buf,size_t nbyte)
23://read函数是负责从fd中读取内容。当读成功时,read返回实际所读的字节数;如果
24://返回的值是0,表示已经读到文件的结束了;小于0表示出现了错误。
25://
26:// 1)如果错误为EINTR说明read出错是由中断引起的,继续读。
27:// 2)如果是ECONNREST表示网络连接出了问题,停止读取。
28:
29:size_t min(size_t a,size_t b)
30: {
31:return( (a<b) ? a : b);
32:}
33:
34:ssize_t my_write(int fd,void *buffer,size_t length)
35:{
36:size_t bytes_left; //尚未写的文件大小
37:size_t writesize = 4* 1024;
38:ssize_t written_bytes; //已经写的文件大小
39:char *ptr;
40:ptr=buffer;
41:bytes_left=length;
42:while(bytes_left>0)
43:{
44://开始写
45:written_bytes=write(fd,ptr,min(bytes_left,writesize));
46://出现了写错误
47:if(written_bytes<=0)
48:{
49://中断错误,置零重新写
50: if(errno==EINTR)
51:written_bytes=0;
52://其他错误,退出不写了
53:else
54:return(-1);
55:}
56://从剩下的地方继续写
57:bytes_left-=written_bytes;
58:ptr+=written_bytes;
59: }
60: return(0);
61: }
62:
63:
64:
65:int main(int argc, char *argv[])
66:{
67:int sockfd; //通信套接字描述符
68:char *buffer; //缓冲区
69:struct sockaddr_in server_addr; //服务器地址结构
70: struct hostent *host; //主机地址与名称信息结构
71:int nbytes; //端口号、字节数
72: FILE *fp; //文件指针
73:int nfilesize; //文件大小
74:char str[128]; //文件名
75:char yes='Y'; //流程控制
76:struct timeval tpstart,tpend; //用于记录文件传输时间
77:float timeuse; //文件传输所用时间
78:char *hostname="127.0.0.1";//主机名/ip地址
79: int portnumber=4321;//端口号
80:
81: //提示用户输入完整的命令行参数
82: if(argc!=3)
83:{
84:fprintf(stderr,"Usage:%s hostname portnumber\a\n",argv[0]);
85:printf("using defaults:\nhostname: %s\nportnumber: %d\n",hostname,portnumber);
86:}
87:
88://如果利用用户输入的域名无法获得正确的主机地址信息,则退出
89:if (argc>1)
90: {
91: if((host=gethostbyname(argv[1]))==NULL)
92: {
93:fprintf(stderr,"Gethostname error\n");
94:exit(1);
95:}
96:}
97:
98:else
99: if((host=gethostbyname(hostname))==NULL)
100:{
101: fprintf(stderr,"Gethostname error\n");
102:exit(1);
103:}
104:
105:if(argc>2)
106:
107://如果用户输入的端口不正确,则提示并退出
108:if((portnumber=atoi(argv[2]))<0)
109:{
110:fprintf(stderr,"Usage:%s hostname portnumber\a\n",argv[0]);
111:exit(1);
112: }
113:
114://客户程序开始建立 sockfd描述符,创建通信套接字
115:if((sockfd=socket(AF_INET,SOCK_STREAM,6))==-1)
116:{
117:fprintf(stderr,"Socket Error:%s\a\n",strerror(errno));
118:exit(1);
119:}
120:
121:
122://客户程序填充服务端的地址信息
123:bzero(&server_addr,sizeof(server_addr));
124:server_addr.sin_family=AF_INET;
125:server_addr.sin_port=htons(portnumber);
126:server_addr.sin_addr=*((struct in_addr *)host->h_addr);
127:
128://客户程序发起连接请求
129:if(connect(sockfd,(struct sockaddr *)(&server_addr),sizeof(struct sockaddr))==-1)
130: {
131:fprintf(stderr,"Connect Error:%s\a\n",strerror(errno));
132:exit(1);
133:}
134:printf("Connection Succeed!\n");
135:
136:while (toupper(yes)=='Y')
137:{
138://提示用户输入文件路径
139:printf("Please input the file location:");
140:scanf("%s",str);
141:while ((fp=fopen(str,"r"))==NULL)
142:{
143:fprintf(stderr,"File open error,Retry!\n");
144:printf("Please input the file location:");
145:scanf("%s",str);
146://exit(1);
147:}
148:getchar();
149:
150: //获取打开的文件的大小,并将文件整个读入内存中
151:fseek(fp,0L,SEEK_END);
152:nfilesize=ftell(fp);
153:rewind(fp);//most important!!!!!
154:char *p=(char *)malloc(nfilesize);
155:if (fread((void *)p,nfilesize,1,fp)<1) {
156:
157:if (feof(fp))
158:printf("read end of file!\nquit!\n");
159: else
160: printf("read file error!\n");
161: }
162:
163://将要传输的文件的大小信息发送给客户端
164:if (my_write(sockfd,(void *)&nfilesize,4)==-1)
165:{
166:fprintf(stderr,"Write Error:%s\n",strerror(errno));
167:exit(1);
168:}
169:
170: printf("Begin to transfer the file!\n");
171:getchar();
172:
173://获取传输初始时间
174:gettimeofday(&tpstart,NULL);
175:
176://传输文件
177:if (my_write(sockfd,p,nfilesize)==-1)
178:{
179: fprintf(stderr,"Transfer failed!");
180:exit(1);
181: }
182:
183://获取传输结束时间
184:gettimeofday(&tpend,NULL);
185://计算整个传输用时
186:timeuse=1000000*(tpend.tv_sec-tpstart.tv_sec)+(tpend.tv_usec-tpstart.tv_usec);
187:timeuse/=1000000;
188:
189:printf("Transfer Succeed!\nFile Name: %s\nFile Size: %d bytes\nTotal Time: %f seconds\nTransfer Speed: %f bytes/second",str,nfilesize,timeuse,((float)nfilesize)/timeuse);
190: free(p); //释放文件内存
191: fclose(fp); //关闭文件
192: // printf("\nTransfer another file?(Y/N): ");
193://scanf("%c",&yes);
194:// getchar();
195:yes='n';
196:}
197://结束通讯,关闭套接字,关闭连接
198:close(sockfd);
199: printf("\nClient Exit!~~\n");
200: exit(0);
201: }
服务器端代码列表:
具体代码如下:
“tcpserver.h”
1: #ifndef TCPSERVER_H
2: #define TCPSERVER_H
3:
4:#include <QTcpServer>
5:
6://继承自QTcpServer,完成TCPSEVER的建立的类
7:
8:class TcpServer : public QTcpServer
9: {
10:Q_OBJECT
11:public:
12: explicit TcpServer(QObject *parent = 0);
13:
14://此信号用来更新UI
15:signals:
16:void bytesArrived(qint64,qint32,int);
17:
18://QTcpServer类自带的函数,详情参考Class Reference
19:protected:
20:void incomingConnection(int socketDescriptor);
21:
22:};
23:
24:#endif // TCPSERVER_H
TCPSERVER继承QTcpServer,主要完成TCP服务器的建立,类中最主要的成员函数为虚函数incomingConnection(int socketDescriptor)的定义。
“tcpserver.cpp”
1: #include "tcpserver.h"
2: #include "tcpthread.h"
3:
4://构造函数
5:
6:TcpServer::TcpServer(QObject *parent) :
7:QTcpServer(parent)
8:{
9: }
10:
11://重新定义了incomingConnection这个虚函数,
12: //开辟一个新的tcpsocket线程,从TcpServer获得socketDescriptor,
13://并完成相应的信号连接。
14:
15:void TcpServer::incomingConnection(int socketDescriptor)
16:{
17:
18:TcpThread *thread = new TcpThread(socketDescriptor, this);
19:
20://将线程结束信号与线程的deleteLater槽关联
21:connect(thread, SIGNAL(finished()),
22:thread, SLOT(deleteLater()));
23:
24://关联相应的UI更新槽
25:connect(thread,SIGNAL(bytesArrived(qint64,qint32,int)),
26:this,SIGNAL(bytesArrived(qint64,qint32,int)));
27:
28://QT的线程都是从start开始,调用run()函数
29:thread->start();
30: }
31:
极其简单的构造函数,在incomingConnection()中,定义一个线程TcpThread,并将socketDescriptor传递给其构造函数,完成线程的创建,并且调用QThread的start函数,开始执行线程的虚函数run()。
“tcpthread.h”
1: #ifndef TCPTHREAD_H
2: #define TCPTHREAD_H
3:
4:#include <QThread>
5:#include <QTcpSocket>
6:#include <QtNetwork>
7:
8://继承QThread的TCP传输线程
9: //主要是完成run()虚函数的定义
10://还有一些辅助变量的声明
11:
12: class QFile;
13:class QTcpSocket;
14:
15:class TcpThread : public QThread
16:{
17:Q_OBJECT
18:public:
19:TcpThread(int socketDescriptor, QObject *parent);
20:
21:void run();
22:
23:signals:
24:void error(QTcpSocket::SocketError socketError);
25:void bytesArrived(qint64,qint32,int);
26:
27:public slots:
28:void receiveFile();
29:
30: private:
31:int socketDescriptor;
32:qint64 bytesReceived; //收到的总字节
33:qint64 byteToRead; //准备读取的字节
34:qint32 TotalBytes; //总共传输的字节
35:QTcpSocket *tcpSocket;
36:QHostAddress fileName; //文件名
37:QFile *localFile;
38:QByteArray inBlock; //读取缓存
39:
40:
41:};
42:
43:#endif // TCPTHREAD_H
继承自QThread类,在此线程中完成TCPSOCKET的建立,和文件的接收。
“tcpthread.cpp”
1: #include "tcpthread.h"
2: #include <QtGui>
3:#include <QtNetwork>
4:
5://构造函数完成简单的赋值/
6:TcpThread::TcpThread(int socketDescriptor, QObject *parent):
7:QThread(parent),socketDescriptor(socketDescriptor)
8:{
9: bytesReceived = 0;
10:}
11:
12: //因为QT的线程的执行都是从run()开始,
13://所以在此函数里完成tcpsocket的创建,相关信号的绑定
14:void TcpThread::run()
15:{
16:tcpSocket = new QTcpSocket;
17:
18://将Server传来的socketDescriptor与刚创建的tcpSocket关联
19:if (!tcpSocket->setSocketDescriptor(socketDescriptor)) {
20:emit error(tcpSocket->error());
21:return;
22:}
23:
24:qDebug()<<socketDescriptor;
25:
26://这是重中之重,必须加Qt::BlockingQueuedConnection!
27://这里困扰了我好几天,原因就在与开始没加,默认用的Qt::AutoConnection。
28://简单介绍一下QT信号与槽的连接方式:
29://Qt::AutoConnection表示系统自动选择相应的连接方式,如果信号与槽在同一线程,就采用Qt::DirectConnection,如果信号与槽不在同一线程,将采用Qt::QueuedConnection的连接方式。
30: //Qt::DirectConnection表示一旦信号产生,立即执行槽函数。
31://Qt::QueuedConnection表示信号产生后,将发送Event给你的receiver所在的线程,postEvent(QEvent::MetaCall,...),slot函数会在receiver所在的线程的event loop中进行处理。
32://Qt::BlockingQueuedConnection表示信号产生后调用sendEvent(QEvent::MetaCall,...),在receiver所在的线程处理完成后才会返回;只能当sender,receiver不在同一线程时才可以。
33://Qt::UniqueConnection表示只有它不是一个重复连接,连接才会成功。如果之前已经有了一个链接(相同的信号连接到同一对象的同一个槽上),那么连接将会失败并将返回false。
34://Qt::AutoCompatConnection与QT3保持兼容性
35://说明一下,对于任何的QThread来说,其线程只存在于run()函数内,其它的函数都不在线程内,所以此处要采用Qt::BlockingQueuedConnection,
36://因为当SOCKET有数据到达时就会发出readyRead()信号,但是此时可能之前的receiveFile()还未执行完毕,之前使用的Qt::AutoConnection,
37://结果传输大文件的时候就会出错,原因就在于只要有数据到达的时候,就会连接信号,但是数据接收还没处理完毕,而Qt::BlockingQueuedConnection会阻塞
38://此连接,直到receiveFile()处理完毕并返回后才发送信号。
39:
40:connect(tcpSocket, SIGNAL(readyRead()),
41:this, SLOT(receiveFile()),Qt::BlockingQueuedConnection);
42:
43:exec();
44:
45:
46:
47:
48:}
49:
50: void TcpThread::receiveFile()
51:{
52:
53:
54://将tcpsocket封装到QDataStream里,便于使用操作符>>
55:QDataStream in(tcpSocket);
56:if(bytesReceived < sizeof(qint32))
57:{
58:
59: //先接收32bit的文件大小
60: if(tcpSocket->bytesAvailable() >= sizeof(qint32))
61: {
62:
63:in.setByteOrder(QDataStream::LittleEndian); //必须的,因为发送端为LINUX系统
64:
65:in>>TotalBytes;
66:
67:TotalBytes += 4;
68:qDebug()<<TotalBytes;
69:
70: bytesReceived += sizeof(qint32);
71:
72: fileName = tcpSocket->peerAddress();
73:quint16 port = tcpSocket->peerPort();
74:localFile = new QFile(fileName.toString()+(tr(".%1").arg(port))); //用户端的IP地址作为保存文件名
75:if (!localFile->open(QFile::WriteOnly ))
76:
77:{
78:
85:}
86:}
87:
88:}
89:
90: //如果读取的文件小于文件大小就继续读
91: if (bytesReceived < TotalBytes){
92: byteToRead = tcpSocket->bytesAvailable();
93:bytesReceived += byteToRead;
94:inBlock = tcpSocket->readAll();
95:
96:qDebug()<<"bytesReceived is:"<<bytesReceived;
97:localFile->write(inBlock);
98:inBlock.resize(0);
99: }
100:
101: emit bytesArrived(bytesReceived,TotalBytes,socketDescriptor);
102:
103:if (bytesReceived == TotalBytes) {
104:localFile->close();
105:qDebug()<<bytesReceived;
106:emit finished();
107:QApplication::restoreOverrideCursor();
108:}
109:
110:}
代码中已经有很详细的注释,需要再说明的一点就是在多线程的编写中,信号/槽的连接方式一定要根据实际情况来进行选择!
“widget.h”
1: #ifndef WIDGET_H
2: #define WIDGET_H
3:
4:#include <QWidget>
5:
6:#include "tcpthread.h"
7:#include "tcpserver.h"
8:
9: class QDialogButtonBox;
10:
11:class QTcpSocket;
12: namespace Ui {
13:class Widget;
14:}
15:
16:class Widget : public QWidget
17:{
18:Q_OBJECT
19:
20:public:
21:explicit Widget(QWidget *parent = 0);
22:~Widget();
23:
24:private:
25:Ui::Widget *ui;
26:TcpServer tcpServer;
27:
28:
29:private slots:
30: void on_OkButton_clicked();
31:void updateProgress(qint64,qint32,int);
32:
33:};
34:
35:#endif // WIDGET_H
简单的widget类。
“widget.cpp”
1: #include "widget.h"
2: #include "ui_widget.h"
3:#include <QtNetwork>
4:#include <QtGui>
5:
6:Widget::Widget(QWidget *parent) :
7:QWidget(parent),
8:ui(new Ui::Widget)
9: {
10:ui->setupUi(this);
11:ui->progressBar->setMaximum(2);
12: ui->progressBar->setValue(0);
13:}
14:
15:Widget::~Widget()
16:{
17:delete ui;
18:}
19:
20:void Widget::on_OkButton_clicked()
21:{
22:ui->OkButton->setEnabled(false);
23:
24:QApplication::setOverrideCursor(Qt::WaitCursor);
25://bytesReceived = 0;
26:
27:while (!tcpServer.isListening() && !tcpServer.listen(QHostAddress::Any,12345))
28:{
29:QMessageBox::StandardButton ret = QMessageBox::critical(this,
30: tr("回环"),
31:tr("无法开始测试: %1.")
32:.arg(tcpServer.errorString()),
33:QMessageBox::Retry
34:| QMessageBox::Cancel);
35:if (ret == QMessageBox::Cancel)
36:return;
37:}
38:ui->statuslabel->setText(tr("监听端口:%1").arg("12345"));
39:connect(&tcpServer,SIGNAL(bytesArrived(qint64,qint32,int)),
40:this,SLOT(updateProgress(qint64,qint32,int)));
41:
42:
43:}
44:void Widget::updateProgress(qint64 bytesReceived, qint32 TotalBytes, intsocketDescriptor)
45:{
46:ui->progressBar->setMaximum(TotalBytes);
47:ui->progressBar->setValue(bytesReceived);
48:ui->statuslabel->setText(tr("已接收 %1MB")
49:.arg(bytesReceived / (1024 * 1024)));
50: ui->textBrowser->setText(tr("现在连接的socket描述符:%1").arg(socketDescriptor));
51:
52:
53:}
完成服务器的监听,和进度条的更新。
点击开始后,处于监听状态。
传输文件时:
相关文章推荐
- QT编写多线程TCP文件接收服务器
- QT编写多线程TCP文件接收服务器
- QT编写多线程TCP文件接收服务器
- QT编写多线程TCP文件接收服务器
- 详解 QT 多线程 TCP 文件接收服务器实例(1)
- 详解 QT 多线程 TCP 文件接收服务器实例(2)
- Qt5 基于TCP传输的发送/接收文件服务器(支持多客户端)
- Qt5 基于TCP传输的发送/接收文件服务器(支持多客户端)
- Qt5 基于TCP传输的发送/接收文件服务器(支持多客户端)
- Qt5 基于TCP传输的发送/接收文件服务器(支持多客户端)
- 动手编写TCP服务器系列之一:日志文件
- java中TCP与UDP协议的使用以及多线程服务器编写
- java 网络流 TCP Socket和SeverSocket 上传文件(字节流)+ 服务器多线程
- 服务器端接收来自多个客户端的文件,服务器要求用多线程
- Java编写一个简单的TCP通信程序。服务器发送一条字符串,客户端接收该信息并显示。
- qt 下UDP消息的接收和发送和TCP发送文件
- Qt5--学习笔记-TCPsocket文件发送、接收
- Qt5.8《网络版够级游戏》编写日志之四:服务器端登陆响应功能实现(TCP多线程)
- java socket 服务器代码 自动接收文件并存储服务器 - 服务器多线程支持和多个客户端同时通信:
- 多进程多线程服务器(tcp_server)编写