前言
IP地址的作用是唯一识别网络中的主机,IP位于网络层
协议+端口号:可以唯一识别主机中的应用程序(进程)
这样,利用三元组(IP地址,协议、端口)就可以标识网络的进程,网络中的进程通信就可以利用这个标识与其他进程进通信。
套接字Socket的来龙去脉:
socket即是一种特殊的文件,一些socket函数就是对其进行的操作(打开、读/写IO、关闭),这些函数我们在后面进行介绍。在组网领域的首次使用是在1970年2月12日发布的文献IETF RFC33中发现的,撰写者为Stephen Carr、Steve Crocker和Vint Cerf。根据美国计算机历史博物馆的记载,Croker写道:“命名空间的元素都可称为套接字接口。一个套接字接口构成一个连接的一端,而一个连接可完全由一对套接字接口规定。”计算机历史博物馆补充道:“这比BSD的套接字接口定义早了大约12年。
8.1 TCP/IP
TCP/IP:传输控制协议/网络协议是指能在多个不同网络间实现信息传输的协议簇。本协议不仅仅指的是TCP和IP两个协议,还有FTP、SMTP、TCP、UDP、IP等协议构成的协议簇。
根据以上介绍,可以看出TCP/IP协议中包含有UDP协议,姑且可以这样认为:UDP是TCP不完全子集,真子集。TCP提供IP环境下的数据可靠传输,它提供的服务包括数据流传输送、可靠性、有效流控、全双工操作和多路复用,是实现为所发送的数据开辟出连接好的通道,然后在进行数据发送。而UDP则不为IP提供可靠性的传输。
可以这样理解,TCP是加强版的UDP,UDP是精简版的TCP。这是因为TCP是可以多路复用的,有两个及以上套接字Socket,其中最基本的一个套接字是由socket()返回的用于监听(Listen)和接受(accept)客户端的连接请求,这个套接字不可以与客户端之间发送和接收数据。
另一个套接字,accept()接受一个客户端的连接请求,并返回一个新的套接字。这个新指的是该套接字与socket()返回的用于监听和接受客户端连接请求的套接字不是一个套接字,与本次客户端的通信是在这个新的套接字上发送和接收数据来完成的。
假设有N个客户端连接服务器,那么复位端共会有N+1个套接字,一个套接字是用于监听(listen())和接受(accept()),其余N个套接字是调用n次accept函数返回的不同套接字。
为什么要绑定?
固定一个端口。
TCP通信过程:
服务器侧:
由监听套接字监听客户端口的连接情况,当监听到客户端口的连接后,开始绑定端口(bind)并由接受(accept)产生一个通信套接字,通过对该通信套接字的读写实现服务器端和客户端的通信。
服务器端.pro文件:
由于是进行网络通信,需要添加network标识,使用lambda表达式,使用C++11特性:
QT+= network
CONFIG+=C++11
服务器端头文件:(serverwidget.h)
#include<QTcpServer> //监听套接字
#include<QTcpSocket> //通信套接字
由于在服务器侧有两个(及以上)套接字,需要定义两个套接字,一个是监听套接字,用于监听连接,另一个是通信套接字,用于通信,所以需要包含两个头文件进行变量定义。
QTcpServer*tcpServer; //监听套接字指针
QTcpSocket*tcpSocket; //通信套接字指针
变量定义操作是采用指针还是变量?
答:两者皆可,用变量不需要在主函数中为变量分配看空间,使用指针需要给指针动态分配空间。
给指针分配动态空间操作再栈上进行,操作方式以监听套接字指针为例:
tcpServer=newQTcpServer(this); //指定父对象,目的是为了自动回收空间
格式:
指针名=new 指针类型(父对象);
连接:
连接是通过给监听套接字添加监听的地址和端口,当客户端与服务器端连接成功后会产生newConnection()信号。
数据接收:
当客户端和服务器端建立连接后,服务器端会产生通信套接字,通过对通信套接字的readReady()函数进行触发即可进行数据的读取,readall()操作读取出的数据是字节序列额,可以直接添加到显示文本编辑区
数据发送:
按钮按下,数据发送,首先是获取发送文本编辑区文本内容QString类型,然后给通信套接字进行写操作,由于写入的数据类型为char*类型,所以需要使用toUtf8()函数将String类型数据转换为字节阵列,再使用data()函数将字节阵列转换为char*类型。
与客户端断开连接:
对通信套接字使用disconnectFromHost函数,然后将通信套接字关闭。
最终界面:
.pro文件
#------------------------------------------------- # # Project created by QtCreator 2020-01-30T23:13:39 # #------------------------------------------------- QT += core gui network greaterThan(QT_MAJOR_VERSION, 4): QT += widgets TARGET = 01_TCP TEMPLATE = app # The following define makes your compiler emit warnings if you use # any feature of Qt which as been marked as deprecated (the exact warnings # depend on your compiler). Please consult the documentation of the # deprecated API in order to know how to port your code away from it. DEFINES += QT_DEPRECATED_WARNINGS # You can also make your code fail to compile if you use deprecated APIs. # In order to do so, uncomment the following line. # You can also select to disable deprecated APIs only up to a certain version of Qt. #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 SOURCES += main.cpp serverwidget.cpp HEADERS += serverwidget.h FORMS += serverwidget.ui CONFIG +=C++11
serverwidget.h文件
#ifndef SERVERWIDGET_H #define SERVERWIDGET_H #include <QWidget> #include<QTcpServer> //监听套接字 #include<QTcpSocket> //通信套接字 namespace Ui { class ServerWidget; } class ServerWidget : public QWidget { Q_OBJECT public: explicit ServerWidget(QWidget *parent = 0); ~ServerWidget(); private slots: void on_buttonSend_clicked(); void on_buttonClose_clicked(); private: Ui::ServerWidget *ui; //用变量不需要为变量分配空间,用指针需要给指针动态分配空间 QTcpServer * tcpServer; //监听套接字指针 QTcpSocket * tcpSocket; //通信套接字指针 }; #endif // SERVERWIDGET_H
serverwidget.cpp文件
#include "serverwidget.h" #include "ui_serverwidget.h" ServerWidget::ServerWidget(QWidget *parent) : QWidget(parent), ui(new Ui::ServerWidget) { ui->setupUi(this); //给指针动态分配空间,监听套接字 tcpServer=new QTcpServer(this); //指定父对象,目的是为了自动回收空间 //在Qt中监听和绑定bind合为一步 /*QHostAddress::Any:绑定网卡所有的ip地址 * 8888:端口参数 * 告诉服务器监听地址和端口上的传入连接。 */ tcpServer->listen(QHostAddress::Any,8888); setWindowTitle("SERVER:8888"); //监听好之后取出accept()返回的套接字 //连接槽函数 connect(tcpServer,&QTcpServer::newConnection, [=]() { //取出建立好连接的套接字 tcpSocket=tcpServer->nextPendingConnection(); //在当前对话框显示连接 //获取对方的IP和端口 QString ip=tcpSocket->peerAddress().toString(); quint16 port=tcpSocket->peerPort(); //显示组包 QString temp=QString("[%1:%2]:successful Link!").arg(ip).arg(port); ui->textRead->setText(temp); //数据接收槽函数,上面已经有tcpSocket才能用。否则是野指针 connect(tcpSocket,&QTcpSocket::readyRead, [=]() { //从通信套接字中取出内容 QByteArray array= tcpSocket->readAll(); ui->textRead->append(array); //添加内容 } ); } ); } ServerWidget::~ServerWidget() { delete ui; } //数据发送槽函数 void ServerWidget::on_buttonSend_clicked() { //获取发送文本编辑区内容 QString str=ui->textWrite->toPlainText(); //发送给谁? //将数据发送给建立好的通信套接字 //给对方发送数据 //QString->char* tcpSocket->write(str.toUtf8().data()); } void ServerWidget::on_buttonClose_clicked() { //主动和客户端断开连接 tcpSocket->disconnectFromHost(); tcpSocket->close(); //之后关闭 }