一、客户端/服务器架构
1.硬件(你的电脑跟打印机)
2.软件C/S架构
淘宝首页是服务器端,你的电脑的浏览器是客户端(这种也叫B/S架构)
百度云上的资源跟你的百度云客户端
你扯这么多,到底C/S架构跟socket有什么关系?
学习socket就是为了完成C/S架构的开发,阿拉索。
二、OSI七层协议
互联网的核心就是由一堆协议组成,协议就是标准,要想开发一款基于网络通信的软件,就必须遵循这些标准
先要回顾一些概念:
ip:标识一台机器位于哪个子网
mac:标识子网中的某台机器
端口号:标识某个应用
ip+端口号:标识互联网中唯一一个应用程序
程序的PID:标识同一台机器上不同的进程或者线程,不能用来区分应用程序,因为有些程序不止一个进程
咦,奇怪了,上面那个图找来找去都没有看到socket?莫急,待我优化优化
socket位于应用层与传输层之间,为什么?
你想想,数据最先产生于应用层,然后往下层层封装,最后发出去。既然应用层的数据要发出去,必须要遵循tcp/ip协议的,那是不是意味着你也要弄懂tcp/ip协议?No!没那么多精力和时间,考虑到这一点,socket层应运而生。
三、什么是socket
Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。所以,我们无需深入理解tcp/udp协议,socket已经为我们封装好了,我们只需要遵循socket的规定去编程,写出的程序自然就是遵循tcp/udp标准的。
四、套接字的发展史
套接字起源于 20 世纪 70 年代加利福尼亚大学伯克利分校版本的 Unix,即人们所说的 BSD Unix。 因此,有时人们也把套接字称为“伯克利套接字”或“BSD 套接字”。一开始,套接字被设计用在同 一台主机上多个应用程序之间的通讯。这也被称进程间通讯,或 IPC。套接字有两种(或者称为有两个种族),分别是基于文件型的和基于网络型的。
注意:进程间是不能直接通信(你的QQ能直接发信息给你的微信吗)
基于文件类型的套接字家族:AF_UNIX
unix一切皆文件,基于文件的套接字调用的就是底层的文件系统来取数据,两个套接字进程运行在同一机器,可以通过访问同一个文件系统间接完成通信
基于网络类型的套接字家族:AF_INET
还有AF_INET6被用于ipv6,还有一些其他的地址家族,不过,他们要么是只用于某个平台,要么就是已经被废弃,或者是很少被使用,或者是根本没有实现,所有地址家族中,AF_INET是使用最广泛的一个,python支持很多种地址家族,但是由于我们只关心网络编程,所以大部分时候我么只使用AF_INET
五、套接字工作流程
一个生活中的场景。你要打电话给一个朋友,先拨号,朋友听到电话铃声后提起电话,这时你和你的朋友就建立起了连接,就可以讲话了。等交流结束,挂断电话结束此次交谈。 生活中的场景就解释了这工作原理。
先从服务器端说起。服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束。
六、最简单socket客户端和服务端
服务器端
import socket phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #买电话 phone.bind(("127.0.0.1",800)) #买电话卡 phone.listen(5) #开机,其实这里是设置最大连接数 print("等待电话中...") conn,addr=phone.accept() #等待电话连接,直达拿到一个电话连接 msg=conn.recv(1024) #接收消息 print("客户端发来的消息是:",msg) conn.send(msg.upper()) #返回消息 conn.close() #挂电话 phone.close() #关机
客户端
import socket phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #买个电话 phone.connect(("127.0.0.1",800)) #拨通电话 phone.send("hello".encode("utf-8")) #发送消息 data=phone.recv(1024) #接收消息 print("收到服务端发来的消息:",data) #注意:客户端没有conn连接
有些时候我们在重启服务端的时候会出现这种情况:
表面原因:前面关闭的时候程序清理得不干净
深层原因:你的服务端仍然存在四次挥手的time_wait状态在占用地址
七、TCP/IP三次握手四次挥手
TCP/IP三次握手
step1:客户端发起第一次握手,请求建立连接
step2:服务端发送确认并请求建立连接(客户端到服务端链接已建立完毕)
step3:客户端发送确认给服务端(服务端到客户端链接已建立完毕)
什么是syn洪水攻击
黑客利用tcp/ip的漏洞,模拟一大堆假客户端发送SYN请求之后就跑路,由于服务端对这些SYN请求进行一一回应,占用了大量的资源,最后导致新的请求无法响应。
什么是半连接
只要没到“客户端发送确认给服务端”这一步都统称“半连接”
什么是半连接池
每来一个请求放到backlog,服务端每次从backlog取出一个连接,再做syn响应
conn.listen(5)就是半连接池,表示最大挂起的连接数为5。由于黑客可以模仿大量连接挤爆你的半连接池,所以,该值需要根据实际情况调。
conn.accept()相当于拿到一个TCP连接,就是服务端与客户端的双向连接
conn.close()触发的是四次挥手
phone.close()是关闭socket
注意:第一,服务端是可以主动发消息的;第二,TCP连接的可靠在于数据不丢失
TCP/IP四次挥手
客户端到服务端的数据发完了,客户端到服务端的连接就会断开
服务端到客户端的数据发完了,服务端到客户端的连接就会断开
为什么断开是四次,而握手是三次?
其实三次和四次的区别取决于中间那两步能够合并。挥手的时候是不可以合并的,以防服务端到客户端的数据还没发完就断开,导致数据丢失。但是三次握手还没涉及到数据传输,不存在数据丢失的问题,所以中间的那两步可以合并成一步。
服务器time_wait原因的分析
首先要知道的一点是:FIN_wait表示断开的主动发起方,有time_wait状态的代表的主动断开的一方
在大并发的场景中,服务器会存在大量的time_wait。这是由于服务端是不会保留客户端的连接,一旦完成服务端到客户端的数据传输后,服务端就会首先断开连接,因为多等你一分钟就是浪费自身的资源。现实生产中的多数情况都是服务器首先断开连接。
为了方便你理解,我画了一个服务器端断开的挥手图,跟TCP的四次挥手的两端是反过来的,因为是服务器先开始断开的。正因为服务器先断开了,所以一直收不到客户端发过来的断开请求,也就是“客户端到服务器端”这一条连接还在。所以你在关闭服务端后,在间隔很短后再起服务,就会出现地址被占用。