qt编写多线程tcp文件接收服务器,[转载]QT编写多线程TCP文件接收服务器

因为项目需要,需要跨平台编写网络传输程序。

目标:

用户端:linux(arm平台),完成文件的传输

服务器:windows ,使用多线程的文件的接收

实现无线的文件传输功能

用户端程序,用标准的socket完成文件传输的功能,代码如下:

1: // Linux下网络编程,客户端程序代码

2: //程序运行参数:

3: // ./client IPADDRESS PORTNUMBER

4: // (其中IPADDRESS是服务端IP地址,PORTNUMBER是服务端用于监听的端口)

5: //

6:

7: #include

8: #include

9: #include

10: #include

11: #include

12: #include

13: #include

14: #include

15: #include

16: #include

17: #include

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

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

服务器端代码列表:

a4c26d1e5885305701be709a3d33442f.png

具体代码如下:

“tcpserver.h”

1: #ifndef TCPSERVER_H

2: #define TCPSERVER_H

3:

4: #include

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

5: #include

6: #include

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

3: #include

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()<

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()<

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()<

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()<

106: emit finished();

107: QApplication::restoreOverrideCursor();

108: }

109:

110: }

代码中已经有很详细的注释,需要再说明的一点就是在多线程的编写中,信号/槽的连接方式一定要根据实际情况来进行选择!

“widget.h”

1: #ifndef WIDGET_H

2: #define WIDGET_H

3:

4: #include

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

4: #include

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, int socketDescriptor)

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

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

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

a4c26d1e5885305701be709a3d33442f.png

传输文件时:

a4c26d1e5885305701be709a3d33442f.png

你可能感兴趣的:(qt编写多线程tcp文件接收服务器,[转载]QT编写多线程TCP文件接收服务器)