一. 软件开发的架构
c/s架构 client-server 用户层面的架构,程序需要先安装后,才能运行在用户的电脑上,对用户的电脑操作系统环境依赖较大,现在不流行了
几乎包括了所有网络开发的架构形态
b/s架构 browser - server 也是c/s架构,大势所趋
其实也是一种client客户端,不需要安装应用程序,可以在网页上操作,比较方便,统一了所有应用的入口
二. 网络基础
ip地址和ip协议:
规定网络地址的协议就叫做ip协议,32位2进制表示,范围0.0.0.0-255.255.255.255
IP协议的作用主要有两个,一个是为每一台计算机分配IP地址,另一个是确定哪些地址在同一个子网络。
mac地址:网卡
以太网规定接入internet的设备都必须具备网卡,发送端和接收端的地址便是指网卡的地址,即mac地址,是唯一的,通常由12位16进制数表示
# mac地址唯一的,为什么要有ip地址?
# 192.168.11.***
# 256台 0-255
# 192.168.***.***
# 256^2
# 192.***.***.***
# 256^3
arp协议:查询ip地址和mac地址的对应关系
交换机:同一个局域网内的机器之间的交流
局域网与交换机
广域网与路由器
路由器:跨局域网机器之间的交流。 是连接因特网中各局域网,广域网的设备,又称网关设备是用于连接多个逻辑上分开的网络,网络层的互联设备。
局域网:是指在某一局域内由多台计算机互联成的计算机组。一般是方圆几千米以内。局域网是封闭性的,可以由办公室内的两台计算器组成,也可以由公司内的上千台计算器组成。
子网掩码:就是表示子网络特征的一个参数。形式上等同于ip地址,也是一个32位二进制数字,网络部分全部为1,主机部分全部为0.
比如,IP地址是172.16.10.1,如果已知网络部分是前24位,主机部分是后8位,那么子网掩码就是11111111,11111111,11111111,00000000,
写成十进制就是255.255.255.0
知道”子网掩码”,我们就能判断,任意两个IP地址是否处在同一个子网络。方法是将两个IP地址与子网掩码分别进行AND运算
(两个数位都为1,运算结果为1,否则为0),然后比较结果是否相同,如果是的话,就表明它们在同一个子网络中,否则就不是。
192.168.12.62 11000000.10101000.00001011.00111110 11111111.11111111.11111111.00000000 11000000.10101000.00001011.00000000 == 192.168.0.0 255.255.0.0 192.168.11.94 255.255.0.0 11000000.10101000.00001011.01011110 11111111.11111111.11111111.00000000 == 192.168.0.0
网管ip:跨局域网的机器之间不能直接通信,只能通过网管ip通信
TCP协议和UDP协议
端口:
通过ip地址+端口来区分不同的服务的
TCP协议:三次握手,四次挥手
当应用程序希望通过 TCP 与另一个应用程序通信时,它会发送一个通信请求。这个请求必须被送到一个确切的地址。在双方“握手”之后,TCP 将在两个应用程序之间建立一个全双工 (full-duplex) 的通信。
这个全双工的通信将占用两个计算机之间的通信线路,直到它被一方或双方关闭为止。
UDP协议:
当应用程序希望通过UDP与一个应用程序通信时,传输数据之前源端和终端不建立连接。
当它想传送时就简单地去抓取来自应用程序的数据,并尽可能快地把它扔到网络上。
tcp和udp的对比:
tcp-传输控制协议
相当于打电话:独占一个通道,在断开连接钱不能建立另一个连接,即两人在通话过程中第三方不能打入电话
是面向连接的,可靠的字节流服务,
当客户与服务器彼此交换数据前,必须先在双方之间建立一个tcp连接,之后才能传输数据。
tcp提供超时重发,丢弃重复数据,检验数据,流量控制的功能,保证数据已定从一端传到另一端
这种优化机制,容易出现黏包现象
udp-用户数据包协议 相当于写信
是一个简单的面相数据包的运输层协议
udp不提供可靠性,由于udp在传输数据包前不用在客户和服务器之间建立一个连接,且没有超时重发等机制,所以传输速度很快,所以不会出现黏包现象
互联网协议与osi模型
每层运行常见物理设备
每层运行常见的协议
socket概念-网络编程和python相关
Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
基于tcp协议的socket
tcp是基于连接的,必须先启动服务器,然后再启动客户端去连接服务器
server端
import socket from socket import SOL_SOCKET,SO_REUSEADDR #加入一条socket配置,重用ip和端口 sk= socket.socket() sk.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #在bind前加 sk.bind(('127.0.0.1',8000)) #把地址绑定到套接字 sk.listen() #监听链接 conn,addr = sk.accept() #接收客户端链接 ret = conn.recv(1024) # 接收客户端信息 print(ret) #打印客户端信息 conn.send(b'hi') #向客户端发送信息 conn.close() #关闭客户端套接字 sk.close() #关闭服务器套接字(可选)
client端
import socket sk = socket.socket() #创建客户套接字 sk.connect(('127.0.0.1',8000)) #尝试连接服务器 sk.send(b'hello') ret = sk.recv(1024) #对话(发送/接收) print(ret) sk.close() #关闭客户套接字
udp是无连接的,启动服务之后可以直接接受消息,不需要提前建立连接
server端
import socket udp_sk = socket.socket(type=socket.SOCK_DGRAM) # 创建一个服务器的套接字 udp_sk.bind(('127.0.0.1',9000)) # 绑定服务器套接字 msg,addr = udp_sk.recvfrom(1024) print(msg) udp_sk.sendto(b'hi',addr) # 对话(接收与发送) udp_sk.close() # 关闭服务器套接字
client端
import socket ip_port = ('127.0.0.1',9000) udp_sk = socket.socket(type=socket.SOCK_DGRAM) udp_sk.sendto(b'hello',ip_port) back_msg,addr = udp_sk.recvfrom(1024) print(back_msg.decode('utf-8'),addr)
qq聊天
server端
import socket ip_port = ('127.0.0.1',8888) udp_server_socket = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) udp_server_socket.bind(ip_port) while True: qq_msg,addr = udp_server_socket.recvfrom(1024) print('来自[%s:%s]的一条消息: 33[1;44m%s 33[0m'%(addr[0],addr[1],qq_msg.decode('utf-8'))) reply_msg = input('请回复:').strip().encode('utf-8') udp_server_socket.sendto(reply_msg,addr)
client端
import socket BUFSIZE = 1024 udp_client_socket = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) qq_name_dic = {'taibai':('127.0.0.1',8888), 'nero':('127.0.0.1',8888), 'egg':('127.0.0.1',8888), 'yuan':('127.0.0.1',8888)} while True: qq_name = input('请输入聊天对象:').strip() while True: msg = input('请输入消息:q退出') if msg =='q':break if not msg or not qq_name or qq_name not in qq_name_dic:continue udp_client_socket.sendto(msg.encode('utf-8'),qq_name_dic[qq_name]) reply_msg,addr = udp_client_socket.recvfrom(BUFSIZE) print('来自[%s:%s]的一条消息: 33[1;44m%s 33[0m' %(addr[0],addr[1],reply_msg.decode('utf-8'))) udp_client_socket.close()
黏包
res=subprocess.Popen(cmd.decode('utf-8'), shell=True, stderr=subprocess.PIPE, stdout=subprocess.PIPE) 的结果的编码是以当前所在的系统为准的,如果是windows,那么res.stdout.read()读出的就是GBK编码的,在接收端需要用GBK解码 且只能从管道里读一次结果
同时执行多条命令之后,得到的结果很可能只有一部分,在执行其他命令的时候又接收到之前执行的另外一部分结果,这种显现就是黏包。
注意:只有TCP有粘包现象,UDP永远不会粘包
tcp协议的拆包机制
当发送端缓冲区的长度大于网卡的MTU时,tcp会将这次发送的数据拆成几个数据包发送出去。 MTU是Maximum Transmission Unit的缩写。意思是网络上传送的最大数据包。MTU的单位是字节。 大部分网络设备的MTU都是1500。如果本机的MTU比网关的MTU大,大的数据包就会被拆开来传送,这样会产生很多数据包碎片,增加丢包率,降低网络速度。
即面向流的通信是无消息保护边界的。
可靠黏包的tcp协议:tcp的协议数据不会丢,没有收完包,下次接收,会继续上次继续接收,己端总是在收到ack时才会清除缓冲区内容。数据是可靠的,但是会粘包。
发送方引起的粘包是由TCP协议本身造成的,TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送一个TCP段。若连续几次需要send的数据都很少,通常TCP会根据优化算法把这些数据合成一个TCP段后一次发送出去,这样接收方就收到了粘包数据。
UDP不会发生黏包:
udp不会使用块的合并优化算法,面对消息的童心是有消息保护边界的。
发生黏包的两种情况:
情况一:发送方的缓存机制
发送端需要等缓冲区满才发送出去,造成黏包(发送时间间隔很短,数据很小,会何在一起,产生黏包
服务端
from socket import * ip_port=('127.0.0.1',8080) tcp_socket_server=socket(AF_INET,SOCK_STREAM) tcp_socket_server.bind(ip_port) tcp_socket_server.listen(5) conn,addr=tcp_socket_server.accept() data1=conn.recv(10) data2=conn.recv(10) print('----->',data1.decode('utf-8')) print('----->',data2.decode('utf-8')) conn.close()
客户端
import socket BUFSIZE=1024 ip_port=('127.0.0.1',8080) s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) res=s.connect_ex(ip_port) s.send('hello'.encode('utf-8')) s.send('egg'.encode('utf-8'))
结果
-----> helloegg
----->
情况二:接收方的缓存机制
接收方不及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包)
服务端
from socket import * ip_port=('127.0.0.1',8080) tcp_socket_server=socket(AF_INET,SOCK_STREAM) tcp_socket_server.bind(ip_port) tcp_socket_server.listen(5) conn,addr=tcp_socket_server.accept() data1=conn.recv(2) #一次没有收完整 data2=conn.recv(10)#下次收的时候,会先取旧的数据,然后取新的 print('----->',data1.decode('utf-8')) print('----->',data2.decode('utf-8')) conn.close()
客户端
import socket BUFSIZE=1024 ip_port=('127.0.0.1',8080) s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) res=s.connect_ex(ip_port) s.send('hello egg'.encode('utf-8'))
结果:
-----> he
-----> llo egg
黏包现象只发生在tcp协议中:
1.从表面上看,黏包问题主要是因为发送方和接收方的缓存机制、tcp协议面向流通信的特点。
2.实际上,主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的
黏包的解决方案
解决方案进阶 struct模块
server端
import os,json,struct,socket filepath = r'/Users/lixinwei/PycharmProjects/untitled1/xxglxt' filename = os.path.basename(filepath) filesize = os.path.getsize(filepath) sk=socket.socket() sk.bind(('127.0.0.1',8300)) sk.listen() conn,addr = sk.accept() print('客户端',addr) dic = {'filename':filename, 'filesize':filesize} str_dic = json.dumps(dic).encode('utf-8') dic_len = len(str_dic) length = struct.pack('i',dic_len) conn.send(length) conn.send(str_dic) with open(filename,'rb') as f: while filesize: content = f.read(4096) conn.send(content) filesize -= len(content) conn.close() sk.close()
client端
import json,socket,struct sk = socket.socket() sk.connect(('127.0.0.1',8300)) dic_len = sk.recv(4) dic_len = struct.unpack('i',dic_len)[0] # 字典真正的长度50 dic = sk.recv(dic_len) str_dic = dic.decode('utf-8') dic = json.loads(str_dic) with open(dic['filename'],'wb') as f: while dic['filesize']: content = sk.recv(4096) dic['filesize'] -= len(content) f.write(content) sk.close()