一. 软件开发的架构
我们了解的涉及到两个程序之间通讯的应用大致可以分为两种:
第一种是应用类:qq、微信、网盘、优酷这一类是属于需要安装的桌面应用
第二种是web类:比如百度、知乎、博客园等使用浏览器访问就可以直接使用的应用
这些应用的本质其实都是两个程序之间的通讯。而这两个分类又对应了两个软件开发的架构~
1. C/S架构
C/S即:Client与Server ,中文意思:客户端与服务器端架构,这种架构也是从用户层面(也可以是物理层面)来划分的。这里的客户端一般泛指客户端应用程序EXE,程序需要先安装后,才能运行在用户的电脑上,对用户的电脑操作系统环境依赖较大。
2. B/S 结构
B/S即:Browser与Server,中文意思:浏览器端与服务器端架构,这种架构是从用户层面来划分的。Browser浏览器,其实也是一种Client客户端,只是这个客户端不需要大家去安装什么应用程序,只需在浏览器上通过HTTP请求服务器端相关的资源(网页资源),客户端Browser浏览器就能进行增删改查。
二 . 网络基础
基础知识
(1) mac地址 : 是一个物理地址,全球唯一.
(2) ip地址 : 是一个四位点分十进制,它标识了计算机在网络中的位置.
(3) 交换机的通信方式: 广播 : 吼一嗓子
单播 : 一对一
组播 : 一对多
(4) 端口 : 操作系统为本机上每一个运行的程序都随机分配一个端口,其他电脑上的程序可以通过端口获取到这个程序.
ip地址 + 端口 能唯一找到某台电脑上的某一个服务程序
(5) arp协议 : 通过目标ip地址获取目标mac地址的一个协议.
(6) 路由器 : 连接不同网段 , 路由
(7) 网关 : 类似于一个局域网的出口和入口
(8) 网段 : 一个局域网内的ip地址范围
(9) 子网掩码 : 子网掩码 & ip地址 得到网段
(10) osi 五层模型:
应用层 : http,https,ftp
传输层 : tcp / udp 四层交换机 四层路由器
网络层 : ip协议 路由器 三层交换机
数据链路层 : arp协议 以太网交换机 网卡 网桥
物理层 : 传输电信号 集线器 网线 光纤
三. 套接字(socket)
socket 是一个模块,模块里有个socket类. sk = socket.socket(family = AF_INET, type=SOCK_STREAM)
family :
一种 : AF_UNIX基于文件类型的套接字(早期socket是源自于unix系统而研发的一个功能,主要是为了同一台电脑上,多个程序直接通信) unix系统的中心思 想是 : 一切皆文件.
一种 : AF_INET基于网络类型的套接字.
type:
一种是基于TCP协议 SOCK_STREAM
一种是基于UDP协议 SOCK_DGRAM
四. TCP协议和UDP协议
tcp 协议 : 可靠的, 面向连接的, 面向字节流形式的传输方式. 使用TCP的应用:Web浏览器;电子邮件、文件传输程序.
udp协议 : 不可靠的, 不面向连接的, 面向数据报的传输方式, 但是它快. 使用TCP的应用:Web浏览器;电子邮件、文件传输程序.
1. 基于TCP协议的socket
sever.py(服务器端)
import socket # TCP sk = socket.socket() # 创建套接字 不传参数,默认使用基于网络类型的套接字, 协议 : TCP sk.bind(('192.168.12.83', 7777)) # 本机ip地址和你设置的端口 绑定到套接字 端口的范围是0-65535 但是 0-1023 这些你别用 sk.listen() # 监听链接 while 1: # 一直接受连接,当与一个客户端断开时,可以连接新的客户端请求 coon, addr = sk.accept() # 接收客户端的连接 # print(coon) # print(addr) # 客户端ip地址和端口 while 1: # 一直进行对话 content1 = coon.recv(30).decode('utf-8') # 接收客户端信息数据(bytes),接收20个字节. print('客户端:', content1) if content1 == 'q': # 当接收客户端的消息为'q'时退出对话 break content2 = input('服务器:') coon.send(content2.encode('utf-8')) # 向客户端发送信息 if content2 == 'q': # 向客户端发送信息为'q'时,退出对话 break coon.close() # 关闭客户端套接字 sk.close() # 关闭服务器套接字
client.py(客户端)
import socket # TCP sk = socket.socket() sk.connect(('192.168.12.83', 7777)) # 连接服务端 while 1: # 一直对话 message1 = input('客户端:') sk.send(message1.encode('utf-8')) # 向服务端发送信息 if message1 == 'q': # 向服务器发送信息为'q'时,退出对话 break message2 = sk.recv(1024).decode('utf-8') # 接收服务端信息 print('服务器:', message2) if message2 == 'q': # 当接收服务器的消息为'q'时退出对话 break sk.close() # 关闭客户套接字
2. 基于UDP协议的socket
sever.py(服务器)
# UDP import socket sk = socket.socket(type=socket.SOCK_DGRAM) # 创建套接字 协议: UDP sk.bind(('192.168.12.83', 4522)) # 绑定 while 1: while 1: coon, addr = sk.recvfrom(1024) # 接收内容(coon)和客户端地址(addr) print(coon.decode('utf-8')) if coon.decode('utf-8') == 'q': # 退出与客户端的对话 break content = input('>>>:') sk.sendto(content.encode('utf-8'), addr) # 必须带上addr if content == 'q': break sk.close()
client.py(客户端)
# UDP import socket ip_port = ('192.168.12.83', 4522) # 服务端地址 sk = socket.socket(type=socket.SOCK_DGRAM) name = input('请输入你的名字:') print(name) while 1: message = input('>>>:') message = name + ' : ' + message sk.sendto(message.encode('utf-8'), ip_port) # 向服务器发送信息 if message == 'q': break coon, addr = sk.recvfrom(1024) print(coon.decode('utf-8')) if coon.decode('utf-8') == 'q': break sk.close()
五. 黏包
发送端发送数据,接收端不知道应该如何去接收,造成的一种数据混乱的现象, 只有tcp协议才会发送粘包,udp不会发生.
在tcp协议中,造成数据混乱的原因有俩: 合包机制, 拆包机制
合包机制(nagle算法): 将多次连续发送且间隔较小的数据,进行打包成一块数据传送.
拆包机制: 在发送端,因为受到网卡的MTU限制,会将大的超过MTU限制的数据,进行拆分,拆分成多个小的数据,进行传输. 当传输到目标主机的操作 系统层时,会重新将多个小的数据合并成原本的数据.
解决方案:
1. 把自己将要发送的字节流总大小让接收端知晓,然后接收端来一个死循环接收完所有数据
我们可以借助一个模块,这个模块可以把要发送的数据长度转换成固定长度的字节。这样客户端每次接收消息之前只要先接受这个固定长度字节的内容看一看接下来要接收的信息大小,那么最终接受的数据只要达到这个值就停止,就能刚好不多不少的接收完整的数据了。
import struct num = 123 b_num = struct.pack('i', num) # bytes型 4字节 'i'表示int型 print(b_num, len(b_num)) # b'{x00x00x00' 4 n = struct.unpack('i', b_num) # 元组 print(n) # (123,) print(n[0]) # 123
六. socketsever
socketsever是处于socket抽象层和应用层之间的一层,比socket更贴近用户. 为了解决TCP协议中服务端无法同事和多个客户端连接的问题.
sever.py(服务器端)
# socketsever 模块 import socketserver class MySocket(socketserver.BaseRequestHandler): # 继承父类socketserver.BaseRequestHandler def handle(self): # 这个方法是固定的 # 收发的逻辑代码: self.request == conn msg = self.request.recv(1024).decode('utf-8') print(msg) self.request.send(b'helloworld') sever = socketserver.ThreadingTCPServer(('127.0.0.1', 4522), MySocket) # 固定的格式 sever.serve_forever() # 开启一个永久性的服务
client.py(客户端)
import socket sk = socket.socket() sk.connect(('127.0.0.1', 4522)) msg = input('>>>') sk.send(msg.encode('utf-8')) mes = sk.recv(1024).decode('utf-8') print(mes) sk.close()