socket是什么?
Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
所以,我们无需深入理解 tcp/udp 协议,socket 已经为我们封装好了,我们只需要遵循 socket 的规定去编程,写出的程序自然就是遵循 tcp/udp 标准的。
也有人将 socket 说成 ip+port,ip 是用来标识互联网中的一台主机的位置,而 port 是用来标识这台机器上的一个应用程序,ip 地址是配置到网卡上的,而 port 是应用程序开启的,ip 与 port 的绑定就标识了互联网中独一无二的一个应用程序
而程序的pid是同一台机器上不同进程或者线程的标识
套接字工作原理
一个生活中的场景。你要打电话给一个朋友,先拨号,朋友听到电话铃声后提起电话,这时你和你的朋友就建立起了连接,就可以讲话了。等交流结束,挂断电话结束此次交谈。 生活中的场景就解释了这工作原理。
先从服务器端说起。服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束
socket()模块
基于 socket 实现简单套接字通信
服务端
import socket
# 1.买手机
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 2.绑定手机卡
phone.bind(('127.0.0.1', 8080)) # 端口范围:0-65535,0-1024给操作系统使用
# 3.开机
phone.listen(5)
# 4.等待连接
print('Waiting...')
conn, client_addr = phone.accept()
# 5.收发消息
data = conn.recv(1024) # 1、单位:bytes 2、1024 代表接收最大 1024 个bytes
print('客户端数据:', data)
conn.send('hello'.encode('utf-8'))
# 6.挂断
conn.close()
# 7.关机
conn.close()
客户端
import socket
# 1.买手机
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 2.拨号
phone.connect(('127.0.0.1', 8080))
# 3.发、收消息
phone.send('ni hao'.encode('utf-8'))
res = phone.recv(1204)
print(res)
# 4.关闭
phone.close()
在简单套接字基础上加上通信循环
服务端
import socket
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.bind(('127.0.0.1',8080))
phone.listen(5)
print('waiting...')
conn,clent_addr = phone.accept()
print(clent_addr)
start = 1
while start: # 通信循环
age = conn.recv(1024)
if age == 'b':
break
print(age)
conn.send(age.upper())
conn.close()
phone.close()
客户端
import socket
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.connect(('127.0.0.1', 8080))
while True:
mes = input('>>>').strip()
if mes == 'b':
break
phone.send(mes.encode('utf-8'))
info = phone.recv(1024)
print(info)
phone.close()
BUG修改
服务端
import socket
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.bind(('127.0.0.1', 8081))
phone.listen(5)
print('waiting...')
conn, clent_addr = phone.accept()
print(clent_addr)
start = 1
while start: # 通信循环
try:
age = conn.recv(1024)
if not age: break
print(age)
conn.send(age.upper())
except ConnectionResetError:
break
conn.close()
phone.close()
客户端
import socket
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
phone.connect(('127.0.0.1', 8081))
while True:
mes = input('>>>').strip()
if not mes: continue
if mes == 'b':
break
phone.send(mes.encode('utf-8'))
info = phone.recv(1024)
print(info.decode('GBK'))
phone.close()
在简单套接字基础上加上通信循环
服务端
import socket
import subprocess
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.bind(('127.0.0.1', 8081))
phone.listen(5)
print('waiting...')
while True: # 链接循环
conn, clent_addr = phone.accept()
print(clent_addr)
start = 1
while start: # 通信循环
try:
age = conn.recv(1024)
if not age: break
print(age)
info =subprocess.Popen(age.decode('utf-8'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout = info.stdout.read()
stderr = info.stderr.read()
conn.send(stdout+stderr)
except ConnectionResetError:
break
conn.close()
phone.close()
客户端
import socket
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
phone.connect(('127.0.0.1', 8081))
while True:
mes = input('>>>').strip()
if not mes: continue
if mes == 'b':
break
phone.send(mes.encode('utf-8'))
info = phone.recv(1024)
print(info.decode('GBK'))
phone.close()
在解决粘包问题——简单版
服务端
import socket
import subprocess
import struct
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(('127.0.0.1', 8080))
server.listen(5)
print('waiting...')
while True:
conn, addr = server.accept()
print(addr)
while True:
ret = conn.recv(1024)
print(ret)
if not ret: break
info = subprocess.Popen(ret.decode('utf-8'), shell=True,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout = info.stdout.read()
stderr = info.stderr.read()
len_std = len(stdout) + len(stderr)
totle = struct.pack('i', len_std)
conn.send(totle)
conn.send(stdout)
conn.send(stderr)
conn.close()
server.close()
客户端
import socket
import struct
center = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
center.connect(('127.0.0.1', 8080))
while True:
res = input('>>>').strip()
if not res: continue
center.send(res.encode('utf-8'))
total = center.recv(4)
total_size = struct.unpack('i', total)
recv_size = 0
recv_data = b''
while recv_size < int(total_size[0]):
ret = center.recv(1024)
recv_data += ret
recv_size += len(ret)
print(recv_data.decode('GBK'))
center.close()
在解决粘包问题——最终版
问题:1.报头是不仅仅只包含文本长度信息的,2.不应该把文本长度直接写入报文内。
服务端
import socket
import subprocess
import json
import struct
cmd = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
cmd.bind(('127.0.0.1', 8090))
cmd.listen(4)
print('waiting...')
while True:
conn, addr = cmd.accept()
print(addr)
while 1:
try:
com = conn.recv(1024)
if not com: break
com_info = subprocess.Popen(com.decode('utf-8'), shell=True,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout = com_info.stdout.read()
stderr = com_info.stderr.read()
size_info = len(stdout) + len(stderr)
header = {
'file_name': 'app.txt',
'hd5': '124482',
'size_data': size_info
}
header_str = json.dumps(header)
header_bytes = header_str.encode('GBK')
conn.send(struct.pack('i', len(header_bytes)))
conn.send(header_bytes)
conn.send(stdout)
conn.send(stderr)
except Exception:
break
conn.close()
cmd.close()
客户端
import socket import struct import json cmd = socket.socket(socket.AF_INET, socket.SOCK_STREAM) cmd.connect(('127.0.0.1', 8090)) while 1: cmp = input('>>>').strip() if not cmp: continue cmd.send(cmp.encode('utf-8')) header = cmd.recv(4) header_size = struct.unpack('i', header) header_str = cmd.recv(header_size[0]) header_json = json.loads(header_str) send_size = 0 send_str = b'' while send_size < int(header_json['size_data']): ret = cmd.recv(1024) send_str += ret send_size += len(ret) print(send_str.decode('GBK')) cmd.closs()