一、网络基础认知
1.1网络认识
(1)操作系统:调用硬件资源的,硬件----操作系统----程序 (2)网络基础 物理层:(电缆 双绞线 无线电波)---二边通过这个线只能相互发高低电压,高是1 低是0 数据链路层:(把物理层得到的0101封装成组,多少位为一组 eg:以太网的格式定义:一组电信号构成一个数据包叫帧,每一数据帧分成:报头head和数据data两部分 head(包含发送者地址6个字节,接收者地址6个字节,数据类型6个字节) data(数据包具体内容,最短46个字节,最长1500个字节) head长度+data长度=最短64字节,最长1518字节,超过最大限制就分片发送 head包含的源和目标地址:ethernet规定接入internet的设备都必须具备网卡,mac地址是每块网卡出厂 被烧制上世界唯一的mac地址,长度为48位2进制,12位16进制(6个字节02:88:65:3e:a1:ec)一个字节是一个组合,一个组合 就可以被认为与ASSIC对应,后来跟unicon来对应 数据链路层如何发送? 物理层决定了是隔离的,在一个局域网里所有的节点都用线连在一起,在这层通信只能用广播的方式 广播(广播域):发送自己是MAC及目标MAC及数据,这样所有的人都能看到,占资源。如何不隔离?看网络层 arp协议来获取对方的mac地址? mac地址学习? 网络层:定义了IP协议,点分十进制来表示IP,每个点段都是8位二进制,2的32次方,4亿多IP ipv4用完了,所以出现了ipv6,IP分为网络位与主机位 ip协议:为了跨子网(局域网)通信,如下 网络部分:在哪个局域网,通过子网掩码来算哪些位是网络部分哪些是主机部分,IP与掩码每二进制相乘 主机部分:局域网里的哪个主机 局域网之间如何互联:通过路由器连不同网络,通过路由表来找其它局域网并找到对应的主机,路由器如何知道这个主机 这时用的就是MAC通信,通过广播来找(发出去用的是广播,到另一个网络还是用广播) 包:分为head和data部分 head:20到60个字节 data:最长为65515字节 所以以太网数据包的‘数据’部分最长只有1500字节,所以IP数据包超过1500字节它就要分割成几个以太网数据包分开发送 以太网头(最外层,这里的MAC一直会再变,不断的换路由器的MAC最后才是真目标MAC)--ip头---ip数据 arp协议:在同一网络时 数据链路head data 源mac 目标mac(FF:FF:FF:FF:FF:FF) 源ip 目标ip 数据---当目标地址看目标Ip是自己时就返回自己的mac,第一次要拆到Ip, 但设备收到后返回数据,有自己的MAC与目标MAC这就是数据链路层拆到MAC就可以,若是在本局域网就不用走路由 网络层要连路由器(二层交换机有路由功能) 路由器之间通过路由表来连接 传输层: 网络层只能找到对应主机,如何通信? 基于端口的传输 (源MAC 目标MCA (源IP 目标IP (head(源端口 目标端口) data))) 1.端口:就是0-65535,0-1023被系统占用,这些端口号与网卡绑定起来,每一个程序起来就会在网卡上有个固定的端口号 2.TCP/UDP 套接字是网卡IP与端口绑定就叫套接字 用户进程----------------------------应用层 | socket抽象层(进程端口与网络IP绑定,一个端口一个服务) | | | | TCP UDP-------------------------传输层 | | ICMP IP-----------------------------网络层 | ARP 硬件接口--------------------------链路层 | 硬件 3.TCP协议要三次握手和四次断开,有链接的而UDP无链接 TCP报文:源端口 目标源口 序号(不固定) 确认号(每通信一个加个1) 数据偏移(多少位是什么数据) 保留(扩展用) syn(发起新链接) ack:确认 fin请求断开 窗口(按什么尺寸发) 检验和(检验数据有没有改过) 紧急指针(是否紧急处理) 选项与填充 data 三次握手: 客 服 1 syn=1 seq=x--------->(客的套接字) 2 <--------------ack=1+x 3 syn=1 seq=y(服务的套接字) 4 ack=1+y-------------> 实际2与3合一起做了二件事,syn=2与ack 虽然发起了二次连接,实际是客户端的套接字,客与服都在这一条路上申请,能相互发数据 四次断开 有二个连接都是要断开 客 服 fin=1 seq=x-------> <---------ack=1+x <---------fin=1 seq=y ack=1+y------> 4.udp没链接而TCP有链接 应用层: 1.ftp http 等协议 总结: 1.设备 传输层:四层交换机、四层的路由器 网络层:路由器 三层交换机 数据链路层:网桥 以太网交换机 网卡 物理层:中继器 集线器 双绞线 2.数据过程 用户数据--装应用层头--TCP头-->IP头-->MAC头 3.由于以太网MAC最多能传1500个字节(MTU值,网卡的传输限制),所以数据为4600个字节就要分四次发 MAC头 IP头 TCP头 data(1500) MAC头 IP头 TCP头 data(1500) MAC头 IP头 TCP头 data(1500) MAC头 IP头 TCP头 data(100) (3)dhcp:是UDP,局域网里要有dhcp服务器(家里的DHCP是集成在路由器里的,还有二层交换层是工作在数据链路层的MAC通信,三层交换器集成了路由功能,它即能路由也能MAC通信) dhcp工作模式: 新设备开机要获取IP 以太网头:DHCP的MAC FF:FF:FF:FF:FF:FF 传输层: 0.0.0.0 255.255.255.255---是一个获取IP的包 udp头: 发送方68 接收方67 广播发出去,所有的设备都能拿到,只有dhcp服务器拿到打开IP后才知道是找自己的 dhcp服发数据给客,有地址池是租的IP发给客端,IP 掩码 网关IP都是dhcp服务器发给这个客户端的 (4)DNS的IP(DNS服务器是通过UDP协议传的,根就封装在UDP协议中) 1.域名解析 (5)子网划分 A类地址:网络是前8位 0*******.主机地址(共有126个网络(1-126),2的24次方个主机,127开头的IP都不可以用) B类址:网络位是前16位 10******.********.主机地址(前一位128-191,后一位网络可变) C类地址:网络位前24位 110*****.********.********.主机地址(前一位192-223,后二位网络位可变) D类地址:1110MMMM.多播组.多播组.多播组 E类地址:11110RRR.保留.保留.保留 私有址私: A类私有地址:10.0.0.0-10.255.255.255(有一个A类私有地址) B类私有地址:172.16.0.0-172.31.255.255(第二位可变网络位,所以有16个网段可做为私有) C类私有地址:192.168.0.0-192.168.255.255(前二位固定,第三位是可变网络位,所以有255个网段可所私有) 私有地址作用:私有地址不能路由的,只有公网的才能路由,所以私有地址要能映射到公网地址,是通过NAT方式 映射 内网 网络 私2---NAT---公1(网关) 路由 公2---NAT----私2 私3 私3 内网发出去到网关,网关会把源IP转换掉做了SNAT,到达对方私网,对方会把目标IP转换掉,网关做了DNAT转换 (6)子网划分 eg:C类向主机会借二位,主机就变少了但网络就变多了,子网掩码 11111111.11111111.11111111.11000000(255.255.255.192) 借二位多出几个网段:2^2-2=2---00与11不能做为网络 如:A类地址:一个网络有2^24-2个主机,广播通信太难,所以向主机借几位,这样A类网络变多了,可以多卖,主机也不会那么多 所以要用子网掩码算出网络,IP一样不一定是同一个网络的,划分子网后,子网之间是不同网络不能直接通信,需要路由器 注:为什么前后二个IP不能用,主机位都是0的表这个网络的网段,主机位都是1的表这个网络的广播地址(eg:客向dhcp发广播就用的是广播IP) (7)vlan是交换机上划分网络 如一个交换机上有10个网口,5个做为一组,vlan1与vlan2,由于二层是不隔离的能广播通信,所以不同的vlan连不用网络 192.168.1.1/25---vlan1---192.168.1.130/25 这是二个子网所以这二个网络就不能通信了,虽然是在一个vlan上但是不能通信
二、网络通信
2.1 网络编程三要素
网络通信三要素:ip(找到主机) 端口(定位进程,找进程) 协议(语言规范来通信,TCP/UDP)
2.2 socket编程流程图
TCP服务端 TCP客户端 socket()--创建socket socket()--创建socket | | bind()--为socket绑定IP与端口 | | | Listen()--监听设置端口等待客端请求 | | | Accept()--ACcept阻塞,直到有客端过来 | | connect()--连接指定计算机端口 |<------------------------------------------------------------ | Recv()<----------------------------------------------------send() | | | Send()------------------------------------------------------>Recv() | | | | close()---关闭socket close()---关闭socket
服务端编程框架:
serve.py---如下 import socket---二台设备连接的中介(通道),这是个模块,是应用层与传输层的抽象层,二个设备都要创建 源码是socketserver.py #family=AF_INET---是ipv4的参数,服务器之间的通信 AF_UNIX:Unix不同进程之间的通信,AF_INET6:ipv6 #type=SOCK_STREAM----TCP的参数,SOCK_Dgram---数据socket,是UDP参数 以上这二个参数是socket里的_init_里定义好的 sk=socket.socket()----创建套接字对象,上面已有二个参数定好了,直接创建socket对象,默认是Ipv4与tcp,要换可以改参数,套接字 address=('127.0.0.1',8888 ) sk.bind(address)---绑定了IP与端口 sk.listen(3)----监听,默认等待3个,再多就接不上了,客户端就报错了 conn,addr=sk.accept()----阻塞在这只要没人连他就不向下走,等待客来接,里面是元组,一个是客户端socket对象的地址一个是IP+端口 当客户端连后就进行后面的通信,这之前三次握手已完成,我们只写握手之后的,三次握手不是我们写的 recv()---收数据 send()--发数据 sendall()---如果发不完就用这个会一直发,在python3中这里参数一定要是bytes类型 发数据到客: conn.send(bytes('约啊','utf8'))-----这个conn就是通道 conn.close()这个只关一个客户端 sk.close()---全关了 sk.settimeout()---超时时间 sk.fileno()--文件描述符
客户端框架:
client.py -----当server运行后,会卡在那边当client启动后server才能再运行 import socket sk1=socket.socket()---socket对象 address=('127.0.0.1',8888)---服务端的IP+端口 sk1.connect(address) 连上server后就开始向server请求 recv()---收数据 send()--发数据 sendall()---如果发不完就用这个会一直发 收服务端的数据 data=sk1.recv(1024)---收到1024个字节,这个sk1是conn print(str(data,'utf-8'))---变为unicon,把bytes类型,在Python3里str编码的是unicon sk1.colse()
实例之聊天软件:
client.py import socket sk1=socket.socket() print(sk1) address=('127.0.0.1',8888) sk1.connect(address)//联上服务端 while True: inp=input('>>>') if inp == 'exit'---退出,这时不给server发信息了,但server.py还在等,处理如下 break sk1.send(bytes(inp,'utf-8')) data=sk1.recv(1024) print(str(data,'utf8')) sk.close() ****如上如果多于3个等待其它客户端会报错,如何做到不报错 server.py import socket sk=socket.socket() address=('127.0.0.1',8888 ) sk.bind(address) sk.listen(3) ------等待3个,再多客端就会报错,只有连的那个客端断开了这三个才能聊天 conn,addr=sk.accept() while True: try: data=conn.recv(1024)----超过的3个报错是这步产生的,想让不产生就捕捉这个错误让他退出循环重新请求 except Exception as e: break if not data:----这时客发的是空的 conn.close() conn,addr = sk.accept()-----可接受其它客户端 print(addr) continue print(str(data,'utf8'))---看到client发的信息 inp=input('>>>') conn.send(bypes(inp,'utf8')) sk.close()
实例之ssh,客户端向服务端发命令
client.py import socket sk1=socket.socket() print(sk1) address=('127.0.0.1',8888) sk1.connect(address) while True: inp=input('>>>')----拿到的是str的 if inp == 'exit'---退出,这时不给server发信息了,但server.py还在等,处理如下 break sk1.send(bytes(inp,'utf-8')) result_len1=int(str(sk1.recv(1024),'utf8'))---收到一个数字,是一个byte类型,转成str类型是union值,再转成int值, data=bytes()----是一个空bytes while len(data)!= result_len1:----len(data)与result_len1都是Int类型 data=sk1.recv(1024) data+=sk1.recv(1024) #data=sk1.recv(1024)---会出现的问题,如果这个结果很大,这里只能接受1024个,太大会有问题,直接改recv也不行,有些视频很多改也不行,只能计算cmd_result有多大,看上处理如何循环接受数据 print(str(data,'gdk'))---把data变为字符串类型 sk.close() 过程:客输入dir命令,服收到subprocess.Popen,执行后结果用send发出去,注,数据只能传bytes server.py如何改? import subprocess----调用shell命令的模块,里面只有一个类Popen import socket import time sk=socket.socket() address=('127.0.0.1',8888 ) sk.bind(address) sk.listen(3) ------等待3个,再多客端就会报错,只有连的那个客端断开了这三个才能聊天 conn,addr=sk.accept() while True: try: data=conn.recv(1024)----超过的3个报错是这步产生的,想让不产生就捕捉这个错误让他退出循环重新请求,data接受的都是byte类型, except Exception as e: break if not data:----这时客发的是空的 conn.close() conn,addr = sk.accept()-----可接受其它客户端 print(addr) continue print(str(data,'utf8'))---看到client发的信息,把bytes解码出来成str //相当于把客户端的数据放在处理shell模块里会执行 obj=subprocess.Popen(str(data,'utf8'),shell=True,stdout=subprocess.PIPE)--对象,data是接受客户端的值,它要是个字符串,所以要解成字符串, 这是个地址,这里就在本地执行了shell命令 cmd_result=obj.stdout.read()--会出现的问题,如果这个结果很大,是bytes类型,是上步转成bytes的,因为是在windows里执行的所以默认是gbk编码的 result_len=(bytes(str(len(cmd_result),'utf8')--是一个长度,是int类型不能直接转成byte,要先转成str conn.sendall(result_len) time.sleep(0.5)---由于前后二个发送数据会有粘包现象,这里可以给个暂时,用下面的粘包来解决 conn.send(cmd_result) print(str(obj.stdout.read)),'gbk')---结果是byte类型转为str类型,所以要用gbk解码 sk.close()
2.3 粘包
2.4 编码拾遗
py3只有二种数据类型:str(存unicode) bytes(存16进制) s='hello丹丹'------存在内存里的是unicode,是str类型,但存的是unicode编码 但传输内容与存在磁盘里只能用bytes类型,因为bytes与底层更近,所以要把s转成bytes类型,这个是编码,如何转? bytes(s,'utf8')-----bytes类型每国语言都能编码,gdk utf8等,但utf8是公认的,都能按自己的规则编码 =s.encode('utf8') 还有一种方式:字符串方法encode decode b=bytes(s,'utf8')=s.encode('utf8')----由于bytes可用utf8 gdk等来编码 str(b,utf8)---bytes用utf8解码=b.decode('utf8') 把b做成gdk b1=s.encode('gbk')
2.5 实例之上传文件
1.实例
post_client(执行时要有个命令与文件名)--如post|11.jpg import socket import os sk1=socket.socket() print(sk1) address=('127.0.0.1',8888) sk1.connect(address) BASE_DIR=os.path.dirname(os.path.abspath(__file__)) while True: inp=input('>>>').strip() cmd,path=inp.split('|')--以|分隔--path可以是文件也可以是一个路径 path=os.path.join(BASE_DIR,path) filename=os.path.basename(path) file_size=os.stat(path).st_size---文件大小 file_info='post|%s|%s'%(filename,file_size) sk.sendall(bytes(file_info,'utf8')) with open(path,'rb') as f:----rb,这个是字节类型发数据,把文件里的数据取出来当字符串发过去,rb是读出字节数据,wb是写入字节类型 has_sent=0 while has_sent!=file_size: data=f.read(1024)--每1024取一次 sk.sendall() has_sent+=len(data)--data的长度 print('上传成功') post_server import subprocess import socket import time sk=socket.socket() address=('127.0.0.1',8888 ) sk.bind(address) sk.listen(3) conn,addr=sk.accept() BASE_DIR=os.path.dirname(os.path.abspath(__file__)) while True: conn,addr = sk.accept() while 1: data=conn.recv(1024) cmd,filename,filesize=str(data,'utf8').split('|') path=os.path.join(BASE_DIR,'new','filename') filesize=int(filesize) f=open(path,'ab') has_receice=0 while has_receice!=filesize: data=conn.recv(1024) f.write(data) has_receive+=len(data) f.close 框架:把重复的内容用框架调用,如上创建socket的过程
2.搭socket框架并实现并发的过程
server并发例: server.py import socketserver 3步 class MyServer(socketserver.BaseRequestHandler):---继承socketserver的一个类 def handle(self):---父类有这个方法,要重写父类的方法,这个就是需求的内容,是listen之后的内容 print("服务端启动") while True: conn=self.request-------------就是接收的客户端的sk print(self.client_address) while True: client_data=conn.recv(1024) print(str(client_data,'utf8')) print('wait...') conn.sendall(client_data) conn.close() if __name__ == '__main__' 1步 server=socketserver.ThreadingTCPServer(('127.0.0.1',8091),MyServer)--调的这个类是多线程TCP服务端的,这里创建socket等过程,这就是个框架,它封装到listen这步,默认listen是5个,这步可走ThreadingTCPServer的构造方法 2步 server.server_forever()----启动程序从server里的方法开始, 里面会实例化Myserver,里面没有__init__,会去找父类的__init__执行,父类时会调用handle,并发是在这里实现的 结果:这时多个客户端能跟server聊天了,多个客户端能同时跟服务器聊天,服务器是多线程的了,每个客户端都有一个线程接受conn
3.socketserver是如何实现上面这个并发过程的
有5个server类:
class ThreadingTCPServer(ThreadingMixIn,TCPServer): pass----TCPserver类,二个父类,是TCP协议的socket连接的类,用于windows
class ThreadingUDPServer(ThreadingMixIn,UDPServer)----UDP协议的socket连接
class ForkingTCPServer(ThreadingMixIn,TCPServer)---用于Linux的TCPserver
class ForkingUDPServer(ThreadingMixIn,UDPServer)
class BaseServer:*****:是一个原生类没有父类