TCPIP通信(即SOCKET通信)是通过网线将服务器Server端和客户机Client端进行连接,在遵循ISOOSI模型的四层层级构架的基础上通过TCPIP协议建立的通讯。控制器可以设置为服务器端或客户端。 关于TCPIP协议可详看:TCPIP协议详解知乎(zhihu。com) 总的来说,TCPIP通讯有两个部分:客户端和服务器QTcpServer(监听套接字)和QTcpSocket(通讯套接字) 监听套接字,顾名思义,监听关于各种通讯的状态,一旦进行通讯,监听套接字会启动通讯套接字,进行通讯 客户端使用connectToHost函数主动连接服务器后,服务器会触发newConnectio这个槽函数,并进行取出QTcpServer(监听套接字),将相关内容取出并赋给QTcpSocket(通讯套接字)。 客户端向服务器发送数据,触发readyRead(),进行处理,彼此传递时,原理都是这样的。 对双方来说都起作用的部分:一旦建立连接,就会触发connected,服务器特殊一点,触发的是newConnectio互传数据也是一样的,一旦接受到,就会触发readyread 服务器中,需要监听套接字以及通讯套接字,监听套接字用于监听客户端是否给服务器发送请求 本篇博文做了初步的学习与尝试,编写了一个客户端和服务器基于窗口通信以及文件传输的小例程。一,客户端 客户端的代码比服务器稍简单,总的来说,使用QT中的QTcpSocket类与服务器进行通信只需要以下5步: (1)创建QTcpSocket套接字对象socketnewQTcpSocket(this); (2)使用这个对象连接服务器QStringipui。lineEditiptext();获取ipintportui。lineEdit2text()。toInt();获取端口数据socketconnectToHost(ip,port); (3)使用write函数向服务器发送数据QByteArraydataui。lineEdit3text()。toUtf8();获取lineEdit控件中的数据并发送给服务器socketwrite(data); (4)当socket接收缓冲区有新数据到来时,会发出readRead()信号,因此为该信号添加槽函数以读取数据connect(socket,QTcpSocket::readyRead,this,QTcpClinet::ReadData);voidQTcpClinet::ReadData(){QByteArraybufsocketreadAll();ui。textEditappend(buf);} (5)断开与服务器的连接(关于close()和disconnectFromHost()的区别,可以按F1看帮助)socketdisconnectFromHost(); 客户端例程:(新建一个qt项目QTcpClinet(客户机))ui界面 本地回路ip:127。0。0。1可以连接到本地ip(电脑内部循环的ip) 如果要和局域网其他ip连接在运行(winR)cmdipconfigipv4地址查看本机ip 点击领取Qt学习资料视频教程链接 QTcpClinet。hincludeQtWidgetsQWidgetincludeuiQTcpClinet。hincludeQTcpSocket。hpragmaexecutioncharacterset(utf8)classQTcpClinet:publicQWidget{QOBJECTpublic:QTcpClinet(QWidgetparentQNULLPTR);QTcpClinet();publicslots:voidonbtnconnectclicked();voidReadData();voidonbtnpushclicked();private:Ui::QTcpClinetClassui;QTcpSocketsocket;创建socket指针};QTcpClinet。cppincludeQTcpClinet。hQTcpClinet::QTcpClinet(QWidgetparent):QWidget(parent){ui。setupUi(this);socketnewQTcpSocket(this);}QTcpClinet::QTcpClinet(){deletethissocket;回收内存}voidQTcpClinet::onbtnconnectclicked(){if(ui。btnconnecttext()tr(连接服务器)){QStringipui。lineEditiptext();获取ipintportui。lineEdit2text()。toInt();获取端口数据取消已有的连接socketabort();连接服务器socketconnectToHost(ip,port);boolisconnectsocketwaitForConnected();等待直到连接成功如果连接成功if(isconnect){ui。textEditappend(Theconnectionwassuccessful!!);ui。btnpushsetEnabled(true);按钮使能修改按键文字ui。btnconnectsetText(断开服务器连接);接收缓冲区(服务器)信息connect(socket,QTcpSocket::readyRead,this,QTcpClinet::ReadData);}else{ui。textEditappend(Theconnectionfalied!!);}}else{断开连接socketdisconnectFromHost();ui。btnconnectsetText(连接服务器);ui。btnpushsetEnabled(false);关闭发送按钮使能}}接收缓冲区信息函数voidQTcpClinet::ReadData(){QByteArraybufsocketreadAll();ui。textEditappend(buf);}发送按钮事件voidQTcpClinet::onbtnpushclicked(){QByteArraydataui。lineEdit3text()。toUtf8();获取lineEdit控件中的数据并发送给服务器socketwrite(data);判断是否写入成功booliswritesocketwaitForBytesWritten();if(iswrite){写入成功}else{没有写入成功}}二,服务器(需要一直运行哦) 服务器除了使用到了QTcpSocket类,还需要用到QTcpSever类。即便如此,也只是比客户端复杂一点点,用到了6个步骤: (1)创建QTcpSever对象servernewQTcpServer(this); (2)侦听一个端口,使得客户端可以使用这个端口访问服务器serverlisten(QHostAddress::Any,6677);监听所有ip和6677端口 (3)当服务器被客户端访问时,会发出newConnection()信号,因此为该信号添加槽函数,并用一个QTcpSocket对象接受客户端访问connect(server,QTcpServer::newConnection,this,TcpServer::ClientConnect);voidTcpServer::ClientConnect(){解析所有客户连接while(serverhasPendingConnections()){连接上后通过socket(QTcpSocket对象)获取连接信息socketservernextPendingConnection();QStringstrQString(〔ip:1,port:2〕)。arg(socketpeerAddress()。toString())。arg(socketpeerPort());监听客户端是否有消息发送connect(socket,QTcpSocket::readyRead,this,TcpServer::ReadData1);}} (4)使用socket的write函数向客户端发送数据socketwrite(data); (5)当socket接收缓冲区有新数据到来时,会发出readRead()信号,因此为该信号添加槽函数以读取数据监听客户端是否有消息发送connect(socket,QTcpSocket::readyRead,this,TcpServer::ReadData1);获取客户端向服务器发送的信息voidTcpServer::ReadData1(){QByteArraybufsocketreadAll();readAll最多接收65532的数据QStringstrQString(〔ip:1,port:2〕)。arg(socketpeerAddress()。toString())。arg(socketpeerPort());ui。textEditserverappend(strQString(buf));socketwrite(ok);服务器接收到信息后返回一个ok} (6)取消侦听serverclose(); 服务器例程:(添加一个新的qt项目TcpServer(服务器))ui界面TcpServer。hincludeQtWidgetsQWidgetincludeuiTcpServer。hincludeqtcpserver。hincludeqtcpsocket。hclassTcpServer:publicQWidget{QOBJECTpublic:TcpServer(QWidgetparentQNULLPTR);TcpServer();publicslots:voidonbtnserverclicked();voidonbtnlistenclicked();private:Ui::TcpServerClassui;QTcpServerserver;QTcpSocketsocket;一个客户端对应一个socketvoidClientConnect();voidReadData1();};TcpServer。cppincludeTcpServer。hincludeqstring。hincludeqdebug。hpragmaexecutioncharacterset(utf8)TcpServer::TcpServer(QWidgetparent):QWidget(parent){ui。setupUi(this);servernewQTcpServer(this);客户机连接信号槽connect(server,QTcpServer::newConnection,this,TcpServer::ClientConnect);}TcpServer::TcpServer(){serverclose();serverdeleteLater();}voidTcpServer::onbtnlistenclicked(){if(ui。btnlistentext()侦听){从输入框获取端口号intportui。lineEditporttext()。toInt();侦听指定端口的所有ipif(!serverlisten(QHostAddress::Any,port)){若出错,则输出错误信息qDebug()servererrorString();return;}修改按键文字ui。btnlistensetText(取消侦听);}else{socketabort();取消侦听serverclose();修改按键文字ui。btnlistensetText(侦听);}}voidTcpServer::ClientConnect(){解析所有客户连接while(serverhasPendingConnections()){连接上后通过socket获取连接信息socketservernextPendingConnection();QStringstrQString(〔ip:1,port:2〕)。arg(socketpeerAddress()。toString())。arg(socketpeerPort());提示连接成功ui。textEditserverappend(strConnecttotheserver);复选框选项为连接服务器的ipui。comboBoxaddItem(str);将socket地址放入combobox属性内ui。comboBoxsetItemData(ui。comboBoxcount()1,QVariant((int)socket));监听客户端是否有消息发送connect(socket,QTcpSocket::readyRead,this,TcpServer::ReadData1);}}获取客户端向服务器发送的信息voidTcpServer::ReadData1(){QByteArraybufsocketreadAll();readAll最多接收65532的数据QStringstrQString(〔ip:1,port:2〕)。arg(socketpeerAddress()。toString())。arg(socketpeerPort());ui。textEditserverappend(strQString(buf));}服务器向客户端发送信息voidTcpServer::onbtnserverclicked(){if(ui。comboBoxcount()0)return;QTcpSocketskt(QTcpSocket)ui。comboBoxitemData(ui。comboBoxcurrentIndex())。valueint();socketwrite(ui。lineEdit1text()。toUtf8());} 注意:write中需要写入char类型的元素或QByteArray类型的元素 效果展示: 三,TCPIP文件传输 上文实现了消息的传输,由于socketreadAll();(readAll最多接收65532的数据),因此对于大文件的传输用此方法是不可取的。 点击领取Qt学习资料视频教程链接 TCPIP文件传输的思路:客户端和服务器连接客户端选择文件,并发送文件给服务器(发送的是文件的帧头,格式:文件名大小)服务器触发readyRead,然后解析文件帧头(获取文件名和大小),并返回客户端一个ok消息客户端触发readyRead,然后发送文件数据,通过progressBar显示进度服务器再次触发readyRead,接收文件数据,并保存(通过ishead判断接收的是文件帧头还是文件数据) 代码实现: 新建服务器项目(TcpServer)TcpServer。hpragmaonceincludeQtWidgetsQWidgetincludeuiTcpServer。hincludeqtcpserver。hincludeqtcpsocket。hpragmaexecutioncharacterset(utf8)classTcpServer:publicQWidget{QOBJECTpublic:TcpServer(QWidgetparentQNULLPTR);voidhasConnect();private:Ui::TcpServerClassui;QTcpServerserver;QTcpSocketsocket;boolishead;QStringfileName;intfileSize;接收文件的总大小intrecvSize;当前接收文件的大小QByteArrayfilebuf;当前接收的文件数据};TcpServer。cppincludeTcpServer。hincludeqfile。hTcpServer::TcpServer(QWidgetparent):QWidget(parent){isheadtrue;ui。setupUi(this);servernewQTcpServer(this);监听1122端口的ipserverlisten(QHostAddress::Any,1122);如果有用户连接触发槽函数connect(server,QTcpServer::newConnection,this,TcpServer::hasConnect);}voidTcpServer::hasConnect(){while(serverhasPendingConnections()0)判断当前连接了多少人{用socket和我们的客户端连接,一个客户端对应一个套接字socketsocketservernextPendingConnection();服务器界面上输出客户端信息ui。textEditappend(QString(1:新用户连接)。arg(socketpeerPort()));如果客户端发送信息过来了,触发匿名函数connect(socket,QTcpSocket::readyRead,〔〕(){QByteArraybufsocketreadAll();用一个标志位ishead判断是头还是数据位if(ishead){如果是头,解析头(文件名,文件大小)QStringstrQString(buf);ui。textEditappend(str);QStringListstrliststr。split();fileNamestrlist。at(0);解析帧头文件名fileSizestrlist。at(1)。toInt();解析帧头文件大小isheadfalse;下次接收到的文件就是我们的数据recvSize0;filebuf。clear();socketwrite(ok);}else{根据文件名和文件大小接收和保存文件filebuf。append(buf);recvSizebuf。size();每接收一次文件,当前文件大小1当接收文件大小等于总文件大小,即文件数据接收完毕if(recvSizefileSize){保存文件QFilefile(ui。lineEdittext()fileName);file。open(QIODevice::WriteOnly);file。write(filebuf);file。close();isheadtrue;}}});}} 新建客户端项目(QTcpClient)QTcpClient。hincludeQtWidgetsQWidgetincludeuiQTcpClient。hincludeqtcpsocket。hpragmaexecutioncharacterset(utf8)classQTcpClient:publicQWidget{QOBJECTpublic:QTcpClient(QWidgetparentQNULLPTR);publicslots:voidonbtnconnectclicked();voidonbtnchooseclicked();voidonbtnopenclicked();private:Ui::QTcpClientClassui;QTcpSocketsocket;};QTcpClient。cppincludeQTcpClient。hincludeqfiledialog。hincludeqfileinfo。hQTcpClient::QTcpClient(QWidgetparent):QWidget(parent){ui。setupUi(this);socketnewQTcpSocket(this);}voidQTcpClient::onbtnconnectclicked(){QStringipui。lineEditiptext();获取ipintportui。lineEditporttext()。toInt();获取端口数据socketconnectToHost(ip,port);连接服务器等待连接成功if(socketwaitForConnected()){ui。textEditappend(fontcolorgreen连接服务器成功!font);ui。btnopensetEnabled(true);如果服务器发送信息到客户端,触发匿名函数connect(socket,QTcpSocket::readyRead,〔〕(){读取服务器发送的信息(即缓冲区信息)QByteArraybufsocketreadAll();if(bufok){QFilefile(ui。labelpathtext());if(!file。open(QIODevice::ReadWrite)){读取文件失败return;}qint64currentlen0;当前已经发送的大小qint64allLengthfile。size();总文件大小do{chardata〔1024〕;qint64msizefile。read(data,1024);读文件放入打他数组中,返回读取到的大小socketwrite(data,msize);把读取到的data数据发送给服务器currentlenmsize;实时获取当前发送的文件大小ui。progressBarsetValue(currentlen100allLength);更新界面进度条}while(currentlenallLength);当发送文件等于文件大小时,发送完毕,循环结束}});}else{ui。textEditappend(fontcolorred连接服务器失败!font);}}选择文件事件voidQTcpClient::onbtnchooseclicked(){QStringpathQFileDialog::getOpenFileName(this,打开文件,,(。));ui。labelpathsetText(path);}发送文件事件voidQTcpClient::onbtnopenclicked(){QFileInfoinfo(ui。labelpathtext());用QFileInfo::fileName,size获取文件名和大小格式:文件名大小服务器用该格式解析文件名和大小QStringheadQString(12)。arg(info。fileName())。arg(info。size());将该格式发送给服务器toUtf8:QString转QByteArray或char类型socketwrite(head。toUtf8());} 效果展示: