socket套接字

2022/4/15 23:16:15

本文主要是介绍socket套接字,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

socket套接字

简介

    Socket并不属于TCP/IP协议簇,它只是一个编程接口,即对TCP/IP的封装和应用,简单理解TCP/IP看看作一
个函数,而Socket用来进行调用,Socket可在网络中对两个程序建立通信通道,Socket可分为两个基本模块,一个
服务端一个客户端,链接后进行通信。

网络编程

常见的套接字对象方法和属性

创建TCP服务端

  import socket
  server = socket.socket()  # 创建一个socket对象(server)
  server.bind(('192.168.1.8', 8080))  # 给socket对象(server)绑定ip和端口号(以元组的形式)
  server.listen(5)  # 设置半连接池,最低为0
  sock, addr = server.accept()  # 接收客户端连接,获得sock连接 和 addr客户端地址。
  data = sock.recv(1024)  # 获取客户端发送的消息
  print(data.decode('utf8')) # 打印接收到的数据,由于传输的时候是二进制所以需要进行解码
  sock.send('你好'.encode('utf8'))  # 服务端发送消息给客户端
  sock.close()  # 断开服务端到客户端的连接
  server.close()  # 关闭服务端

创建一个TCP客户端

  import socket
  client = socket.socket()  # 创建一个socket对象(client)
  client.connect(('192.168.1.8', 8080))  # 根据ip和端口连接服务端(元组的形式),主动发起连接
  msg = input('要发送的消息: ').strip()  # 获取要发送给服务端的消息
  client.send(msg.encode('utf8'))  # 将消息先编码后在发送给服务端
  data = client.recv(1024)  # 获取服务端发送的消息
  print(data.decode('utf8'))  # 打印接收到的数据,由于传输的时候是二进制所以需要进行解码
  client.close()  # 断开连接,关闭客户端

在重启服务器的时候可能会遇到的BUG(mac居多),windows频率较少

解决办法
    import socket
    from socket import SOL_SOCKET,SO_REUSEADDR(导入对应的包)
    server = socket.socket()  # 创建一个socket对象(server)

    # -------------------------------------
    # 加上它就可以防止重启报错了(注意位置)
    # -------------------------------------
    server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)

    server.bind(('192.168.1.8', 8080))  # 给socket对象(server)绑定ip和端口号(以元组的形式)
    server.listen(5)  # 设置半连接池,最低为0
    sock, addr = server.accept()  # 接收客户端连接,获得sock连接 和 addr客户端地址。
    data = sock.recv(1024)  # 获取客户端发送的消息
    print(data.decode('utf8')) # 打印接收到的数据,由于传输的时候是二进制所以需要进行解码
    sock.send('你好'.encode('utf8'))  # 服务端发送消息给客户端
    sock.close()  # 断开服务端到客户端的连接
    server.close()  # 关闭服务端

通信循环

linux、mac断开链接时不会报错,会一直返回空(b‘’)

服务端

    import socket
    from socket import SOL_SOCKET,SO_REUSEADDR
    server = socket.socket()  # 创建一个socket对象(server)

    # -------------------------------------
    # 加上他就可以防止重启报错了(注意位置)
    # -------------------------------------
    server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)

    server.bind(('192.168.1.8', 8080))  # 给socket对象(server)绑定ip和端口号(以元组的形式)
    server.listen(5)  # 设置半连接池,最低为0
    sock, addr = server.accept()  # 接收客户端连接,获得sock连接 和 addr客户端地址。
    while True:
        data = sock.recv(1024)  # 获取客户端发送的消息
        print(data.decode('utf8')) # 打印接收到的数据,由于传输的时候是二进制所以需要进行解码
        msg = input('要发送的消息: ').strip()
        sock.send(msg.encode('utf8'))  # 服务端发送消息给客户端

    # sock.close()  # 断开服务端到客户端的连接
    # server.close()  # 关闭服务端

客户端

    import socket

    client = socket.socket()  # 创建一个socket对象(client)
    client.connect(('192.168.1.8', 8080))  # 根据ip和端口连接服务端(元组的形式),主动发起连接
    while True:
        msg = input('要发送的消息: ').strip()  # 获取要发送给服务端的消息
        if len(msg) == 0: continue  # 避免消息为空时,造成双方等待
        client.send(msg.encode('utf8'))  # 将消息先编码后在发送给服务端
        data = client.recv(1024)  # 获取服务端发送的消息
        print(data.decode('utf8'))  # 打印接收到的数据,由于传输的时候是二进制所以需要进行解码
    client.close()  # 断开连接,关闭客户端

链接循环

 """
  如果是windows 客户端异常退出之后服务端会直接报错
  	处理方式
  		异常处理
  如果是mac或linux 服务端会接收到一个空消息
  	处理方式
  		len判断
  """
  客户端如果异常断开 服务端代码应该重新回到accept等待新的客人

  import socket
  from socket import SOL_SOCKET, SO_REUSEADDR

  server = socket.socket()  # 创建一个socket对象(server)

  # -------------------------------------
  # 加上他就可以防止重启报错了(注意位置)
  # -------------------------------------
  server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)

  server.bind(('192.168.1.8', 8080))  # 给socket对象(server)绑定ip和端口号(以元组的形式)
  server.listen(5)  # 设置半连接池,最低为0
  while True:
      sock, addr = server.accept()  # 接收客户端连接,获得sock连接 和 addr客户端地址。

      while True:
          try:
              data = sock.recv(1024)  # 获取客户端发送的消息
              print(data.decode('utf8'))  # 打印接收到的数据,由于传输的时候是二进制所以需要进行解码
              msg = input('要发送的消息: ').strip()
              sock.send(msg.encode('utf8'))  # 服务端发送消息给客户端
          except BaseException:
              break # 客户端如果异常断开 服务端代码应该重新回到accept等待新的客人

  # sock.close()  # 断开服务端到客户端的连接
  # server.close()  # 关闭服务端

半连接池,允许等待的最大个数

    1.什么是半连接池:当服务器在响应了客户端的第一次请求后会进入等待状态,会等客户端发送的ack信息,这时候这个连接就称之为半连接
    2.半连接池其实就是一个容器,系统会自动将半连接放入这个容器中,可以避免半连接过多而保证资源耗光
    3.产生半连接的两种情况:
        客户端无法返回ACK信息
        服务器来不及处理客户端的连接请求

    设置的最大等待人数  >>>:  节省资源 提高效率
    server.listen(5)指定5个等待席位

黏包问题

多次发送被并为一次

# TCP协议的特点
  会将数据量比较小并且时间间隔比较短的数据整合到一起发送
  并且还会受制于recv括号内的数字大小(核心问题!!!)
    流式协议:跟水流一样不间断

  黏包现象只发生在tcp协议中
  1.从表面上看,黏包问题主要是因为发送方和接收方的缓存机制、tcp协议面向流通信的特点

  2.实际上,主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的

  粘包是接收长度没对上导致的
  控制recv接收的字节数与之对应(你发多少字节我收多少字节)

  在很多情况下并不知道数据的长度,服务端不能写死
  """
  问题产生的原因其实是因为recv括号内我们不知道即将要接收的数据到底多大
  如果每次接收的数据我们都能够精确的知道它的大小 那么肯定不会出现黏包
  """
  思路一如果在不知道数据有多长的情况下就会出现意外,那么我们可以先传一个固定长度的数据过去告诉他
真实数据有多长,就可以对应着收了

struct模块

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

    这里利用struct模块模块的struct.pack() struct.unpack() 方法来实现打包(将真实数据长度变
为固定长度的数字)解包(将该数字解压出打包前真实数据的长度)

    pack unpack模式参数对照表(standard size 转换后的长度)
  i 模式的范围:-2147483648 <= number <= 2147483647
  在传真实数据之前还想要传一些描述性信息
  如果在传输数据之前还想要传一些描述性信息,那么就得在中间再加一步了(传个电影,我告诉你电影名,
大小,大致情节,演员等信息,你再选择要不要),前面的方法就不适用了

粘包问题解决思路

  服务器端
    先制作一个发送给客户端的字典
    制作字典的报头
    发送字典的报头
    发送字典
    再发真实数据
  客户端
    先接收字典的报头
    解析拿到字典的数据长度
    接收字典
    从字典中获取真实数据的长度
    循环获取真实数据

ps:为什么要多加一个字典
  pack打包的数据长度(的长度)有限,字典再打包会很小(长度值也会变很小)(120左右)
  可以携带更多的描述信息

模板

服务端

  import socket
  from socket import SOL_SOCKET, SO_REUSEADDR
  import os
  import json
  import struct

  server = socket.socket()  # 创建一个socket对象(server)

  # -------------------------------------
  # 加上他就可以防止重启报错了(注意位置)
  # -------------------------------------
  server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)

  server.bind(('127.0.0.1', 9000))  # 给socket对象(server)绑定ip和端口号(以元组的形式)
  server.listen(5)  # 设置半连接池,最低为0
  sock, addr = server.accept()  # 接收客户端连接,获得sock连接 和 addr客户端地址。

  # 1.先制作一个字典(可以放入一些描述性的信息)
  data_dict = {
      'file_name': '11.jpg',
      'file_title': '壁纸',
      'file_size': os.path.getsize(r'D:\pythonproject\test\11.jpg') # 可以改为自己的文件地址
  }
  # 2.制作字典报头
  data_dict_json_str = json.dumps(data_dict)  # 转化成字符串
  data_dict_json_str_bytes = data_dict_json_str.encode('utf8')  # 将字符串编码
  data_dict_package = struct.pack('i', len(data_dict_json_str_bytes))  # 以i模式打包,打包成4个字节
  # 3.发送报头
  sock.send(data_dict_package)  # 服务端将字典发送给客户端
  # 4.发送字典
  sock.send(data_dict_json_str_bytes)
  # 5.发送真实数据
  with open(r'D:\pythonproject\test\11.jpg', 'rb') as f:
      for i in f:
          sock.send(i)
  print('发送完成')

客户端

  import socket
  import struct
  import json

  client = socket.socket()  # 创建一个socket对象(client)
  client.connect(('127.0.0.1', 9000))  # 根据ip和端口连接服务端(元组的形式),主动发起连接

  # 1.先接收字典的报头
  data_dict_package = client.recv(4)
  # 2.解析拿到字典的数据长度
  data_dict_len = struct.unpack('i', data_dict_package)[0]
  # 3.接收字典数据
  data_dict = client.recv(data_dict_len)
  data_dict = data_dict.decode('utf8')
  data = json.loads(data_dict)
  # 4.循环接收文件数据 不要一次性接收
  recv_size = 0
  with open('12.jpg', 'wb') as f:
      while recv_size < data.get('file_size'):
          write_data = client.recv(1024)
          recv_size += len(write_data)
          f.write(write_data)

今日作业

项目下载
可以实现上传文件(上传到项目的shipin文件中,可以更改)和下载文件(默认下载在桌面,下载时会从项目的shipin文件中进行下载)

编写一个cs架构的软件
	就两个功能
  	一个视频下载:从服务端下载视频
        一个视频上传:从客户端上传视频

服务端

import socket
import json
import os
import sys
import struct
from socket import SOL_SOCKET, SO_REUSEADDR

sys.path.append(os.path.dirname(__file__))
shipin_path = os.path.join(os.path.dirname(__file__), 'shipin')
if not os.path.exists(shipin_path):
    os.makedirs(shipin_path)

server = socket.socket()

server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)

server.bind(('127.0.0.1', 8001))

server.listen(5)
while True:
    sock, addr = server.accept()
    while True:
        try:
            print('连接成功')
            msg = sock.recv(1024)
            msg = msg.decode('utf8')
            msg1, name = msg.split(' ')
            if msg1 == '下载':
                if name not in os.listdir(shipin_path): sock.send('文件不存在'.encode('utf8'))
                data_dict = {
                    'file_name': name,
                    'file_size': os.path.getsize(os.path.join(shipin_path, name))
                }
                data_dict_json = json.dumps(data_dict)
                data_dict_json_bytes = data_dict_json.encode('utf8')
                data_dict_json_bytes_package = struct.pack('i', len(data_dict_json_bytes))
                sock.send(data_dict_json_bytes_package)
                sock.send(data_dict_json_bytes)
                with open(os.path.join(shipin_path, name), 'rb') as f:
                    for i in f:
                        sock.send(i)
                print('发送完成')
            else:
                print('上传')
                data_dict_package = sock.recv(4)
                data_dict_len = struct.unpack('i', data_dict_package)[0]
                data_dict = sock.recv(data_dict_len)
                data = json.loads(data_dict)
                recv_size = 0
                with open(os.path.join(shipin_path, data.get('file_name')), 'wb') as f:
                    while recv_size < data.get('file_size'):
                        write_data = sock.recv(1024)
                        recv_size += len(write_data)
                        f.write(write_data)
                    print('上传完成')
        except ConnectionRefusedError:
            print('操作失败')

客户端

import socket
import json
import os
import struct

client = socket.socket()

client.connect(('127.0.0.1', 8001))
while True:
    msg = input('''格式:上传/下载 文件名(下载 文件名 指定地址(默认桌面))
                (上传 地址(绝对地址)))''').strip()
    if msg.split(' ')[0] == '下载':
        client.send(msg.encode('utf8'))
        data_dict_package = client.recv(4)
        data_dict_len = struct.unpack('i', data_dict_package)[0]
        data_dict = client.recv(data_dict_len)
        data = json.loads(data_dict)
        recv_size = 0
        if msg.split(' ') == 3:
            addr = msg.split(' ')[-1]
        else:
            addr = os.path.join(os.path.expanduser('~'),"Desktop") # 动态获取当前电脑桌面路径
        print(addr)
        with open(os.path.join(addr,data.get('file_name')), 'wb') as f:
            while recv_size < data.get('file_size'):
                write_data = client.recv(1024)
                recv_size += len(write_data)
                f.write(write_data)
            print('下载完成')
    elif msg.split(' ')[0] == '上传':
        client.send(msg.encode('utf8'))
        path = msg.split(' ')[1]
        name = path.split(os.sep)[-1]
        print(path,name,sep='            ')
        data_dict = {
            'file_name': name,
            'file_size': os.path.getsize(path)
        }
        data_dict_json = json.dumps(data_dict)
        data_dict_json_bytes = data_dict_json.encode('utf8')
        data_dict_json_bytes_package = struct.pack('i', len(data_dict_json_bytes))
        client.send(data_dict_json_bytes_package)
        client.send(data_dict_json_bytes)
        with open(path, 'rb') as f:
            for i in f:
                client.send(i)
        print('上传成功')




这篇关于socket套接字的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程