星光不问赶路人,时光不负|

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()  # 关闭服务端

通信循环

服务端

    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判断
  """
  linux、mac断开链接时不会报错,会一直返回空(b‘’)
  客户端如果异常断开 服务端代码应该重新回到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('上传成功')


本文作者:春游去动物园

本文链接:https://www.cnblogs.com/chunyouqudongwuyuan/p/16150690.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   春游去动物园  阅读(41)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
展开
  1. 1 生分 川青
生分 - 川青
00:00 / 00:00
An audio error has occurred.

生分 - 川青

词:莎子

曲:邵翼天

编曲:林亦

混音:罗杨轩

吉他:林亦

制作人:谢宇伦

监制:曾炜超/陈显

策划:+7

统筹:黄染染

出品:漫吞吞文化

『酷狗音乐人 • 星曜计划』

全方位推广,见证星力量!

「版权所有未经许可 不得商业翻唱或使用」

我们怎么变得那么生分

用了几年也没解开疑问

有些事你不提我也不问

在陌生与熟悉间找平衡

有些话一开口会伤人

有些话一开口会伤人

所以我选择默不作声

所以我选择默不作声

爱一个人

若甘愿陪衬

甘愿牺牲

也许换个名分

也不是没可能

我不怕在爱里做个蠢人

我不怕在爱里做个蠢人

也不怕爱过之后再分

也不怕爱过之后再分

爱一个人

有万种身份

万种可能

只是没想到

我们最后友人相称

我们怎么变得那么生分

我们怎么变得那么生分

连说话都要掌握好分寸

怕不注意流言

见缝插针

怕不小心我们

成陌生人

我们怎么变得那么生分

用了几年也没解开疑问

有些事你不提我也不问

在陌生与熟悉间找平衡

有些话一开口会伤人

有些话一开口会伤人

所以我选择默不作声

所以我选择默不作声

爱一个人

若甘愿陪衬

甘愿牺牲

也许换个名分

也不是没可能

我不怕在爱里做个蠢人

我不怕在爱里做个蠢人

也不怕爱过之后再分

也不怕爱过之后再分

爱一个人

有万种身份

万种可能

只是没想到我们最后

友人相称

我们怎么变得那么生分

连说话都要掌握好分寸

怕不注意流言见缝插针

怕不小心我们成陌生人

我们怎么变得那么生分

用了几年也没解开疑问

有些事你不提我也不问

在陌生与熟悉间找平衡

我们怎么变得那么生分

我们怎么变得那么生分

连说话都要掌握好分寸

怕不注意流言见缝插针

怕不小心我们成陌生人

我们怎么变得那么生分

用了几年也没解开疑问

有些事你不提我也不问

在陌生与熟悉间找平衡