这里写目录标题
线程池高并发服务器
UDP服务器
TCP与UDP机制的对比
TCP是面向连接的,可靠的数据包传输,也就是他会在复杂的网络环境中,在通信两端建立起连接,可以弥补网络层的不稳定,如果传输过程中丢包,那么会重新传递丢掉的包(主要依赖于ACK应答实现)
而UDP没有连接,且使用不可靠的数据报传递数据,类似于发电报的形式,他的每次数据传输路径都是随机的,且他并不负责查看是否数据被对方收到,我知道了你的地址,我只负责向目标地址发过去,具体收没收到,并不理会
TCP与UDP优缺点比较
TCP稳定性强,但是效率慢,开销大。主要应用于大数据传输、文件传输
UDP稳定性差,但是效率快,开销小。主要应用于游戏、视频会议、视频电话
但是,对于一些大厂,他会在利用UDP效率高的基础上,在应用层加入数据校验协议,弥补UDP的不稳定性等不足,相当于自己封装了协议
而针对于稳定性的原因:
TCP在网络中,在一个一个的路由节点网中,一旦建立了AB两端的连接,那么接下来所有的数据,都会按照既定顺序在该路线进行传输,所以,稳定性强,且数据的到达顺序是可预见的(因为只有一条线,谁先发送谁就会先到达):
而UDP:
他不会建立连接,他只知道B端的地址,所以会以随机的路径进行传输,那么第一个是路径随机,稳定性会无法保证,更重要的是,因为每次数据传输的路径随机,可能第二个发送的数据,他的路径要比第三个发送的数据的路径复杂,也就出现了第三个发送的数据会比第二个发送的数据更早到达目的地,所以,有顺序会被打乱的可能性:
UDP的C/S模型实现思路
模型分析
实现思路(对照TCP的C/S模型)
首先,由于UDP并不进行三次握手,所以,服务端的accept()、客户端的connect()函数都被舍弃
所以,对于server端来说:
首先还是要创建一个socket套接字,只不过这里的第二个参数传入SOCK_DGRAM,表示报式协议,且第三个参数还是传入0,表示默认的报式协议,即UDP
之后bind(),绑定地址结构
listen(),本来的功能是设置于服务端同时进行三次握手的最大客户端数量,但是UDP并不进行三次握手,所以,该函数也被舍弃
之后进入while循环,在循环内,无需做任何连接相关的操作,所以我们可以直接进行数据的读写,但是这里read、write函数都无法使用了,因为我们没有connfd,且recv、send函数也不能用,因为他们只能用于TCP,所以,这里read被替换成recvfrom函数,之后进行业务代码,最后将结过通过sendto函数写回。
最后关闭
然后来看client端:
首先在客户端需要创建一个客户端的socket,这里命名有些不妥,可以命名为clientfd,之后无需connect函数了,直接进行数据的读写即可,直接sendto,向服务端发送数据,然后revfrom接收服务端处理完之后的数据,最后写到屏幕,然后关闭客户端套接字
recvfrom函数、sendto函数
UDP的C/S模型的代码实现
server(对照TCP的socket的server)
以上是TCP的代码
与TCP对照来看,首先不再需要两个fd了,因为UDP不需要建立连接,所以,一个sockfd就够了,表示服务端socket,
且socket创建时使用报式协议
但是对于DUP,还是需要两个地址结构,分别表示服务端的地址结构和客户端的地址结构(因为虽然不用建立连接,但是总归是两端进行通信,所以地址结构是需要的,而套接字只有服务端一个就可以)
之后bind绑定服务端地址结构之后,无需listen来设置同时建立连接的上限了(俗称监听上限),因为不建立连接
客户端地址结构的长度可以保留,因为后续会用到
accept也不需要了,还是因为不用建立连接,不用三次握手,所以,不需要accept。
进入while循环:
可以将输出客户端的地址结构放在循环内,因为UDP无需建立连接,每次写入都会去到一个客户端,且并不保证是否到了指定的客户端,所以,将其放在每次recvfrom之后,可以查看是否发送到位
而read和write也都被改为了recvfrom和sendto,recvfrom时,会收到对端的地址结构,而sendto会指定目标端地址结构(一般是recvfrom收到的对端地址结构)
recvfrom第三个参数是传出参数,sendto第三个参数是传入参数
client(对照TCP的socket的client)
以上是TCP的client代码
首先,客户端也不能叫cfd(通信套接字),因为并不建立连接,所以,直接叫sockfd即可
首先还是用报式协议
之后,无需进行connect,因为没有三次握手
while循环里,首先是向服务器写入,注意,这里写入时,写入buf的实际大小,有利于节省空间,提升效率,如果使用sizeof,那么无论buf内有效内容是多少,都会把这个buf都写过去,然后地址结构传服务器地址结构(我们已知)
接收业务完成后的结果时,要使用sizeof(buf)接收,因为并不确定会写回来多少,地址结构填NULL,因为我们并不需要对端的地址结构(即服务器的地址结构,我们已知)
测试
一个终端启动服务端,其他终端启动client端,可以看到,基于TCP1对1的socket改写的UDP,可以实现多客户端连接,这是跟TCP不一样的
socket-IPC
与网络socket的不同
1、所有传AF_INET(ipv4)的地方,都改为AF_UNIX或者AF_LOCAL,
2、地址结构改变:
(1)地址结构类型变为struct sockaddr_un
(2)地址结构中的成员也发送了改变,
首先,AF_INET改为AF_UNIX
之后,在本地套接字中,并没有IP+port,因为这不是网络环境,所以,只有一个char数组,表示地址结构socket文件名(如下图),我们对该成员定义一个名字即可(该名字自定义即可,且可以包含路径,他是一种设定,而这个设定在此处不会执行,会到bind的时候,才会执行,如果没有路径,则在当前目录下创建套接字。且该名字与前面代码并无关系,前面socket使用变量名fd接收,且用于bind第一个参数,与此处无关)
对于strcpy函数:见后续图解(使用该方法,可以对一个已经定义好了的字符数组重命名)
(3)绑定地址结构时,参数都没变,只有第三个参数,要传入的是结构体实际大小,而这个大小不能使用sizeof,需要手动计算,计算len:offsetof(地址结构结构体类型,结构体第二个成员原生名)+strlen(第二个成员重命名之后的名字)
offsetof,是计算第二个参数到第一个参数之前的空间大小,第二个参数使用原生名(且仅仅使用sun_path即可,无需对象去点调用),因为其是固定的,可以确保求出来的是第二个参数之前所占用的空间,第一个参数传入结构体类型,是因为这样的话,会从首地址进行计算。所以实际上计算的是地址结构中,char数组之前占用了多少空间**(我们可以称之为首部长度,他固定是2)**
而后面的strlen传入重命名之后的名字,之所以使用重命名之后的名字,从逻辑上来看,他更能表示自定义内容的大小,或者说不完全固定的部分的空间大小,当然使用srv_addr.sun_path也是可以的,二选一即可
3、bind调用成功后,会创建一个socket,该socket就是以地址结构第二个成员命名的,所以,地址结构那里只是一种设定,而该设定会在bind处实际执行。所以bind调用之前,会进行unlink传入(设定的socket名),保证在创建该socket之前,该socket不存在(虽然该socket在后续也不会被用于通信,而只是标识一个地址,相当于网络编程中的IP+port)
unlink 是一个在 Unix 和类 Unix 系统(如 Linux)中常用的系统调用,用于删除一个文件系统中的文件或符号链接。当文件被成功删除后,其占用的磁盘空间(如果存在)会在没有其他进程引用该文件时被释放。
具体来说,unlink 并不直接删除文件的内容,而是从文件系统的目录结构中移除文件的一个引用(即文件的名字)。如果该文件没有其他引用(如硬链接),并且没有进程打开该文件,那么该文件占用的磁盘空间就会被标记为可重用,并在文件系统需要空间时被覆盖。
补充:关于strcpy:
代码实现
server
首先是创建一个监听套接字,lfd
之后对创建出的地址结构清零(相当于初始化为0)
然后对地址结构赋值
之后求出其实际长度:首部长度+strlen(char数组)
bind之前,使用unlink对重命名进行删除,保证以该名字命名的socket不存在,之后执行bind,以重命名的名字为名称的socket被创建(虽然并不会用于通信,而只是标识一个地址),该步的主要目的还是将服务器的地址结构绑定到 lfd 上
之后listen,设置监听上限
while循环中进行监听,使用accept,进行阻塞监听。(注意,参数传入时,第三个参数是socklen_t * 类型,且这里是使用sizeof求“预存客户端地址结构变量”开辟的空间大小。第二个参数传出对端的地址结构)
返回cfd,这个就是与客户端建立了通信的cfd。
接下来的三行,是打印客户端的地址结构中的文件名,可有可无
之后就是while循环进行业务处理,最后关闭cfd、lfd即可
client
在本地套接字通信中,客户端方面,与网络不同的是,还需要创建自己的地址结构,而之前网络套接字中,是不需要手动设置客户端的地址结构的,而是系统直接进行隐式绑定,如下图:
只有对服务端地址结构的处理,没有对自己地址结构的处理,因为客户端会随机一个可用的地址结构用于通信,并将该地址结构传给服务端
以下是client的代码:
可以看到,在分割线上方,是对本地的地址结构进行了显式创建,并绑定给cfd,分割线下方,是跟网络编程类似的,拿到服务端的地址结构后,传入connect,将两个地址的fd进行connect
后面就跟网络socket一样了,进行读写
注意:在创建客户端地址结构的时候,也需要unlink其套接字的名字,否则,如果本地有的话,会导致启动失败,如下图:
网络socket与本地socket对比
注意,最后accept中第三个参数是sizeof(clie_addr)
注意,网络socket中,并不会对client自己的地址结构进行创建or其他处理,而是依赖系统随机分配一个地址结构给服务端
而本地socket中,需要自己创建客户端的地址结构