socket在python下的使用
- 创建套接字对象
- 套接字对象方法
- socket缓冲区与阻塞
- 粘包(数据的无边界性)
- 案例之模拟ssh命令
- 案例之文件上传
1.1创建套接字对象
Linux中的一切都是文件,每个文件都有一个整数类型的文件描述符;socket也可以视为一个文件对象,也有文件描述符。
import socket
sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
# <socket.socket fd=496, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0>
print(sock)
1.AF为地址族(address family),也就是IP地址类型,常用的有AF_INET和AF_INET6,INET是“Internet”的简写,AF_INET表示IPV4地址,例如127.0.0.1;AF_INET6表示ipv6地址。127.0.0.1,他是一个特殊IP地址,表示本机地址,会经常用到。
2.type为数据传输方式/套接字类型,常用的有SOCK_STREAM(流格式套接字/面向连接的套接字)和SOCK_DGRAM(数据报套接字/无连接的套接字)。
3.protocol表示传输协议,常用的有IPPROTO_TCP和IPPTOTO_UDP,分别表示TCP传输协议和UDP传输协议。有了地址类型和数据传输方式,还不足以决定采用哪种协议吗?为什么还需要第三个参数呢?一般情况下有了af和type两个参数地址就可以创建套接字了,操作系统会自动推演出协议类型,除非遇到这样的情况:有两种不同的协议支持同一种地址类型和数据传输类型。如果我们不指明使用哪种协议,操作系统是没有办法自动推演的。
如果使用sock_stream传输数据,那么满足这两个条件的协议只有TCP,因此可以这样来代用socket():
# IPPROTO_TCP表示TCP协议
tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP)
这种套接字称为 TCP 套接字。
如果使用 SOCK_DGRAM 传输方式,那么满足这两个条件的协议只有 UDP,因此可以这样来调用 socket() :
# IPPROTO_UDP表示UDP协议
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
这种套接字称为 UDP 套接字。
上面两种情况都只有一种协议满足条件,可以将 protocol 的值设为 0,系统会自动推演出应该使用什么协议,如下所示:
tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) # 创建TCP套接字
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0) # 创建UDP套接字
4、sock = socket.socket()默认创建TCP套接字。
1.2套接字对象方法
服务端:bind方法
socket用来创建套接字对象,确定套接字的各种属性,然后在服务器端要用bind()方法将套接字与特定的IP地址和端口的数据才能交给套接字处理。类似的,客户端也要用connect()方法建立连接。
import socket #调用socket模块
sock = socket.socket() #调用docket
sock.bind(("127.0.0.1",8899)) #创建地址和端口,使用默认tcp协议。
服务端:listen方法
通过listen()方法可以让套接字进入被动监听状态,sock为需要进入监听状态的套接字,backlog为请求队列的最大长度,所谓被动监听,是指当没有客户端请求时,套接字处于“睡眠”状态,只有当接收到客户端请求时,套接字太会被“唤醒”来响应请求,当套接字正在处理客户端请求时,如果有新的请求进来,套接字是没办法处理的,只能把它放进缓冲区,待当前请求处理完毕后,再冲缓冲区读取出来处理。如果不断有新的请求,他们就按照先后顺序在缓冲区中排队,知道缓冲区满。这个缓冲区,就称为请求队列(request queue)
缓冲区的长度(能存放多少个客户端请求)可以通过listen()方法和backlog参数指定,但究竟为多少并没有什么标准,可以根据你需求来定,并发量小的话可以是10或者20.
如果将backlog的值设置为SOMAXCONN,就由系统来决定请求队列长度,这个值比较庞大,可能是几百,或者更多。当请求队列满时,就不再接受新的请求。
注意:listen()只是让套接字处于监听状态,并没有接受请求,接收请求需要使用accept()函数
sock.listen(5)
服务端:accept方法
当套接字处于监听状态时,可以通过accept()函数来接收客户端请求。accept()返回一个新的套接字来和客户端通信,addr保存了客户端的IP地址和端口号,而sock是服务器端的套接字,大家注意区分。后面和客户端通信是,要使用这个新生成的套接字,而不是原来服务器端的套接字。
conn,addr=sock.accept()
print("conn:",conn) # conn: <socket.socket fd=560, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8899), raddr=('127.0.0.1', 64915)>
print("addr:",addr) # addr: ('127.0.0.1', 64915)
客户端:connect方法
connect()是客户端程序用来连接服务器的方法:
import socket
ip_port=("127.0.0.1",8899)
sk=socket.socket()
sk.connect(ip_port)
注意:只有经过connect连接成功后的套接字对象才能调用发送和接收方法(send/recv)。所以服务端的sock对象不能send or recv。
收发数据方法:send和recv
s.recv()
接收TCP数据,数据以字符串形式返回,bufsize指定要接收的最大数据量。flag提供有关消息的其他消息,通常可以忽略。
s.send()
发送TCP数据,将string中的数据发送到连接的套接字。返回值是要发送的字节数量,该数量可能小于string的字节大小。
简单聊天案例
服务端:
import socket
# 1.构建套接字对象
sock = socket.socket()
# 2.绑定:度无端的ip和端口
sock.bind(("127.0.0.1",8899))
# 3.确定最大监听数
sock.listen(3)
# 4.等待连接
while 1:
print("等待服务器连接。。。")
#获取客户端的套接字对象和地址
conn,addr = sock.accept()
print(conn,addr)
#conn:发送消息 send 方法 接收数据 recv方法
try:
while 1:
msg = conn.recv(1024) #
print(msg.decode())
if msg.decode() == "quit":
break
res = input("响应>>>")
conn.send(res.encode())
except ConnectionResetError:
print("客户端断开,连接下一个客户端")
客户端:
import socket
# 创建客户端的套接字对象
sock = socket.socket()
# 连接服务器
sock.connect(("127.0.0.1",8899))
while 1:
data = input(">>>")
sock.send(data.encode())
if data == "quit":
break
res = sock.recv(1024)
print("服务器响应",res.decode())
图解socket函数:
1.3、socket缓冲区与阻塞
1.socket缓冲区
每个socket被创建后,都会分配两个缓冲区,输入缓冲区和输出缓冲区。write()/send()并不立即向网路中传输数据,而是现将数据写入缓冲区中,再由TCP协议将数据从缓冲区发送到目标及其。一旦将数据写入到缓冲区,函数就可以成功返回,不管它们有么有到达目标及其,也不管它们何时被发送到网络,这些都是TCP协议负责的事情。
TCP协议独立于write()/send()函数,数据有可能刚被写入缓冲区就发送到网络,也可能在缓冲区中不断积压,多次写入的数据被一次性发送到网络,这取决于当时网络情况、当前线程是否空闲等诸多因素,不由程序员控制。read()/recv()函数亦是如此,也从输入缓冲区中读取数据,而不是直接从网络中读取。
这些I/O缓冲区特性可整理如下:
- I/O缓冲区在每个TCP套接字中单独存在;
- I/O缓冲区在创建套接字时自动生成;
- 即使关闭套接字也会继续传送输出缓冲区中遗留的数据;
- 关闭套接字将丢失输入缓冲区中的数据。
输入输出缓冲区的默认大小一般都是8K!
2、阻塞模式
对于TCP套接字(默认情况下),当使用send()发送数据时:
1.首先会检查缓冲区,如果缓冲区的可用空间长度小于要发送的数据,那么send()会被阻塞(暂停执行),直到缓冲区中的数据被发送到目标机器,腾出足够的空间,才唤醒send()函数继续写入数据。
2.如果TCP协议正在向网络发送数据,那么输出缓冲区会被锁定,不允许写入,send()也会被阻塞,直到数据发送完毕缓冲区解锁,send()才会被唤醒。
3.如果要写入的数据大于缓冲区的最大长度,那么将分批写入。
4.直到所有数据被写入缓冲区send()才能返回。
当使用recv()读取数据时:
1.首先会检查缓冲区,如果缓冲区中有数据,那么就读取,否则函数会被阻塞,直到网络上有数据到来。
2.如果要去读的数据长度 小于缓冲区中的数据长度,那么就不能一次性将缓冲区中所有的数据读出,剩余数据将不断积压,直到有recv()函数再次读取。
3.直到读取到数据后,recv()函数才会返回,否则就一直被阻塞
TCP套接字默认情况下是阻塞模式,也是常用的。当然也可以更改为非阻塞模式。
粘包(数据的无边界性)
socket数据的接收和发送是无关的,recv()不管数据发送了多少次,都会尽可能多的接收数据,也就是说,recv()和send()的执行次数可能不同。
例如,send()重复执行三次,每次都会发送字符串“abc”,那么目标机器上的read()/recv()可能分三次接收,每次都接收“abc”;也可能分两次接收,第一次接收“abcab”,第二次接收“cabc”;也可能一次就接收到字符串“abcabc”.
假设我们希望客户端每次发送一位学生的学号,让服务端返回该学生的姓名、住址、成绩等信息,这时候可能就会出现问题,服务端不能区分学生的学号,例如第一次发送1,第二次发送3,服务器可能当成13来处理,返回的信息显然是错误的。
这就是数据的“粘包”问题,客户端发送的多个数据包被当做一个数据包接收,也称数据的无边界性,recv()函数不知道数据包的开始或结束标志(实际上也没有任何开始或结束标志),只把它们当做连续的数据流来处理。
服务端
import socket
import time
s = socket.socket()
s.bind(("127.0.0.1",8888)) #暴露的地址端口协议
s.listen(5) #最大连接数
client,addr = s.accept() #接收客户端信息
time.sleep(10) #程序睡眠10秒
data = client.recv(1024) #接收客户端数据信息,单次最大接收1024字节
print(data)
client.send(data) #将客户端到发送过来的数据给返回去
客户端
import socket
s = socket.socket() #调用socket
s.connect(("127.0.0.1",8888)) #连接服务端信息
data = input(">>>")
s.send(data.encode()) #将输入信息转换字节码发送给服务端
s.send(data.encode()) #将输入信息转换字节码发送给服务端
s.send(data.encode()) #将输入信息转换字节码发送给服务端
res = s.recv(1024) #打印服务端返回数据
print(res)
上传文件
服务端
import socket
import time
import struct
import json
import os
#基础配置 IP地址 端口 协议 最大监听数
sock = socket.socket()
sock.bind(("127.0.0.1",8899))
sock.listen(5)
while True:
print("server is waiting....")
client_sock,addr = sock.accept()
print("客户端%s建立连接"%str(addr))
while True:
try:
msg_len = client_sock.recv(4) #接收客户端上传的文件,前4字节
except Exception:
print("客户端断开")
break
msg_len = struct.unpack("i",msg_len)[0] #解析出客户端上传文件的大小
info = client_sock.recv(msg_len) #接收客户端上传基本内容,msg_len:上传文件的大小
info_dict = json.loads(info) #将接收的数据反序列化
print(info_dict)
cmd = info_dict.get("cmd") #拿去客户端列表中cmd的值,判断客户端是上传/下载文件,put上传/get下载
if cmd == "put":
"""拿出客户端传输过来的基本信息"""
file_name = info_dict["file_name"] #获取客户端上传的名字
# file_name = file_name
print(file_name)
file_size = info_dict["file_size"] #获取客户端上传的文件大小
"""保存客户端上传文件"""
with open(file_name,"wb") as f: #以客户端上传的名字在服务端创建文件
receive_len = 0
while receive_len < file_size:
tmp = client_sock.recv(4095)
f.write(tmp)
receive_len += len(tmp)
"""验证上传文件是否完整"""
if file_size == os.path.getsize(file_name):
print("文件上传完成!!!")
client_sock.send("上传完成!".encode())
else:
print("文件上传不完整,请重新上传!!!")
elif cmd == "get":
"""下载"""
FileName = info_dict["FileName"]
print(FileName)
"""下载文件"""
try:
f = open(FileName,"rb")
except Exception:
KK = "文件不存在或权限错误!"
print(KK)
ll = json.dumps(KK).encode() # 内容json化和字节化
megs = struct.pack("i", len(ll)) # 转为4字节,存放ll的大小
client_sock.send(megs) # 发送struct存放的数据 (2121,)
client_sock.send(ll) # 发送具体数据内容 (json后的文件不存在或权限错误)
else: #如果try中的语句没有引发异常,则执行else中的语句
"""文件大小,发送给客户端"""
megs = json.dumps(os.path.getsize(FileName)) #判断发送给客户端文件大小转为字节码,并且json化
print(megs)
print(type(megs))
megs_len = struct.pack("i",int(megs)) #封装成4字节
print(megs_len)
client_sock.send(megs_len) #发送struce封装的数据 (21232,)
print("发送成功")
"""发送文件内容给服务端,每次发送给客户端4095字节,发送完次跳出循环"""
while True:
date = f.read(4095)
if date == b"": # 表示传输完成,已经拿不到数据了
break
client_sock.send(date)
f.close()
客户端
import socket
import struct
import os
import json
import time
ip_port = ("127.0.0.1",8899) #服务端地址
sk = socket.socket()
sk.connect(ip_port) #连接服务端
while 1: #使可以多次上传
msg = input("输入执行命令>>>") #格式 put meinv.jpg
cmd,params = msg.split(" ") #以空格拆分用户输入命令
if cmd == "put":
"""上传文件"""
try:
f = open(params,mode="rb") #以字节方式打开用户上传的文件
except Exception:
print("上传文件不存在")
continue
"""获取上传基本信息,名称 大小"""
file_name = f.name #获取上传文件名称,就是params
file_size = os.path.getsize(params) #获取上传文件大小
"""将基本信息打包,json化 传输给服务端"""
data = {} #将基本信息存放列表
data["file_name"] = file_name
data["file_size"] = file_size
data["cmd"] = "put"
msg = json.dumps(data).encode() #将基本信息json化
msg_len = struct.pack("i",len(msg)) #统计基本信息的大小并将结果进行封装成4字节文件
# print(data)
"""发送文件基本信息到服务端"""
sk.send(msg_len) #将4字节文件大小发送给服务端
sk.send(msg) #将序列化后的基本信息传输给服务端
"""发送文件内容给服务端,每次发送给客户端4095字节,发送完次跳出循环"""
while True:
date = f.read(4095)
sk.send(date)
if date == b"": #表示传输完成,已经拿不到数据了
break
f.close() #关闭打开的文件
print(sk.recv(1024).decode()) #打印上传文件是否完整
elif cmd == "get":
"""下载文件"""
"""打包基本信息发送给服务端,名称 方式,以字典的形式"""
Data = {}
Data["cmd"] = "get"
Data["FileName"] = params
# print(Data)
"""将数据json化,封装"""
msgs = json.dumps(Data).encode() #将数据Jason化,并转为字节码
msgs_len = struct.pack("i",len(msgs)) #基本信息字节大小
"""发送数据给服务端"""
sk.send(msgs_len) #发送基本信息长度到服务端
sk.send(msgs) #基本数据发送给服务端
"""接受服务端数据"""
# time.sleep(5)
msg_len = sk.recv(4) # 接收服务端传的文件,前4字节,内容:传输的文件大小
msg_len = struct.unpack("i", msg_len)[0] # 解析出服务端传文件的大小
print(msg_len)
# info = sk.recv(msg_len) # 接收客户端上传基本内容,msg_len:上传文件的大小
# print(info)
# info = info.decode()
# print(info)
# info_dict = json.loads(info) # 将接收的数据反序列化,下载文件大小数据
"""文件内容下载"""
with open(params,"wb") as f: #以客户端上传的名字在服务端创建文件
receive_len = 0
while receive_len < msg_len:
tmp = sk.recv(2048)
f.write(tmp)
receive_len += len(tmp)
案例-模拟ssh命令
服务端
import socket
import subprocess
import struct
socket = socket.socket()
socket.bind(("127.0.0.1", 8899)) # 创建服务端IP地址,端口,默认tcp协议
socket.listen(5) # 最大监听数
while 1:
client,addr = socket.accept() #接收客户端连接信息
print("客户端%s建立连接"%str(addr))
while 1:
try:
cmd = client.recv(1024)
cmd_res_bytes = subprocess.getoutput(cmd.decode()) #使用subprocess模块去自己内存中拿去客户端输入的命令信息
except :
print("客户端%s退出"%str(addr))
client.close() #断开连接
break #跳出本次循环
print(cmd_res_bytes) #打印输出客户端发送过来的命令信息
cmd_res_bytes = cmd_res_bytes.encode()
print("len:",len(cmd_res_bytes)) #查看输出字符串长度,便于验证传输是否一致
data_len = len(cmd_res_bytes) #长度赋值变量
data_len_pack = struct.pack("i",data_len) #发送输出内容大小到客户端,客户端取固定长度,解决粘包问题
#打包数据长度发送
client.send(data_len_pack) #发送长度到客户端
client.send(cmd_res_bytes) #发送数据到客户端
客户端
import socket
import time
import struct
sk = socket.socket()
sk.connect(("127.0.0.1",8899)) #连接服务端
while 1:
data = input("输入执行命名>>>") #输入远程连接的命令
sk.send(data.encode()) #发送数据过去,字节码
#接收数据长度
time.sleep(2) #触发粘包
data_len = sk.recv(4) #发送过来的字节大小,解决粘包
total_len = struct.unpack("i",data_len)[0] #解压内容为元组,传输字符串的大小值
ret = b"" #接收到的内容
receive_len = 0 #本地从0计算
while receive_len < total_len: #当本地接收到的字节数不小于传输过的字节数时,表示文件传输完成 #
temp = sk.recv(1024) #单次拿去数据长度
receive_len += len(temp) #接收字节大小做累加
ret += temp #接收内容做累加
print(ret.decode()) #打印传输过来的内容
print(len(ret)) #打印接收到的大小
print("total_len",total_len)
标签:socket,python,len,send,使用,print,接字,客户端
From: https://www.cnblogs.com/megshuai/p/18518397