IP地址的作用是唯一识别网络中的主机,IP位于网络层
协议+端口号:可以唯一识别主机中的应用程序(进程)
这样,利用三元组(IP地址,协议、端口)就可以标识网络的进程,网络中的进程通信就可以利用这个标识与其他进程进通信。
socket即是一种特殊的文件,一些socket函数就是对其进行的操作(打开、读/写IO、关闭),这些函数我们在后面进行介绍。在组网领域的首次使用是在1970年2月12日发布的文献IETF RFC33中发现的,撰写者为Stephen Carr、Steve Crocker和Vint Cerf。根据美国计算机历史博物馆的记载,Croker写道:“命名空间的元素都可称为套接字接口。一个套接字接口构成一个连接的一端,而一个连接可完全由一对套接字接口规定。”计算机历史博物馆补充道:“这比BSD的套接字接口定义早了大约12年。
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函数返回的不同套接字。为什么要绑定?:固定一个端口
服务器侧:
由监听套接字监听客户端口的连接情况,当监听到客户端口的连接后,开始绑定端口(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函数,然后将通信套接字关闭。
通信套接字连接到主机,三次握手,握手成功后产生connected()信号,当服务器端向客户服务器端发送数据时,客户端会产生readyRead()信号,将会触发读取槽函数。
客户端.pro文件:
由于是网络通信应用,所以添加
QT += network
CONFIG+=C++11 //Lambda
客户端头文件:
客户端无需监听服务器端,所以只需要一个通信套接字,所以需要包含头文件:
#include<QTcpSocket>//通信套接字
定义通信套接字指针:
QTcpSocket*tcpSocket;//通信套接字
客户端.cpp文件:
1)、首先是需要两个行编辑区,分别输入IP和端口号
2)、需要一个按钮,用于连接服务器端
3)、需要两个文本编辑区,其中一个位只读属性,用于显示服务器端发送的数据。另一个作用是输入将要发送的数据。
4)、需要两个按键,一个是用于发送,另一个是用于断开客户端与服务器端的连接。
由于客户端使用通信套接字进行通信,通信套接字的定义形式为指针,所以需要在使用指针之前开辟指针分配空间,并指定父对象(用于程序结束时的内存回收):
tcpSocket=newQTcpSocket(this);
客户端输入IP地址和端口号之后,点击按钮connect和服务器端进行连接,连接的具体方式为:首先获取服务器的IP,也就是获取行编辑区的输入内容,然后是获取端口信号,然后将前两者作为传入参数使用连接函数与服务器进行连接:
voidClientWidget::on_buttonConnect_clicked()
{
//获取服务器ip和端口
QStringip=ui->lineEditIP->text();
qint16port=ui->lineEditPort->text().toInt();
//主动和服务器建立连接
tcpSocket->connectToHost(QHostAddress(ip),port);
}
为了了解客户端与服务器端的连接情况,使用lambda函数进行信息打印,客户端与服务器端连接成功之后走,会产生connected信号:
connect(tcpSocket,&QTcpSocket::connected,
[=]()
{
ui->textEditRead->setText("successfulLink!");
} );
当send按钮按下时,将数据进行发送,发送的方式与服务器端的处理方式类似:
voidClientWidget::on_buttonSend_clicked()
{
//获取编辑框内容,内容转换为字符串形式
QStringstr=ui->textEditWrite->toPlainText();
//发送数据,首先将数据转换为utf8格式,然后通过data函数将数据转换为char*格式
tcpSocket->write(str.toUtf8().data());
}
当服务器端发送数据给客户端时,客户端会产生一个readyRead信号,该信号会触发数据读取操作:
connect(tcpSocket,&QTcpSocket::readyRead,
[=]()
{
//获取对方发送的内容
QByteArrayarray=tcpSocket->readAll();
//追加到编辑区中
ui->textEditRead->append(array);
});
当客户端想要与服务器端断开连接时,首先是通过通信套接字通信套接字断开与服务器端的连接,然后将通信套接字关闭:
voidClientWidget::on_buttonClose_clicked()
{
//主动和对方断开连接
tcpSocket->disconnectFromHost();
tcpSocket->close();
}
运行结果: