31.Python之网络编程(socket模块)

Python之网络编程(socket模块)

  1. 什么是socket?

    • Socket是应用层与TCP / IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个“门面模式”,它把复杂的TCP / IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
    • 基于文件类型的套接字家族:
      • 套接字家族的名字:AF_UNIX
        Unix一切皆文件,基于文件的套接字调用的就是底层的文件系统来取数据,两个套接字进程运行在同一机器,可以通过访问同一个文件系统间接完成通信。
    • 基于网络类型的套接字家族
      • 套接字家族的名字:AF_INET
        还有AF_INET6被用于ipv6,还有一些其他的地址家族,不过,他们要么是只用于某个平台,要么就是已经被废弃,或者是很少被使用,或者是根本没有实现,所有地址家族中,AF_INET是使用最广泛的一个,Python支持很多种地址家族,但是由于我们只关心网络编程,所以大部分时候我么只使用AF_INET。

  1. 为什么要用socket?

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

  1. 基于TCP协议的socket(无并发)

    • 服务端

      # tcp是基于可靠链接的,必须先启动服务端,然后再启动客户端去链接服务端
      import socket
      
      # 由于 socket 模块中有太多的属性。因此可以使用'from module import *'语句。也就是 'from socket import *',这样我们就把 socket 模块里的所有属性都带到我们的命名空间里了,这样能大幅减短代码。
      # from socket import *
      # socket_server = socket(AF_INET,SOCK_STREAM)
      socket_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # 创建服务器套接字,SOCK_STREAM流式协议,指的是TCP协议;SCOK_DGRAM数据报协议,指的是UDP协议。
      socket_server.bind(('127.0.0.1', 8080))  # 把地址绑定到套接字,只有服务端需要绑定,IP地址填写服务器IP,端口是数字类型,1025-65530任选,端口0-1024系统占用
      socket_server.listen(5)  # 监听链接,backlog = 5 表示同一时间能接受5个请求,并不是最大连接数
      
      # 等待连接
      conn, client_address = socket_server.accept()  # 程序阻塞,等待连接,有两个参数,一个连接对象conn,一个客户端地址client_address(包含IP和端口),对象conn是tcp三次握手的产物,用来收发消息,而socket_server对象是专门用来建立连接的
      
      # 收发消息
      msg = conn.recv(1024)  # 收消息,有个返回值给msg,1024是一个最大的限制,表示最多能收 1024 bytes
      conn.send('Hello!!!'.encode('utf-8'))  # 发消息,网络中只能传输bytes类型
      
      conn.close()  # 断开(关闭)客户端套接字,回收系统资源。完成TCP四次挥手。
      
      socket_server.close()  # 断开(关闭)服务器套接字,回收系统资源
      
    • 客户端

      import socket
      
      socket_client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # 创建客户端套接字,SOCK_STREAM流式协议,指的是TCP协议;SCOK_DGRAM数据报协议,指的是UDP协议。
      
      socket_client.connect(('127.0.0.1', 8080))  # 与服务器建立连接,地址为服务器的IP地址和端口号
      
      socket_client.send('Hello!'.encode('utf-8'))  # 发消息,注意字符串不能直接直接发,需要转换成二进制
      msg = socket_client.recv(1024)  # 收消息
      
      socket_client.close()
      

  1. 加上通信循环和连接循环的socket(无并发)(解决服务端不可以循环接收客户端信息的问题)

    • 服务端

      import socket
      
      socket_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # 如果什么参数都不传,默认就是这个
      socket_server.bind(('127.0.0.1', 8080))
      socket_server.listen(5)
      
      while True:  # 加连接循环
          conn, client_address = socket_server.accept()
      
          while True:  # 加通信循环
              try:
                  msg = conn.recv(1024)
                  if not msg: break  # 针对Linux操作系统
                  print('客户端:', client_address)
                  conn.send(msg + b'_SB')
              except ConnectionResetError:
                  break
      
          conn.close()
      
      socket_server.close()
      
    • 客户端

      import socket
      
      socke_client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
      
      socke_client.connect(('127.0.0.1', 8080))
      
      while True:
          msg = input('>>>:')
          socke_client.send(msg.encode('utf-8'))
          msg = socke_client.recv(1024)
          print(msg)
      
      socke_client.close()
      

  1. 远程执行命令的小程序(无并发)

    • 服务端

      from socket import *
      import subprocess
      import struct  # Python提供了一个struct模块来解决str和其他二进制数据类型的转换。struct的pack函数把任意数据类型变成字符串
      
      socket_server = socket(AF_INET, SOCK_STREAM)
      socket_server.bind(('127.0.0.1', 8080))
      socket_server.listen(5)
      
      while True:
          conn, client_address = socket_server.accept()
          print('正在监听……')
          while True:
              try:
                  cmd = conn.recv(1024)
                  if not cmd: break
                  print('开始接收文件……')
                  obj = subprocess.Popen(cmd.decode('utf-8'),
                                         shell=True,
                                         stdout=subprocess.PIPE,
                                         stderr=subprocess.PIPE
                                         )
                  res_stdout = obj.stdout.read()
                  res_stderr = obj.stderr.read()
              except ConnectionResetError:
                  break
      
              # -*- 解决TCP粘包问题 -*-#
              # 制作固定长度的报头
              total_size = len(res_stdout) + len(res_stderr)
              header = struct.pack('i', total_size)  # 制作固定长度的报头
      
              # 发送报头
              conn.send(header)
      
              # conn.send(res_stdout+res_stderr)
              # 由于TCP的优化,使用了Nagle算法,将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包,实际上以下两条命令会合并到一起发送。
              conn.send(res_stdout)
              conn.send(res_stderr)
      
          conn.close()
      
      socket_server.close()
      
    • 客户端

      from socket import *
      import struct
      
      socket_client = socket(AF_INET, SOCK_STREAM)
      socket_client.connect(('127.0.0.1', 8080))
      
      while True:
          cmd = input('>>>:')
          if not cmd: continue  # 判断cmd不为空,if x is not None:continue 是最好的写法
          socket_client.send(cmd.encode('utf-8'))
      
          # -*- 解决TCP粘包问题 -*-#
          # 先收固定长度的报头
          header = socket_client.recv(4)
      
          # 解析报头
          total_size = struct.unpack('i', header)[0]
      
          # 根据报头,收取数据,为防止数据过大,撑爆内存,以小单位循环收取
          recv_size = 0
          res = b''
          while recv_size < total_size:
              recv_date = socket_client.recv(1024)
              res += recv_date
              recv_size += len(recv_date)
          # info = socket_client.recv(1024)
          print(res.decode('gbk'))
      
      socket_client.close()
      

  1. 远程执行命令的小程序(自定义报头,无并发)

    • 服务端

      from socket import *
      import subprocess
      import struct  # Python提供了一个struct模块来解决str和其他二进制数据类型的转换。struct的pack函数把任意数据类型变成字符串
      import json
      
      socket_server = socket(AF_INET, SOCK_STREAM)
      socket_server.bind(('127.0.0.1', 8081))
      socket_server.listen(5)
      
      while True:
          conn, client_address = socket_server.accept()
          print('正在监听……')
          while True:
              try:
                  cmd = conn.recv(1024)
                  if not cmd: break
                  print('开始接收文件……')
                  obj = subprocess.Popen(cmd.decode('utf-8'),
                                         shell=True,
                                         stdout=subprocess.PIPE,
                                         stderr=subprocess.PIPE
                                         )
                  res_stdout = obj.stdout.read()
                  res_stderr = obj.stderr.read()
              except ConnectionResetError:
                  break
      
              # 制作报头
              header_dic = {'total_size': len(res_stdout) + len(res_stderr), 'md5': 'xxxxxxxxxxxx', 'filename': 'xxx.py'}
              header_json = json.dumps(header_dic)  # 将字典转换成字符串
              header_bytes = header_json.encode('utf-8')  # 将字符串转换成
      
              # 获取报头长度
              header_size = len(header_bytes)
      
              # 发送报头
              conn.send(struct.pack('i', header_size))
      
              # conn.send(res_stdout+res_stderr)
              # 由于TCP的优化,使用了Nagle算法,将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包,实际上以下两条命令会合并到一起发送。
              # 发送数据
              conn.send(res_stdout)
              conn.send(res_stderr)
      
          conn.close()
      
      socket_server.close()
      
    • 客户端

      from socket import *
      import struct
      import json
      
      socket_client = socket(AF_INET, SOCK_STREAM)
      socket_client.connect(('127.0.0.1', 8081))
      
      while True:
          cmd = input('>>>:')
          if not cmd: continue  # 判断cmd不为空,if x is not None:continue 是最好的写法
          socket_client.send(cmd.encode('utf-8'))
      
          # 先收报头的长度
          header_size = struct.unpack('i', socket_client.recv(4))[0]
      
          # 接收报头
          header_bytes = socke_client.recv(header_size)
      
          # 解析报头
          header_json = header_bytes.decode('utf-8')
          header_dic = json.loads(header_json)
          total_size = header_dic['total_size']
      
          # 根据报头,收取数据,为防止数据过大,撑爆内存,以小单位循环收取
          recv_size = 0
          res = b''
          while recv_size < total_size:
              recv_date = socke_client.recv(1024)
              res += recv_date
              recv_size += len(recv_date)
          # info=socket_client.recv(1024)
          print(res.decode('gbk'))
      
      socket_client.close()
      
  1. 基于UDP的socket

    • 数据报协议(UDP)没有粘包问题,UDP协议面向无连接,发送数据,无需对方确认,发送效率高,但UDP协议有效传输数据大小为 512 bytes,超过这个大小就非常容易丢包,DNS服务使用UDP协议,由于这个限制,导致全球根服务器数量限制在13台。

    • 服务端

      import socket
      
      socket_server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
      socket_server.bind(('127.0.0.1', 8080))
      
      while True:
          client_msg, client_address = socket_server.recvfrom(1024)
          socket_server.sendto(client_msg.upper(), client_address)
      
    • 客户端

      import socket
      
      socket_client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
      
      while True:
          msg = input('>>>:').strip()
          socket_client.sendto(msg.encode('utf-8'), ('127.0.0.1', 8080))
          server_msg, server_address = socket_client.recvfrom(1024)
          print(server_msg.decode('utf-8'))
      

你可能感兴趣的:(31.Python之网络编程(socket模块))