Python Socket网络通信
发表于更新于
字数总计:2.2k阅读时长:9分钟阅读量: 长沙
LearnpythonPython Socket网络通信
lingyun互联网协议
按照功能的不同分为七层。
OSI七层协议:应,表,会,传,网,数,物。
其中应表会可以归为应用层。
- 应用层:应用程序。http,ftp协议
- 传输层:tcp/udp协议。端口来标识运行中的一款应用程序。
以太网头|IP头|tcp头|数据
- 网络层:IP协议,用于寻找子网。
- 数据链路层:Ethernet(以太网)协议,将数据按标准分组。
- 物理层:物理设备,发射电信号,如:101001
tcp也称作流式协议,需要双向管道(来回)。
通过握手确认建立临时管道。
udp协议不需要通道,不会等待对方确认是否收到。
效率比tcp协议高,但是不可靠。(只负责丢)
socket层
在应用层和传输层(tcp/udp)之间的中间软件抽象层,它是一组接口。
socket为套接字编程。
一个简单的服务端和客户端
服务端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| iimport socket
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.bind(('127.0.0.1', 8080))
phone.listen(5)
conn, client_address = phone.accept()
data = conn.recv(1024) print(data)
conn.send(data.upper())
conn.close() phone.close()
|
客户端
1 2 3 4 5 6 7 8 9 10 11 12 13
| iimport socket
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.connect(('127.0.0.1', 8080))
phone.send('hello'.encode('utf-8'))
data = phone.recv(1024) print(data) phone.close()
|
改进客户端和服务端,实现循环通信
服务端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| import socket
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) phone.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) phone.bind(('127.0.0.1', 8080))
phone.listen(5) print('staring...')
while True: conn, client_address = phone.accept()
while True: try: data = conn.recv(1024) if not data: break print(data) conn.send(data.upper()) except ConnectionResetError: break conn.close()
phone.close()
|
客户端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import socket
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.connect(('127.0.0.1', 8080))
while True: msg = input('>> ').strip() if not msg: continue phone.send(msg.encode('utf-8')) data = phone.recv(1024) print(data.decode('utf-8'))
phone.close()
|
实现类似ssh的效果(远程执行命令)
服务端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| import socket import subprocess
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) phone.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) phone.bind(('127.0.0.1', 8080))
phone.listen(5) print('staring...')
while True: conn, client_address = phone.accept()
while True: try: cmd = conn.recv(1024) if not cmd: break obj = subprocess.Popen(cmd.decode('utf-8'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout = obj.stdout.read() stderr = obj.stdout.read() conn.send(stdout+stderr)
except ConnectionResetError: break conn.close()
phone.close()
|
客户端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import socket
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.connect(('127.0.0.1', 8080))
while True: cmd = input('>> ').strip() if not cmd: continue phone.send(cmd.encode('utf-8')) data = phone.recv(1024) print(data.decode('utf-8'))
phone.close()
|
这里会留下一个问题,每次接受限制了1024个字节,会产生了粘包现象。
粘包现象
TCP粘包是指发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾。
底层原理
1.不管是recv还是send都不是直接接受对方的消息,而是操作自己的操作系统。
(所以recv没法超过自己内存的大小)
2.tcp协议会对间隔短的多个包进行优化合并
3.recv和send方法,可以一直调用,直到内存里的值被取完
解决粘包现象
一种方法:先把数据的长度发送给客户端,再发送真实数据。
服务端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| import socket import struct import subprocess
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) phone.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) phone.bind(('127.0.0.1', 8080))
phone.listen(5) print('staring...')
while True: conn, client_address = phone.accept()
while True: try: cmd = conn.recv(1024) if not cmd: break obj = subprocess.Popen(cmd.decode('utf-8'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout = obj.stdout.read() stderr = obj.stdout.read()
total_size = len(stdout) + len(stderr) header = struct.pack('i', total_size)
conn.send(header)
conn.send(stdout) conn.send(stderr)
except ConnectionResetError: break conn.close()
phone.close()
|
客户端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| import socket import struct
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.connect(('127.0.0.1', 8080))
while True: cmd = input('>> ').strip() if not cmd: continue phone.send(cmd.encode('utf-8'))
header = phone.recv(4) total_size = struct.unpack('i', header)[0]
recv_size = 0 recv_data = b'' while recv_size < total_size: res = phone.recv(1024) recv_data += res recv_size += len(res)
print(recv_data.decode('utf-8'))
phone.close()
|
上述的方法有个弊端是,pack
方法打包不能超过2147483647
。
1 2 3
| res = struct.pack('l', 10000000000000) print(res, len(res))
|
另一个解决办法:
1.先发送报头长度
2.再发送报头的数据
3.最后发送真实数据
服务端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| import json import socket import struct import subprocess
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) phone.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) phone.bind(('127.0.0.1', 8080))
phone.listen(5) print('staring...')
while True: conn, client_address = phone.accept()
while True: try: cmd = conn.recv(1024) if not cmd: break obj = subprocess.Popen(cmd.decode('utf-8'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout = obj.stdout.read() stderr = obj.stdout.read()
header_dic = { 'filename': 'test.txt', 'size': len(stdout) + len(stderr) } header_json = json.dumps(header_dic) header_bytes = header_json.encode('utf-8')
conn.send(struct.pack('i', len(header_bytes)))
conn.send(header_bytes)
conn.send(stdout) conn.send(stderr)
except ConnectionResetError: break conn.close()
phone.close()
|
客户端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| import json import socket import struct
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.connect(('127.0.0.1', 8080))
while True: cmd = input('>> ').strip() if not cmd: continue phone.send(cmd.encode('utf-8'))
obj = phone.recv(4) header_size = struct.unpack('i', obj)[0]
header_bytes = phone.recv(header_size)
header_json = header_bytes.decode('utf-8') header_dic = json.loads(header_json) print(header_dic) total_size = header_dic['size']
recv_size = 0 recv_data = b'' while recv_size < total_size: res = phone.recv(1024) recv_data += res recv_size += len(res)
print(recv_data.decode('utf-8'))
phone.close()
|
文件传输功能
服务端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| import json import os import socket import struct import subprocess
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) phone.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) phone.bind(('127.0.0.1', 8080))
phone.listen(5) print('staring...')
while True: conn, client_address = phone.accept()
while True: try: res = conn.recv(1024) if not res: break
cmds = res.decode('utf-8').split() filename = cmds[1]
header_dic = { 'filename': filename, 'size': os.path.getsize(filename) }
header_json = json.dumps(header_dic) header_bytes = header_json.encode('utf-8')
conn.send(struct.pack('i', len(header_bytes)))
conn.send(header_bytes)
with open(filename, 'rb') as f: for line in f: conn.send(line)
except ConnectionResetError: break conn.close()
phone.close()
|
客户端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| import json import socket import struct
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.connect(('127.0.0.1', 8080))
while True: cmd = input('>> ').strip() if not cmd: continue phone.send(cmd.encode('utf-8'))
obj = phone.recv(4) header_size = struct.unpack('i', obj)[0]
header_bytes = phone.recv(header_size)
header_json = header_bytes.decode('utf-8') header_dic = json.loads(header_json) print(header_dic) total_size = header_dic['size'] filename = header_dic['filename']
with open(filename + '_download', 'wb') as f: recv_size = 0 while recv_size < total_size: line = phone.recv(1024) f.write(line) print('{0}/{1}'.format(recv_size, total_size)) recv_size += len(line)
phone.close()
|
udp协议实现
比如查询操作就适合udp协议,效率快,不需要双方进行握手确认,且不会产生粘包现象。
(虽然不会粘包,但是数据超出限制则会丢失)
服务端
1 2 3 4 5 6 7 8
| import socket
server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) server.bind(('127.0.0.1', 8080))
while True: data, client_addr = server.recvfrom(1024) server.sendto(data.upper(), client_addr)
|
客户端
1 2 3 4 5 6 7 8 9 10
| import socket
client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
while True: msg = input('>>: ').strip() client.sendto(msg.encode('utf-8'), ('127.0.0.1', 8080))
data, server_addr = client.recvfrom(1024) print(data, server_addr)
|