V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Distributions
Ubuntu
Fedora
CentOS
中文资源站
网易开源镜像站
llr8031
V2EX  ›  Linux

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

  •  
  •   llr8031 · 2022-02-20 21:56:18 +08:00 · 2734 次点击
    这是一个创建于 788 天前的主题,其中的信息可能已经有所发展或是发生改变。

    硬件背景

    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 条回复    2022-02-22 12:52:59 +08:00
    Kinnice
        1
    Kinnice  
       2022-02-20 22:07:00 +08:00 via Android   ❤️ 1
    可能是发送太快,接受太慢,设置 sleep 试试,让它慢慢发
    flynaj
        2
    flynaj  
       2022-02-20 22:14:00 +08:00 via Android   ❤️ 1
    其它协议正常不,samba ,FTP ,HTTP 这类
    wevsty
        3
    wevsty  
       2022-02-20 22:14:51 +08:00   ❤️ 1
    nRecv == sizeof(PacketHeader)

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

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

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

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

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

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

    上面的事情都解决后,恢复成正常 buffer 收发。你甚至可以用超大 buffer 来提高性能。
    llr8031
        14
    llr8031  
    OP
       2022-02-21 00:43:26 +08:00
    @documentzhangx66 感谢指点,我结合书本试试看,喟叹 V 站颇有知乎遗风= v =
    koloonps
        15
    koloonps  
       2022-02-21 00:54:12 +08:00
    你可以直接发送一个 byte[]用\n 作为结束符号
    wevsty
        16
    wevsty  
       2022-02-21 01:02:31 +08:00   ❤️ 1
    @llr8031
    TCP 是流式协议啊,粘包警察警告。
    julyclyde
        17
    julyclyde  
       2022-02-22 12:52:59 +08:00
    其实发送的时候可以随便 send 吧
    接收的时候才存在收不奇的问题,需要预先知道长度才能知道是否需要继续收
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   2775 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 27ms · UTC 12:38 · PVG 20:38 · LAX 05:38 · JFK 08:38
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.