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

2022-02-20 21:56:18 +08:00
 llr8031

硬件背景

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;
    }
}

2906 次点击
所在节点    Linux
17 条回复
Kinnice
2022-02-20 22:07:00 +08:00
可能是发送太快,接受太慢,设置 sleep 试试,让它慢慢发
flynaj
2022-02-20 22:14:00 +08:00
其它协议正常不,samba ,FTP ,HTTP 这类
wevsty
2022-02-20 22:14:51 +08:00
nRecv == sizeof(PacketHeader)

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

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

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

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

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

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

上面的事情都解决后,恢复成正常 buffer 收发。你甚至可以用超大 buffer 来提高性能。
llr8031
2022-02-21 00:43:26 +08:00
@documentzhangx66 感谢指点,我结合书本试试看,喟叹 V 站颇有知乎遗风= v =
koloonps
2022-02-21 00:54:12 +08:00
你可以直接发送一个 byte[]用\n 作为结束符号
wevsty
2022-02-21 01:02:31 +08:00
@llr8031
TCP 是流式协议啊,粘包警察警告。
julyclyde
2022-02-22 12:52:59 +08:00
其实发送的时候可以随便 send 吧
接收的时候才存在收不奇的问题,需要预先知道长度才能知道是否需要继续收

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/835264

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX