网络编程演变过程
单机架构:不需要联网,如超级玛丽、坦克大战等。
C(client)/S(server)架构:客户端直接和服务端交互,如QQ、大型网络游戏等。
B(browser)/S(server)架构:客户端嫁接在浏览器上,浏览器和服务器交互,如淘宝、京东等。
客户端:用户安装的软件。
服务端:统一管理数据库的主机中的软件叫做服务器,再后来服务端不只是管理数据外加处理业务逻辑。
服务端特性:固定IP且稳定运行,支持并发。
大白话OSI五层协议(应传网数物)
物理层:电信号
数据链路层:
以太坊协议:将电信号进行分组,每组(数据报/数据帧)由报头和数据组成。
MAC地址/物理地址:每块网卡有唯一一个MAC地址,12位16进制数表示(前六位是厂商编号,后六位是流水线号),发送者地址和接收者地址就是MAC地址,可确定唯一计算机。
广播:同一局域网通信,会产生广播风暴。
网络层:
IP地址:目前用IPv4由32位二进制表示,从0.0.0.0到255.255.255.255范围内。
子网掩码:判断两个IP是否处于同一网段。
ARP协议:广播的方式发送数据包,获取目标主机的MAC地址。
MAC地址学习:MAC地址和IP地址的映射表,第一次接收就会在IP/MAC映射表中添加一条数据{'172.16.10.1':ddsadfgegsdgsdg}
传输层:
端口号:端口范围0-65535,0-1023为系统占用端口,找到独一无二的应用程序。
半连接池:限制连接的最大数。
DOS攻击——拒绝服务攻击和DDoS攻击——分布式拒绝服务攻击。
UDP协议和TCP协议(后面详细讲)
应用层:应用程序均工作于应用层,数据交互。
上网流程分析
在浏览器输入域名,隐藏端口。
通过dns服务器将域名解析成IP地址。
向IP+端口号这个地址发送请求,就会访问到域名所在的服务器。
TCP协议和UDP协议相关
tcp协议(流式协议):通信需要建立连接,通信结束要释放连接,不允许发空数据,所以是可靠传输,但增加确认、流量等开销,对应的应用层协议如HTTP、FTP等。
基于TCP协议的套接字编程:
import socket
server = socket.socket(type=socket.SOCK_STREAM) # 建立socket对象,默认tcp协议
server.bind(('127.0.0.1', 8001)) # 绑定IP和端口号
server.listen(5) # 半连接池大小
while True: # 连接循环
conn, addr = server.accept()
while True: # 通信循环
try:
data = conn.recv(1024) # 接收客户端数据
print(data)
conn.send(data.upper()) # 发送数据
except Exception:
break
客户端.py
import socket
client = socket.socket(type=socket.SOCK_STREAM) # 建立socket对象
client.connect(('127.0.0.1', 8001)) # 和客户端建立连接
while True: # 通信循环
msg = input('>>>:')
if not msg: continue # 发送数据不能为空
client.send(bytes(msg, encoding='utf-8')) # 以bytes格式发送数据
data = client.recv(1024) # 接受服务端数据
print(data)
udp协议(数据报协议):通信不需要建立连接和确认,允许发空数据,所以是不可靠传输,省去很多开销,对应的应用层协议如DNS、NFS等。
基于UDP协议的套接字编程:
服务端.py
import socket
server = socket.socket(type=socket.SOCK_DGRAM) # 建立socket对象
server.bind(('127.0.0.1', 8000)) # 绑定IP和端口号
while True: # 通信循环
data, addr = server.recvfrom(1024) # 接受客户端信息
print(data)
server.sendto(data.upper(), addr) # 发送服务端信息
客户端.py
import socket
client = socket.socket(type=socket.SOCK_DGRAM) # 建立socket对象
while True: # 通信循环
msg = input('>>>:')
client.sendto(bytes(msg, encoding='utf-8'), ('127.0.0.1', 8000)) # 以bytes格式发送数据
data = client.recvfrom(1024) # 接受服务端数据
print(data)
套接字(socket):在应用层和传输层之间的一个抽象层,把TCP/IP层复杂的操作抽象为几个简单的接口供应用层调用已实现进程在网络中通信,即IP地址+端口号组成,但是套接字所写的软件均属于应用层。
模拟ssh远程执行命令
在客户端模拟ssh发送指令,服务端通过subprocess执行该命令,然后返回命令的结果。
服务端.py
import socket
import subprocess
server = socket.socket(type=socket.SOCK_STREAM) # 建立socket对象,默认tcp协议
server.bind(('127.0.0.1', 8001)) # 绑定IP和端口号
server.listen(5) # 半连接池大小
while True: # 连接循环
conn, addr = server.accept()
while True: # 通信循环
try:
cmd = conn.recv(1024) # 接收客户端数据
print(cmd)
# 执行cmd命令,然后把执行结果保存到管道里
pipeline = subprocess.Popen(str(cmd, encoding='utf-8'), # 输入的字符串形式的cmd命令
shell=True, # 通过shell来运行
stdout=subprocess.PIPE, # 把正确输出放入管道,以便打印
stderr=subprocess.PIPE) # 把错误输出放入管道,以便打印
stdout = pipeline.stdout.read() # 打印正确输出
stderr = pipeline.stderr.read() # 打印错误输出
conn.send(stdout) # 发送数据
conn.send(stderr) # 发送数据
except Exception:
break
客户端.py
import socket
client = socket.socket(type=socket.SOCK_STREAM) # 建立socket对象
client.connect(('127.0.0.1', 8001)) # 和客户端建立连接
while True: # 通信循环
cmd = input('>>>:')
if not cmd: continue # 发送数据不能为空
client.send(bytes(cmd, encoding='utf-8')) # 以bytes格式发送数据
data = client.recv(1024) # 接受服务端数据
print(str(data, encoding='gbk'))
输入dir命令,由于服务端发送字节少于1024字节,客户端可以接受。
输入tasklist命令,由于服务端发送字节多于1024字节,客户端只接受部分数据,并且当你再次输入dir命令的时候,客户端会接收dir命令的结果,但是会打印上一次的剩余未发送完的数据,这就是粘包问题。
粘包问题
形成粘包问题的原因:两个数据非常小且间隔时间短;数据太大,一次取不完,下一次还会取这个数据。
解决粘包问题的方案:为字节流加上自定义固定长度报头,报头中包含字节流长度,然后一次send到对端,对端在接收时,先从缓存中取出定长的报头,然后再取真实数据。
服务端.py
import socket
import subprocess
import json
import struct
server = socket.socket(type=socket.SOCK_STREAM) # 建立socket对象,默认tcp协议
server.bind(('127.0.0.1', 8001)) # 绑定IP和端口号
server.listen(5) # 半连接池大小
while True: # 连接循环
conn, addr = server.accept()
while True: # 通信循环
cmd = conn.recv(1024) # 接收客户端数据
print(cmd)
# 执行cmd命令,然后把执行结果保存到管道里
pipeline = subprocess.Popen(str(cmd, encoding='utf-8'), # 输入的字符串形式的cmd命令
shell=True, # 通过shell来运行
stdout=subprocess.PIPE, # 把正确输出放入管道,以便打印
stderr=subprocess.PIPE) # 把错误输出放入管道,以便打印
stdout = pipeline.stdout.read() # 打印正确输出
stderr = pipeline.stderr.read() # 打印错误输出
# 制作报头
header_dic = {
'filename': 'a.txt',
'total_size': len(stdout) + len(stderr),
'hash': 'asdfpoi79032'
}
# 将报头序列化成字符串报头
header_json = json.dumps(header_dic)
# 将字符串报头转换成bytes格式
header_bytes = bytes(header_json, encoding='utf-8')
# 1.先将报头的长度len(header_bytes)打包成4个bytes,然后发送
conn.send(struct.pack('i', len(header_bytes)))
# 2.再发送报头
conn.send(header_bytes)
# 3.最后发送真实数据
conn.send(stdout)
conn.send(stderr)
conn.close()
server.close()
客户端.py
import socket
import struct
import json
client = socket.socket(type=socket.SOCK_STREAM) # 建立socket对象
client.connect(('127.0.0.1', 8001)) # 和客户端建立连接
while True: # 通信循环
cmd = input('>>>:')
if not cmd: continue # 发送数据不能为空
client.send(bytes(cmd, encoding='utf-8')) # 以bytes格式发送数据
# 1.先收4个字节,这4个字节包含报头的长度
header_len = struct.unpack('i', client.recv(4))[0]
# 2.再接收报头
header_bytes = client.recv(header_len)
# 3.从包中解析出想要的数据
header_json = str(header_bytes, encoding='utf-8')
header_dic = json.loads(header_json)
total_size = header_dic['total_size']
# 4.最后接收真实的数据
recv_size = 0
res = b''
while recv_size < total_size:
data = client.recv(1024)
res += data
recv_size += len(data)
print(str(res, encoding='gbk'))
client.close()
基于socketserver实现并发的socket套接字编程
让服务端同时和多个客户端进行连接。
服务端.py
import socketserver
自己定义一个类,必须继承BaseRequestHandler
class MyTcp(socketserver.BaseRequestHandler):
# 必须重写handle方法
def handle(self):
try:
while True: # 通信循环
# 给客户端回消息
# conn对象就是request
data = self.request.recv(1024) # 接收数据
print(self)#<main.MyTcp object at 0x0000015941D1ECC0>
print(self.client_address)#('127.0.0.1', 11515)
print(data)
if len(data) == 0:
return
self.request.send(data.upper()) # 发送数据
except Exception:
pass
if name == 'main':
# 实例化得到一个tcp连接的对象,Threading是说,只要来了请求,自动的开线程来处理连接跟交互数据
# 第一个参数是绑定的地址,第二个参数传一个类
server = socketserver.ThreadingTCPServer(('127.0.0.1', 8009), MyTcp)
# 一直在监听,只要来一个请求,就起一个线程做交互
server.serve_forever()
客户端.py
import socket
client = socket.socket(type=socket.SOCK_STREAM) # 建立socket对象
client.connect(('127.0.0.1', 8009)) # 和客户端建立连接
while True: # 通信循环
cmd = input('>>>:')
if not cmd: continue # 发送数据不能为空
client.send(bytes(cmd, encoding='utf-8')) # 以bytes格式发送数据
data = client.recv(1024) # 接受服务端数据
print(str(data, encoding='gbk'))