Python学习笔记--网络通信--socket

1.socket里面的,AF_INET和AF_UNIX有什么区别?

 

 2.socket 通信示例?

  • 服务端:
    • 代码:
    • import socket
      
      # 1.买手机
      phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
          # AF_INET和AF_UNIX区别,AF_INET远程交互,AF_UNIX本地交互
          # SOCK_STREAM 流式协议 = TCP ; SOCK_DGRAM 数据报协议 = UDP
      
      print(phone)   # <socket.socket fd=436, family=AddressFamily.AF_INET, type=SocketKind.SOCK_DGRAM, proto=0>
      
      # 2.绑定电话卡
      phone.bind(("127.0.0.1",8080))  # 绑定ip和端口 # 为了让客户端访问,这里一般固定后就不变了.
      
      # 3.开机
      phone.listen(5) # __backlog = 5 半连接池数量为5
      
      # 4.接受链接信息
      conn,client_addr = phone.accept()   # 返回两个值. conn是链接通路,是三次握手的成果;client_addr是客户端的地址.
      print(conn)
      #<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', 1873)>
      print(client_addr)
      # ('127.0.0.1', 1873)
      
      # 5.收发消息
      data = conn.recv(1024) # 最大接收字节数 1024 ; 注意 这里的data是byte类型
      print(data) # b'hello'
      conn.send(data.upper()) # 回消息 ,这里为了演示,把data的大写返回去。
      
      # 6.挂电话
      conn.close()
      
      # 7.关机
      phone.close()

       

  •  客户端:
    • 代码:
    • import socket
      
      # 1.买手机
      phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
          # AF_INET和AF_UNIX区别,AF_INET远程交互,AF_UNIX本地交互
          # SOCK_STREAM 流式协议 = TCP ; SOCK_DGRAM 数据报协议 = UDP
      
      # 2. 打电话
      phone.connect(("127.0.0.1",8080))
      
      
      # 3.发,收数据
      phone.send("hello".encode("utf-8"))
      # phone.send(b"hello") # 这样也可以
      
      data = phone.recv(1024) # 接收数据 同样设置为1024
      print(data.decode("utf-8")) # 返回的也是byte类型,需要解码
      
      
      # 4.关机
      phone.close()

       

 3.socket通信,升级--加上循环。

  • 服务端:
    • import socket
      
      # 1.买手机
      phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
      
      # 2.绑定电话卡
      phone.bind(("127.0.0.1",8080))
      
      # 3.开机
      phone.listen(5)
      
      # 4.接受链接信息
      conn,client_addr = phone.accept()   # 返回两个值. conn是链接通路,是三次握手的成果;client_addr是客户端的地址.
      
      # 5.收发消息
      while True:
          data = conn.recv(1024) # 最大接收字节数 1024 ; 注意 这里的data是byte类型
          print(data) # b'hello'
          conn.send(data.upper()) # 回消息 ,这里为了演示,把data的大写返回去。
      
      # 6.挂电话
      conn.close()
      
      # 7.关机
      phone.close()

       

  • 客户端:
    • import socket
      
      # 1.买手机
      phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
          # AF_INET和AF_UNIX区别,AF_INET远程交互,AF_UNIX本地交互
          # SOCK_STREAM 流式协议 = TCP ; SOCK_DGRAM 数据报协议 = UDP
      
      # 2. 打电话
      phone.connect(("127.0.0.1",8080))
      
      
      # 3.发,收数据
      # 建立循环
      while True:
          msg = input("请输入信息:")
          phone.send(msg.encode("utf-8"))
      
          data = phone.recv(1024) # 接收数据 同样设置为1024
          print(data.decode("utf-8")) # 返回的也是byte类型,需要解码
      
      
      # 4.关机
      phone.close()

       

4.socket通信--升级2--解决客户端发空报错。

  • 服务端代码不动:
    • import socket
      
      # 1.买手机
      phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
      
      # 2.绑定电话卡
      phone.bind(("127.0.0.1",8080))
      
      # 3.开机
      phone.listen(5)
      
      # 4.接受链接信息
      conn,client_addr = phone.accept()   # 返回两个值. conn是链接通路,是三次握手的成果;client_addr是客户端的地址.
      
      # 5.收发消息
      while True:
          data = conn.recv(1024) # 最大接收字节数 1024 ; 注意 这里的data是byte类型
          print(data) # b'hello'
          conn.send(data.upper()) # 回消息 ,这里为了演示,把data的大写返回去。
      
      # 6.挂电话
      conn.close()
      
      # 7.关机
      phone.close()

       

  •  客户端--增加为空判断
    • import socket
      
      # 1.买手机
      phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
      
      # 2. 打电话
      phone.connect(("127.0.0.1", 8080))
      
      # 3.发,收数据
      # 建立循环
      while True:
          msg = input("请输入信息:")
          if len(msg) == 0:
              continue  # 跳过本次
          phone.send(msg.encode("utf-8"))
          # print("1111111111") 调试1
          data = phone.recv(1024)  # 接收数据  # 发空消息,会阻塞这里
          # print("222222222") 调试2
          print(data.decode("utf-8"))
      
      # 4.关机
      phone.close()

       

5.服务端,应该具备的能力是?

  • 一直提供服务
  • 并发的能力

 

6.socket通信--升级3--解决服务端一直接受“空”问题。

  • 服务端:
    • 增加长度判断
    • 代码:
    • import socket
      
      # 1.买手机
      phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
      
      # 2.绑定电话卡
      phone.bind(("127.0.0.1",8080))
      
      # 3.开机
      phone.listen(5)
      
      # 4.接受链接信息
      conn,client_addr = phone.accept()   # 返回两个值. conn是链接通路,是三次握手的成果;client_addr是客户端的地址.
      
      # 5.收发消息
      while True:
          try:    # win 其他版本, 会报错
              data = conn.recv(1024)
              if len(data) == 0:  # linux win11 会收到空 b''
                  break
              print(data) # b'hello'
              conn.send(data.upper())
          except Exception:
              break
      
      # 6.挂电话
      conn.close()
      
      # 7.关机
      phone.close()

 

 

 

  • 客户端:
    • import socket
      
      # 1.买手机
      phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
      
      # 2. 打电话
      phone.connect(("127.0.0.1", 8080))
      
      # 3.发,收数据
      # 建立循环
      while True:
          msg = input("请输入信息:")
          if len(msg) == 0:
              continue  # 跳过本次
          phone.send(msg.encode("utf-8"))
          # print("1111111111") 调试1
          data = phone.recv(1024)  # 接收数据  # 发空消息,会阻塞这里
          # print("222222222") 调试2
          print(data.decode("utf-8"))
      
      # 4.关机
      phone.close()

       

7.socket通信--升级4--解决服务端只能提供一次服务。

  • 想让服务端一直提供服务。
  • 服务端:
    • 增加循环。
    • import socket
      
      # 1.买手机
      phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
      
      # 2.绑定电话卡
      phone.bind(("127.0.0.1", 8080))
      
      # 3.开机
      phone.listen(5)
      
      # 4.接受链接信息
      # 循环执行
      while True:
          conn, client_addr = phone.accept()
      
          # 5.收发消息
          while True:
              try:  # win 其他版本, 会报错
                  data = conn.recv(1024)
                  if len(data) == 0:  # linux win11 会收到空 b''
                      break
                  print(data)  # b'hello'
                  conn.send(data.upper())
              except Exception:
                  break
      
          # 6.挂电话
          conn.close()
      
      # 7.关机
      phone.close()

       

  • 客户端:
    • 代码:
    • import socket
      
      # 1.买手机
      phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
      
      # 2. 打电话
      phone.connect(("127.0.0.1", 8080))
      
      # 3.发,收数据
      # 建立循环
      while True:
          msg = input("请输入信息:")
          if len(msg) == 0:
              continue  # 跳过本次
          phone.send(msg.encode("utf-8"))
          # print("1111111111") 调试1
          data = phone.recv(1024)  # 接收数据  # 发空消息,会阻塞这里
          # print("222222222") 调试2
          print(data.decode("utf-8"))
      
      # 4.关机
      phone.close()

       

8.如何写一个执行远程命令的C/S程序?

  • 服务端--调用subprocess来执行
  • import socket
    import subprocess
    # 1.买手机
    phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    
    # 2.绑定电话卡
    phone.bind(("127.0.0.1", 8080))
    
    # 3.开机
    phone.listen(5)
    
    # 4.接受链接信息
    # 循环执行
    while True:
        conn, client_addr = phone.accept()
    
        # 5.收发消息
        while True:
            try:  # win 其他版本, 会报错
                cmd = conn.recv(1024)
                if len(cmd) == 0:  # linux win11 会收到空 b''
                    break
                cmd = cmd.decode("utf-8")   # 先解码
                # 再执行
                obj = subprocess.Popen(cmd,shell=True,
                                       stderr=subprocess.PIPE,
                                       stdout=subprocess.PIPE
                                       )
                res1 = obj.stdout.read()
                res2 = obj.stderr.read()
                conn.send(res1+res2)    # 把两个值都返回去
            except Exception:
                break
    
        # 6.挂电话
        conn.close()
    
    # 7.关机
    phone.close()

     

  • 客户端--注意解码方式(win用gbk,linux用utf-8)
  • import socket
    
    # 1.买手机
    phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    
    # 2. 打电话
    phone.connect(("127.0.0.1", 8080))
    
    # 3.发,收数据
    # 建立循环
    while True:
        msg = input("请输入信息:")
        if len(msg) == 0:
            continue  # 跳过本次
        phone.send(msg.encode("utf-8"))
        data = phone.recv(1024)  # 接收数据  # 发空消息,会阻塞这里
        print(data.decode("gbk"))   # 在win解码用gbk
    
    # 4.关机
    phone.close()
    
    # !!!注意,这里输入tasklist会出现不全。输入dir可以展示

     

  • 问题1,是输入tasklist会出现显示不全的现象。
  • 问题2,是输入tasklist后再输入dir会发现显示的上次剩余的内容。(这可以帮助理解【流协议】)
  • 我的解决:可以尝试recv(1024) -->recv(更大的数字)

 

 9.梳理--服务端

  • ①实例化服务
  • ②绑定ip+port
  • ③建立监听池
  • ④连接循环(接收连接)(类比,饭店迎宾)
  • ⑤通信循环(收发数据) (类比,服务员)
  • ⑥通信结束+连接结束。
  • ⑦补充通信连接的异常。
  • 示例代码(演示):
  • from socket import *
    
    server = socket(AF_INET, SOCK_STREAM)
    server.bind(('127.0.0.1', 8080))
    server.listen(5)
    
    while True: # 链接循环
        conn, clint_addr = server.accept()
        print("客户端的地址")
    
    while True: # 通信循环
        data = conn.recv(1024)  #
        conn.send(回复的数据)
    
    # 关闭通信
    conn.close()
    
    # 整体关机
    server.close()

     

  • 运行代码:
  • from socket import *
    
    server = socket(AF_INET, SOCK_STREAM)
    server.bind(('127.0.0.1', 8080))
    server.listen(5)
    
    # 链接循环(接收)
    while True:
        conn, client_addr = server.accept()
    
        # 通信循环
        while True:
            try:
                data = conn.recv(1024)  #
                conn.send(数据)  #
            except Exception:
                break
        # 结束通信
        conn.close()
    
    # 结束链接
    server.close()
    # 补充异常(为了简洁省略了win7 win10 的data为0的判断)

     

10.梳理--客户端

  • ①实例化客户端
  • ②连接远程ip+port
  • ③通信循环
  • ④补充异常输入为空的判断

 

  • 代码:
  • from socket import *
    
    # 实例化客户端
    client = socket(AF_INET, SOCK_STREAM)
    # 连接远程的ip+port
    client.connect(('127.0.0.1', 8080))
    # 通信循环
    while True:
        cmd = input('>>>').strip()
        if len(cmd) == 0:
            continue
        client.send(cmd.encode('utf-8'))
        data = client.recv(1024)
        print(data.decode('utf-8'))
    
    # 补充异常输入为空的判断: 在10,11行体现

     

11.梳理--服务器--增加执行命令功能

  • ①导入subprocess模块的Popen,PIPE
  • ②实例化Popen,shell打开,管道打开
  • ③从返回值里读取结果。
  • 代码

 

from socket import *
from subprocess import PIPE, Popen # *====增加执行命令的功能======*

server = socket(AF_INET, SOCK_STREAM)
server.bind(('127.0.0.1', 8080))
server.listen(5)

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

    # 通信循环
    while True:
        try:
            data = conn.recv(1024)  #
            # *====增加执行命令的功能======*
            cmd = data.decode("utf-8")
            obj = Popen(cmd,
                        shell=True,
                        stdout=PIPE,
                        stderr=PIPE)
            res_out = obj.stdout.read()  # 读出管道的输出
            res_err = obj.stderr.read() # 读出管道的错误输出
            # 因为这里res_out是byte类型,所以可以直接发送
            # *==========================*
            conn.send(res_out+res_err)  # 发 byte类型
        except Exception:
            break
    # 结束通信
    conn.close()

# 结束链接
server.close()
# 补充异常(为了简洁省略了win7 win10 的为0的判断)
  • 客户端补充一行解码格式为gbk
  • from socket import *
    
    # 实例化客户端
    client = socket(AF_INET, SOCK_STREAM)
    # 连接远程的ip+port
    client.connect(('127.0.0.1', 8080))
    # 通信循环
    while True:
        cmd = input('>>>').strip()
        if len(cmd) == 0:
            continue
        client.send(cmd.encode('utf-8'))
        data = client.recv(1024)
        print(data.decode('gbk')) # *===执行命令的机器是win,改编码为gbk======*
    
    # 补充异常输入为空的判断: 在10,11行体现

     

 12.解决粘包--基础

  • 服务端:
  • from socket import *
    from subprocess import PIPE, Popen  # *====增加执行命令的功能======*
    import struct  # 导入把数据包变为固定长度的模块
    
    server = socket(AF_INET, SOCK_STREAM)
    server.bind(('127.0.0.1', 8080))
    server.listen(5)
    
    # 链接循环(接收)
    while True:
        conn, client_addr = server.accept()
        print("对方的ip地址为:", client_addr)
    
        # 通信循环
        while True:
            try:
                data = conn.recv(1024)  #
                if len(data) == 0:
                    continue
                # *====增加执行命令的功能======*
                cmd = data.decode("utf-8")
                obj = Popen(cmd,
                            shell=True,
                            stdout=PIPE,
                            stderr=PIPE)
                res_out = obj.stdout.read()  # 读出管道的输出
                res_err = obj.stderr.read()  # 读出管道的错误输出
                # 因为这里res_out是byte类型,所以可以直接发送
                # *==========================*
                res_size = len(res_out) + len(res_err)
                print("长度为:", res_size)
                # ①先发数据头,包含数据长度
                header = struct.pack("i", res_size)
                conn.send(header)
                # ②再发数据
                conn.send(res_out)  # 发 byte类型
                conn.send(res_err)
    
            except Exception:
                break
        # 结束通信
        conn.close()
    
    # 结束链接
    server.close()
    # 补充异常(为了简洁省略了win7 win10 的为0的判断)

     

  • 客户端:
  • from socket import *
    import struct
    # 实例化客户端
    client = socket(AF_INET, SOCK_STREAM)
    # 连接远程的ip+port
    client.connect(('127.0.0.1', 8080))
    # 通信循环
    while True:
        cmd = input('>>>').strip()
        if len(cmd) == 0:
            continue
        client.send(cmd.encode('utf-8'))
        # *===增加数据头的接收======*
        header = client.recv(4)
        total_size = struct.unpack("i",header)[0]
        print("客户端接收到的header为:",total_size) #(437,),这里要取[0]
        print("="*50)
    
        # *===增加数据长度的判断======*
        # total_size = 437  # 假设总大小为total_size
        recv_size = 0
        res = b''
        while recv_size < total_size:
            data = client.recv(1024)
            recv_size += len(data)
            res += data # 累加到res
        res = res.decode("gbk")   # 再统一解码gbk
        print(res)
        # *=======================*
    
    
    # 补充异常输入为空的判断: 在10,11行体现

     

 13.解决粘包--升级(终极版)

  • 服务端:
from socket import *
from subprocess import PIPE, Popen  # *====增加执行命令的功能======*
import struct  # 导入把数据包变为固定长度的模块
import json

server = socket(AF_INET, SOCK_STREAM)
server.bind(('127.0.0.1', 8080))  # 根据需要修改ip
server.listen(5)

# 链接循环(接收)
while True:
    conn, client_addr = server.accept()
    print("对方的ip地址为:", client_addr)

    # 通信循环
    while True:
        try:
            data = conn.recv(1024)  #
            if len(data) == 0:
                continue
            # *====增加执行命令的功能======*
            cmd = data.decode("utf-8")
            obj = Popen(cmd,
                        shell=True,
                        stdout=PIPE,
                        stderr=PIPE)
            res_out = obj.stdout.read()  # 读出管道的输出
            res_err = obj.stderr.read()  # 读出管道的错误输出
            # print(res_out.decode("gbk"))
            # *==========================*
            # 计算总长度
            total_size = len(res_out) + len(res_err)
            # ①先构建header_dic
            header_dic = {
                "filename": "a.txt",
                "total_size": total_size,
                "md5": "123456789"
            }
            header_json = json.dumps(header_dic)  # 转换为json
            header_byte = header_json.encode("utf-8")   # 转换为byte
            header_len = len(header_byte)   # 求出长度
            header_pack = struct.pack("i", header_len)  # 压成包,发送
            # ②先发4字节
            conn.send(header_pack)
            # ③再发送header_byte
            conn.send(header_byte)
            # ④最后发送真正的数据
            conn.send(res_out)
            conn.send(res_err)

        except Exception:
            break
    # 结束通信
    conn.close()

# 结束链接
server.close()
# 补充异常(为了简洁省略了win7 win10 的为0的判断)
  • 客户端
import json
from socket import *
import struct
# 实例化客户端
client = socket(AF_INET, SOCK_STREAM)
# 连接远程的ip+port
client.connect(('127.0.0.1', 8080))# 这里根据需要修改
# 通信循环
while True:
    cmd = input('>>>').strip()
    if len(cmd) == 0:
        continue
    client.send(cmd.encode('utf-8'))
    # *===增加数据头的接收======*
    # ①接收对面的4字节
    header = client.recv(4)
    # ②解包
    header_size = struct.unpack("i",header)[0]  #
    # ③接收header_byte
    header_byte = client.recv(header_size)
    # ④解码为json
    header_json = header_byte.decode("utf-8")
    # ⑤转换为python的字典
    header_dic = json.loads(header_json)
    # ⑥拿到字典里的total_size
    total_size = header_dic["total_size"]

    # ⑦循环拿取数据
    recv_size = 0
    res = b''
    while recv_size < total_size:
        data = client.recv(1024)
        recv_size += len(data)
        res += data # 累加到res
    res = res.decode("gbk")   # 再统一解码gbk,linux就用utf-8
    print(res)
    # *=======================*

 

 

 14.后台运行?

  • python3 xxx.py &

15.查看端口状态

  • netstat - an | grep 8080

 

16.什么是粘包问题? 

  • 主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的。
  • 简单来讲就是【界限不清楚】

 

 17.为何tcp是可靠传输,udp是不可靠传输?

  • tcp会发送一个ack=1来确认消息。而udp不会。
  • 所以,基于这一点,我们认为tcp是可靠的。
  • 也就是说,这里的可靠与不可靠的界限是,是否有确认消息的过程。

 

 18.什么是并发,什么是并行?

  • 4个服务员,并行数最大是4个,并发N个

 

19.什么时候用到struct模块?

  • 数据需要压缩为定长的时候,用到struct模块
  • 代码:
    • import struct
      
      # 压缩数据
      res = struct.pack("i",1234567)
      print(res)  # b'\x87\xd6\x12\x00'
      
      # 解压数据
      data = struct.unpack("i",res)
      print(data) # (1234567,)

 

 

 20.UDP实现方式?示例代码。

  • 服务端:
  • from socket import *
    
    server = socket(AF_INET,SOCK_DGRAM)
    server.bind(('127.0.0.1',9999))
    
    # 通信
    while True:
        res = server.recvfrom(1024)
        print(res)  # 结果是  (b'123', ('127.0.0.1', 62180))

     

  • 客户端
  • from socket import *
    
    client = socket(AF_INET,SOCK_DGRAM)
    
    
    # 通信
    while True:
        msg  = input(">>>") # 输入 123 测试一下
        client.sendto(msg.encode("utf-8"),('127.0.0.1',9999))

     

 

21.UDP协议--如何实现收发消息?

  • 服务端
  • from socket import *
    
    server = socket(AF_INET,SOCK_DGRAM)
    server.bind(('127.0.0.1',9999))
    
    # 通信
    while True:
        res,addr = server.recvfrom(1024)
        print("接受到的消息为:",res)
        # 回消息
        server.sendto(res.upper(),addr)

     

  • 客户端
  • from socket import *
    
    client = socket(AF_INET, SOCK_DGRAM)
    
    # 通信
    while True:
        msg = input(">>>")  # 输入 123 测试一下
        client.sendto(msg.encode("utf-8"), ('127.0.0.1', 9999))
        res, addr = client.recvfrom(1024)
        print("客户端收到消息:", res.decode("utf-8"))

     

22.UDP需要处理空数据吗?

  • 不需要。
  • UDP传输数据,是以数据报的形式传递。
  • 数据报的意思是,每次传输数据都会带上自己的IP信息。
  • 也就是说,每次数据内容可以是空,但数据头永远有信息。
  • 所以,也就是说,不用特殊处理空数据的情况。

 

 

23. socektserver模块实现并发?

  • 服务端--改造
  • import socketserver  # 导入socketserver
    
    
    # 第一步:处理通信循环
    class MyRequestHandler(socketserver.BaseRequestHandler):
        def handle(self) -> None:
            # 下面代码借用原来的,把conn改成self.request
            # socketserver把conn封装了成了对象
            while True:
                try:
                    data = self.request.recv(1024)  #
                    if len(data) == 0:
                        break
                    print(data)
                    self.request.send(data.upper())  #
                except Exception:
                    break
            self.request.close()  #
    
    
    # 第二步:处理连接循环
    # IO密集型,推荐用线程(后续会讲为什么)
    server = socketserver.ThreadingTCPServer(('127.0.0.1', 8080), MyRequestHandler, bind_and_activate=True)
    # bind_and_activate 意思是绑定并监听
    server.serve_forever()  # 一直提供服务

     

  • 客户端--不用动
  • import socket
    
    # 1.买手机
    phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    
    # 2. 打电话
    phone.connect(("127.0.0.1", 8080))
    
    # 3.发,收数据
    # 建立循环
    while True:
        msg = input("请输入信息:")
        if len(msg) == 0:
            continue  # 跳过本次
        phone.send(msg.encode("utf-8"))
        data = phone.recv(1024)  # 接收数据  # 发空消息,会阻塞这里
        print(data.decode("utf-8"))
    
    # 4.关机
    phone.close()

     

  • 效果:
  •  

     

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

参考资料:https://www.cnblogs.com/linhaifeng/articles/6129246.html#_label9

posted @ 2023-02-24 16:50  o蹲蹲o  阅读(65)  评论(0编辑  收藏  举报