Python中socket解读
操作系统底层原理
操作系统:(Operating System,简称OS)是管理和控制计算机硬件与软件资源的计算机程序,是直接运行在“裸机”上的最基本的系统软件,任何其他软件都必须在操作系统的支持下才能运行。
注:计算机(硬件)->os->应用软件
网络通信原理
互联网的本质就是一系列的网络协议
一台硬设有了操作系统,然后装上软件你就可以正常使用了,然而你也只能自己使用
像这样,每个人都拥有一台自己的机器,然而彼此孤立
如何能大家一起玩耍
结论:英语成为世界上所有人通信的统一标准,如果把计算机看成分布于世界各地的人,那么连接两台计算机之间的internet实际上就是一系列统一的标准,这些标准称之为互联网协议,互联网的本质就是一系列的协议,总称为‘互联网协议’(Internet Protocol Suite).
互联网协议的功能:定义计算机如何接入internet,以及接入internet的计算机通信的标准。
网络基础架构
1.C/S 架构 :client客户端 和 server服务器端 / 客户机和服务器结构
优点:
1.充分利用两端硬件环境的优势,将任务合理分配
2.能够实现复杂的应用构造,安全性高,数据传输速度快。
3.应用服务器运行数据负荷较轻
4.数据的储存管理功能较为透明
缺点:
1.高昂的维护成本且投资大
2.B/S架构:browser浏览器和server服务器端 / 浏览器和服务器结构
优点:
1.统一了应用的接口,简化了客户端电脑载荷,减轻了系统维护与升级的成本和工作量,
降低了用户的总体成本(TCO)。
2.可以在任何地方进行操作而不用安装任何专门的软件,只要有一台能上网的电脑就能使用,
客户端零安装、零维护。系统的扩展非常容易
缺点:
1.应用服务器运行数据负荷较重
注:C/S和B/S并没有本质的区别:B/S是基于特定通信协议(HTTP)的C/S架构,也就是说B/S包含在C/S中,是特殊的C/S架构。之所以在C/S架构上提出B/S架构,是为了满足瘦客户端、一体化客户端的需要,最终目的节约客户端更新、维护等的成本,及广域资源的共享。
1.B/S属于C/S,浏览器只是特殊的客户端;
2.C/S可以使用任何通信协议,而B/S这个特殊的C/S架构规定必须实现HTTP协议;
3.浏览器是一个通用客户端,本质上开发浏览器,还是实现一个C/S系统。
应用场景:
B/S适用于用户群庞大,或客户需求经长发生变化的情况。
C/S功能强大,可以减轻服务器端压力,如果用户的需求特别复杂,用C/S。
局域网与交换机/网络常见术语
原主机第一次查询广播,目标机收到就返回自己的ip地址和mac地址
回去用单播,因为有互相的ip地址了,交换机五分钟清空记录
广播:有了mac地址,同一网络内的两台主机就可以通信了(一台主机通过arp协议获取另外一台主机的mac地址)
ethernet采用最原始的方式,广播的方式进行通信,即计算机通信基本靠吼
1.单播(Unicast)
交换机记录mac地址,是在一个单个的发送者和一个接受者之间通过网络进行的通信。
2.组播
传输在发送者和每一接收者之间实现点对多点网络连接。 如果一台发送者同时给多个接收者传输相同的数据,也只需复制一份相同的数据包。 它提高了数据传送效率,减少了骨干网络出现拥塞的可能性。
3.Mac地址
是物理地址:唯一的作用可以唯一标识一台电脑,相当于一个人是身份证
4.ipv4地址
四位点分十进制 相当于当前所在位置的定位,相当于一个人的学号
5.请求帧
ip Mac 要找的ip
6.arp协议
通过ip地址获取目标Mac地址的协议
7.端口
操作系统为本机上运行的程序都随机分配一个端口,其他电脑上的程序可以通过端口获取到这个程序
8.子网掩码(subnet mask)
将某个IP地址划分成网络地址和主机地址两部分
9.默认网关(default gateway)
网关内的所有ip向外通讯都要经过它,出口和入口
10.DNS服务器
域名服务器(Domain Name Server)在Internet上域名与IP地址之间是一一对应的,域名虽然便于人们记忆,但机器之间只能互相认识IP地址,它们之间的转换工作称为域名解析,域名解析需要由专门的域名解析服务器来完成,DNS就是进行域名解析的服务器
11.DHCP服务器
就是能够自动识别当前网络环境,并依照当前网络环境给你分配合适的ip地址的服务器。它的存在就是使手动设置Ip变得更加傻瓜式,实现一键上网。不需要你自己进行手动的配置
12.ping
it术语中的ping类似于下图的声呐设置,如果目的电脑联网,目的电脑会把这个Ping包发送回来。如果目的电脑不联网,则ping包不会返还任何信息。所以在It界会经常使用这个命令来检测电脑是否已经连上网络
13.网段号
我们知道ip由两部分构成 网段号+主机号。当我们使用自身的子网掩码和目的主机进行&运算时,我们就能得出目的主机的网段号,进而判断目的主机与自己是否处在同一个网络中。
注:交换机和路由器的区别:
交换机主要功能:组织局域网,经过交换机内部处理解析信息之后,将信息已点对点,点对多的形式,发送给固定端
交换机是利用物理地址或者说MAC地址来确定转发数据的目的地址。
路由器主要功能:进行跨网段进行数据传输,路由选择最佳路径
注:如果需要将多太电脑连接到一根网线,用交换机即可
如果只有一个外网ip,多台电脑想上网,用路由即可
OSI七层模型
互联网协议按照功能不同分为osi七层或tcp/ip五层或tcp/ip四层
每层运行常见物理设备
TCP/IP五层模型讲解
我们将应用层,表示层,会话层并作应用层,从tcp/ip五层协议的角度来阐述每层的由来与功能,搞清楚了每层的主要协议,就理解了整个互联网通信的原理。
首先,用户感知到的只是最上面一层应用层,自上而下每层都依赖于下一层,所以我们从最下一层开始切入,比较好理解,每层都运行特定的协议,越往上越靠近用户,越往下越靠近硬件。
物理层
如:光纤,集线器,网线
物理层由来:
1.实现计算机之间物理连接
2.计算机之间交流必须完成组网。
物理层功能:
1.主要是基于电器特性发送高低电压(电信号),高电压对应数字1,低电压对应数字0
数据链路层(arp协议)
如:交换机,网卡,网桥
数据链路层由来:
1.规定了二进制数据分组原理
2.规定了只要接入物联网的计算机,都必须有一块网卡,网卡号唯一
3.单纯的电信号0和1没有任何意义,必须规定电信号多少位一组,每组什么意思
注:其实也就是以太网协议
数据链路层的功能:
1.定义了电信号的分组方式
网络层(ip协议)
如:路由器,三层交换机,
网络层功能:
1.规定了计算机必须有一个ip地址
2.ip协议可以跨局域网传输
3.引入一套新的地址用来区分不同的广播域/子网,这套地址即网络地址
传输层(端口协议,UDP,TCP)
如:四层交换机,四层路由器
TCP,UDP基于端口工作的协议
端口(port): 唯一标识一台机器上某一个基于网络通信的应用程序
端口范围:(动态分配)0-65535,0-1023为系统端口,也叫BSD保留端口
(1024-8000之间都有使用的),起端口最好在8000后
传输层的由来:
网络层的ip帮我们区分子网,以太网层的mac帮我们找到主机,然后大家使用的都是应用程序,你的电脑上可能同时开启qq,暴风影音,等多个应用程序,
传输层功能:
建立端口到端口的通信
tcp三次握手和四次挥手
建立连接三次握手:
1.客户端向服务器发送建立连接请求 ,主机A向主机B发送TCP连接请求数据包
2.服务器收到请求后发送确认请求给客户端 ,主机B收到请求后,会返回连接确认数据包
3.客户端收到信息后确认且返回信息 ,主机A收到主机B的确认报文后,还需作出确认
断开连接四次挥手:
1.客户端先向服务器发送断开请求,等待服务器确认
2.服务器向客户端发送信息确认释放报文
3.服务器再发送关闭信息给客户端确认释放端口
4.客户端收到后返回一个确认信息给服务器
注:服务器发送关闭信息给客户端,如果没有接受到返回信息,会等待俩个报文的时间在这之间也会一段时间发一次,超过就自动销毁关闭报文
应用层(HTTP,HTTPS,FTP)
应用层功能:规定应用程序的数据格式。
每层常见的协议
TCP协议和UDP协议
TCP(Transmission Control Protocol)可靠的、面向连接的协议(eg:打电话)、传输效率低全双工通信(发送缓存&接收缓存)、面向字节流。
使用TCP的应用:Web浏览器;电子邮件、文件传输程序。
TCP协议的编码流程:
服务器端:
1.实例化对象
2.绑定IP地址和端口号
3.监听
4.接收客户端的连接
5.收发
6.关闭
客户端:
1.实例化对象
2.连接服务器
3.收发
4.关闭
UDP(User Datagram Protocol)不可靠的、无连接的服务,传输效率高(发送前时延小),一对一、一对多、多对一、多对多、面向报文,尽最大努力服务,无拥塞控制。
使用UDP的应用:域名系统 (DNS);视频流;IP语音(VoIP)
粘包
只有TCP有粘包现象,UDP永远不会粘包
TCP协议是面向流的协议:
发送端可以是一K一K地发送数据,而接收端的应用程序可以两K两K地提走数据,当然也有可能一次提走3K或6K数据,或者一次只提走几个字节的数据,也就是说,应用程序所看到的数据是一个整体,或说是一个流(stream),一条消息有多少字节对应用程序是不可见的,因此TCP协议是面向流的协议,这也是容易出现粘包问题的原因,相当于一条河流你用刀砍是不可能砍断的,字节流没有明确的边界。
UDP是面向消息的协议:
每个UDP段都是一条消息,应用程序必须以消息为单位提取数据,不能一次提取任意字节的数据,这一点和TCP是很不同的
粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的
粘包是由TCP协议本身造成的,TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送一个TCP段。若连续几次需要send的数据都很少,通常TCP会根据优化[算法](http://lib.csdn.net/base/datastructure)把这些数据合成一个TCP段后一次发送出去,这样接收方就收到了粘包数据。
1. TCP(transport control protocol,传输控制协议)是面向连接的,面向流的,提供高可靠性服务。收发两端(客户端和服务器端)都要有一一成对的socket,因此,发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。这样,接收端,就难于分辨出来了,必须提供科学的拆包机制。 即面向流的通信是无消息保护边界的。
2. UDP(user datagram protocol,用户数据报协议)是无连接的,面向消息的,提供高效率服务。不会使用块的合并优化算法,, 由于UDP支持的是一对多的模式,所以接收端的skbuff(套接字缓冲区)采用了链式结构来记录每一个到达的UDP包,在每个UDP包中就有了消息头(消息来源地址,端口等信息),这样,对于接收端来说,就容易进行区分处理了。 即面向消息的通信是有消息保护边界的。
3. tcp是基于数据流的,于是收发的消息不能为空,这就需要在客户端和服务端都添加空消息的处理机制,防止程序卡住,而udp是基于数据报的,即便是你输入的是空内容(直接回车),那也不是空消息,udp协议会帮你封装上消息头
udp的recvfrom是阻塞的,一个recvfrom(x)必须对唯一一个sendinto(y),收完了x个字节的数据就算完成,若是y>x数据就丢失,这意味着udp根本不会粘包,但是会丢数据,不可靠
tcp的协议数据不会丢,没有收完包,下次接收,会继续上次继续接收,己端总是在收到ack时才会清除缓冲区内容。数据是可靠的,但是会粘包。
两种情况下会发生粘包
1.发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据了很小,会合到一起,产生粘包)
2.接收方不及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包)
总结:
在tcp协议中,有一个合包机制(nagle算法),将多次连续发送且间隔较小的数据,进行打包成一块数据传送.
还有一个机制是拆包机制,在发送端因为受到网卡的MTU限制,会将大的超过MTU限制的数据,进行拆分,
拆分成多个小的数据,进行传输,当传输到目标主机的操作系统层时,会重新将多个小的数据合并成原本的数据
udp不会发生粘包,udp协议本层对一次收发数据大小的限制是:65535 - ip包头(20) - udp包(8) = 65507,
站在数据链路层,因为网卡的MTU一般被限制在了1500,所以对于数据链路层来说,一次收发数据的大小被限制在 1500 - ip包头(20) - udp包头(8) = 1472,1472< num < 65507 会在数据链路层拆包,而udp本身就是不可靠协议,所以一旦拆包之后,造成的多个小数据包在网络传输中,如果丢任何一个,那么此次数据传输失败。
对应方法:
有矛必有盾,知道了粘包的原理就是应为接收方不知道每次传输数据的大小导致的,我们可以在每次发送的时候告知接收方这次数据的大小是多少字节,下次就接收多少字节,就解决了粘包问题
Python中Socket模块解读
cockeet是什么?
Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
socket又叫做套接字
分为多种:
1.基于文件类型的套接字:AF_UNIX
unix一切皆文件,基于文件的套接字调用的就是底层的文件系统来取数据,两个套接字进程运行在同一机器,可以通过访问同一个文件系统间接完成通信
2.基于网络类型的套接字:AF_INET/6
还有AF_INET6被用于ipv6,还有一些其他的地址家族,不过,他们要么是只用于某个平台,要么就是已经被废弃,或者是很少被使用,或者是根本没有实现,所有地址家族中,AF_INET是使用最广泛的一个,python支持很多种地址家族,但是由于我们只关心网络编程,所以大部分时候我么只使用AF_INET
套接字工作流程
一个生活中的场景。你要打电话给一个朋友,先拨号,朋友听到电话铃声后提起电话,这时你和你的朋友就建立起了连接,就可以讲话了。等交流结束,挂断电话结束此次交谈。 生活中的场景就解释了这工作原理。
服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。
客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束
socket()模块函数用法
import socket # 导入socket
socket.socket(socket_family,socket_type,protocal=0)
# 参数:
socket_family可以是 AF_UNIX 或 AF_INET
(AF_UNIX : 文件类型的套接字
AF_INET/6 :网络类型的套接字)
# 不写默认是基于网络
socket_type 可以是 SOCK_STREAM 或 SOCK_DGRAM
(SOCK_STREAM:面向连接的稳定数据传输,即TCP协议)
(SOCK_DGRAM:基于UDP的,专门用于局域网)
protocol 一般不填,默认值为 0 默认为0填写的就是tcp协议
#获取tcp/ip套接字
tcpSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
#获取udp/ip套接字
udpSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
#由于 socket 模块中有太多的属性。我们在这里破例使用了'from module import *'语句。使用 'from socket import *',我们就把 socket 模块里的所有属性都带到我们的命名空间里了,这样能 大幅减短我们的代码。
#例如tcpSock = socket(AF_INET, SOCK_STREAM)
涉及到的参数
AF_UNIX : 文件类型的套接字
AF_INET/6 :网络类型的套接字
SOCK_STREAM:提供面向连接的稳定数据传输,即TCP协议
SOCK_DGRAM :是基于UDP的,专门用于局域网
protocal : 协议默认为0填写的就是tcp协议
服务端套接字函数
s.bind() 绑定(主机,端口号)到套接字
s.listen() 开始TCP监听,半连接池可以指定等待数量
s.accept() 被动接受TCP客户的连接,(阻塞式)等待连接的到来
客户端套接字函数
s.connect() 主动初始化TCP服务器连接
s.connect_ex() connect()函数的扩展版本,出错时返回出错码,而不是抛出异常
公共用途的套接字函数
s.recv() 接收TCP数据
s.send() 发送TCP数据(send在待发送数据量大于己端缓存区剩余空间时,数据丢失,不会发完)
s.sendall() 发送完整的TCP数据(本质就是循环调用send,sendall在待发送数据量大于己端缓存区剩余空间时,数据不丢失,循环调用send直到发完)
s.recvfrom() 接收UDP数据
s.sendto() 发送UDP数据
s.getpeername() 连接到当前套接字的远端的地址
s.getsockname() 当前套接字的地址
s.getsockopt() 返回指定套接字的参数
s.setsockopt() 设置指定套接字的参数
s.close() 关闭套接字
面向锁的套接字方法
s.setblocking() 设置套接字的阻塞与非阻塞模式
s.settimeout() 设置阻塞套接字操作的超时时间
s.gettimeout() 得到阻塞套接字操作的超时时间
面向文件的套接字的函数
s.fileno() 套接字的文件描述符
s.makefile() 创建一个与该套接字相关的文件
Socket TCP通讯
单次通讯socket
服务端
# Server
import socket
# 实例化对象
server = socket.socket()
# 绑定ip和端口
server.bind(('127.0.0.1', 8000))
# 开监听,半连接池,最大等待用户
server.listen(5)
# 被动接受TCP客户的连接,(阻塞式)等待连接的到来
sock, addr = server.accept() # sock 连接通道 和 ip地址
msg = sock.recv(1024).decode('utf-8') # 单次接收的最大字节
print(msg)
# 返回给客户端信息
sock.send('已收到'.encode('utf-8'))
sock.close() # 关闭连接
server.close() # 关闭socket
客户端
# client
import socket
# 实例化对象
client = socket.socket()
# 绑定
client.connect(('127.0.0.1', 8000))
# 发送信息给服务器
client.send('123'.encode('utf-8'))
# 接收字节
msg = client.recv(1024).decode('utf-8')
print(msg)
# 关闭
client.close()
链接循环
服务端
# server
import socket
# 实例化对象 = 开机
server = socket.socket()
# 绑定 = 插电话卡
server.bind(('127.0.0.1', 18000))
# 开监听,半连接池,最大等待用户
server.listen(5)
while True:
# 被动接受TCP客户的连接,(阻塞式)等待连接的到来
# 来电话建立通讯
sock, addr = server.accept() # sock 连接通道 和 ip地址
while True:
# 等于每次来电话他最多说多少个字
try:
msg = sock.recv(1024).decode('utf-8') # 单次接收的最大字节
print(msg)
# 返回给客户端信息
# 等于对方说话你给他说的回复信息
sock.send('已收到'.encode('utf-8'))
except Exception:
break
# 如果客户端异常断开服务器也会断开,所有用异常处理
# 挂电话
sock.close() # 关闭连接
server.close() # 关闭socket 也就等于出现意外的异常直接关闭
客户端
# client
import socket
# 实例化对象
client = socket.socket()
# 绑定
client.connect(('127.0.0.1', 18000))
# 发送信息给服务器
while True:
msg = input('发送给服务器的内容:').encode('utf-8')
if not msg:
print('你自己终止了程序')
break
client.send(msg)
# 接收字节
msg = client.recv(1024).decode('utf-8')
print('服务器发送回来的内容:%s' % msg)
Socket UDP通讯
单次通讯
服务端
import socket
server = socket.socket(type=socket.SOCK_DGRAM) # UDP协议传输
server.bind(('127.0.0.1', 8080)) # 绑定ip和端口
# 收发
msg, addr = server.recvfrom(1024) # 接收来自于哪里的数据
print(msg.decode('utf-8'))
server.close()
客户端
import socket
client = socket.socket(type=socket.SOCK_DGRAM) # UDP协议传输
msg = input('发送给服务器端信息:').encode('utf-8')
client.sendto(msg, ('127.0.0.1', 8080)) # 发送数据给哪里
client.close()
循环链接
服务端
import socket
server = socket.socket(type=socket.SOCK_DGRAM) # UDP协议传输
server.bind(('127.0.0.1', 8080)) # 绑定ip和端口
# 收发
while True:
msg, addr = server.recvfrom(1024) # 接收来自于哪里的数据
print("来自客户端%s的信息:%s" % (addr, msg.decode('utf-8')))
# 发送信息给客户端
msg_c = input('发送给客户端的数据:')
server.sendto(msg_c.encode('utf-8'), addr) # 发送信息给客户端,addr里面是客户端的ip和端口
server.close()
客户端
import socket
client = socket.socket(type=socket.SOCK_DGRAM) # UDP协议传输
while True:
msg = input('发送给服务器端信息:').encode('utf-8')
if not msg:
print('输入为空,客户端自动退出')
break
client.sendto(msg, ('127.0.0.1', 8080)) # 发送数据给哪里
# 接收来来自于服务器的数据
msg_s, addr = client.recvfrom(1024)
print("来自服务器%s的信息:%s" % (addr, msg_s.decode('utf-8')))
client.close()
UDP协议及编码