首页 > 编程语言 >socket在python下的使用

socket在python下的使用

时间:2024-10-31 17:09:39浏览次数:5  
标签:socket python len send 使用 print 接字 客户端

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

相关文章

  • 进程的同步与互斥,特别是使用PV操作(也称为信号量操作)
    这道题目考察的知识点是进程的同步与互斥,特别是使用PV操作(也称为信号量操作)来实现进程间的同步和互斥。知识点相关内容:进程同步:指的是在多进程系统中,协调各个进程的执行顺序,使得它们能够按照一定的规则协同工作,避免出现数据不一致或者资源竞争等问题。进程互斥:指的是在多进......
  • webstorm 使用 SVN
    1.打开settings搜索svn,输入svn.bin的所在路径(在svn的安装目录中)如果在安装目录没有找到svn.bin文件,是因为安装的时候少选了一个选项重新安装2.在webstorm中选择版本控制工具为svn......
  • 在使用asm包进行动态类加载的时候的打包问题
     如图所示,开发时使用的jdk包下面的asm包,在进行打包时提示asm包不存在,打包方式使用如下: 目前提供两种解决方案:1:修改打包方式,将jdk的包也打进去:<plugin><artifactId>maven-compiler-plugin</artifactId><configuration><source>1.8</source><t......
  • Python GUI编程 tkinter编程
    tkinter编程思路比喻对于tkinter编程,主要用两个比喻来描述,重点理解容器、组件和布局管理器。 第一个,作画。我们都见过美术生写生的情景,先支一个画架,放上画板,蒙上画布,构思内容,用铅笔画草图,组织结构和比例,调色板调色,最后画笔勾勒。相应的,对应到tkinter编程,那么我们的显示屏就是支......
  • VSCode编辑器极简使用入门
    VSCode(VisualStudioCode)是一款开源、跨平台、轻量级的代码编辑器,具有非常丰富的插件生态。他本身就是JavaScript + Electron (/ɪˈlektrɒn/电子)代码开发的。官方下载地址:https://code.visualstudio.com/,支持绿色无安装。01、常用快捷键工具快捷键描述Ctrl+Shi......
  • Linux系列-gcc/g++的使用
    ......
  • 使用ThreadPoolExecutor线程池消化线程执行代码
    此处记录一个使用ThreadPoolExecutor线程池的demo线程代码publicclassExcutorRunnableimplementsRunnable{@Overridepublicvoidrun(){System.out.println(Thread.currentThread().getName()+":线程执行666");try{Thread.......
  • python实战(五)——构建自己的大模型助手
    一、任务目标    本文将利用大语言模型强大的对话能力,搭建一个PC端问答助手。具体来说,我们将使用API来调用我们想要的大模型,并结合Prompt让大模型根据任务类型生成对应的输出。为了更方便地调用大模型助手,我们将结合python第三方库中的语音识别库进行开发,实现调用麦克......
  • Python工具箱系列:Pandas 数据清洗与预处理详解
    目录一、数据清洗与预处理的重要性二、Pandas简介三、Pandas数据清洗与预处理技巧1.读取数据2.查看数据3.处理缺失值4.处理重复值5.处理异常值6.处理数据类型不一致7.处理数据格式不一致8.数据标准化和归一化9.数据编码四、案例:使用Pandas进行数据清......
  • Python数据类型之自定义类型——Class与面向对象编程详解
    目录引言一、面向对象编程基础二、类的定义与对象的创建三、封装性四、继承性五、多态性六、特殊方法与数据类七、使用dataclass装饰器八、面向对象编程的优势结论引言Python是一门功能强大的编程语言,其面向对象编程(OOP)的特性更是为开发者提供了极大的灵活性和......