【Python-Socket】socket通信记录

socket通信例程

  1. 先来一个简单的demo

服务端

import socket
# 建立一个服务端
server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server.bind(('0.0.0.0', 18888)) #绑定要监听的端口
server.listen(5) #开始监听 表示可以使用五个链接排队
while True:# conn就是客户端链接过来而在服务端为期生成的一个链接实例
    conn,addr = server.accept() #等待链接,多个链接的时候就会出现问题,其实返回了两个值
    print(conn,addr)
    while True:
        try:
            data = conn.recv(1024)  #接收数据
            print('recive:',data.decode()) #打印接收到的数据
            conn.send(data.upper()) #然后再发送数据
        except ConnectionResetError as e:
            print('关闭了正在占线的链接!')
            break
    conn.close()

客户端

import socket# 客户端 发送一个数据,再接收一个数据
client = socket.socket(socket.AF_INET,socket.SOCK_STREAM) #声明socket类型,同时生成链接对象
client.connect(('10.49.33.80',8888)) #建立一个链接,连接到本地的6969端口
while True:
    # addr = client.accept()
    # print '连接地址:', addr
    msg = '欢迎访问菜鸟教程!'  #strip默认取出字符串的头尾空格
    client.send(msg.encode('utf-8'))  #发送一条信息 python3 只接收btye流
    data = client.recv(1024) #接收一个信息,并指定接收的大小 为1024字节
    print('recv:',data.decode()) #输出我接收的信息
client.close() #关闭这个链接

socket 中最主要的是accept-connect 和send-recv 这两组函数的连接,其中accept-connect 负责两台主机之间的连接,send-recv负责连接完成后主机间数据的收发。

【1】章节概览

  1. 网络数据、网络错误
    编码-解码
    封帧-引用
    socket.shutdown()的用法
  2. 服务器架构
    单线程服务器
    多线程和多进程服务器
    异步服务器
  3. 缓存与消息队列
    socket缓冲区解释
    消息队列
    散列和分区
    Memcached

【2】通信详解

  1. 串口通信

  2. 多线程通信

  3. 多进程通信

  4. Queue

  5. pipe

  6. Manager

    Python中的线程和进程

  7. 缓冲区

  • socket的缓冲区机制

    每个 socket 被创建后,都会分配两个缓冲区,输入缓冲区和输出缓冲区
    write()/send() 并不立即向网络中传输数据,而是先将数据写入缓冲区中,再由 TCP 协议将数据从缓冲区发送到目标机器。一旦将数据写入到缓冲区,函数就可以成功返回,不管它们有没有到达目标机器,也不管它们何时被发送到网络,这些都是 TCP 协议负责的事情。
    TCP 协议独立于 write()/send() 函数,数据有可能刚被写入缓冲区就发送到网络,也可能在缓冲区中不断积压,多次写入的数据被一次性发送到网络,这取决于当时的网络情况、当前线程是否空闲等诸多因素,不由程序员控制。

    read()/recv() 函数也是如此,也从输入缓冲区中读取数据,而不是直接从网络中读取。

    SEND 写入
    TCP 发送
    TCP 接收
    RECV 读取
    SEND 写入
    TCP 发送
    TCP 接收
    RECV 读取
    socket主机
    缓冲区
    网络通信
    缓冲区
    socket从机

    归根结底write/send/read/recv这些函数只对缓冲区进行控制,独立于 TCP 协议之外。
    这些 I/O 缓冲区特性可整理如下:
    1. I/O 缓冲区在每个 TCP 套接字中单独存在;(1个TCP对应1个I/O缓冲区)
    2. I/O 缓冲区在创建套接字时自动生成;
    3. 即使关闭套接字也会继续传送输出缓冲区中遗留的数据
    4. 关闭套接字将丢失输入缓冲区中的数据

  • 对文件的控制

    当然,受限于网络环境还有设备的问题,缓冲区有几率存在积压数据没有被发送出去的情况。
    此时我们需要考虑python 中关于文件操作的函数:
    python.file对象

    • file.open(file, mode='r', buffering=-1, encoding=None)
      关于file.open()函数的更多参数,请访问Python3 File(文件) 方法
    • file.close()
    • file.flush()刷新文件内部缓冲,直接将内部缓冲区的数据立刻写入文件,不需等待输出缓冲区写入
    • file.write()
    • file.read()
    • file.readline()读取整行
    • file.writelines()写入字符串列表,换行须自己加入\n
    • file.fileno()返回文件描述符
    • file.isatty()是否连接到终端设备
    • file.seek(offset[, whence])设置文件读取指针,并会影响read()函数的读取顺序
      whence:可选,默认值为 0。给 offset 定义一个参数,表示要从哪个位置开始偏移;0 代表从文件开头开始算起,1 代表从当前位置开始算起,2 代表从文件末尾算起。
    f = open('workfile', 'rb+')
    f.write(b'0123456789abcdef')
    16
    f.seek(5)      # 移动到文件的第六个字节
    5
    f.read(1)
    b'5'
    f.seek(-3, 2)  # 移动到文件倒数第三个字节
    13
    f.read(1)
    b'd'
    
    • file.tell()返回文件读取指针的位置
    • file.truncate()返回截断后的文件

    程序例程

    	# 打开文件
    fo = open("runoob.txt", "r")
    print ("文件名为: ", fo.name)
     
    for line in fo.readlines():                          #依次读取每行  
        line = line.strip()                             #去掉每行头尾空白  
        print ("读取的数据为: %s" % (line))
     
    # 关闭文件
    fo.close()
    

【3】函数笔记

  1. makefile
    特点:和普通的socket 通信对象相比,makefile 对象更侧重于I/O类型的对象,等到以后有空的时候再进行更新。
    参考资料:

    Python 网络编程 makefile (三)
    socket常用方法和makefile使用(面试会考)

  2. socket.settimeout()

【4】socket 传输图片的问题

以下是作者参考其它博客的传输图片例程
服务端

fileinfo_size = struct.calcsize('32si')
buf = server.recv(fileinfo_size)
if buf:
    filename, filesize = struct.unpack('32si', buf)
    fn = filename.strip(b'\00')
    fn = fn.decode()
    print('file  name is {0}, filesize if {1}'.format(str(fn), filesize))
recvd_size = 0  # 定义已接收文件的大小
# 存储在该脚本所在目录下面
fp = open('./' + str(filename), 'wb')
print('pic start receiving...')

# 将分批次传输的二进制流依次写入到文件
while not recvd_size == filesize:
    if filesize - recvd_size > 1024:
        data = conn.recv(1024)
        recvd_size += len(data)
    else:
        data = conn.recv(filesize - recvd_size)
        recvd_size = filesize
    fp.write(data)
fp.close()

程序分别两个部分,第一部分解开struct包头文件,获取filename和filesize,第二部分是一个循环,终止条件是已接收图片大小等于客户端图片大小(详见客户端代码,引入了python struct 的概念并制作包头文件),这里定义一次接收的大小为1024字节(这个也是socket 通信的限制),因此if 剩余大小大于1024字节,执行接收1024字节;else 剩余大小小于1024字节,则接收剩下长度,并将已接收大小设置为客户端图片大小这里着重强调,至于原因底下说)。
客户端

filepath = 'camera.jpg'
# 判断是否为文件
if os.path.isfile(filepath):
    # 定义定义文件信息。32s表示文件名为32bytes长,i表示一个int或log文件类型(Windows中i为4个字节,Linux中i为8个字节),在此为文件大小
    fileinfo_size = struct.calcsize('32si')
    # 定义文件头信息,包含文件名和文件大小
    fhead = struct.pack('32si', os.path.basename(filepath).encode('utf-8'), os.stat(filepath).st_size)
    # 发送文件名称与文件大小
    socket.send(fhead)

    # 将传输文件以二进制的形式分多次上传至服务器
    fp = open(filepath, 'rb')
    while 1:
        data = fp.read(1024)
        if not data:
            print('{0} file send over...'.format(os.path.basename(filepath)))
            break
        socket.send(data)
    # 关闭当期的套接字对象
    socket.close()

还是两部分,第一部分建立包头文件,请自行参考struct的用法,第二部分建立fp 文件对象读取图片,然后使用while 循环重复读取并发送图片流直至全部发送完成。

结果

Traceback (most recent call last):
  File "d:\vsproject\pyhelloworld\success\1216\Serverimprove.py", line 127, in <module>
    main()
  File "d:\vsproject\pyhelloworld\success\1216\Serverimprove.py", line 124, in main
    t0.run()
  File "D:\Users\18120\Anaconda3\lib\threading.py", line 870, in run
    self._target(*self._args, **self._kwargs)
  File "d:\vsproject\pyhelloworld\success\1216\Serverimprove.py", line 61, in servergps
    dataname, filesize = struct.unpack('32si', data)
struct.error: unpack requires a buffer of 36 bytes

服务端接收包头文件时发生错误,这里经过读者的定位发现,上一张图片服务端没有接收完全,导致部分数据挺在缓冲区,被下一张图片接收包头文件的recv()接收,导致这里的data 是图片数据而非包头文件数据。

数据示例

# 错误数据1
b"\x85\x97v\x00\xa0\xcb\x8fJV\xb7c\xfc'\xdb\x8aT\xb4=X~t\x92H\x0f\xff\xd9camera93.jpg\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xeb\n\x01\x00"

# 错误数据2
b'\xa8q\xb6\xa2j\xc4{x\xddQ\x1f\x97\xadM\xbf\x1c\x01Ls\x91\xc9\xe4P\xec\x1a\x08\xae\x0f&\x95]\t\xc3q\xefP3\xf9m\xb4\x0e;\xd3\xc3\x02\x03z\xd3\xb5\xb5\r\xb5&\x1c\x9f\x96\xa4\x8eLeNq\xdf\x15]X\x92\x05<>\xc3S\xa3\x1139#\x1b\xb23\xc54\xe7\xad&\xf0\xc74\xe0Gq@\x0c\x01\xbb\xd2\xee\xe8\xb4\xaeT\x1f\x94\xf1H\x0eW#\xd3\x9akT1\xdea-\x9ct\x18\xe9Ry\xe3\x1c\n\x85I$\xe6\x85}\xa7\x00\xf7\xe2\x85kY\x81d\x1e\xf4\xc3>0q\xd7\xde\x98er88\xe2\xa2rs\x8a\x94\xbb\x88\xff\xd9'

# 正确数据 正好符合32si结构可以解包
b'camera92.jpg\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x007\x1c\x01\x00'

找到原因后我们得知道怎么修改,回到上面所说的,else 剩余大小小于1024字节,则接收剩下长度,并将已接收大小设置为客户端图片大小这里着重强调

while not recvd_size == filesize:
    if filesize - recvd_size > 1024:
        data = conn.recv(1024)
        recvd_size += len(data)
    else:
        data = conn.recv(filesize - recvd_size)
        recvd_size = filesize
    fp.write(data)
fp.close()

假设一张图片大小总共7521字节,当前接收6144字节
倒数第二次按理接收1024字节(达到7168字节) ,然而缓冲区只有436个字节,只能接收436个字节,此时服务端接收6580字节
最后一次只有941字节长度,进行最终传输,然而只接收588字节(达到7168字节),并终止传输,并将大小设置为客户端图片大小
这里服务端明显的睁眼说瞎话,明明没有接收完图片可是还设置了实际图片的大小,因此我们只要修改这一个错误的行为即可,以下是修改后的成功代码

while not recvd_size == filesize:
	if filesize - recvd_size > 1024:
	   data = server.recv(1024)
	   recvd_size += len(data)
	else:
	   data = server.recv(filesize - recvd_size)
	   recvd_size += len(data)
	fp.write(data)
fp.close()

另外可以参考:

socket传输图片底部失真

问题解决~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值