您的位置:首页 > 理论基础 > 计算机网络

QT编写多线程TCP文件接收服务器

2016-07-10 15:44 597 查看
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 portnumberan",argv[0]);

85:printf("using defaults:nhostname: %snportnumber: %dn",hostname,portnumber);

86:}

87:

88://如果利用用户输入的域名无法获得正确的主机地址信息,则退出

89:if (argc>1)

90: {

91: if((host=gethostbyname(argv[1]))==NULL)

92: {

93:fprintf(stderr,"Gethostname errorn");

94:exit(1);

95:}

96:}

97:

98:else

99: if((host=gethostbyname(hostname))==NULL)

100:{

101: fprintf(stderr,"Gethostname errorn");

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 portnumberan",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:%san",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:%san",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:%sn",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: %snFile Size: %d bytesnTotal Time: %f secondsnTransfer 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


20b80
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:}


完成服务器的监听,和进度条的更新。

点击开始后,处于监听状态。





传输文件时:



内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: