【问题标题】:Error while reading image received from socket读取从套接字接收的图像时出错
【发布时间】:2021-12-19 18:46:00
【问题描述】:

我有一个 android 应用程序,它使用 DataOutputStream 通过套接字将图像从画廊发送到 Python 服务器,以写入客户端应用程序中的套接字。图像从外部存储目录加载并在发送前缓冲。图像由服务器接收并写入磁盘内存。当我尝试打开图像时,它给了我:“读取图像文件的致命错误。不是 PNG”。然而,图像占用的实际图像大小为 430 KiB。当我打印接收到的数据时,它会给我一些看起来像原始图像的东西:

b'\x97\xa7p\xc0\x04\xfbv\xf6\\\xed\x8a\xe9^\xbf\xa4p9\xae\x8eu:N\xb5\x8e\xcc\x06\xa6\xf1\tyL\xf3.^W\xb5RR\xd3)\x7fS\xf3\x8f\x1b\xc6\xf8\xa7\x9b\xf5\xb8\xc3f\xa9\xdf\xa1\xbd\xaa\xbeS\xbc\x84zt\xedT\xbfn|I\xfb\x0e\xfb\xae6\x18sS\x9b\x9e\xd8\xff\xc4>\xaf\xeb\xba\xbe>{\xe2\x87~\xe8\x87~\xe8\x87~\xe8\x87~\xe8\x87~\xe8\x87~\xe8\x87~\xe8\x87~\xe8\x87~\xe8\x87~\xe8\x87\xfe\xbf\xa4\xff\x07\xe5\x9f\xdc\xd5\xe2d\xc5\xcb\x00\x00\x00\x00IEND\xaeB`\x82'
b''

文字比较长,但是我删了..

从目录加载图像并写入套接字的客户端代码:

class send extends AsyncTask<Void, Void, Void> {
    Socket s; //Socket Variable

    @Override
    protected Void doInBackground(Void... params) {
        try {
            s = new Socket("192.168.0.14", 9999);
            String image = getLatestFilefromDir("/storage/emulated/0/DCIM");
            File file = new File(image);
            try (InputStream is = new BufferedInputStream(new FileInputStream(file));
                 DataOutputStream dos = new DataOutputStream(s.getOutputStream())) {
                 dos.writeLong(file.length());
                int val;
                while ((val = is.read()) != -1) {
                    dos.write(val);
                }
                dos.flush();

            } catch (IOException e) {
                e.printStackTrace();
            }
        } catch (UnknownHostException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;

    }
}


private String getLatestFilefromDir(String dirPath){
    File dir = new File(dirPath);
    File[] files = dir.listFiles();
    if (files == null || files.length == 0) {
        return null;
    }

File lastModifiedFile = files[0];
for (int i = 1; i < files.length; i++) {
    if (lastModifiedFile.lastModified() < files[i].lastModified()) {
        lastModifiedFile = files[i];
    }
}
return lastModifiedFile.toString();

}

Python 服务器:

#Imports modules

    import socket
    import datetime
    
    date_string = datetime.datetime.now().strftime("%Y-%m-%d-%H:%M")
    listensocket = socket.socket()
    listenPort = 9999
    numberOfConnections=1
    
    
    
    thisIp = socket.gethostname()
    listensocket.bind(('', listenPort))
    
    listensocket.listen(numberOfConnections)
    print("Started Listening")
    
    (clientsocket, address) = listensocket.accept()
    print("Connected")
    
    fname = "/home/pi/Desktop/Images/"+date_string+".PNG"
    
    f = open(fname, 'wb')
    datain = 1
    
    
    while datain:
        datain = clientsocket.recv(100000000)
        print(datain)
        bytearray(f.write(datain)) 
    
    f.close()
    listensocket.close()

【问题讨论】:

  • 您的 python 代码包含一些无意义的语句,例如 datain = clientsocket.recv(100000000)bytearray(f.write(datain))。但最重要的问题是你的 python 协议没有计算你的 Java 协议。您的 Java 代码首先将要跟随的数据长度写为 64 位大端整数。您的 python 不会尝试读取这个 64 位整数,而是将这 8 个字节视为图像文件的一部分。
  • 那将来自 dos.writeLong。但是如何在 python 中将二进制文件写为 Long 类型?
  • @PresidentJamesK.Polk 提出了很好的观点。在您的 Python 服务器中,如果您只是跳过前 8 个字节,您最终可能会得到一个可行的图像文件。更好的是,读取这 8 个字节并将它们解释为整数值(注意字节顺序)。然后,您可以通过添加 socket.MSG_WAITALL 以显式长度调用 recv() 在这种情况下,您不需要循环
  • @JohnJones 看看 struct 模块中可用的打包和解包功能。您还可以查看我给出的答案 here
  • 到目前为止的 cmets 确实让我走上了正轨。我确定它与发送到服务器的 Long 类型图像有关。但我不知道如何阅读它。此外,如果我可以添加灵活的策略来读取不同的图像尺寸会有所帮助,而不仅仅是对我。我是一个尝试学习的初学者,有没有关于如何达到预期结果的例子?这对我来说并不容易

标签: python java android-studio sockets tcp


【解决方案1】:

这个答案可能并不可靠,但确实展示了一种将文件从 Java 应用程序有效传输到 Python 服务器的机制。出于演示目的,某些路径是硬编码的。另请注意,如果要传输的文件非常大,则这可能不合适,因为在写入目标文件之前,整个文件内容将保存在服务器端的内存中。显然,这可以通过更复杂的代码来解释。

这是 Java 客户端:

package com.aprk;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.Socket;
import java.util.logging.Level;
import java.util.logging.Logger;

public class GetImage {
    public static void main(String[] args) {
        try {
            Socket s = new Socket("localhost", 7070);
            File image = new File("/Volumes/G-DRIVE Thunderbolt 3/Pictures/rgb_colour_wheel.png");
            BufferedInputStream bis = new BufferedInputStream(new FileInputStream(image));
            BufferedOutputStream bos = new BufferedOutputStream(s.getOutputStream());
            bos.write(nio(image.length()));
            byte[] buff = new byte[4096];
            int n;
            while ((n = bis.read(buff, 0, buff.length)) > 0) {
                bos.write(buff, 0, n);
            }
            bos.flush();
        } catch (IOException ex) {
            Logger.getLogger(GetImage.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
    private static byte[] nio(long n) {
        byte[] v = new byte[8];
        int shift = 56;
        for (int i = 0; i < 8; i++) {
            v[i] = (byte)(n>>shift);
            shift -= 8;
        }
        return v;
    }
}

这是 Python 服务器:

import socket
from struct import unpack

HOST = '0.0.0.0'
PORT = 7070


def main():
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        s.bind((HOST, PORT))
        s.listen()
        conn, _ = s.accept()
        with conn:
            p = conn.recv(8, socket.MSG_WAITALL)
            n = unpack('!Q', p)[0]
            with open('/tmp/image.png', 'wb') as image:
                while n > 0:
                    data = conn.recv(n)
                    image.write(data)
                    n -= len(data)


if __name__ == '__main__':
    main()

【讨论】:

  • 这是一个非常清晰的例子,也有效。但是,有没有办法实时发送图像?
  • @JohnJones 您需要详细说明您想要实现的目标
  • 不一定是实时的,但是不等待就写入,目前由于发送的数据块存在延迟,直到文件被完全写入
  • 我已经重写了我的答案,以演示如何通过接收套接字上可用的任何内容来减少对服务器端内存的影响。在客户端,我现在使用 BufferedOutputStream。不幸的是,该类不支持 writeLong 所以有一个新的私有方法来处理它。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-01-30
  • 2019-01-26
  • 2015-03-22
  • 2012-06-08
  • 2020-07-08
  • 2018-08-29
  • 1970-01-01
相关资源
最近更新 更多