socket套接字

socket套接字简介

对于实现编写C\S架构的程序来实现数据交互的需求,我们需要编写代码对OSI七层进行操作。因为OSI七层是所有C\S架构的程序都必须要有的过程,所以就有相应的socket模块。而socket套接字只是一门技术,是底层原理,我们写代码的时候基本看不到。socket模块提供了一种简单的方式对OSI七层进行操作。

socket模块

对于C\S架构的程序,我们都应该先考虑服务端,也就是有了服务器,客户端才能进行访问。

服务端

      import socket
      server = socket.socket()  # 买手机
      """
      通过查看源码得知 
      括号内不写参数默认就是基于网络的遵循TCP协议的套接字
      """
      server.bind(('127.0.0.1', 8080))  # 插电话卡
      """
      服务端应该具备的特征
          固定的地址
          ...  
      127.0.0.1是计算机的本地回环地址 只有当前计算机本身可以访问
      """
      server.listen(5)  # 开机
      """
      半连接池(暂且忽略 先直接写 后面讲)
      """
      sock, addr = server.accept()  # 等待并接听电话  没有人来就原地等待(程序阻塞)
      """
      listen和accept对应TCP三次握手服务端的两个状态
      """
      print(addr)  # 客户端的地址
      data = sock.recv(1024)  # 听别人说话
      print(data.decode('utf8'))
      sock.send('你好啊'.encode('utf8'))  # 回复别人说的话
      """
      recv和send接收和发送的都是bytes类型的数据
      """
      sock.close()  # 挂电话
      server.close()  # 关机

客户端

      import socket


      client = socket.socket()  # 产生一个socket对象
      client.connect(('127.0.0.1', 8080))  # 根据服务端的地址链接

      client.send(b'hello sweet heart!!!')  # 给服务端发送消息
      data = client.recv(1024)  # 接收服务端回复的消息
      print(data.decode('utf8'))

      client.close()  # 关闭客户端

服务端与客户端首次交互。一边是recv那么另一边必须是send 两边不能相同 否则就'冷战'了

通信循环

通信循环要解决的问题主要就是消息固定并且只能进行一次交互的问题
解决方式:用获取用户输入的方式解决消息固定的问题,用循环解决一次交互的问题

# 服务端

      import socket

      server = socket.socket()
      server.bind(('127.0.1.1', 9090))
      server.listen(5)
      sock, addr = server.accept()
      while True:
          data = sock.recv(1024)
          print(data.decode('utf8'))
          msg = input('请输入要回复的信息>>>:').strip()
          sock.send(msg.encode('utf8'))

# 客户端

      import socket

      client = socket.socket()
      client.connect(('127.0.1.1', 9090))
      while True:
          msg = input('请输入要发送的信息:>>>').strip()
          client.send(msg.encode('utf8'))
          data = client.recv(1024)
          print(data.decode('utf8'))
      client.close()

代码优化和链接循环

      1.发送消息不能为空
      	统计长度并判断即可
      2.反复重启服务端可能会报错>>>:address in use
        这个错在苹果电脑报的频繁 windows频率较少
        from socket import SOL_SOCKET,SO_REUSEADDR
        server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) # 在bind前加
      3.链接循环
        """
        如果是windows 客户端异常退出之后服务端会直接报错
        	处理方式
        		异常处理
        如果是mac或linux 服务端会接收到一个空消息
        	处理方式
        		len判断
        """
        客户端如果异常断开 服务端代码应该重新回到accept等待新的客人

      # 目前我们的服务端只能实现一次服务一个人 不能做到同事服务多个 学了并发才可以实现

半连接池

      listen(5)
      # py文件默认同一时间只能运行一次 如果想单独分开运行多次

      # 半连接池
      	设置的最大等待人数  >>>:  节省资源 提高效率

黏包问题

      data1 = conn.recv(1024)
      print(data1)
      data2 = conn.recv(1024)
      print(data2)
      data3 = conn.recv(1024)
      print(data3)

      client.send(b'hello')
      client.send(b'jason')
      client.send(b'kevin')
      """
      三次打印的结果
        b'hellojasonkevin'
        b''
        b''
      """
      # TCP协议的特点
      	会将数据量比较小并且时间间隔比较短的数据整合到一起发送
        并且还会受制于recv括号内的数字大小(核心问题!!!)
        流式协议:跟水流一样不间断
          
      """
      问题产生的原因其实是因为recv括号内我们不知道即将要接收的数据到底多大
      如果每次接收的数据我们都能够精确的知道它的大小 那么肯定不会出现黏包
      """

      思路: 
        困扰我们的核心问题是不知道即将要接收的数据多大
        如果能够精准的知道数据量多大 那么黏包问题就自动解决了!!!

struct模块(黏包问题的解决方式)

      方向:精准获取数据的大小
        
      # struct模块
      	import struct

        data1 = 'hello world!'
        print(len(data1))  # 12
        res1 = struct.pack('i', len(data1))  # 第一个参数是格式 写i就可以了
        print(len(res1))  # 4
        ret1 = struct.unpack('i', res1)
        print(ret1)  # (12,)


        data2 = 'hello baby baby baby baby baby baby baby baby'
        print(len(data2))  # 45
        res2 = struct.pack('i', len(data2))
        print(len(res2))  # 4
        ret2 = struct.unpack('i', res2)
        print(ret2)  # (45,)


        """
        pack可以将任意长度的数字打包成固定长度
        unpack可以将固定长度的数字解包成打包之前数据真实的长度


        思路:
            1.先将真实数据打包成固定长度的包
            2.将固定长度的包先发给对方
            3.对方接收到包之后再解包获取真实数据长度
            4.接收真实数据长度
        """

黏包问题的代码实现

# 服务端
      import json
      import socket
      import struct
      import os

      server = socket.socket()
      server.bind(('127.0.0.1', 9090))
      server.listen(5)
      while True:
          sock, addr = server.accept()
          while True:
              data_dict = {
                  'file_name': '演员.flac',
                  'file_des': 'XXX',
                  'file_size': os.path.getsize(r'C:\Users\Administrator\Music\薛之谦 - 演员.flac')
              }
              dict_json = json.dumps(data_dict)
              dict_bytes = dict_json.encode('utf8')
              dict_pack = struct.pack('i', len(dict_bytes))  # 字典打包
              sock.send(dict_pack)  # 报头
              sock.send(dict_bytes)  # 字典
              with open(r'C:\Users\Administrator\Music\薛之谦 - 演员.flac', 'rb') as f:
                  for line in f:
                      sock.send(line)  # 真实数据

# 客户端
      import json
      import socket
      import struct

      client = socket.socket()
      client.connect(('127.0.0.1', 9090))

      dict_header_len = client.recv(4)  # 接收报头
      dict_real_len = struct.unpack('i', dict_header_len)[0]  # 解析字典真实长度
      dict_bytes = client.recv(dict_real_len)  # 接收字典数据
      dict_data = json.loads(dict_bytes)
      print(dict_data)
      recv_size = 0
      with open(dict_data.get('file_name'), 'wb') as f:  # 循环接收文件数据
          while recv_size < dict_data.get('file_size'):
              data = client.recv(1024)
              recv_size += len(data)
              f.write(data)

      client.close()

posted @ 2022-04-17 23:58  Oliver-Chance  阅读(44)  评论(0编辑  收藏  举报