day 29
2018-04-20 本文已影响5人
两分与桥
socket 编程
1.客户端服务器架构
2.osi 七层
3.socket 就是封装底层的 TCP 和UDP、IP 等协议的库,我们直接调用 socket 为我们封装的方法就可以利用 TCP 和 UDP 进行通信
4.套接字,也就是 socket 的中文名,ip address + port,百度百科上面这样写:TCP用主机的IP地址加上主机上的端口号作为TCP连接的端点,这种端点就叫做套接字(socket)或插口
5.工作流程,看代码吧,TCP/IP 协议通信
socket TCP 的 recv 自己这端的缓冲区为空时,阻塞
socket UDP 的recvfrom 自己这端的缓冲区为空时,就收一个空
# 服务器端
import socket
socket1 = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 基于网络,tcp/ip通信
socket1.bind(('127.0.0.1',8088)) #绑定到固定端口,等待客户端来访问
socket1.listen(5) #最多有5个等待连接
conn,addr = socket1.accept() #没有连接时,会在这句一直等待
print('conn = ',conn)
print('addr = ',addr) #IP地址和端口 ('127.0.0.1', 1941)
data = conn.recv(1024)
print('data = ', data.decode('utf-8'))
conn.send('你好啊'.encode('utf-8')) # 发送只能是二进制编码
conn.close()
socket1.close()
输出结果:
conn = <socket.socket fd=492, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8088), raddr=('127.0.0.1', 1941)>
addr = ('127.0.0.1', 1941)
data = 天天向上
客户端只需要负责连接就行了
# 客户端
import socket
socket1 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
socket1.connect(('127.0.0.1', 8088)) # 发起 TCP 三次握手连接
socket1.send('天天向上'.encode('utf-8'))
data = socket1.recv(1024)
print(data.decode('utf-8'))
socket1.close()
输出结果:
你好啊
升级版服务器端、客户端,server 可以捕捉异常并继续运行。
# server
import socket
ip_port = ('127.0.0.1', 8088)
backlog = 5
buffersize = 1024
socket_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
socket_server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) # 减少地址复用的时间
socket_server.bind(ip_port)
socket_server.listen(backlog)
while True:
print('wait to connect')
conn,addr = socket_server.accept()
print('conn = ', conn)
print('addr = ', addr)
try:
while True:
data = conn.recv(buffersize)
print('>>>', data.decode('utf-8'))
if data.decode('utf-8') == 'bye':
print('prepare to close connect')
break
send = input('>>>').strip()
conn.send(send.encode('utf-8'))
if not send or send == 'bye':
print('prepare to close connect')
conn.send('bye'.encode('utf-8'))
break
except Exception as e:
print(e)
conn.close()
print('close')
socket_server.close()
输出结果:
wait to connect
conn = <socket.socket fd=404, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8088), raddr=('127.0.0.1', 6802)>
addr = ('127.0.0.1', 6802)
>>>
>>>d
[WinError 10053] 你的主机中的软件中止了一个已建立的连接。
close
wait to connect
conn = <socket.socket fd=484, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8088), raddr=('127.0.0.1', 6803)>
addr = ('127.0.0.1', 6803)
>>> 413
# client
import socket
ip_port = ('127.0.0.1', 8088)
buffersize = 1024
socket_client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
socket_client.connect(ip_port)
while(True):
send = input('>>>').strip()
socket_client.send(send.encode('utf-8'))
if not send or send == 'bye':
print('prepare to close connect')
socket_client.send('bye'.encode('utf-8'))
break
data = socket_client.recv(buffersize)
print('>>>', data.decode('utf-8'))
if data.decode('utf-8') == 'bye':
print('prepare to close connect')
break
socket_client.close()
print('close')
输出结果:
>>>413
Traceback (most recent call last):
File "C:/Users/libai/PycharmProjects/begin/test.py", line 14, in <module>
data = socket_client.recv(buffersize)
KeyboardInterrupt #手动终止客户端,服务端自动断开连接
基于UDP的套接字
#udp_server
import socket
import time
ip_port = ('127.0.0.1', 8088)
buffersize = 1024
udp_server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
udp_server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
udp_server.bind(ip_port)
print('udp is working')
while True:
data,addr = udp_server.recvfrom(buffersize)
print('addr = ', addr)
print("data = ",data.decode('utf-8'))
udp_server.sendto(time.strftime('%Y-%m-%d %X').encode('utf-8'), addr)
输出结果:
udp is working
addr = ('127.0.0.1', 50122)
data = 123
addr = ('127.0.0.1', 50122)
data = 88
# udp_client
import socket
ip_port = ('127.0.0.1', 8088)
buffersize = 1024
udp_client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
while True:
data = input('>>>').strip()
udp_client.sendto(data.encode('utf-8'),ip_port)
ntp_time, addr = udp_client.recvfrom(buffersize)
print('ntp返回的时间:',ntp_time.decode('utf-8'))
print(addr)
输出结果:
>>>123
ntp返回的时间: 2018-04-21 11:04:03
('127.0.0.1', 8088)
>>>88
ntp返回的时间: 2018-04-21 11:04:08
('127.0.0.1', 8088)
>>>
远程执行系统命令,调用 subprocess 模块,十分有意思
# server_tcp
import socket
import subprocess
ip_port = ('127.0.0.1', 8088)
buffersize = 1024
backlog = 5
socket_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
socket_server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
socket_server.bind(ip_port)
socket_server.listen(backlog)
while True:
print('watiing for connect')
conn, addr = socket_server.accept()
print('conn = ', conn)
print('addr = ', addr)
while True:
data = conn.recv(buffersize)
print('输入>>>', data.decode('utf-8'))
if not data or data.decode('utf-8')=='bye':
break
response = subprocess.Popen(data.decode('utf-8'),shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
stdin=subprocess.PIPE)
error = response.stderr.read()
if error:
cmd_res = error
else:
cmd_res = response.stdout.read()
conn.send(cmd_res)
conn.close()
输出结果:
watiing for connect
conn = <socket.socket fd=456, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8088), raddr=('127.0.0.1', 27419)>
addr = ('127.0.0.1', 27419)
输入>>> dir
输入>>> 156
输入>>> bye
watiing for connect
# client_tcp
import socket
ip_port = ('127.0.0.1', 8088)
buffersize = 1024
socket_client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
socket_client.connect(ip_port)
while True:
data = input('>>>')
socket_client.send(data.encode('utf-8'))
if not data: continue
if data == 'bye':
break
response = socket_client.recv(buffersize)
print(response.decode('gbk'))
print('connect close')
输出结果:
2018/04/21 15:25 <DIR> .
2018/04/21 15:25 <DIR> ..
2018/04/21 15:28 <DIR> .idea
2018/04/20 17:23 <DIR> a
2018/04/18 16:03 25 a.txt
2018/04/21 15:25 1,028 begin
2018/04/21 15:00 19 process.py
2018/04/21 15:23 392 test.py
4 个文件 1,464 字节
4 个目录 68,050,141,184 可用字节
>>>156
'156' 不是内部或外部命令,也不是可运行的程序
或批处理文件。
>>>bye
connect close
socket TCP 会出现粘包现象,也就是recv命令收取的buffersize没有全部收完,就会等到下次再继续收
例如:先运行ipconfig 命令,没有收完缓冲区使得打印信息不全,再运行别的命令,下次的命令会打印运行ipconfig命令剩余的信息。
而UDP不会出现粘包现象
解决粘包 第一个版本
# server
import socket
import subprocess
ip_port = ('127.0.0.1', 8088)
buffersize = 1024
backlog = 5
socket_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
socket_server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
socket_server.bind(ip_port)
socket_server.listen(backlog)
while True:
print('watiing for connect')
conn, addr = socket_server.accept()
print('conn = ', conn)
print('addr = ', addr)
while True:
data = conn.recv(buffersize)
print('输入>>>', data.decode('utf-8'))
if not data or data.decode('utf-8')=='bye':
break
response = subprocess.Popen(data.decode('utf-8'),shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
stdin=subprocess.PIPE)
error = response.stderr.read()
if error:
cmd_res = error
else:
cmd_res = response.stdout.read()
if not cmd_res:
cmd_res = 'execution succeed'.encode('gbk')
length = len(cmd_res)
conn.send(str(length).encode('gbk'))
ready = conn.recv(buffersize)
if ready.decode('gbk') == 'ready':
conn.send(cmd_res)
conn.close()
#client
import socket
ip_port = ('127.0.0.1', 8088)
buffersize = 1024
socket_client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
socket_client.connect(ip_port)
try:
while True:
data = input('>>>')
socket_client.send(data.encode('utf-8'))
if not data: continue
if data == 'bye':
break
length = socket_client.recv(buffersize)
length = int(length.decode('gbk'))
socket_client.send('ready'.encode("gbk"))
response = b''
while len(response) < length:
response += socket_client.recv(buffersize)
print(response.decode('gbk'))
except Exception as e:
print(e)
print('connect close')
socket_client.close()
解决粘包 第二个版本
# server
import socket
import subprocess
ip_port = ('127.0.0.1', 8088)
buffersize = 1024
backlog = 5
socket_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
socket_server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
socket_server.bind(ip_port)
socket_server.listen(backlog)
while True:
print('watiing for connect')
conn, addr = socket_server.accept()
print('conn = ', conn)
print('addr = ', addr)
while True:
data = conn.recv(buffersize)
print('输入>>>', data.decode('utf-8'))
if not data or data.decode('utf-8')=='bye':
break
response = subprocess.Popen(data.decode('utf-8'),shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
stdin=subprocess.PIPE)
error = response.stderr.read()
if error:
#response.stderr.read() 返回的值为系统编码的bytes类型,需要解码
cmd_res = error.decode('gbk')
else:
cmd_res = response.stdout.read().decode('gbk')
if not cmd_res:
cmd_res = 'execution succeed'
length = len(cmd_res)
send_data = str(length)+'!o!'+cmd_res
# 字符长度,'!o!'符号作为分隔符,再加上返回的数据一起发送
conn.send(send_data.encode('gbk'))
conn.close()
输出结果:
watiing for connect
conn = <socket.socket fd=456, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8088), raddr=('127.0.0.1', 28581)>
addr = ('127.0.0.1', 28581)
输入>>> ipconfig
输入>>> bye
watiing for connect
import socket
ip_port = ('127.0.0.1', 8088)
buffersize = 1024
socket_client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
socket_client.connect(ip_port)
try:
while True:
data = input('>>>')
socket_client.send(data.encode('utf-8'))
if not data: continue
if data == 'bye':
break
send_data = socket_client.recv(buffersize)
# 不知道发送的字符长度,从收取的第一个data中截取出来,没读完再继续读取
send_data = send_data.decode('gbk')
key_line = send_data.find('!o!')# 找到分隔符,返回的是第一个!的位置
length = int(send_data[0:key_line]) # 切割出返回字符长度
response = send_data[key_line+3:].encode('gbk') # 切割出返回字符,再次编码以便和未发送完的字符串拼接
while len(response) < length:
response += socket_client.recv(buffersize)
print(response.decode('gbk'))
print('返回%s个字节' %length)
except Exception as e:
print(e)
print('connect close')
socket_client.close()
输出结果:#只复制了一部分
以太网适配器 VMware Network Adapter VMnet8:
连接特定的 DNS 后缀 . . . . . . . :
本地链接 IPv6 地址. . . . . . . . : fe80::8dae:48ae:e19c:207d%9
IPv4 地址 . . . . . . . . . . . . : 192.168.1.1
子网掩码 . . . . . . . . . . . . : 255.255.255.0
默认网关. . . . . . . . . . . . . :
无线局域网适配器 本地连接* 12:
连接特定的 DNS 后缀 . . . . . . . :
本地链接 IPv6 地址. . . . . . . . : fe80::7ca0:36c4:83dd:d3ac%22
IPv4 地址 . . . . . . . . . . . . : 192.168.23.1
子网掩码 . . . . . . . . . . . . : 255.255.255.0
IPv4 地址 . . . . . . . . . . . . : 192.168.137.1
子网掩码 . . . . . . . . . . . . : 255.255.255.0
默认网关. . . . . . . . . . . . . :
返回1631个字节
>>>bye
解决粘包 第三版
#server
import socket
import subprocess
import struct
ip_port = ('127.0.0.1', 8088)
buffersize = 1024
backlog = 5
socket_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
socket_server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
socket_server.bind(ip_port)
socket_server.listen(backlog)
while True:
print('waiting for connect')
conn, addr = socket_server.accept()
print('conn = ', conn)
print('addr = ', addr)
while True:
data = conn.recv(buffersize)
print('输入>>>', data.decode('utf-8'))
if not data or data.decode('utf-8')=='bye':
break
response = subprocess.Popen(data.decode('utf-8'),shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
stdin=subprocess.PIPE)
error = response.stderr.read()
if error:
#response.stderr.read() 返回的值为系统编码的bytes类型,需要解码
cmd_res = error.decode('gbk')
else:
cmd_res = response.stdout.read().decode('gbk')
if not cmd_res:
cmd_res = 'execution succeed'
length = len(cmd_res)
length_pack = struct.pack('i', length) #struct.pack 编码出来的length_pack 为四个字节
conn.send(length_pack)
conn.send(cmd_res.encode('gbk'))
conn.close()
#client
import socket
import struct
ip_port = ('127.0.0.1', 8088)
buffersize = 1024
socket_client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
socket_client.connect(ip_port)
try:
while True:
data = input('>>>')
socket_client.send(data.encode('utf-8'))
if not data: continue
if data == 'bye':
break
length_pack = socket_client.recv(4) #取出四字节的编码字符数据长度
length = int(struct.unpack('i',length_pack)[0])
data = b''
while len(data) < length:
data += socket_client.recv(buffersize)
print(data.decode('gbk'))
print('返回%s个字节' %length)
except Exception as e:
print(e)
print('connect close')
socket_client.close()
day 29 结束