首页 > 编程语言 >Python模块-socket

Python模块-socket

时间:2023-04-15 21:33:27浏览次数:40  
标签:00 socket Python phone dict 模块 data conn

1、基于TCP协议的socket通信

以打电话为理解方式进行TCP的通信

# Server端
import socket
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)     #购买电话卡,AF_INET服务器之间网络通信,socket.SOCK_STREAM,流式协议,就是TCP协议
phone.bind(('127.0.0.1', 8080))                               #选择电话号码,绑定IP地址
phone.listen(5)                                               #开机,监控5个请求,最多能进入6个,第7个会报错
conn, addr = phone.accept()                                   #等待接听电话,阻塞状态,获取IP和端口
print(conn, addr)                                             #打印IP和端口
client_data = conn.recv(1024)                                 #交流过程
print(client_data)                                            #打印接收到的数据
conn.send(client_data.upper())                                #接收到的英文字母变成大写并回传数据
conn.close()
phone.close()

# Client端
import socket
phone = socket.socket()
phone.connect(('127.0.0.1', 8080))
msg = input('>>>').strip()
phone.send(msg.encode('utf-8'))
server_data = phone.recv(1024)                                #限制最大接收字节
print(server_data)
phone.close()

# 先运行Server端,在运行Client端
------------------------------------Client端运行结果---------------------------------------
>>>Others laugh at me for being mad. I laugh at others for not being able to see through.       #>>>符号右侧为输入内容
b'OTHERS LAUGH AT ME FOR BEING MAD. I LAUGH AT OTHERS FOR NOT BEING ABLE TO SEE THROUGH.'       #为服务端返回内容
------------------------------------------------------------------------------------------

------------------------------------Server端运行结果----------------------------------------
<socket.socket fd=244, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8080), raddr=('127.0.0.1', 64096)> ('127.0.0.1', 64096)
b'Others laugh at me for being mad. I laugh at others for not being able to see through.'       #接收到客户端的内容
-------------------------------------------------------------------------------------------

2、单循环模式

# Server端
import socket
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  #使用TCP协议创建网络通信
phone.bind(('127.0.0.1', 8080))                            #绑定IP地址和端口
phone.listen(5)                                            #设置最大连接数,但是同一时间只能处理一个请求
while 1:                                                   #使服务端能够一直接受数据
    conn, addr = phone.accept()                            #获取连接到服务器的IP和端口
    try:
        client_data = conn.recv(1024)                      #接收1024字节的数据
        print(client_data)                                 #打印数据(数字为bytes类型)
        conn.send(client_data + b'-yes-')                  #源数据加上-yes-并返回给客户端
    except Exception:
        break
conn.close()
phone.close()

# Client端
import socket
phone = socket.socket()
phone.connect(('127.0.0.1', 8080))

while 1:
    msg = input('>>>').strip()                             #可输入要传输的字符
    if msg.upper() == 'Q':                                 #如果输出q则退出
        break
    elif not msg:                                          #如果msg接到到的内容为空,则结束本次循环
        continue
    phone.send(msg.encode('utf-8'))                        #使用utf-8的编码进行发送数据
    server_data = phone.recv(1024)                         #最多只能接受1024字节,只问题会产生粘包
    print(server_data.decode('utf-8'))                     #打印解码后的数据
phone.close()

# 先运行Server端,再运行Client端
------------------------------------Client端运行结果---------------------------------------
>>>laugh                                                   #输入要传到S端的内容
laugh-yes-                                                 #打印出来的是S端返回的内容
>>>crazy
crazy-yes-
>>>q

Process finished with exit code 0
-------------------------------------------------------------------------------------------
------------------------------------Server端运行结果----------------------------------------
b'laugh'                                                   #收到C端发送来的内容
b'crazy'
-------------------------------------------------------------------------------------------

3、远程执行命令

程执行命令之粘包现象的产生

# Server端
import socket
import subprocess                                          #远程执行命令需要使用到subprocess模块
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)#(一般不会立即关闭而经历TIME_WAIT的过程)后想继续重用该socket
phone.bind(('127.0.0.1', 8080))
phone.listen(5)
while 1:
    conn, addr = phone.accept()
    try:
        cmd = conn.recv(1024)
        obj = subprocess.Popen(cmd.decode('utf-8'),        #要执行的命令或可执行文件的路径。一个由字符串组成的序列(通常是列表),列表的第一个元素是可执行程序的路径,\
                                                           #剩下的是传给这个程序的参数,如果没有要传给这个程序的参数,args 参数可以仅仅是一个字符串。
                               shell=True,                 #Linux下相当于执行/bin/bash,Windows下相当于执行cmd.exe
                               stdout=subprocess.PIPE,     #指定子进程的标准输出
                               stderr=subprocess.PIPE,     #指定子进程的标准错误输出
                               )
        ret = obj.stdout.read()                            #把标准的输出结果赋值给ret变量
        ret1 = obj.stderr.read()                           #把标准的错误输出结果赋值给ret1变量 
        conn.send(ret + ret1)
    except Exception:
        break
conn.close()
phone.close()

# Client端
import socket
phone = socket.socket()
phone.connect(('127.0.0.1', 8080))
while 1:
    msg = input('>>>').strip()
    if msg.upper() == 'Q':                                  #输出q则退出程序
        break
    elif not msg:
        continue                                            #解决的是输入为空程序会夯住的问题。
    phone.send(msg.encode('utf-8'))                         #将接收到的数据进行编码后发送到服务端
    server_data = phone.recv(1024)                          #每次只接收1024个字节
    print(server_data.decode('gbk'))                        #打印接收数据
phone.close()

# 先运行Server端,再运行Client端
------------------------------------Client端运行结果---------------------------------------
>>>ipconfig /all                                            #查看机器所有的IP地址

Windows IP 配置

   主机名  . . . . . . . . . . . . . : WINDOWS-CP2ICQS
   主 DNS 后缀 . . . . . . . . . . . : 
   节点类型  . . . . . . . . . . . . : 混合
   IP 路由已启用 . . . . . . . . . . : 否
   WINS 代理已启用 . . . . . . . . . : 否

以太网适配器 本地连接:

   连接特定的 DNS 后缀 . . . . . . . : 
   描述. . . . . . . . . . . . . . . : Realtek PCIe GBE Family Controller
   物理地址. . . . . . . . . . . . . : 20-89-84-D1-E1-8B
   DHCP 已启用 . . . . . . . . . . . : 否
   自动配置已启用. . . . . . . . . . : 是
   本地链接 IPv6 地址. . . . . . . . : fe80::91d8:554a:b7fb:f16e%13(首选) 
   IPv4 地址 . . . . . . . . . . . . : 192.168.77.77(首选) 
   子网掩码  . . . . . . . . . . . . : 255.255.255.0
   默认网关. . . . . . . . . . . . . : 192.168.77.1
   DHCPv6 IAID . . . . . . . . . . . : 287345028
   DHCPv6 客户端 DUID  . . . . . . . : 00-01-00-01-21-DE-4B-33-20-89-84-D1-E1-8B
   DNS 服务器  . . . . . . . . . . . : 114.114.114.114
   TCPIP 上的 NetBIOS  . . . . . . . : 已启用

隧道适                                                      #到此时数据明显没有接收全,因为每次只接收1024个字节 
>>>dir                                                     #直到发送下一个命令的时候,才把剩余的字节接收完成,这就是粘包现象之一  
配器 isatap.{D3F0159A-0A26-4494-9B2A-92BE10466E18}:         #dir命令并未列出当前Windows目录的内容

   媒体状态  . . . . . . . . . . . . : 媒体已断开
   连接特定的 DNS 后缀 . . . . . . . : 
   描述. . . . . . . . . . . . . . . : Microsoft ISATAP Adapter
   物理地址. . . . . . . . . . . . . : 00-00-00-00-00-00-00-E0
   DHCP 已启用 . . . . . . . . . . . : 否
   自动配置已启用. . . . . . . . . . : 是

隧道适配器 Teredo Tunneling Pseudo-Interface:

   媒体状态  . . . . . . . . . . . . : 媒体已断开
   连接特定的 DNS 后缀 . . . . . . . : 
   描述. . . . . . . . . . . . . . . : Teredo Tunneling Pseudo-Interface
   物理地址. . . . . . . . . . . . . : 00-00-00-00-00-00-00-E0
   DHCP 已启用 . . . . . . . . . . . : 否
   自动配置已启用. . . . . . . . . . : 是

>>>q

Process finished with exit code 0
-------------------------------------------------------------------------------------------

4、备注:什么是粘包

所谓粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的。
此外,发送方引起的粘包是由TCP协议本身造成的,TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送一个TCP段。若连续几次需要send的数据都很少,通常TCP会根据优化算法把这些数据合成一个TCP段后一次发送出去,这样接收方就收到了粘包数据。
tcp的协议数据不会丢,没有收完包,下次接收,会继续上次继续接收,己端总是在收到ack时才会清除缓冲区内容。数据是可靠的,但是会粘包。
# 以下两种情况发发生粘包(只有tcp协议会产生粘包):
接收方没有及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包)
发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据也很小,会合到一起,产生粘包

5、解决粘包的方法

使用struct模块
该模块可以把一个类型,如数字转换成固定长度的bytes

Python模块-socket_TCP

import struct
ret = struct.pack('i', 183346)                       #将一个int型数字转化成等长度的bytes类型。
print(ret, type(ret), len(ret))

----------------------------------------运行结果-------------------------------------------
b'2\xcc\x02\x00' <class 'bytes'> 4
------------------------------------------------------------------------------------------

ret1 = struct.unpack('i', ret)[0]                    #通过unpack反解回来
print(ret1, type(ret1))

----------------------------------------运行结果-------------------------------------------
183346 <class 'int'>
------------------------------------------------------------------------------------------


ret = struct.pack('l', 4323241232132324)              # 但是通过struct 处理不能处理太大
print(ret, type(ret), len(ret))                       # 报错

----------------------------------------运行结果-------------------------------------------
  File "D:/PycharmProjects/python/pack.py", line 21, in <module>
    ret = struct.pack('l', 4323241232132324)
struct.error: argument out of range

6、方案代码

整个流程的大致解释:
我们可以把报头做成字典,字典里包含将要发送的真实数据的描述信息(大小之类的),然后json序列化,然后用struck将序列化后的数据长度打包成4个字节。
我们在网络上传输的所有数据 都叫做数据包,数据包里的所有数据都叫做报文,报文里面不止有你的数据,还有ip地址、mac地址、端口号等等,其实所有的报文都有报头,这个报头是协议规定的,看一下
发送时:
先发报头长度
再编码报头内容然后发送
最后发真实内容
接收时:
先收报头长度,用struct取出来
根据取出的长度收取报头内容,然后解码,反序列化
从反序列化的结果中取出待取数据的描述信息,然后去取真实的数据内容

# Server端
import socket
import subprocess
import struct
import json

phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
phone.bind(('127.0.0.1', 8080))
phone.listen(5)
conn, addr = phone.accept()
while 1:
    try:
        cmd = conn.recv(1024)
        obj = subprocess.Popen(cmd.decode('utf-8'),
                               shell=True,
                               stdout=subprocess.PIPE,
                               stderr=subprocess.PIPE,
                               )

        ret = obj.stdout.read()                          #1.获取执行命令返回的结果,计算结果的总大小
        ret1 = obj.stderr.read()
        total_size = len(ret + ret1)
        head_dict = {                                    #2.构建header字典
            'filename': 'file.txt',
            'md5': '11111111111111',
            'total_size': total_size,
        }
        dict_json = json.dumps(head_dict)                #3.将字典转换json格式
        dict_bytes = dict_json.encode('utf-8')           #4.将字典转化成bytes
        dict_len = len(dict_bytes)                       #5.计算字典bytes类型的大小
        head_dict_len = struct.pack('i', dict_len)       #6.将bytes类型的 字典的长度转化成固定长度的bytes
        conn.send(head_dict_len)                         #7.发送字典的总大小
        conn.send(dict_bytes)                            #8.发送 bytes类型的字典
        conn.send(ret)                                   #9.发送数据部分。
        conn.send(ret1)
    except Exception:
        break
conn.close()
phone.close()

# Client端
import socket
import struct
import json

phone = socket.socket()
phone.connect(('127.0.0.1', 8080))
while 1:
    msg = input('>>>').strip()
    if msg.upper() == 'Q':
        break
    elif not msg:
        continue
    phone.send(msg.encode('utf-8'))
    dict_size = struct.unpack('i', phone.recv(4))[0]         #1.接收字典的大小。
    header_dict_json = phone.recv(dict_size).decode('utf-8') #2.获取报头字典json格式。
    header_dict = json.loads(header_dict_json)               #3.通过json 反解成 字典
    data_size = 0                                            #4.接收数据部分。
    res = b''
    while data_size < header_dict['total_size']:
        data = phone.recv(1024)
        res = res + data
        data_size = data_size + len(data)
    print(res.decode('gbk'))
phone.close()

# 先运行Server端,再运行Client端
>>>ipconfig /all

Windows IP 配置

   主机名  . . . . . . . . . . . . . : WINDOWS-CP2ICQS
   主 DNS 后缀 . . . . . . . . . . . : 
   节点类型  . . . . . . . . . . . . : 混合
   IP 路由已启用 . . . . . . . . . . : 否
   WINS 代理已启用 . . . . . . . . . : 否

以太网适配器 本地连接:

   连接特定的 DNS 后缀 . . . . . . . : 
   描述. . . . . . . . . . . . . . . : Realtek PCIe GBE Family Controller
   物理地址. . . . . . . . . . . . . : 20-89-84-D1-E1-8B
   DHCP 已启用 . . . . . . . . . . . : 否
   自动配置已启用. . . . . . . . . . : 是
   本地链接 IPv6 地址. . . . . . . . : fe80::91d8:554a:b7fb:f16e%13(首选) 
   IPv4 地址 . . . . . . . . . . . . : 192.168.77.77(首选) 
   子网掩码  . . . . . . . . . . . . : 255.255.255.0
   默认网关. . . . . . . . . . . . . : 192.168.77.1
   DHCPv6 IAID . . . . . . . . . . . : 287345028
   DHCPv6 客户端 DUID  . . . . . . . : 00-01-00-01-21-DE-4B-33-20-89-84-D1-E1-8B
   DNS 服务器  . . . . . . . . . . . : 114.114.114.114
   TCPIP 上的 NetBIOS  . . . . . . . : 已启用

隧道适配器 isatap.{D3F0159A-0A26-4494-9B2A-92BE10466E18}:

   媒体状态  . . . . . . . . . . . . : 媒体已断开
   连接特定的 DNS 后缀 . . . . . . . : 
   描述. . . . . . . . . . . . . . . : Microsoft ISATAP Adapter
   物理地址. . . . . . . . . . . . . : 00-00-00-00-00-00-00-E0
   DHCP 已启用 . . . . . . . . . . . : 否
   自动配置已启用. . . . . . . . . . : 是

隧道适配器 Teredo Tunneling Pseudo-Interface:

   媒体状态  . . . . . . . . . . . . : 媒体已断开
   连接特定的 DNS 后缀 . . . . . . . : 
   描述. . . . . . . . . . . . . . . : Teredo Tunneling Pseudo-Interface
   物理地址. . . . . . . . . . . . . : 00-00-00-00-00-00-00-E0
   DHCP 已启用 . . . . . . . . . . . : 否
   自动配置已启用. . . . . . . . . . : 是

>>>dir
 驱动器 D 中的卷没有标签。
 卷的序列号是 FCBB-EB88

 D:\PycharmProjects\python\01.py 的目录

2018/12/17  17:00    <DIR>          .
2018/12/17  17:00    <DIR>          ..
2018/12/17  16:11               581 su.py
2018/12/17  15:57             1,011 客户.py
2018/12/17  17:00             1,802 服务.py
               3 个文件          3,394 字节
               2 个目录 43,045,126,144 可用字节

>>>q

Process finished with exit code 0

标签:00,socket,Python,phone,dict,模块,data,conn
From: https://blog.51cto.com/zzzhao/6192450

相关文章

  • Linux内核Socket通信原理和实例讲解
    关于对Socket的认识,大致分为下面几个主题,Socket是什么,Socket是如何创建的,Socket是如何连接并收发数据的,Socket套接字的删除等。Socket是什么以及创建过程一个数据包经由应用程序产生,进入到协议栈中进行各种报文头的包装,然后操作系统调用网卡驱动程序指挥硬件,把数据发送到......
  • 部署Python3
    1、安装编译工具yum-ygroupinstall"Developmenttools"yum-yinstallzlib-develbzip2-developenssl-develncurses-develsqlite-develreadline-develtk-develgdbm-develdb4-devellibpcap-develxz-develyuminstalllibffi-devel2、下载软件包并解压wgethttps://ww......
  • Python虚拟环境下使用Pyinstaller打包
    PyInstaller ,他是一款帮助我们把整个项目完整打包的工具。目前已经兼容Py3.7,以及MacApp和WindowsExe由于在进行Pyinstaller打包时,会一同将Pyinstaller所在环境里所有的package一起打进去,这就导致了非常多曾经下载过的,但是在这个项目中没用到的package,也会成为程序的一部分,......
  • Python实现字符串模糊匹配
      在一个字符串中,有时需对其中某些内容进行模糊匹配以实现条件的判定,如在“你好,hello,world”中判断是否含有“llo”。Python中通过re.search()方法实现,特别地,对于首位起始的内容匹配,也可通过re.match()方法实现。若匹配成功,它们返回一个re.Match对象;若匹配失败,返回None。re.s......
  • Python之虚拟环境venv实战详解
     安装配置虚拟环境virtualenv官方给出的建议是最好在一开始就创建虚拟环境。在不同环境下,我们使用不同命令,即可创建出一个名为venv的虚拟环境最简的方式是使用python指令之间创建#windowspython-mvenvvenv#linux/macospython3-mvenvvenvWindows重点讲解......
  • Python3基本请求库-urllib
    urlliburlopen一个基本请求fromurllibimportrequest,parsedefApi():#禁用证书验证ssl._create_default_https_context=ssl._create_unverified_contextresponse=request.urlopen('https://www.baidu.com/')print(response.read().decode(�......
  • LYT-C#-Socket——简单的TCP服务端-客户端连接通信
    https://learn.microsoft.com/zh-cn/dotnet/api/system.net.sockets.tcplistener?view=netframework-4.8.1TCP编程的服务器端一般步骤是:1、创建一个socket,用函数socket()。2、设置socket属性。3、绑定本机的IP地址、端口等信息到socket上,用函数bind()。......
  • 深入理解 python 虚拟机:字节码教程(3)——深入剖析循环实现原理
    深入理解python虚拟机:字节码教程(3)——深入剖析循环实现原理在本篇文章当中主要给大家介绍cpython当中跟循环相关的字节码,这部分字节码相比起其他字节码来说相对复杂一点,通过分析这部分字节码我们对程序的执行过程将会有更加深刻的理解。循环普通for循环实现原理我们使......
  • 一文弄懂Python中的内存管理
    1.引言Python是一种解释性语言,这意味着它在运行之前不需要编译。当Python程序运行时,它会动态地为所有变量和对象分配相应的内存。这意味着Python的内存管理是自动处理的,使得开发人员能够专注于编写代码,而不用担心相关内存分配和释放。本文就Python的内存管理进行详述,闲话少说,我们......
  • [oeasy]python00134_[趣味拓展]python起源_历史_Guido人生_ABC编程语言_Tanenbaum
    python历史回忆上次内容颜文字是kaomoji把字符变成一种图画的方法一层叠一层很多好玩儿的kaomoji是一层层堆叠起来的meme虚拟的表情也在真实世界有巨大影响一步步地影响字符编码就是这样一步步发展过来的python也是一步步发展到今天的python究竟是怎么发展的呢?......