V2EX = way to explore
V2EX 是一个关于分享和探索的地方
Sign Up Now
For Existing Member  Sign In
Distributions
Ubuntu
Fedora
CentOS
中文资源站
网易开源镜像站
lucinakushinada
V2EX  ›  Linux

求助关于局域网 Socket 传送文件时好时坏的问题

  •  
  •   lucinakushinada · Feb 20, 2022 · 3680 views
    This topic created in 1536 days ago, the information mentioned may be changed or developed.

    硬件背景

    Client 树莓派 4B 2G 运行 debian 11 x64 Server Macbook macOS 两者采用有线连接至同一台交换机中

    问题表现

    在传输文件时有时成功,有时失败。失败时在终端打印乱码,但对于同一文件打印出来的乱码相同,个人猜测是编码问题,但是 debian 和 macOS 用的应该都是 UTF-8 ?而且传字符串没有问题

    代码贴图

    下面给出 Server 端和 Client 端的代码

    //Server 接收端
    struct PacketHeader {
        int size;            // 大小:字节
        PacketHeader() {
            size = 0;
        }
    };
    
    void *receiveMsg(void *sock) {
        // system("pwd");
        char buffer[1024];
        int *socket = (int *) sock;
        while (1) {
            usleep(500);
            int nRecv = recv(*socket, buffer, 1024, 0);
            if (nRecv <= 0) {
                continue;
            } else if (nRecv == sizeof(PacketHeader)) {
                PacketHeader ph;
                memcpy(&ph, buffer, nRecv);
                cout << "大小:" << ph.size << "Bytes" << endl;
                string filename = ("../Received/" + getCurrentTime() + ".jpg");
                FILE *fp = fopen(filename.c_str(), "wb");
                if (fp == NULL) {
                    cout << "file not found" << endl;
                }
                cout << "开始接收图片" << endl;
                nRecv = 0;
                while (nRecv < ph.size) {
                    usleep(500);
                    memset(buffer, 0, sizeof(buffer));
                    int byteCount = recv(*socket, buffer, 1024, 0);
                    if (byteCount <= 0) {
                        continue;
                    }
                    fwrite(buffer, 1, byteCount, fp);
                    nRecv += byteCount;
                    cout << "已接收 " << nRecv << " Bytes" << endl;
                }
                cout << "共接收 " << nRecv << " Bytes" << endl;
                fclose(fp);
            } else {
                buffer[nRecv] = '\0';
                cout << buffer << endl;
            }
        }
        
    
    # Client 发送端
    struct PacketHeader
    {
        int size; // 大小:字节
        PacketHeader()
        {
            size = 0;
        }
    };
    
    void *sendMsg(void *socket)
    {
        int sock = *((int *)socket);
        while (1)
        {
            char msg[4096];
            string filename;
            cin >> filename;
            FILE *fp = fopen(("../image/" + filename).c_str(), "rb");
            if (fp == NULL)
            {
                cout << "file not found" << endl;
                continue;
            }
            int sz = FileSize(("../image/" + filename).c_str());
            cout << "文件打开成功,大小:" << sz << " Bytes" << endl;
    
            PacketHeader ph;
            ph.size = sz;
            send(sock, (const char *)&ph, sizeof(ph), 0);
            cout << "已发送头部信息" << endl;
    
            int nSend = 0;
    		char buffer[1024];
    	    while(nSend < sz)
            {
                int nBytes = fread(buffer, sizeof(char), sizeof(buffer), fp);
                if (nBytes <= 0)
                    break;
                send(sock, buffer, nBytes, 0);
                cout << "已发送 " << nBytes << " Bytes" << endl;
                nSend += nBytes;
            }
            cout<<"总计发送 "<<nSend<<" Bytes"<<endl;
    		fclose(fp);
            cout << "successfully send " + filename << endl;
        }
    }
    
    
    17 replies    2022-02-22 12:52:59 +08:00
    Kinnice
        1
    Kinnice  
       Feb 20, 2022 via Android   ❤️ 1
    可能是发送太快,接受太慢,设置 sleep 试试,让它慢慢发
    flynaj
        2
    flynaj  
       Feb 20, 2022 via Android   ❤️ 1
    其它协议正常不,samba ,FTP ,HTTP 这类
    wevsty
        3
    wevsty  
       Feb 20, 2022   ❤️ 1
    nRecv == sizeof(PacketHeader)

    这里并不能保证第一次接收正好收到一个 sizeof(PacketHeader)的大小。
    虽然你先发送一个 sizeof(PacketHeader)大小的数据在发送其他的数据,但是接收端收到的时候很可能接收到更多的数据。
    ysc3839
        4
    ysc3839  
       Feb 20, 2022   ❤️ 1
    @wevsty TCP 发送的是流数据,多次调用 send 都是看作一整条流发出去的。接收端应该循环接收直到收到的数据长度大于等于 PacketHeader 的长度后再处理。另外 send 和 recv 的长度可能比缓冲区长度短的,也需要处理,建议调 API 时看文档怎么写的,不要想当然。
    lucinakushinada
        5
    lucinakushinada  
    OP
       Feb 20, 2022
    @Kinnice 感谢回复,这边尝试了取消 recv 的 sleep ,在 send 部分增加了 usleep(1000),错误的概率比之前低了些,大概收发 5 次左右会出现一次乱码,一定程度上有效,谢谢
    lucinakushinada
        6
    lucinakushinada  
    OP
       Feb 20, 2022
    @flynaj 感谢回复,正常的,很尴尬=。=,我自己不会写这些,不过其他设备的商业级代码保证了其他协议正常
    lucinakushinada
        7
    lucinakushinada  
    OP
       Feb 20, 2022
    @wevsty 感谢回复,嗯…我是看到 TCP 的粘包问题,网上的解决方案就是在发送前先发一个带有文件大小的包,参考的代码是 https://blog.csdn.net/zhoujielunzhimi/article/details/8190601 ,作者似乎没有遇到这个问题…不过作者测试的是 40B 的小文件,我测试的是 200KB 左右的大文件,一次 buffer 肯定装不下,所以猜测大概是这里的问题
    lucinakushinada
        8
    lucinakushinada  
    OP
       Feb 20, 2022
    @ysc3839 感谢回复。注意到了,我去翻一下 API ,谢谢
    inframe
        9
    inframe  
       Feb 20, 2022   ❤️ 1
    懒惰一点,服务端监听的时候套一层 tls ,让 ssl 协议帮你处理 tcp 包问题,顺便安全问题也解决了;
    koloonps
        10
    koloonps  
       Feb 20, 2022   ❤️ 1
    没有看到标识位,没有看到长度位,没有看到检验位...............................
    lucinakushinada
        11
    lucinakushinada  
    OP
       Feb 20, 2022
    @koloonps 感谢回复,刚学网络编程,尴尬=。=
    lucinakushinada
        12
    lucinakushinada  
    OP
       Feb 20, 2022
    @inframe 感谢回复,刚学网络编程,尴尬=。=
    documentzhangx66
        13
    documentzhangx66  
       Feb 21, 2022   ❤️ 1
    1.初学别发文件,也别发字符串,而是发一个长度为 16 或 32 的 byte array ,方便你调试、对比与检查。

    2.发送前,把要发送的内容,全部先装入一个完整的 byte array 里。

    3.发送端,一个一个字节地发送。接收端,一个一个字节地接受。使用调试模式,单步运行,收到一个字节,就对比一下。

    这样子,问题在哪,你就能自己找出来了。

    数组发送没问题后,再来说说文件。

    接收端,别单方面进行接受,而是,需要发送的文件,在接收端,也存一份。然后还是一个一个字节地收发,接收端,收到一个字节,就拿去和文件对比。当接收到不一样的字节时,那个代码位置,下个断点,进行调试,看看发生了什么问题。

    上面的事情都解决后,恢复成正常 buffer 收发。你甚至可以用超大 buffer 来提高性能。
    lucinakushinada
        14
    lucinakushinada  
    OP
       Feb 21, 2022
    @documentzhangx66 感谢指点,我结合书本试试看,喟叹 V 站颇有知乎遗风= v =
    koloonps
        15
    koloonps  
       Feb 21, 2022
    你可以直接发送一个 byte[]用\n 作为结束符号
    wevsty
        16
    wevsty  
       Feb 21, 2022   ❤️ 1
    @llr8031
    TCP 是流式协议啊,粘包警察警告。
    julyclyde
        17
    julyclyde  
       Feb 22, 2022
    其实发送的时候可以随便 send 吧
    接收的时候才存在收不奇的问题,需要预先知道长度才能知道是否需要继续收
    About   ·   Help   ·   Advertise   ·   Blog   ·   API   ·   FAQ   ·   Solana   ·   5392 Online   Highest 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 56ms · UTC 09:26 · PVG 17:26 · LAX 02:26 · JFK 05:26
    ♥ Do have faith in what you're doing.