网络编程:使用编程语言实现多台计算机的通信。
网络编程三要素:
- IP地址
- 端口
- 协议
一、socket通信案例
server端
import socket
# 1.创建socket对象:默认是ipv4的TCP协议
sock = socket.socket()
# 2.bind方法
sock.bind(('127.0.0.1', 8890))
# 3.建立最大监听数
sock.listen(5)
while 1:
# 4.等待连接
print('server is waiting...')
client_sock, client_addr = sock.accept()
print(client_sock)
print(client_addr)
# 正式通信
while 1:
# 5.接受消息
data = client_sock.recv(1024)
if data.decode() == 'q':
break
print('客户端发送的数据:', data.decode())
# 6.回复消息
msg = input('请输入要发送给client的消息')
client_sock.send(msg.encode())
client端:
import socket
# 1.创建socket对象:默认是ipv4的TCP协议
sock = socket.socket()
# 2.连接服务端
sock.connect(('127.0.0.1', 8890))
# 正式通信
while 1:
msg = input('请输入要发送给server的数据:')
# 3.发送数据:发送的必须是字节数据
sock.send(msg.encode())
# 接收服务端的响应
res = sock.recv(1024)
print('服务端的响应数据:', res.decode())
sock.close()
二、 TCP的粘包
根据socket缓冲区和数据的传递过程,可以看到数据的接收和发送是无关的,read()/recv() 函数不管数据发送了多少次,都会尽可能多的接收数据。也就是说,read()/recv() 和 write()/send() 的执行次数可能不同。
数据的“粘包”问题,客户端发送的多个数据包被当做一个数据包接收。也称数据的无边界性,read()/recv() 函数不知道数据包的开始或结束标志(实际上也没有任何开始或结束标志),只把它们当做连续的数据流来处理。
server端
import socket
import time
sock = socket.socket()
sock.bind(('127.0.0.1', 8891))
sock.listen(5)
# 等待连接
print('server is waiting...')
client_sock, client_addr = sock.accept()
time.sleep(5)
data = client_sock.recv(1024)
print('client传来的数据:', data.decode())
client端
import socket
sock = socket.socket()
sock.connect(('127.0.0.1', 8891))
msg = input('请输入要给server发送的消息')
sock.send(msg.encode())
sock.send(msg.encode())
sock.send(msg.encode())
三、ssh案例
server端:
import socket
import subprocess
import struct
import time
sock = socket.socket()
sock.bind(('127.0.0.1', 8890))
sock.listen(5)
# 等待连接
print('server is waiting...')
while 1:
client_sock, client_addr = sock.accept()
while 1:
msg = client_sock.recv(1024)
ret = subprocess.getoutput(msg.decode('gbk'))
print('server的响应是:', ret)
ret_len = struct.pack('i', len(ret.encode('gbk'))) # struct.pack()解决粘包问题
print('server响应的字节串大小是:', ret_len)
# print(len(ret.encode('gbk')))
client_sock.send(ret_len) # send只能发str字符串,不能发int
client_sock.send(ret.encode('gbk'))
client端
import socket
import math
import time
import struct
sock = socket.socket()
sock.connect(('127.0.0.1', 8890))
while 1:
msg = input('请输入要发送给server端的消息')
sock.send(msg.encode())
time.sleep(5) # 模拟粘包
# 接收数据大小
data = sock.recv(4)
size = struct.unpack('i', data)[0]
# size = data.decode('gbk')
print('server发来的消息总大小:', size) # 循环读取
# 接收数据内容
s = ''
num = math.ceil(int(size)/1024)
print('循环次数:', num)
for i in range(0, math.ceil(int(size)/1024)): # 解决内存问题
print('i', i)
data = sock.recv(1024)
a = data.decode('gbk')
s = s + a
print('server发来的消息', s)
四、FTP案例
server端:
import os.path
import socket
import struct
import json
class FTP():
addr = ('127.0.0.1', 8890)
def __init__(self):
self.sock = socket.socket()
# bind方法
self.sock.bind(self.addr)
# listen
self.sock.listen(5)
# 等待连接
print('server is waiting...')
self.client_sock, self.client_addr = self.sock.accept()
# print('客户端sock', self.client_sock)
def run(self):
data = self.client_sock.recv(1024)
data_json = json.loads(data.decode('gbk'))
cmd = data_json['cmd']
file_data = data_json['file_data']
print('接收的文件信息:', data_json, type(data_json))
if hasattr(self, cmd):
getattr(self, cmd)(data_json)
else:
print('请输入正确shell')
def put(self, file_data):
print('server端put函数...')
file_name = file_data['file_data']['name']
file_size = file_data['file_data']['size']
size = 0
with open(file_name, mode='wb') as f:
while size < file_size:
content = self.client_sock.recv(1024)
f.write(content)
size += len(content)
def get(self, file_data):
print('server端get函数...')
# 发送文件信息
file_size = os.path.getsize(file_data['file_data']['name'])
file_data['file_data']['size'] = file_size
self.client_sock.send(json.dumps(file_data).encode('gbk'))
# 发送文件
recv_size = 0
with open(file_data['file_data']['name'], mode='rb') as f:
while recv_size < file_size:
content = f.readline()
self.client_sock.send(content)
recv_size += len(content)
print('server端文件已发送...')
if __name__ == '__main__':
FTP().run()
client端:
import os.path
import socket
import struct
import json
class FTP():
server_addr = ('127.0.0.1', 8890)
def __init__(self):
# 创建socket对象
self.sock = socket.socket()
# 连接server
self.sock.connect(self.server_addr)
def run(self):
print('上传:put 文件地址 ; 下载:get 文件地址')
inputs = input('请输入命令')
cmd, path = inputs.split()
if hasattr(self, cmd):
print('反射结果:', getattr(self, cmd), type(getattr(self, cmd)))
getattr(self, cmd)(path)
else:
print('请输入正确的shell命令')
def put(self, path):
print('put函数...')
# 获取文件信息
file_size = os.path.getsize(path)
file_name = os.path.basename(path)
data = {'cmd': 'put', 'file_data': {'size': file_size,'name': file_name}}
print('文件信息:', data)
# 发送data
self.sock.send(json.dumps(data).encode('gbk'))
# 发送文件
with open(path, mode='rb') as f:
for line in f:
self.sock.send(line)
print('文件已发送')
def get(self, path):
print('get函数...')
data = {'cmd': 'get', 'file_data': {'name': path}}
print('get函数的文件信息类型', type(data))
self.sock.send(json.dumps(data).encode('gbk'))
# 接收文件大小
data = self.sock.recv(1024)
print('接收到的文件信息', data, type(data))
data = json.loads(data.decode('gbk'))
file_size = data['file_data']['size']
# 写入文件
recv_size = 0
with open(path, mode='wb') as f:
while recv_size < file_size:
content = self.sock.recv(1024)
f.write(content)
recv_size += len(content)
if __name__ == '__main__':
FTP().run()
如上ftp案例目前仅实现了文件的上传put和下载get;感兴趣的朋友可以在此基础上增加如下功能;
- 断点续传
- 上传/下载展示进度条
- 解决粘包问题