socket网络编程
socket网络编程
1.OSI七层协议
2.基于tcp协议的套接字通信
3.模拟ssh远程执行命令
4.tcp的粘包问题已经解决方案
5.基于udp协议套接字通信
6.socketserver
目标:开发一个C/S架构的软件
C/S:客户端-----》server
B/S:浏览器-----》server
server端
1、位置必须固定/绑定一个固定地址
2、对外一直提供服务,稳定运行
3、支持并发(让对个客户端感觉是同时被服务着)
网络:
物理连接介质+互联网协议)(互联网协议就是世界英语)
ip+port可以标识世界范围内独一无二的应用软件(基于网络通信)
任何的数据报都应该分为报头和数据两部分,其中报头是用来描述数据的,报头的长度是固定的
一、基于tcp协议的套接字通信(简单版)
服务端:
import socket
#1.买手机
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #tcp流式协议==》SOCK_STREAM
#2.插入手机卡
phone.bind(('127.0.0.1',8080)) #ip应该是服务端这个软件运行的那台机器的ip地址,port(0-65535)
#3.开机
phone.listen(5)#半连接池:控制的是同一时刻的链接请求数
print('服务端启动...')
#4.等待电话连线请求
conn,client_addr=phone.accept()#(套接字对象,存放有客户端的ip和端口的元组)
print(conn,client_addr)
#5.收\发消息
data=conn.recv(1024)
print('收到消息',data)
conn.send(data.upper())
#6.挂电话
conn.close()
#7.关机
phone.close()
客户端:
import socket
#1.买手机
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#2.拨号
phone.connect(('127.0.0.1',8080)) #ip和port应该是服务端的ip和端口
#3.收\发消息
phone.send('hello'.encode('utf-8')) #必须是bytes类型
data=phone.recv(1024)
print('服务端消息',data)
#4.关闭
phone.close()
二、基于tcp协议的套接字通信(通信循环)
服务端收发消息增加while True:
服务端:
import socket
#1.买手机
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #tcp流式协议==》SOCK_STREAM
#2.插入手机卡
phone.bind(('127.0.0.1',8080)) #ip应该是服务端这个软件运行的那台机器的ip地址,port(0-65535)
#3.开机
phone.listen(5)#半连接池:控制的是同一时刻的链接请求数
print('服务端启动...')
#4.等待电话连线请求
conn,client_addr=phone.accept()#(套接字对象,存放有客户端的ip和端口的元组)
print(conn,client_addr)
#5.收\发消息
while True:
try:
data=conn.recv(1024)
if len(data)==0:break #针对linux或者Mac
print('收到消息',data)
conn.send(data.upper())
except ConnectionResetError: #针对windows
break
#6.挂电话
conn.close()
#7.关机
phone.close()
#windows电脑cmd命令查看监听端口
netstat -an|findstr 8080
客户端:
import socket
#1.买手机
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#2.拨号
phone.connect(('127.0.0.1',8080)) #ip和port应该是服务端的ip和端口
#3.收\发消息
while True:
msg=input('>>>>:').strip()
phone.send(msg.encode('utf-8')) #必须是bytes类型
data=phone.recv(1024)
print('服务端消息',data)
#4.关闭
phone.close()
因为服务端半连接池请求数限制5(服务端:phone.listen(5)),客户端最多可以打开5个客户端,每个客户端和服务端单线链接,不可以同时链接
三、基于tcp协议的套接字通信(通信循环+连接循环)
服务端请求链接增加while True: #循环链接
服务端:
import socket
#1.买手机
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #tcp流式协议==》SOCK_STREAM
#2.插入手机卡
phone.bind(('127.0.0.1',8080)) #ip应该是服务端这个软件运行的那台机器的ip地址,port(0-65535)
#3.开机
phone.listen(5)#半连接池:控制的是同一时刻的链接请求数
print('服务端启动...')
#4.等待电话连线请求
while True: #循环链接
conn,client_addr=phone.accept()#(套接字对象,存放有客户端的ip和端口的元组)
print(conn,client_addr)
#5.收\发消息
while True:
try:
data=conn.recv(1024)
if len(data)==0:break #针对linux或者Mac
print('收到消息',data)
conn.send(data.upper())
except ConnectionResetError: #针对windows
break
#6.挂电话
conn.close()
#7.关机
phone.close()
客户端:
import socket
#1.买手机
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#2.拨号
phone.connect(('127.0.0.1',8080)) #ip和port应该是服务端的ip和端口
#3.收\发消息
while True:
msg=input('>>>>:').strip()
phone.send(msg.encode('utf-8')) #必须是bytes类型
data=phone.recv(1024)
print('服务端消息',data)
#4.关闭
phone.close()
四、bug处理
客户端增加一行代码:if len(msg)==0:continue
服务端:
import socket
#1.买手机
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #tcp流式协议==》SOCK_STREAM
#2.插入手机卡
phone.bind(('127.0.0.1',8080)) #ip应该是服务端这个软件运行的那台机器的ip地址,port(0-65535)
#3.开机
phone.listen(5)#半连接池:控制的是同一时刻的链接请求数
print('服务端启动...')
#4.等待电话连线请求
while True: #循环链接
conn,client_addr=phone.accept()#(套接字对象,存放有客户端的ip和端口的元组)
print(conn,client_addr)
#5.收\发消息
while True:
try:
data=conn.recv(1024)
if len(data)==0:break #针对linux或者Mac
print('收到消息',data)
conn.send(data.upper())
except ConnectionResetError: #针对windows
break
#6.挂电话
conn.close()
#7.关机
phone.close()
客户端:
import socket
#1.买手机
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#2.拨号
phone.connect(('127.0.0.1',8080)) #ip和port应该是服务端的ip和端口
#3.收\发消息
while True:
msg=input('>>>>:').strip()
if len(msg)==0:continue
phone.send(msg.encode('utf-8')) #必须是bytes类型
data=phone.recv(1024)
print('服务端消息',data)
#4.关闭
phone.close()
五、模拟ssh远程执行命令
Subprocess模块使用
import subprocess
obj=subprocess.Popen('dir',
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
# print(obj)
stdout=obj.stdout.read()
print(stdout.decode('GBK'))
stdout=obj.stdout.read()
print('第二次读',stdout)
注:stdout只能取一次数
说明:服务端增加subprocess模块,命令需要cmd.decode('utf-8')
客户端发送命令需要cmd.encode('utf-8'),接收输出消息需要res.decode('gbk')
服务端:
import socket
import subprocess
#1.买手机
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #tcp流式协议==》SOCK_STREAM
#2.插入手机卡
phone.bind(('127.0.0.1',8080)) #ip应该是服务端这个软件运行的那台机器的ip地址,port(0-65535)
#3.开机
phone.listen(5)#半连接池:控制的是同一时刻的链接请求数
print('服务端启动...')
#4.等待电话连线请求
while True: #循环链接
conn,client_addr=phone.accept()#(套接字对象,存放有客户端的ip和端口的元组)
print(conn,client_addr)
#5.收\发消息
while True:
try:
cmd=conn.recv(1024)
if len(cmd)==0:break #针对linux或者Mac
obj = subprocess.Popen(cmd.decode('utf-8'),
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
stdout = obj.stdout.read()
stderr = obj.stderr.read()
print(len(stdout)+len(stderr))
conn.send(stdout+stderr)
except ConnectionResetError: #针对windows
break
#6.挂电话
conn.close()
#7.关机
phone.close()
客户端:
import socket
#1.买手机
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#2.拨号
phone.connect(('127.0.0.1',8080)) #ip和port应该是服务端的ip和端口
#3.收\发消息
while True:
cmd=input('>>>>:').strip()
if len(cmd)==0:continue
phone.send(cmd.encode('utf-8')) #必须是bytes类型
res=phone.recv(1024)
print('服务端消息',res.decode('gbk'))
#4.关闭
phone.close()
六、粘包问题
客户端通过time.sleep延迟发送三条信息,服务端能够正常接收到三条信息
服务端:
import socket
server=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #tcp流式协议=>SOCK_STREAM
server.bind(('127.0.0.1',9090))
server.listen(5)
conn,client_addr=server.accept()
res1=conn.recv(1024)
print('第一次收: ',res1)
res2=conn.recv(1024)
print('第二次收: ',res2)
res3=conn.recv(1024)
print('第三次收: ',res3)
客户端:
import socket
import time
client=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
client.connect(('127.0.0.1',8081))
#tcp的nagle算法:将发送的时间间隔比较短并且数据量小的多个数据包合成一个包发送
client.send(b'hello')
time.sleep(0.1)
client.send(b'world')
time.sleep(0.1)
client.send(b'frank')
执行结果:
七、自定义报头解决粘包问题
Struct模块:
import struct
#可以将整型打包成固定长度的bytes类型,可以基于网络传输
res=struct.pack('q',123)
print(res,len(res))
res1=struct.unpack('q',res)
print(res1[0])
import json
header_dic={
'head_title':123123,
'MD5':'adfasdf2343',
'file':'a.txt'
}
header_json=json.dumps(header_dic)
print(type(header_json))
header_bytes=header_json.encode('utf-8')
print(type(header_bytes))
obj=struct.pack('i',len(header_bytes))
print(obj,len(obj))
说明:服务端,导入struc模块,制作一个报头,并发送;客户端,先收报头,并解出报头总长度,接收文件通while循环判断是否收干净,直到收干净结束
服务端:
import socket
import subprocess
import struct
#1.买手机
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #tcp流式协议==》SOCK_STREAM
#2.插入手机卡
phone.bind(('127.0.0.1',8081)) #ip应该是服务端这个软件运行的那台机器的ip地址,port(0-65535)
#3.开机
phone.listen(5)#半连接池:控制的是同一时刻的链接请求数
print('服务端启动...')
#4.等待电话连线请求
while True: #循环链接
conn,client_addr=phone.accept()#(套接字对象,存放有客户端的ip和端口的元组)
print(conn,client_addr)
#5.收\发消息
while True:
try:
cmd=conn.recv(1024)
if len(cmd)==0:break #针对linux或者Mac
obj = subprocess.Popen(cmd.decode('utf-8'),
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
stdout = obj.stdout.read()
stderr = obj.stderr.read()
#先制作一个固定长度的报头
header=struct.pack('i',len(stdout)+len(stderr))
#发送报头
conn.send(header)
print(len(stdout)+len(stderr))
conn.send(stdout+stderr)
except ConnectionResetError: #针对windows
break
#6.挂电话
conn.close()
#7.关机
phone.close()
客户端:
import socket
import struct
#1.买手机
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#2.拨号
phone.connect(('127.0.0.1',8081)) #ip和port应该是服务端的ip和端口
#3.收\发消息
while True:
cmd=input('>>>>:').strip()
if len(cmd)==0:continue
phone.send(cmd.encode('utf-8')) #必须是bytes类型
#先收报头,从报头里面解出报头信息(数据总长度)
header=phone.recv(4)
total_size=struct.unpack('i',header)[0]
#收数据
res=b''
recv_size=0
while recv_size < total_size:
data=phone.recv(1024)
res+=data
recv_size+=len(data)
print('服务端消息',res.decode('gbk'))
#4.关闭
phone.close()
八、自定义报头解决粘包问题(终极版)
说明:服务端,先制作报头header_dic,把它转换json格式header_json,转换二进制header_bytes,然后发送报头struct.pack('i',len(header_bytes))长度,在发送报头header_bytes
,再发送数据;客户端,先4个bytes,然后提取报头header_size长度,然后精准提取报头header_bytes,转换header_json,获取hearer_dic,进而活动total_size
服务端:
import socket
import subprocess
import json
import struct
#1.买手机
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #tcp流式协议==》SOCK_STREAM
#2.插入手机卡
phone.bind(('127.0.0.1',8082)) #ip应该是服务端这个软件运行的那台机器的ip地址,port(0-65535)
#3.开机
phone.listen(5)#半连接池:控制的是同一时刻的链接请求数
print('服务端启动...')
#4.等待电话连线请求
while True: #循环链接
conn,client_addr=phone.accept()#(套接字对象,存放有客户端的ip和端口的元组)
print(conn,client_addr)
#5.收\发消息
while True:
try:
cmd=conn.recv(1024)
if len(cmd)==0:break #针对linux或者Mac
obj = subprocess.Popen(cmd.decode('utf-8'),
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
stdout = obj.stdout.read()
stderr = obj.stderr.read()
#先制作报头
header_dic={
'total_size':len(stdout)+len(stderr),
'filename':'a.txt'
}
header_json=json.dumps(header_dic)
header_bytes=header_json.encode('utf-8')
#发送报头的长度(固定4个字节)
conn.send(struct.pack('i',len(header_json)))
#再发报头
conn.send(header_bytes)
#再发送真实数据
conn.send(stdout)
conn.send(stderr)
except ConnectionResetError: #针对windows
break
#6.挂电话
conn.close()
#7.关机
phone.close()
客户端:
import socket
import struct
import json
#1.买手机
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#2.拨号
phone.connect(('127.0.0.1',8082)) #ip和port应该是服务端的ip和端口
#3.收\发消息
while True:
cmd=input('>>>>:').strip()
if len(cmd)==0:continue
phone.send(cmd.encode('utf-8')) #必须是bytes类型
#先4个bytes,然后提取报头长度
header_size=struct.unpack('i',phone.recv(4))[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['total_size']
#最后接收真实数据
res=b''
recv_size=0
while recv_size < total_size:
data=phone.recv(1024)
res+=data
recv_size+=len(data)
print('服务端消息',res.decode('gbk'))
#4.关闭
phone.close()
九、stockserver模块实现并发
服务端:
import socketserver
class MyTCPhandler(socketserver.BaseRequestHandler):
def handle(self):
while True:
try:
data=self.request.recv(1024)
if len(data)==0:break
print('收到%s:%s的数据:%s' % (self.client_address[0],
self.client_address[1],
data
))
self.request.send(data.upper())
except ConnectionResetError:
break
if __name__ == '__main__':
server=socketserver.ThreadingTCPServer(('127.0.0.1',9997),MyTCPhandler)
socketserver.TCPServer.request_queue_size=10
server.serve_forever()
客户端:
import socket
client=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
client.connect(('127.0.0.1',9997))
while True:
msg=input('>>>:').strip()
if len(msg)==0:break
client.send(msg.encode('utf-8'))
data=client.recv(1024)
print('服务消息',data)
client.close()