网络基础之网络协议
http://www.cnblogs.com/linhaifeng/articles/5937962.html
基于tcp协议实现简单的套接字通信
原理图:
先从服务器端说起。服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束
socket模块:
import socket
socket.socket(socket_family,socket_type,protocal=0)
socket_family #可以是 AF_UNIX 或 AF_INET。socket_type 可以是 SOCK_STREAM 或 SOCK_DGRAM。protocol 一般不填,默认值为 0。
#获取tcp/ip套接字
tcpSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
#获取udp/ip套接字
udpSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
#由于 socket 模块中有太多的属性。我们在这里破例使用了'from module import *'语句。使用 'from socket import *',我们就把 socket 模块里的所有属性都带到我的命名空间里了,这样能 大幅减短我们的代码。
#例如tcpSock = socket(AF_INET, SOCK_STREAM)
服务端套接字函数
s.bind() 绑定(主机,端口号)到套接字
s.listen() 开始TCP监听
s.accept() 被动接受TCP客户的连接,(阻塞式)等待连接的到来
客户端套接字函数
s.connect() 主动初始化TCP服务器连接
s.connect_ex() connect()函数的扩展版本,出错时返回出错码,而不是抛出异常
公共用途的套接字函数
s.recv() 接收TCP数据
s.send() 发送TCP数据(send在待发送数据量大于己端缓存区剩余空间时,数据丢失,不会发完)
s.sendall() 发送完整的TCP数据(本质就是循环调用send,sendall在待发送数据量大于己端缓存区剩余空间时,数据不丢失,循环调用send直到发完)
s.recvfrom() 接收UDP数据
s.sendto() 发送UDP数据
s.getpeername() 连接到当前套接字的远端的地址
s.getsockname() 当前套接字的地址
s.getsockopt() 返回指定套接字的参数
s.setsockopt() 设置指定套接字的参数
s.close() 关闭套接字
面向锁的套接字方法
s.setblocking() 设置套接字的阻塞与非阻塞模式
s.settimeout() 设置阻塞套接字操作的超时时间
s.gettimeout() 得到阻塞套接字操作的超时时间
面向文件的套接字的函数
s.fileno() 套接字的文件描述符
s.makefile() 创建一个与该套接字相关的文件
TCP服务端:
import socket
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #TCP协议
phone=socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) #开启tcp连接重用功能
phone.bind(('127.0.0.1',8080)) #把地址绑定到套接字
phone.listen(5) #监听链接数
while True:
print('starting...')
conn,client_addr=phone.accept() #连接后得到一个连接通道,和连接的地址
print(client_addr)
while True:
try:
data=conn.recv(1024) #在在通道中收信息的时候一次只收1024字节
if not data:break #针对的是linux系统
print('>>>>%s' %data)
conn.send(data)
except ConnectionResetError:
print('断开连接')
break
conn.close() #断开连接
phone.close() #系统回收连接
TCP客户端:
import socket
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #TCP协议
phone.connect(('127.0.0.1',8080)) #元组
while True:
vl_info=input('>>>')
if vl_info == 'exit':break
if not vl_info:continue
phone.send(vl_info.encode('utf-8')) #发消息的时候必须要转换为bytes
data=phone.recv(1024) #收的时候一次收1024字节
print(data.decode('utf-8'))
phone.close()
实现ssh远程执行命令
服务端:
from socket import *
import subprocess
server=socket(AF_INET,SOCK_STREAM)
server.bind(('127.0.0.1',8090))
server.listen(5)
while True:
conn,client_addr=server.accept()
print(client_addr)
while True:
try:
cmd=conn.recv(1024)
if not cmd:break
#ls -l;sadfasdf;pwd;echo 123
obj=subprocess.Popen(cmd.decode('utf-8'),shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
stdout=obj.stdout.read()
stderr=obj.stderr.read()
cmd_res=stdout+stderr
print(len(cmd_res))
conn.send(cmd_res)
except ConnectionResetError:
break
conn.close()
server.close()
客户端:
from socket import *
client=socket(AF_INET,SOCK_STREAM)
client.connect(('127.0.0.1',8090))
while True:
cmd=input('>>: ').strip()
if not cmd:continue
client.send(cmd.encode('utf-8'))
data=client.recv(1024)
print(data.decode('gbk'))
client.close()
粘包现象
所谓粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的。
解决粘包问题1
struct打包解包模块
import struct
# 打包工具
headers=struct.pack('i',123123)
print(headers,len(headers))
#注意解包后得到的是一个元组
res=struct.unpack('i',headers)
print(res[0])
print('王王王'.encode('utf-8')) #把字符串转换为bytes
print(b'\xe5\x88\x98\xe7\xa5\xba\xe7\xa5\xa5'.decode('utf-8')) #把bytes转换为utf-8格式
#如果报头过大:可以使用json模块序列化,然后得到字符串再使用struct模块打包
import json
headers={
'filepath':'a.txt',
'md5':'123sfsaddvx23sd23',
'total_size':123123123123123123412419847184678146154612541827461872641846127841264127
}
headers_json=json.dumps(headers)
print(headers_json)
print(b' \xc7\xfd\xb6\xaf\xc6\xf7 D \xd6\xd0\xb5\xc4\xbe\xed\xca\xc7 \xcf\xb5\xcd\xb3\r\n \xbe\xed\xb5\xc4\xd0\xf2\xc1\xd0\xba\xc5\xca\xc7 C001-01AB\r\n\r\n D:\\python2'.decode('utf-8'))
#server端
import socket
import subprocess
import struct
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #TCP协议
phone.bind(('127.0.0.1',8080))
phone.listen(5)
while True:
print('starting...')
conn,client_addr=phone.accept()
print(client_addr)
while True:
try:
# 1、收client端发送的命令
cmd=conn.recv(1024) #在收信息的时候一次只收1024字节
if not cmd:break #针对的是linux系统
obj=subprocess.Popen(cmd.decode('utf-8'),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
stdout=obj.stdout.read()
stderr=obj.stderr.read()
#2、制作包头,先发送包头,避免粘包
cmd_size=int(len(stdout))+int(len(stderr))
total_size=struct.pack('i',cmd_size)
conn.send(total_size)
# 3、发送命令执行的结果
conn.send(stdout+stderr)
except ConnectionResetError:
print('断开连接')
break
conn.close() #断开连接
phone.close() #系统回收连接
#client端
import socket
import struct
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #TCP协议
phone.connect(('127.0.0.1',8080)) #元组
while True:
cmd=input('>>>')
if cmd == 'exit':break
if not cmd:continue
# 1、把命令传给server端
phone.send(cmd.encode('utf-8')) #发消息的时候必须要转换为bytes
#2、先收包头使用打包模块固定4个字节(struct)
hearder=phone.recv(4)
total_size=struct.unpack('i',hearder)[0]
#3、 收server端执行完命令的结果
recv_size=0
data=b''
while recv_size < total_size:
res=phone.recv(1024) #收的时候一次收1024字节
data+=res
recv_size+=len(res)
print(data.decode('gbk'))
phone.close()
#如果client端发送一个空,client端和server端都会卡住
# 原因:client端会send一个空给自己操作系统的一个缓存(用户态到内核态的转换),然后操作系统的缓存收到了一个空(空也就是没有),操作系统的缓存收到一个空,也就不会发送给server端,也就是server端什么也没收到,会在等client端发送信息,而client端recv也在等server端发送信息。
#accept和recv会阻塞:
#在建立连接的时候,需要等待client去连接,1、等待数据来的时间 加上2、数据来了以后,等待程序把数据拷贝给自己的时间
# send不会阻塞的原因:send会把数据拷贝给操作系统,send拷贝的过程非常快,只涉及到本地硬盘拷贝到内存的时间
#解决粘包的问题:制作包头,给client发送信息的收发送一个固定的包头长度,让client端收的时候先收包头的信息
解决粘包问题2
#server端
import socket
import subprocess
import struct
import json
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #TCP协议
phone.bind(('127.0.0.1',8080))
phone.listen(5)
while True:
print('starting...')
conn,client_addr=phone.accept()
print(client_addr)
while True:
try:
# 1、收client端发送的命令
cmd=conn.recv(1024) #在收信息的时候一次只收1024字节
if not cmd:break #针对的是linux系统
obj=subprocess.Popen(cmd.decode('utf-8'),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
stdout=obj.stdout.read()
stderr=obj.stderr.read()
total_size = len(stdout) + len(stderr)
#2、使用json序列化制作包头,然后先发包头的大小
hearder_dic={
'filename':'a.txt',
'md5':'123saf12asqe4',
'file_size':total_size
}
hearder_json=json.dumps(hearder_dic)
hearder_bytes=hearder_json.encode('utf-8')
hearder_size = struct.pack('i',len(hearder_bytes))
# 3、发送包头长度
conn.send(hearder_size) #包头大小
#3.1发送包头
conn.send(hearder_bytes) #包头内容
# 4、发送命令执行的结果
conn.send(stdout+stderr)
except ConnectionResetError:
print('断开连接')
break
conn.close() #断开连接
phone.close() #系统回收连接
#client端
import socket
import struct
import json
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #TCP协议
phone.connect(('127.0.0.1',8080)) #元组
while True:
cmd=input('>>>')
if cmd == 'exit':break
if not cmd:continue
# 1、把命令传给server端
phone.send(cmd.encode('utf-8')) #发消息的时候必须要转换为bytes
#2、先收包头使用打包模块固定4个字节(struct)
hearder=phone.recv(4)
hearder_size=struct.unpack('i',hearder)[0] #解包,得到hearder的大小
#3、再收包头信息
hearder_bytes=phone.recv(hearder_size) #使用hearder大小,收取hearder_info(bytes)
hearder_json=hearder_bytes.decode('utf-8')
hearder_info=json.loads(hearder_json) #反序列化,得到hearder的dict
# print(hearder_info) #{'filename': 'a.txt', 'md5': '123saf12asqe4', 'file_size': 446}
#4、 收server端执行完命令的结果
recv_size=0
data=b''
while recv_size < hearder_info['file_size']:
res=phone.recv(1024) #收的时候一次收1024字节
data+=res
recv_size+=len(res)
print(data.decode('gbk'))
phone.close()
实现上传下载文件
#server端:
import socket
import json
import os
import struct
SHARE_DIR=r'D:\python20期课程\day8\8 上传下载文件\SERVICE'
class FtpServer:
def __init__(self,host,port):
self.host=host
self.port=port
self.server=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
self.server.bind((self.host,self.port))
self.server.listen(5)
def server_forever(self):
print('server starting....')
while True:
self.conn,self.client_addr=self.server.accept()
print(self.client_addr)
while True:
try:
data=self.conn.recv(1024)
if not data:break
params=json.loads(data.decode('utf-8'))
# params=data.decode('utf-8')
cmd=params[0]
print(params)
if hasattr(self,cmd):
func=getattr(self,cmd)
func(params)
else:
print('\033[45mnot %s valuse exists\033[0m' %params)
print('==>end')
except ConnectionResetError:
break
self.conn.close()
def get(self,params):
filename=params[1]
filepath=os.path.join(SHARE_DIR,filename)
if os.path.exists(filepath):
#1、制作报头
hearder={
'filename':filename,
'md5':'12312312',
'filesize': os.path.getsize(filepath)
}
hearder_json=json.dumps(hearder)
hearder_bytes=hearder_json.encode('utf-8')
#2、先发报头的长度
self.conn.send(struct.pack('i',len(hearder_bytes)))
#3、发送报头
self.conn.send(hearder_bytes)
#4、发送真实的数据
with open(filepath,'rb') as f:
for line in f:
self.conn.send(line)
if __name__=='__main__':
server=FtpServer('127.0.0.1',8081)
server.server_forever()
#client端
import socket
import struct
import json
import os
import time
DOWNLOAD_DIR=r'D:\python20期课程\day8\8 上传下载文件\CLIENT'
class FtpClient:
def __init__(self,host,port):
self.host=host
self.port=port
self.client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.client.connect((self.host,self.port))
def interactive(self):
while True:
data=input('>>>:').strip()
if not data:continue
params=data.split()
cmd=params[0]
if hasattr(self,cmd):
func=getattr(self,cmd)
func(params)
def get(self,params):
params_json=json.dumps(params)
self.client.send(params_json.encode('utf-8'))
#1、先接收报头的长度
hearder_by=self.client.recv(4)
headers_size=struct.unpack('i',hearder_by)[0]
#2、再收报头
headers_bytes=self.client.recv(headers_size)
headers_json=headers_bytes.decode('utf-8')
headers_dic=json.loads(headers_json)
filename=headers_dic['filename']
filesize=headers_dic['filesize']
filepath=os.path.join(DOWNLOAD_DIR,filename)
#3、再收取真实的数据
with open(filepath,'wb') as f:
recv_size=0
while recv_size < filesize:
line=self.client.recv(1024)
recv_size+=len(line)
f.write(line)
self.progress(recv_size/filesize)
def progress(self,percent,width=50):
if percent <=1:
percent=1
show_str=('[%%-%ds]' %width) % ('#' * int(width*percent))
print('\033[45m\r%s %d%%\033[0m' %(show_str,int(100*percent)),end='')
if __name__=='__main__':
client=FtpClient('127.0.0.1',8081)
client.interactive()
socketserver并发编程
socketserver.ThreadingTCPServer #启动多线程,实现并发编程
import socketserver
class MyTCPHandler(socketserver.BaseRequestHandler):
def handle(self):
while True:
self.data=self.request.recv(1024).strip() #接受数据
print('{} wrote:'.format(self.client_address[0]))
print(self.data)
self.request.sendall(self.data.upper()) #返回数据
if __name__=='__main__':
HOST,PORT='localhost',9000
# server=socketserver.TCPServer((HOST,PORT),MyTCPHandler) #把定义的类当作参数传入socketserver这个类
server=socketserver.ThreadingTCPServer((HOST,PORT),MyTCPHandler) #使用ThreadingTCPServer就可以实现并发方法
server.serve_forever() #启动一个监听
基于UDP协议的套接字
UDP关键字
SOCK_DGREM #数据报协议 server.recvfrom() #udp使用recvfrom来接收消息 sendto(‘vlues’,sendto ipaddress) #udp使用sendto来发消息
ps:注意,udp接受消息不会产生粘包现象,原因是客户端发多少,服务端收多少,如果客户端发的1024字节,服务器端最大收512字节,那么剩下的数据会丢弃,udp收到消息也是一条一条的去处理的,可以开多个客户端给服务端发送,(给我们感觉到是并发的)其实不会实现并发的现象,只是数据量小,收发快。
#服务端
from socket import *
server=socket(AF_INET,SOCK_DGRAM) #数据报协议
server.bind(('127.0.0.1',9090))
while True:
data,client_addr=server.recvfrom(1024) #udp使用recvfrom 来收消息
print(data,client_addr)
server.sendto(data.upper(),client_addr)
#客户端
from socket import *
client=socket(AF_INET,SOCK_DGRAM)
while True:
msg=input('>>>').strip()
client.sendto(msg.encode('utf-8'),('127.0.0.1',9090))
data=client.recvfrom(1024)
print(data)