码迷,mamicode.com
首页 > 其他好文 > 详细

网络编程基础:网络基础之网络协议、socket模块

时间:2018-03-13 23:52:38      阅读:177      评论:0      收藏:0      [点我收藏+]

标签:learning   上层   网关   工作   也有   七层   bug   循环   方式   

操作系统(简称OS)基础:

应用软件不能直接操作硬件,能直接操作硬件的只有操作系统;所以,应用软件可以通过操作系统来间接操作硬件

 

网络基础之网络协议:

网络通讯原理: 

  连接两台计算机之间的Internet实际上就是一系列统一的标准,这些标准称之为互联网协议;互联网的本质就是一系列的协议,总称为“互联网协议” (Internet Protocol Suite)

  互联网协议的功能:定义计算机何如接入Internet,以及接入Internet的计算机通信的标准。

 

  osi七层协议: 互联网协议按照功能不同分为OSI七层或TCP/IP五层或TCP/IP四层

技术分享图片

用户感知到的只是最上面的一层应用层,自上而下每层都依赖于下一层;每层都运行特定的协议,越往上越靠近用户,越往下越靠近硬件

  物理层功能:主要是基于电气特性发送高低电压(电信号),高电压对应的数字为1,低电压对应数字0

 

  数据链路层:

    数据链路层的由来: 单纯的电信号0、1没有任何意义,必须要规定电信号多少位一组,每组什么意思

    数据链路层的功能:定义了电信号的分组方式

    以太网协议(Ethernet):Ethernet协议规定了:1. 一组电信号构成一个数据包,叫做“帧”;2. 每一数据帧分成“报头”head和数据data两部分

       head包含(固定18个字节):1. 发送者/原地址,6个字节; 2. 数据类型,6个字节; 3. 接受者/目标地址,6个字节

       data包含:数据包的具体内容

    Mac地址:head中包含的源、目标地址的由来:Ethernet规定接入Internet的设备都必须具备网卡,发送端和接收端的地址便是指网卡的地址,即Mac地址;

      (每块网卡出厂时都被烧制上一个世界唯一的Mac地址,长度为48位2进制,通常由12位16进制数表示(前六位是厂商编号,后六位是流水线号))

    广播: 有了Mac地址,同一网络内的两台主机就可以通信了;Ethernet采用最原始的广播的方式进行通信,即计算机通信基本靠吼

  网络层:有了Ethernet、Mac地址、广播的发送方式,同一个局域网内的计算机就可以彼此通讯了,但世界范围内的互联网是由一个个彼此隔离的小的局域网(子网)组成的,

      所以不能所有的通信都采用以太网的广播方式

技术分享图片

    从上图可以看出:必须找出一种方法来区分哪些计算机属于同一广播域、哪些不是,如果是就采用广播的方式发送;如果不是就采用路由的方式(向不同广播域/子网分发数据包),

            Mac地址是无法区分的,它只跟厂商有关

 

    网络层功能:引入一套新的地址来区分不同的广播域(子网),这套地址即网络地址

    IP协议:1. 规定网络地址的协议叫IP协议,它定义的地址称为IP地址,广泛采用的v4版本即ipv4,它规定网咯地址由32位2进制表示;

        2. 范围0.0.0.0-255.255.255.255

        3. 一个IP地址通常写成四段十进制数,例如:172.16.10.1

      IP地址分成两部分: 1. 网络部分:标识子网; 2. 主机部分:标识主机

        注:单纯的IP地址段只是标识了IP地址的种类,从网络部分或主机部分都无法辨识一个IP所处的子网

      子网掩码:表示子网络特征的一个参数;知道了“子网掩码”,我们就能判断任意两个IP地址是否处在同一个子网络。

      网络层作用总结:IP协议的主要作用有两个:1. 为每台计算机分配IP地址;2.确定哪些地址在同一个子网络

    IP数据包:分为head和data两个部分,然后直接放入以太包的data部分,如下所示:

技术分享图片

    ARP协议:由来:计算机通信基本靠吼,即广播的方式,所有上层的包到最后都要封装上以太网头,然后通过以太网协议发送;通信是基于Mac的广播方式实现,计算机在发包时

            获取自身的Mac容易,如何获取目标主机的Mac就需要通过ARP协议。

          ARP协议功能:广播的方式发送数据包,获取目标主机的Mac地址

         协议工作方式: 每台主机IP都是已知的

           1. 首先通过IP地址和子网掩码区分出自己所处的子网

           2. 分析是否处于同一网络(如果不是同一网络。通过ARP获取的是网关的Mac)

          技术分享图片

           3. 这个包以广播的方式在发送端所处的子网内传输,所有主机接收后拆开包,发现目标IP是自己的就响应返回自己的Mac(这点还不是很理解,发送端所处的子网??)

 

    传输层:由来:网络层的IP帮我们区分子网,以太网层的Mac帮我们找到主机,然后大家使用的都是应用程序,那么我们通过IP和Mac找到了一台特定的主机;

           然后,标识这台主机上的应用程序就是端口,端口即应用程序和网卡关联的编号。

        传输层功能:建立端口到端口的通信

        补充:端口范围0-65535,0-1023为操作系统占用端口

        TCP协议: 可靠传输,需要挖双向“通道“,””3次“握手”和4次“挥手”;流式协议

        UDP协议:不可靠传输,不需要挖“通道”

    

    应用层:由来:用户使用的都是应用程序,均工作于应用层,互联网是开发的,大家都可以开发自己的应用程序,数据多种多样,必须规定好数据的组织形式

        应用层功能: 规定应用程序的数据格式

          例如: TCP协议可以为各种各样的程序传递数据,比如Email、www、FTP等;那么必须有不同协议规定电子邮件、网页、发图片数据的格式,这些应用程序协议就构成了“应用层”

          技术分享图片

 

Socket:

socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,socket是一个门面模式,他爸复杂的TCP/IP协议族隐藏在socket接口的后面,对于用户来说,一组简单的接口就是全部,让socket去组织数据,以符合指定的协议。

技术分享图片

所以我们无需深入理解TCP/UDP协议,socket已经为我们封装好了,我们只需要遵循socket的规定去编程,写出的程序自然就是遵循TCP、UDP标准的

附:也有人将socket说成IP+port,IP是用来标识互联网中的一台主机的位置,而port是用来标识这台机器上的一个应用程序,IP地址是配置到网卡上的,而port是应用程序开启的,IP与port的绑定就标识了互联网中独一无二的一个应用程序; 而程序的pid是同一台机器上不同进程或线程的标识 

套接字: 套接字有两种(或者说两个种族),分别是基于文件型的和基于网络型的。

基于文件类型的套接字家族: 套接字家族的名字是 : AF_UNIX

基于网络类型的套接字家族: 套接字家族的名字是:AF_INET  

  还有AF_INET6被用于ivp6;AF_INET是使用最广泛的一个,python支持很多地址家族,但是由于我们只关心网络编程,所以大部分时候我们只是用AF_INET

 

套接字(socket) 工作流程:以打电话为例说明:

客户端代码如下:

import socket

# 1. 买“手机”
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
print(phone)
# 打印结果:
# <socket.socket fd=300, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0>


# 2. “拨号” (客户端不需要绑定IP和端口)
phone.connect(("127.0.0.1",8080))
"""
# connect 发起连接, ("127.0.0.1",8080)是服务端的IP和端口; 
# 客户端的connect对应服务端的accept(),connect()和服务端的accept()底层进行的就是TCP的“三次握手”
# 服务端accept()之后,客户端的phone就相当于服务端的那个 conn,就是那根“电话线”
"""
print(phone)
# 运行结果:  # 服务端accept()之后客户端的phone就发生了变化,变得和服务端中的conn对应
# <socket.socket fd=300, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=(‘127.0.0.1‘, 62064), raddr=(‘127.0.0.1‘, 8080)>



# 发、收消息;发收的消息都是bytes类型
phone.send("hello".encode("utf-8"))  #
"""
# 不能直接发字符串, 物理层传输的0101,这一步需要发送bytes类型; 
# 字符串转bytes: string.encode(编码格式)
# phone.send() 对应服务端的 conn.recv()
"""
data = phone.recv(1024)  #
print(data)

# 关闭
phone.close()


# 先启动服务端,再启动客户端,运行结果如下:
# b‘HELLO‘

服务端代码如下:

import socket

# 1. 买“手机”
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # 基于网络通讯的、基于TCP协议的套接字;phone就是一个套接字对象  # 这一步得到一个服务端的套接字phone
"""
全称是: phone = socket.socket(family=socket.AF_INET,type=socket.SOCK_STREAM)
socket.socket() # socket下面的socket类;
family=socket.AF_INET # 地址家族(socket的类型)是基于网络通讯的AF_INET  
type=socket.SOCK_STREAM # 用的是流式的协议,即 TCP协议 
"""
# print(phone)
# 打印结果
# <socket.socket fd=316, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0>

# 2. 绑定“手机卡”(服务端的IP地址需要固定下来、并公示给别人;客户端虽然有IP和端口但是不需要绑定)
phone.bind(("127.0.0.1",8080))
"""
# 服务端需要绑定IP和Port(IP和端口),ip和端口需要以元祖的形式传进来;
其中第一个参数是字符串形式的IP地址; 127.0.0.1是指本机,专门用于测试的,IP写成这个就意味着服务端和客户端都必须在同一台主机上;
第二个参数是端口;端口范围是0-65535,其中0-1023是给操作系统使用的,2014以后的你可以使用
"""

# 3. “开机”
phone.listen(5)  # 开始TCP监听
"""
# 5代表最大挂起的链接数; 通常这个数写在配置文件中
# Enable a server to accept connections. If backlog is specified, it must be at least 0 (if it is lower, it is set to 0); it specifies the number of unaccepted connections that the system will allow before refusing new connections. If not specified, a default reasonable value is chosen.
"""

# 4. 等电话
# res = phone.accept()
# print(res)
# print(phone)
"""
# 等待链接; 等待的结果赋值给一个变量 
# 服务端程序启动后,程序会停在这一步; 
# 服务端的accept()对应客户端的connect()
# accept()底层建立的就是TCP的“三次握手”,“三次握手”之后会建成一个双向的链接(下面的conn),然后客户端得到一个对象(新的phone)、服务端得到一个对象(conn),这两个对象都可以收、发消息
"""
# 客户端的程序启动后,服务端的程序也从 res = phone.accept()这一步接着往下运行
# 其中一次的运行结果:
# (<socket.socket fd=356, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=(‘127.0.0.1‘, 8080), raddr=(‘127.0.0.1‘, 56572)>, (‘127.0.0.1‘, 56572))
# 元祖的形式,元祖里面有2个元素,第一个元素是发送端的链接对象(套接字对象),第二个元素是客户端的IP和端口

# <socket.socket fd=312, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=(‘127.0.0.1‘, 8080)>

"""
由于phone.accept()得到的结果是元祖的形式,里面有两个元素:第一个、客户端的链接对象(相当于拨号人的电话线);第二个、客户端的IP和端口,所以phone.accept()可以写成如下形式
"""

conn,client_addr = phone.accept()
print(conn)
# 打印结果:
# <socket.socket fd=328, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=(‘127.0.0.1‘, 8080), raddr=(‘127.0.0.1‘, 62064)>


# 5. 收、发消息(基于刚刚建好的那根“电话线”(conn)收发消息),收发的消息都是bytes类型
data = conn.recv(1024)  #
print("客户端的数据",data)
"""
# conn.recv(1024):接收conn这个发送端对象发来的数据(或者理解成沿着conn这根“电话线”接收消息) 
# 括号内的数字需要注意两个地方: 1. 数字单位:bytes;2. 数字2014代表最大接收1024个bytes
# conn.recv(1024)接收到的数据赋值给变量 data
"""

conn.send(data.upper())  #
"""
# conn.send(data.upper()):给conn的客户端发送消息(沿着conn这个“电话线”发送消息)
# .upper() # 把字符串里面的都变成大写
"""

# 挂电话(关闭)
conn.close()

# 关机
phone.close()

# 运行结果:
# 客户端的数据 b‘hello‘


"""
1. 服务端有两种套接字对象:服务端的phone和conn
     服务端的phone用于:绑定(IP和端口)、监听TCP和最重要的接收接收客户端的链接和客户端的IP、端口
     conn用于收发消息
2. 客户端有一种套接字对象:客户端的phone(其实客户端的phone在服务端accept之后也发生了变化),它的作用是:发起建链接请求(.connect())和发、收消息 
"""

 

简单套接字加上通信循环:

把上面的代码加上 while True 就变成了循环通信,如下所示

客户端代码:

import socket

phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.connect(("127.0.0.1",8080))
while True:
    msg = input(">>>").strip()
    phone.send(msg.encode("utf-8"))
    data = phone.recv(1024)
  
print(data) # 也是bytes形式
   """
   如果想要打印正常的形式,可利用利用:
   print(data.decode("utf-8))    """ phone.close()

服务端代码:

import socket

phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.bind(("127.0.0.1",8080))
phone.listen(5)

conn,client_addr = phone.accept()
print(client_addr)

while True:
    data = conn.recv(1024)  # 在收到消息之前,程序也“卡”在这一步; 所以,recv()的具体含义是“等待接收消息”
    print("客户端的数据",data)
    conn.send(data.upper())

conn.close()

phone.close()

 

重启服务端的时候可能出现端口仍然被占用的情况,原因是端口被操作系统回收需要时间,解决办法如下:

为服务端加一句代码:

import socket

phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) # 在绑定之前加上这句代码; # reuseaddr表示重新用该端口
phone.bind(("127.0.0.1",8081))
phone.listen(5)

conn,client_addr = phone.accept()
data = conn.recv(1024)
print("客户端的数据",data)
conn.send(data.upper())
conn.close()
print(phone)
phone.close()

 

客户端和服务端代码bug修复:

客户端可以发空消息,但服务端却收不到空消息,如下代码:

客户端:

import socket

phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.connect(("127.0.0.1",8080))
while True:
    msg = input(">>>").strip()
    """
    客户端可以发空数据,但是服务端却收不到空数据
    解决客户端发空消息可以用如下代码:
    """
    if not msg:continue  # 如果发的消息为空,则重新发
    phone.send(msg.encode("utf-8"))
    data = phone.recv(1024)
    print(data.decode("utf-8"))

   """
   recv和send都是python(应该说是应用程序)发给操作系统的命令
   收发消息需要通过Internet进行传输,而Internet需要通过网卡去发送、接收数据,只有操作系统才能调用网卡这个硬件
   所以,具体执行发送、接收消息动作的是操作系统(就如文件处理中的open(file)一样),python(应用程序)把发送的消息的内存原封不动地复制给操作系统,然后操作系统去发送消息;

   当客户端发送空消息时,应用程序会把这个空消息复制给操作系统,正常情况下操作系统会根据TCP协议调用网卡,但由于操作系统收到的是空消息,所以操作系统没有调用任何硬件,
   也就是说,python(应用程序)发送的空消息只发到了客户端操作系统这一步,然后客户端的操作系统并没有接着往下发这个空消息;所以客户端的程序就卡在了这一步

   """
phone.close()

服务端:

import socket

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,client_addr = phone.accept()
print(client_addr)

while True:
    data = conn.recv(1024)
    print("客户端的数据",data)
    conn.send(data.upper())

conn.close()

phone.close()

 

还有一种情况:以上面的代码为例, 由于conn是基于客户端和服务端建立起来的一个双向通道,假如客户端被强行终止掉了,那么这个双向通道conn就没有意义了;在Windows系统下,假如客户端被强行终止,那么服务端就会报错,但在Linux系统下,服务端不会报错,而是进入了while的死循环,为了防止Linux的这个死循环,可以利用如下方法解决:

import socket

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,client_addr = phone.accept()
print(client_addr)

while True:
    data = conn.recv(1024)
    if not data:break
    """
    由上面的分析可知:正常情况下服务端不可能收到空消息,因为假如客户端发了空消息,那么客户端的操作系统根本不会把这个空消息发出去;
    所以,假如data变成了空消息,那一定是因为conn这个双向通道少了一方,也就是客户端单方面终止了;
    所以 if not data:break  # 就是说,假如客户端已经终止了,那就结束服务端的这个while True循环
    """
    print("客户端的数据",data)
    conn.send(data.upper())

conn.close()

phone.close()

上述方法是针对Linux的;Windows下客户端当方面终止程序,服务端直接报错,所以应该用 try...except...去解决:

while True:
    try:
        data = conn.recv(1024)

        print("客户端的数据",data)
        conn.send(data.upper())
    except ConnectionResetError:
        break

conn.close()
phone.close()

 

服务端为多个客户端提供服务:

服务端代码如下:

import socket

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)
"""
这个服务端可以为多个服务端服务,但同一时间只能服务于一个客户端;
当有其他服务端发来建链接请求时就挂起,当正在被服务的服务端退出后,挂起的其他服务端建链接的请求就会执行;
5为最大的挂起链接数
"""

while True:  # 链接循环
    conn,client_addr = phone.accept()
    print(client_addr)

    while True: # 通讯循环
        try:
            data = conn.recv(1024)
            print("客户端的数据",data)
            conn.send(data.upper())
        except ConnectionResetError:
            break

    conn.close()
phone.close()

 

模拟ssh远程执行命令:

关于系统命令的知识点补充:

# 一、系统命令:

# windows:
# dir  # 查看某个文件夹下的子文件名和子文件夹名
# ipconfig # 查看本地网卡的IP信息
# tasklist # 查看运行的进程

# Linux系统对应的是:
# ls
# ifconfig
# ps aux

"""
系统命令不能直接在pycharm上写,而应该在cmd上输入(Windows系统);
cmd也是一个程序,它的功能非常单一,就是来接收你输入的有特殊意义的单词(命令),然后把你输入的有特殊意义的单词(命令)解析成操作系统认识的指令去执行;所以这个程序称之为“命令解释器”
如: dir f;\learning
Linux系统中: / 代表c盘
"""

# 二、执行系统命令:
# 1、考虑使用os模块
# import os
# os.system("dir f;\learning")  # 字符串形式的命令

# 但是这种方法是在服务端的终端上打印了dir f:\learning 的子文件和子文件夹名;而我们想要的结果是把命令结果拿到客户端然后再客户端打印

"""
res = os.system("dir f;\learning")  # res 只是 os.system("dir f:\learning") 的执行状态结果:0或者非0(0代表命令执行成功),并不是命令的查看结果
"""

# 执行系统命令,并拿到命令的结果
# 2. subprocess模块的Popen
import subprocess
obj = subprocess.Popen("dir f:\learning",shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)  # 命令的结果赋值给obj
"""
# 第一个参数是字符串格式的命令
要写: shell = True # shell是指命令解释器 # 启动一个程序来解析前面的字符串,把这个字符串解析成相应的命令去执行  # 相当于起了一个cmd
这个事例中,不管执行结果正确与否,命令的结果都只有一个,你没告诉subprocess把命令的结果给谁,它就把结果默认给了终端;但我们想要的是把命令的结果给客户端,而不是终端
所以我们需要通过某种手段告诉subprocess不要把结果给终端,而是把结果先存到一个地方,等我调用的时候发送给客户端,所以就用到了“管道”的概念;
把命令的结果放到一个管道里面(操作系统的内存),等你需要的时候再去管道里面取
让subprocess把结果放到管道里的方法:
stdout = subprocess.PIPE  # stdout是命令的正确执行结果  # 命令的正确执行结果放到一个管道里面
stderr = subprocess.PIPE # stderr是命令的错误执行结果  # 每次的 .PIPE都触发一次PIPE的功能,从而产生一个新的管道;so 这两个 PIPE是不一样的
"""

print(obj)
# 打印结果:
# <subprocess.Popen object at 0x0000007994CEA8D0>

print("stdout---->",obj.stdout.read())   # obj从stdout(正确结果)这个管道里面读 (从管道读取一次之后再取就没有了)

# 打印结果:(bytes格式)(不管服务端还是客户端,收、发消息都得是bytes形式)
# stdout----> b‘ \xc7\xfd\xb6\xaf\xc6\xf7 F \xd6\xd0\xb5\xc4\xbe\xed\xc3\xbb\xd3\xd0\xb1\xea\xc7\xa9\xa1\xa3\r\n \xbe\xed\xb5\xc4\xd0\xf2\xc1\xd0\xba\xc5\xca\xc7 BCA5-0E10\r\n\r\n f:\\learning \xb5\xc4\xc4\xbf\xc2\xbc\r\n\r\n2018/01/17  16:19    <DIR>          .\r\n2018/01/17  16:19    <DIR>          ..\r\n2018/01/12  01:04    <DIR>          funny\r\n2018/01/15  14:12    <DIR>          IDLE\xd7\xf7\xd2\xb5\xb2\xe2\xca\xd4\r\n2018/02/05  12:04    <DIR>          pycharm_pro\r\n2018/01/19  09:16    <DIR>          pythontest\r\n2018/03/10  11:05    <DIR>          \xd7\xf7\xd2\xb5\xcc\xe1\xbd\xbb\r\n2018/03/08  11:45    <DIR>          \xb2\xa9\xbf\xcd\xa1\xa2\xb4\xed\xce\xf3\xa1\xa2\xd2\xc9\xce\xca\xbd\xd8\xcd\xbc\r\n2018/01/27  18:01    <DIR>          \xbd\xd8\xcd\xbc\r\n2018/01/16  10:33    <DIR>          \xd7\xd4\xd1\xa7\r\n2018/01/11  14:53    <DIR>          \xc4\xac\xd0\xb4\r\n               0 \xb8\xf6\xce\xc4\xbc\xfe              0 \xd7\xd6\xbd\xda\r\n              11 \xb8\xf6\xc4\xbf\xc2\xbc 115,108,585,472 \xbf\xc9\xd3\xc3\xd7\xd6\xbd\xda\r\n‘


# obj.stdout.read()是bytes格式,如果想看bytes格式里面具体是什么内容,则需要 decode();
print("stdout---->",obj.stdout.read().decode("gbk"))
"""
encode()是按照什么编码,decode()也需要按照相应的编码;
subprocess.Popen("dir f;\learning")执行的是系统命令,这个命令是提交给操作系统的,由操作系统执行完后拿到一个结果;
由于没告诉操作系统命令的结果用什么格式编码,所以系统会用它默认的编码格式;所以:
obj.stdout.read().decode("gbk")
"""

print("stderr--->",obj.stderr.read().decode("gbk"))    # 执行结果不一定正确,所以也要从obj.stderr 读取

# 打印结果:
# stdout---->
# stderr---> 

 

模拟ssh远程执行命令具体代码:

客户端:

import socket

phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.connect(("127.0.0.1",8080))
while True:
    # 1. 发命令
    cmd = input(">>>").strip()  # 客户端在这行代码输入一条命令
    if not cmd:continue
    phone.send(cmd.encode("utf-8"))

    # 2. 得到命令的结果,并打印
    data = phone.recv(1024)  # data是bytes格式,打印需要解码  # 1024是个坑,待优化
    print(data.decode("gbk"))
    """
    data 解码需要是gbk,因为:data是由服务端传来的(stdout+stderr),而stdout和stderr是由 subprocess.Popen()得到的
    subprocess.Popen("命令")是把命令交给了操作系统去处理,操作系统处理命令后会按照自己默认的编码把处理结果encode,而Windows的默认编码是 gbk
    """

phone.close()

 

服务端:

import subprocess
import socket

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)

while True:  # 链接循环
    conn,client_addr = phone.accept()

    while True: # 通讯循环
        try:
            # 1. 接收命令
            cmd = conn.recv(1024)  # cmd是bytes格式

            # 2. 执行命令,拿到执行后的结果
            obj = subprocess.Popen(cmd.decode("utf-8"), shell=True, stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE)  # subprocess.Popen()中需要的是字符串格式的命令,所以需要把cmd decode;由于客户端是按照utf-8进行的encode,所以这步需要decode("utf-8")
            stdout = obj.stdout.read()  # obj.stdout需要read
            stderr = obj.stderr.read()  # stderr和stdout都是bytes格式的

            # 3. 把命令的结果返回给客户端
            conn.send(stdout+stderr)  # + 会影响效率;因为 + 是重新创建了一份stdout和stderr的新的内存空间(把stdout和stderr的内存空间重新copy了一遍)# 所以+是一个可以优化的点
        except ConnectionResetError:
            break

    conn.close()
phone.close()

 

网络编程基础:网络基础之网络协议、socket模块

标签:learning   上层   网关   工作   也有   七层   bug   循环   方式   

原文地址:https://www.cnblogs.com/neozheng/p/8540140.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!