网络编程
网络编程
B/S与C/S架构
客户端:Client 浏览器:Browser 服务端:Server
B/S架构
-
优点 :丰富满足客户的个性化要求,安全性很容易保证,响应速度较快
-
缺点 : 需要开发客户端和服务器两套程序,开发成本维护成本较高,兼容性差,用户群固定
B/S架构
- 优点:分布性强,客户端几乎无需维护,开发简单,共享性强,维护简单方便
- 缺点:个性化低,安全性以及响应速度需要花费巨大设计成本
osi五层协议
-
物理层
- 物理机:中继器,集线器,双绞线
-
数据链路层
- 物理机:网桥,以太网交换机,网卡
- 以太网协议
- mac地址:每块网卡出厂时都被烧制上一个世界唯一的mac地址(可识别特定主机)
- 可实现广播,单播,组播
-
网络层
- 物理机:路由器,三层交换机
- IP协议:规定网络地址的协议
- 子网掩码:表示子网络特征的一个参数(通过AND运算判断两个IP地址是否在同一地址)
- ARP协议:广播的方式发送数据包,获取目标主机的mac地址(功能)
-
传输层
- 物理机:四层交换机,四层的路由器
- tcp协议:可靠传输(tcp的三次握手和四次挥手)
- udp协议:不可靠传输
-
应用层(同时包含了会话层和表示层)
socket模块
Socket又称为套接字,它是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,通过这个接口就可以统一、方便的使用tcp/ip协议的功能
TCP和UDP对比
TCP : 可靠的, 面向连接的协议, 传输效率低, 全双工通信(发送缓存&接收缓存), 面向字节流
UDP : 不可靠, 无连接服务, 传输效率高, 无拥塞控制
TCP协议下的socket

socket服务端
import socket
sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # tcp协议的serve
sk.bind(('127.0.0.1',800)) # 调用操作系统资源,绑定一个ip和端口
sk.listen(5) # 监听
conn, addr = phone.accept() # 等客户端访问并建立连接
while True: # 循环收发消息
try:
res = conn.recv(1024) # 接收最大为1024字节
print(res.decode('utf-8'))
ret = input('>>>').encode('utf-8')
conn.send(ret) # 直接发送消息, 不需要地址
except ConnectionResetError:
break
conn.close() # 关闭服务/连接
sk.close()
client客户端
import socket
sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
sk.connect(('127.0.0.1',8080)) # 客户端/tcp协议的方法,和server端建立连接
while True: # 循环收发消息
res = input('>>>')
sk.send(res.encode('utf-8'))
ret = sk.recv(1024)
print(ret.decode('utf-8'))
sk.close() # 挂电话
粘包
-
粘包发生的本质 : tcp协议的传输是流式传输 数据与数据之间没有边界
-
发生情况:
- 接收方没有及时接受缓冲区的包, 造成多个包接受(客户端发送了一段数据, 服务端只收了一小部分,服务端下次再收的时候还是从缓冲区那上次遗留的数据,产生粘包)
- 发送端需要等缓冲区满才发送出去,造成粘包(发送时间间隔很短,数据也很小,会合到一起,产生粘包)
-
解决办法
- 问题实质:接收端不知道发送端将要传送的字节流的长度
- 解决粘包问题的本质:设置边界
方法
- 发送时
- 先发报头长度
- 再编码报头内容然后发送
- 最后发真实内容
- 接收时:
- 先手报头长度,用struct取出来
- 根据取出的长度收取报头内容,然后解码,反序列化
- 从反序列化的结果中取出待取数据的描述信息,然后去取真实的数据内容
import struct
# 将一个数字转换成长度的bytes类型。
ret = struct.pack('i',183346)
# 通过unpack反解回来
res = struct.unpack('i',ret)[0]
################## 服务端如下
import struct
import socket
sk = socket.socket()
sk.bind(('127.0.0.1',9001))
sk.listen()
conn,addr = sk.accept()
msg1 = input('>>>').encode()
msg2 = input('>>>').encode()
# num = str() # '10001'
# ret = num.zfill(4) # '0006'
# conn.send(ret.encode('utf-8'))
blen = struct.pack('i',len(msg1))
conn.send(blen)
conn.send(msg1)
conn.send(msg2)
conn.close()
sk.close()
验证客户端的合法性
# 生成一个随机字符串
import os
ret = os.urandom(32) # 生成一个由32个随机组成的字节
print(ret)
# 在进行加密传输,返回一个加密值与本地对照,可使用hashlib
#也可以使用hmac代替,更加简便
import hmac
a = hmc.new(加的字节, os.urandom32)
ret = h.digest() #加密的字节
print(ret)
udp协议下的socket

socket服务端
import socket
sk = socket.socket(type=socket.SOCK_DGRAM) #创建一个服务器的套接字
sk.bind(('127.0.0.1',9000)) #绑定服务器套接字
conn,addr = udp_sk.recvfrom(1024)
sk.sendto(b'hi',addr) # 对话(接收与发送)
sk.close() # 关闭服务器套接字
socket客户端
import socket
ip_port=('127.0.0.1',9000)
udp_sk=socket.socket(type=socket.SOCK_DGRAM)
udp_sk.sendto(b'hello',ip_port)
msg,addr=sk.recvfrom(1024)
print(msg.decode('utf-8'),addr)
socketserver模块
- 基于socket完成的(socket属于底层模块)
- 可实现并发编程(两个作用)
- 循环建立链接的部分,每个客户链接都可以连接成功
- 通讯循环的部分,就是每个客户端链接成功之后,要循环的和客户端进行通信
# 固定格式
import time
import socketserver # 引入模块
class Myserver(socketserver.BaseRequestHandler): # 类名自定义,但必须继承
def handle(self):
conn = self.request # 等同于conn管道
while True: # 编写传输内容
try:
content = conn.recv(1024).decode('utf-8')
conn.send(content.upper().encode('utf-8'))
time.sleep(0.5)
except ConnectionResetError:
break
server = socketserver.ThreadingTCPServer(('127.0.0.1',9001),Myserver)
server.serve_forever()

浙公网安备 33010602011771号