Socket技术详解(⼀篇就够了)
Socket原理
1、什么是Socket
在计算机通信领域,socket 被翻译为“套接字”,它是计算机之间进⾏通信的⼀种约定或⼀种⽅式。通过 socket 这种约定,⼀台计算机可以接收其他计算机的数据,也可以向其他计算机发送数据
socket起源于Unix,⽽Unix/Linux基本哲学之⼀就是“⼀切皆⽂件”,都可以⽤“打开open –> 读写write/read –> 关闭close”模式来操作。
我的理解就是Socket就是该模式的⼀个实现:即socket是⼀种特殊的⽂件,⼀些socket函数就是对其进⾏的操作(读/写IO、打开、关闭)。
Socket()函数返回⼀个整型的Socket描述符,随后的连接建⽴、数据传输等操作都是通过该Socket实现的。
2、⽹络中进程如何通信
既然Socket主要是⽤来解决⽹络通信的,那么我们就来理解⽹络中进程是如何通信的。
2.1、本地进程间通信
a、消息传递(管道、消息队列、FIFO)
b、同步(互斥量、条件变量、读写锁、⽂件和写记录锁、信号量)?【不是很明⽩】
c、共享内存(匿名的和具名的,eg:channel)
d、远程过程调⽤(RPC)
2.2、⽹络中进程如何通信
我们要理解⽹络中进程如何通信,得解决两个问题:
a、我们要如何标识⼀台主机,即怎样确定我们将要通信的进程是在那⼀台主机上运⾏。
b、我们要如何标识唯⼀进程,本地通过pid标识,⽹络中应该怎样标识?
解决办法:
a、TCP/IP协议族已经帮我们解决了这个问题,⽹络层的“ip地址”可以唯⼀标识⽹络中的主机
b、传输层的“协议+端⼝”可以唯⼀标识主机中的应⽤程序(进程),因此,我们利⽤三元组(ip地址,协议,端⼝)就可以标识⽹络的进程了,⽹络中的进程通信就可以利⽤这个标志与其它进程进⾏交互
3、Socket怎么通信
现在,我们知道了⽹络中进程间如何通信,即利⽤三元组【ip地址,协议,端⼝】可以进⾏⽹络间通信了,那我们应该怎么实现了,因此,我们socket应运⽽⽣,它就是利⽤三元组解决⽹络通信的⼀个中间件⼯具,就⽬前⽽⾔,⼏乎所有的应⽤程序都是采⽤socket,如UNIX BSD的套接字(socket)和UNIX System V的TLI(已经被淘汰)。
Socket通信的数据传输⽅式,常⽤的有两种:
a、SOCK_STREAM:表⽰⾯向连接的数据传输⽅式。数据可以准确⽆误地到达另⼀台计算机,如果损坏或丢失,可以重新发送,但效率相对较慢。常见的 http 协议就使⽤ SOCK_STREAM 传输数据,因为要确保数据的正确性,否则⽹页不能正常解析。
b、SOCK_DGRAM:表⽰⽆连接的数据传输⽅式。计算机只管传输数据,不作数据校验,如果数据在传输中损坏,或者没有到达另⼀台计算机,是没有办法补救的。也就是说,数据错了就错了,⽆法重传。因为 SOCK_DGRAM 所做的校验⼯作少,所以效率⽐
SOCK_STREAM ⾼。
例如:QQ 视频聊天和语⾳聊天就使⽤ SOCK_DGRAM 传输数据,因为⾸先要保证通信的效率,尽量减⼩延迟,⽽数据的正确性是次要的,即使丢失很⼩的⼀部分数据,视频和⾳频也可以正常解析,最多出现噪点或杂⾳,不会对通信质量有实质的影响
4、TCP/IP协议
4.1、概念
TCP/IP【TCP(传输控制协议)和IP(⽹际协议)】提供点对点的链接机制,将数据应该如何封装、定址、传输、路由以及在⽬的地如何接收,都加以标准化。它将软件通信过程抽象化为四个抽象层,采取协议堆栈的⽅式,分别实现出不同通信协议。协议族下的各种协议,依其功能不同,被分别归属到这四个层次结构之中,常被视为是简化的七层OSI模型。
它们之间好⽐送信的线路和驿站的作⽤,⽐如要建议送信驿站,必须得了解送信的各个细节。
TCP(Transmission Control Protocol,传输控制协议)是⼀种⾯向连接的、可靠的、基于字节流的通信协议,数据在传输前要建⽴连接,传输完毕后还要断开连接,客户端在收发数据前要使⽤ connect() 函数和服务器建⽴连接。建⽴连接的⽬的是保证IP地址、端⼝、物理链路等正确⽆误,为数据的传输开辟
通道。
TCP建⽴连接时要传输三个数据包,俗称三次握⼿(Three-way Handshaking)。可以形象的⽐喻为下⾯的对话:
[Shake 1] 套接字A:“你好,套接字B,我这⾥有数据要传送给你,建⽴连接吧。”
[Shake 2] 套接字B:“好的,我这边已准备就绪。”
[Shake 3] 套接字A:“谢谢你受理我的请求。
4.2、TCP的粘包问题以及数据的⽆边界性:
4.4、TCP数据报结构:
带阴影的⼏个字段需要重点说明⼀下:
(1) 序号:Seq(Sequence Number)序号占32位,⽤来标识从计算机A发送到计算机B的数据包的序号,计算机发送数据时对此进⾏标记。
(2) 确认号:Ack(Acknowledge Number)确认号占32位,客户端和服务器端都可以发送,Ack = Seq + 1。
(3) 标志位:每个标志位占⽤1Bit,共有6个,分别为 URG、ACK、PSH、RST、SYN、FIN,具体含义如下:
(1)URG:紧急指针(urgent pointer)有效。
(2)ACK:确认序号有效。
(3)PSH:接收⽅应该尽快将这个报⽂交给应⽤层。
(4)RST:重置连接。
(5)SYN:建⽴⼀个新连接。
(6)FIN:断开⼀个连接。
4.5、连接的建⽴(三次握⼿):
使⽤ connect() 建⽴连接时,客户端和服务器端会相互发送三个数据包,请看下图:
客户端调⽤ socket() 函数创建套接字后,因为没有建⽴连接,所以套接字处于CLOSED状态;服务器端调⽤ listen() 函数后,套接字进⼊LISTEN状态,开始监听客户端请求
这时客户端发起请求:
1) 当客户端调⽤ connect() 函数后,TCP协议会组建⼀个数据包,并设置 SYN 标志位,表⽰该数据包是⽤来建⽴同步连接的。同时⽣成⼀个随机数字 1000,填充“序号(Seq)”字段,表⽰该数据包的序号。完成这些⼯作,开始向服务器端发送数据包,客户端就进⼊了SYN-SEND状态。
2) 服务器端收到数据包,检测到已经设置了 SYN 标志位,就知道这是客户端发来的建⽴连接的“请求包”。服务器端也会组建⼀个数据包,并设置 SYN 和 ACK 标志位,SYN 表⽰该数据包⽤来建⽴连接,ACK ⽤来确认收到了刚才客户端发送的数据包
服务器⽣成⼀个随机数 2000,填充“序号(Seq)”字段。2000 和客户端数据包没有关系。
服务器将客户端数据包序号(1000)加1,得到1001,并⽤这个数字填充“确认号(Ack)”字段。
服务器将数据包发出,进⼊SYN-RECV状态
3) 客户端收到数据包,检测到已经设置了 SYN 和 ACK 标志位,就知道这是服务器发来的“确认包”。客户端会检测“确认号(Ack)”字段,看它的值是否为 1000+1,如果是就说明连接建⽴成功。
接下来,客户端会继续组建数据包,并设置 ACK 标志位,表⽰客户端正确接收了服务器发来的“确认
包”。同时,将刚才服务器发来的数据包序号(2000)加1,得到 2001,并⽤这个数字来填充“确认号(Ack)”字段。
客户端将数据包发出,进⼊ESTABLISED状态,表⽰连接已经成功建⽴。
4) 服务器端收到数据包,检测到已经设置了 ACK 标志位,就知道这是客户端发来的“确认包”。服务器会检测“确认号(Ack)”字段,看它的值是否为 2000+1,如果是就说明连接建⽴成功,服务器进⼊ESTABLISED状态。
⾄此,客户端和服务器都进⼊了ESTABLISED状态,连接建⽴成功,接下来就可以收发数据了。
4.6、TCP四次握⼿断开连接
建⽴连接⾮常重要,它是数据正确传输的前提;断开连接同样重要,它让计算机释放不再使⽤的资源。如果连接不能正常断开,不仅会造成数据传输错误,还会导致套接字不能关闭,持续占⽤资源,如果并发量⾼,服务器压⼒堪忧。
断开连接需要四次握⼿,可以形象的⽐喻为下⾯的对话:
[Shake 1] 套接字A:“任务处理完毕,我希望断开连接。”
[Shake 2] 套接字B:“哦,是吗?请稍等,我准备⼀下。”
等待⽚刻后……
[Shake 3] 套接字B:“我准备好了,可以断开连接了。”
[Shake 4] 套接字A:“好的,谢谢合作。”
下图演⽰了客户端主动断开连接的场景:
建⽴连接后,客户端和服务器都处于ESTABLISED状态。这时,客户端发起断开连接的请求:
1. 客户端调⽤ close() 函数后,向服务器发送 FIN 数据包,进⼊FIN_WAIT_1状态。FIN 是 Finish 的缩写,表⽰完成任务需要断开连
接。
2. 服务器收到数据包后,检测到设置了 FIN 标志位,知道要断开连接,于是向客户端发送“确认包”,进⼊CLOSE_WAIT状态。
注意:服务器收到请求后并不是⽴即断开连接,⽽是先向客户端发送“确认包”,告诉它我知道了,我需要准备⼀下才能断开连接。
3. 客户端收到“确认包”后进⼊FIN_WAIT_2状态,等待服务器准备完毕后再次发送数据包。
4. 等待⽚刻后,服务器准备完毕,可以断开连接,于是再主动向客户端发送 FIN 包,告诉它我准备好了,断开连接吧。然后进⼊
LAST_ACK状态。
5. 客户端收到服务器的 FIN 包后,再向服务器发送 ACK 包,告诉它你断开连接吧。然后进⼊TIME_WAIT状态。
6. 服务器收到客户端的 ACK 包后,就断开连接,关闭套接字,进⼊CLOSED状态。
4.7、关于 TIME_WAIT 状态的说明
客户端最后⼀次发送 ACK包后进⼊ TIME_WAIT 状态,⽽不是直接进⼊ CLOSED 状态关闭连接,这是为什么呢?
TCP 是⾯向连接的传输⽅式,必须保证数据能够正确到达⽬标机器,不能丢失或出错,⽽⽹络是不稳定的,随时可能会毁坏数据,所以机器A每次向机器B发送数据包后,都要求机器B”确认“,回传ACK包,告诉机器A我收到了,这样机器A才能知道数据传送成功了。如果机器B没有回传ACK包,机器A会重新发送,直到机器B回传ACK包。
客户端最后⼀次向服务器回传ACK包时,有可能会因为⽹络问题导致服务器收不到,服务器会再次发送 FIN 包,如果这时客户端完全关闭了连接,那么服务器⽆论如何也收不到ACK包了,所以客户端需要等待⽚刻、确认对⽅收到ACK包后才能进⼊CLOSED状态。那么,要等待多久呢?
数据包在⽹络中是有⽣存时间的,超过这个时间还未到达⽬标主机就会被丢弃,并通知源主机。这称为
报⽂最⼤⽣存时间进程通信方式
(MSL,Maximum Segment Lifetime)。TIME_WAIT 要等待 2MSL 才会进⼊ CLOSED 状态。ACK 包到达服务器需要 MSL 时间,服务器重传 FIN 包也需要 MSL 时间,2MSL 是数据包往返的最⼤时间,如果 2MSL 后还未收到服务器重传的 FIN 包,就说明服务器已经收到了 ACK 包
4.8.优雅的断开连接–shutdown()
close()/closesocket()和shutdown()的区别
确切地说,close() / closesocket() ⽤来关闭套接字,将套接字描述符(或句柄)从内存清除,之后再也不能使⽤该套接字,与C语⾔中的fclose() 类似。应⽤程序关闭套接字后,与该套接字相关的连接和缓存也失去了意义,TCP协议会⾃动触发关闭连接的操作。
shutdown() ⽤来关闭连接,⽽不是套接字,不管调⽤多少次 shutdown(),套接字依然存在,直到调⽤ close() / closesocket() 将套接字从内存清除。
调⽤ close()/closesocket() 关闭套接字时,或调⽤ shutdown() 关闭输出流时,都会向对⽅发送 FIN 包。FIN 包表⽰数据传输完毕,计算机收到 FIN 包就知道不会再有数据传送过来了。
默认情况下,close()/closesocket() 会⽴即向⽹络中发送FIN包,不管输出缓冲区中是否还有数据,⽽shutdown() 会等输出缓冲区中的数据传输完毕再发送FIN包。也就意味着,调⽤ close()/closesocket() 将丢失输出缓冲区中的数据,⽽调⽤ shutdown() 不会
5、OSI模型
TCP/IP对OSI的⽹络模型层进⾏了划分如下:
TCP/IP协议参考模型把所有的TCP/IP系列协议归类到四个抽象层中
应⽤层:TFTP,HTTP,SNMP,FTP,SMTP,DNS,Telnet 等等
传输层:TCP,UDP
⽹络层:IP,ICMP,OSPF,EIGRP,IGMP
数据链路层:SLIP,CSLIP,PPP,MTU
每⼀抽象层建⽴在低⼀层提供的服务上,并且为⾼⼀层提供服务,看起来⼤概是这样⼦的
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论