QT4中构建多线程的服务器

来源:互联网 发布:自动下棋软件 编辑:程序博客网 时间:2024/04/30 15:02


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

目标:

  1. 用户端:linux(arm平台),完成文件的传输
  2. 服务器:windows ,使用多线程的文件的接收
  3. 实现无线的文件传输功能

用户端程序,用标准的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: }

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

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

监听状态

传输文件时:

接收文件


0 0