由服务器(cpp)向c# unity android 传输文件

需求

客户端(安卓手机)每帧(一秒30帧)向服务端传送想要的文件名,服务端向客户端传送文件

过程

在传文件这个过程中,主要困在了传文件总会少几KB的bug。

导致传输的中间文件不可用。

经过探究,主要在于以下几个知识点(或者未知点):

  1. 服务器和客户端的缓冲数组大小没有说非要一致大小,原来以为服务器发送用1024字节的数组分块发,那客户端也要用1024字节的数组分块接受。但不是,两边可以不一致。

​ 客户端每次会不一定能接受多少,比如服务端发送了一个1024的块,客户端用1024的缓冲接,发现只接受了880的字节,下一次再接会是144的字节。不一定能接多少。但TCP能保证这些字节的顺序及块的顺序(实践得来)

  1. 由于第一条,所以服务器不用纠结与数组的大小,直接用了一个函数,就传走了文件

    sendfile
    
  2. 客户端 是c# unity android

    服务器传输了639KB的中间文件,但客户端每次就接受了636KB的文件,少了3KB,导致中间文件不可用。

    经过探究,(错误,会造成文件损耗)原本客户端每次接受一个块后,就用binarywriter 往创建好的文件写入这部分数据。

    (正确,没有损耗)后来先把文件大小传输过来

    并使用list 存储每次接收到的字节块,然后接受完毕后,用File.writeallbyte全部写入文件。就没有任何损失且可以正常解码。

服务端的代码

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include<string>
#include<iostream>
#include<vector>
#include<sys/stat.h>
#include<sys/sendfile.h>
#include<fcntl.h>


#define PORT 8081            //服务器端监听端口号
#define MAX_BUFFER 1024         //数据缓冲区最大值
using namespace std;

// void sendfile(string filepath,string filename,int client_sockfd)
// /*  传输一个文件
//     in:
//         filename: 文件名,客户端将以同样的文件名存储
//         client_sockfd: 已经绑定端口并监听的socket文件句柄号
// */
// {
//     string start="START";
//     char buf[1024] = { 0 };
//     strcpy(buf, start.c_str());
//     write(client_sockfd,buf,MAX_BUFFER);
//     memset(buf, 0, sizeof(buf)); 


//     string name =filename;
//     strcpy(buf, name.c_str());
//     write(client_sockfd,buf,MAX_BUFFER);
//     memset(buf, 0, sizeof(buf)); 
//     int bytes;
//     FILE* fp =NULL;
//     if((fp = fopen(&filepath[0], "rb"))==NULL)
//     {
//         printf("打开文件失败");
//         exit(0);
//     }

//     while ((bytes = fread(buf, 1, 1024, fp) )> 0)//读取文件内容,每次一个1024字节
//     {
//         // sleep(1);
//         //cout<<bytes<<endl;
//         // for(auto byte:buf)
//         // {
//         //     cout<<byte<<" ";
//         // }
//         // cout<<endl;`
//         write(client_sockfd,buf,MAX_BUFFER);//把buf里存的东西send给服务端
//         memset(buf, 0, sizeof(buf)); 
//     }

//     sleep(1);
//     string end="END";
//     strcpy(buf, end.c_str());
//     // for(auto byte:buf)
//     //     {
//     //         cout<<byte<<" ";
//     //     }
//     //     cout<<endl;
//     write(client_sockfd,buf,MAX_BUFFER);
//     memset(buf, 0, sizeof(buf)); 

// }
int main()
{
    struct sockaddr_in server_addr, client_addr;
    int server_sockfd, client_sockfd;
    int size, write_size;
    int filehandle;
    char buffer[MAX_BUFFER];
    struct stat obj;
    if ((server_sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)    //创建Socket
    {
        perror("Socket Created Failed!\n");
        exit(1);
    }
    printf("Socket Create Success!\n");

    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr.sin_port = htons(PORT);
    bzero(&(server_addr.sin_zero), 8);

    int opt = 1;
    int res = setsockopt(server_sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));    //设置地址复用
    if (res < 0)
    {
        perror("Server reuse address failed!\n");
        exit(1);
    }

    if (bind(server_sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1)  //绑定本地地址
    {
        perror("Socket Bind Failed!\n");
        exit(1);
    }
    printf("Socket Bind Success!\n");

    if (listen(server_sockfd, 5) == -1)                 //监听
    {
        perror("Listened Failed!\n");
        exit(1);
    }
    printf("Listening ....\n");

    socklen_t len = sizeof(client_addr);

    printf("waiting connection...\n");
    if ((client_sockfd = accept(server_sockfd, (struct sockaddr *)&client_addr, &len)) == -1)  //等待客户端连接
    {
        perror("Accepted Failed!\n");
        exit(1);
    }
    printf("connection established!\n");
    printf("waiting message...\n"); 
    // 开始传文件
    //发送一个文件名
    //bytes[]

    // vector<string> filenames={"test1.txt","test2.txt","test3.txt","test4.txt"};

    // for(auto filename:filenames)
    // {
    //     string filepath ="output/"+filename;
        
    //     cout<<filename<<"已经传送完毕"<<endl;
    // }
    char buf[1024] = { 0 };
    int total_byte=0;
    int total_bags=0;
    for(;;)
    {
        memset(buf,0,1024);
        int rec_len = recv(client_sockfd,buf,sizeof(buf),0);
        buf[rec_len]='\0';
        if(rec_len<=0)
        {
        break;
        }
    
        cout<<"recive_len:"<<rec_len<<endl;
        string filename=buf;
        cout<<"接受到的文件名:"<<filename<<endl;
        memset(buf, 0, sizeof(buf));
        stat(filename.c_str(), &obj);

        // FILE* fp =NULL;
        // if((fp = fopen(&filename[0], "rb"))==NULL)
        // {
        //     printf("打开文件失败");
        //     exit(0);
        // }
        // 发送文件大小
        
        string filesize=to_string(obj.st_size);
        cout<<filesize<<endl;
        strcpy(buf, filesize.c_str());
        write_size = write(client_sockfd, buf, MAX_BUFFER);
        memset(buf, 0, sizeof(buf));


        int fd=open(filename.c_str(), O_RDONLY);
        int ret=sendfile(client_sockfd,fd,NULL,obj.st_size);
        if(ret<0)
        {
            cout<<"发送文件失败"<<endl;
        }
        cout<<ret<<endl;



        //sendf(client_sockfd,buf,MAX_BUFFER,0);
        // int bytes=0;
        // while ((bytes = fread(buf, 1,1024, fp) )> 0)//读取文件内容,每次一个4096字节
        // {
        //     total_byte+=bytes;
        //     total_bags+=1;
        //     cout<<bytes<<endl;
        //     // for(auto byte:bytes)
        //     // {
        //     //     cout<<byte<<" ";
        //     // }
        //     send(client_sockfd,buf,MAX_BUFFER,0);//把buf里存的东西send给服务端
        //     memset(buf, 0, sizeof(buf)); 
        // }
        close(filehandle);
    }
    close(client_sockfd);   //关闭Socket
    close(server_sockfd);
    cout<<"total_byte:"<<total_byte<<endl;
    cout<<"total_bags:"<<total_bags<<endl;

    return 0;
}

客户端的代码

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System;
using System.IO;
using static UnityEngine.Networking.UnityWebRequest;
using System.Runtime.InteropServices.ComTypes;
using System.Runtime.InteropServices;
using UnityEngine.XR;
using System.Linq;

public class TransferFile : MonoBehaviour
{
    string editString = "hello wolrd"; //编辑框文字

    Socket serverSocket; //服务器端socket
    IPAddress ip; //主机ip
    IPEndPoint ipEnd;
    string recvStr; //接收的字符串
    string sendStr; //发送的字符串
    byte[] recvData = new byte[1024]; //接收的数据,必须为字节
    byte[] sendData = new byte[1024]; //发送的数据,必须为字节
    int recvLen; //接收的数据长度
    Thread connectThread; //连接线程
    public string filename;
    public string filepath;
    public string record_filename;
    public string record_filepath;
    int total_bytes;
    int total_bags;
    public int is_name = 0; // 0 代表准备接受下个文件
                            // 1 代表准备接受文件名
                            // 2 代表准备接受数据文件
    BinaryWriter writer = null;
    FileStream fs;
    FileStream record_fs;



    void init_log_file(string log_file_name)
    {
        record_filename = log_file_name; //log文件的名字
        record_filepath = Application.persistentDataPath + "/" + record_filename;
        File.WriteAllText(record_filepath, "");//初始化文件
    }
    void log_file(string debug_txt)
    {
        string millenSecond = DateTime.UtcNow.Millisecond.ToString();
        //string content = string.Format("time:{0}_{1}\n {2}\n", DateTime.Now, millenSecond, debug_txt);
        string content = string.Format("{0}\n", debug_txt);
        // XXXXXXXXXX是debug的内容
        File.AppendAllText(record_filepath, content);
    }
    //初始化
    void InitSocket()
    {
        //定义服务器的IP和端口,端口与服务器对应
        ip = IPAddress.Parse("81.6213218.123.212141"); //可以是局域网或互联网ip,此处是本机
        ipEnd = new IPEndPoint(ip, 8081);
        //开启一个线程连接,必须的,否则主线程卡死
        try
        {
            connectThread = new Thread(new ThreadStart(SocketReceive));
            connectThread.Start();
            //log_file("连接成功");
        }
        catch {
            //log_file("连接失败");
        }
        
    }


    void SocketConnet()
    {
        if (serverSocket != null)
            serverSocket.Close();
        //定义套接字类型,必须在子线程中定义
        print("ready to connect");
        //连接
        try
        {
            serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            serverSocket.Connect(ipEnd);
            //log_file("connect success");
            //log_file("连接成功时的serversocket"+serverSocket.ToString());
        }
        catch(Exception ex) {
            Debug.Log("connect fail");
            //log_file("connect fail");
        }
        

        输出初次连接收到的字符串
        //recvLen = serverSocket.Receive(recvData);
        //recvStr = Encoding.ASCII.GetString(recvData, 0, recvLen);
        //print(recvStr);
    }

    void SocketSend(string sendStr)
    {
        //清空发送缓存
        sendData = new byte[1024];
        //数据类型转换
        sendData = Encoding.ASCII.GetBytes(sendStr);                                            
        //发送
        serverSocket.Send(sendData, sendData.Length, SocketFlags.None);
    }

    void SocketReceive()
    {
        SocketConnet();
        //不断接收服务器发来的数据
        try
        {
            SocketSend(filename);
            //log_file("发送文件名成功");
        }
        catch (Exception e)
        {
            //og_file("发送文件名失败");
        
        }
        // 接受文件的大小
        recvData = new byte[1024];
        recvLen = serverSocket.Receive(recvData, 0, recvData.Length, SocketFlags.None);
        log_file(Encoding.UTF8.GetString(recvData));
        int filesize=int.Parse(Encoding.UTF8.GetString(recvData));
        log_file("文件的大小:" + filesize);
        int recv_size = 0;
        //fs = new FileStream(filepath, FileMode.Create, FileAccess.Write);
        //writer = new BinaryWriter(fs);
        //log_file("创建文件流成功");  `
        //List<byte> bytelist = new List<byte>();
        //bytelist.AddRange(recvData);

        //不断接收服务器发来的数据

        total_bags = 0;
        List<byte> byte_ls = new List<byte>();
        while (recv_size<filesize)
        {
            recvData = new byte[1024];
            int real_len=cut_byte(recvData);

            recvLen = serverSocket.Receive(recvData,0,recvData.Length, SocketFlags.None);
            recv_size += recvLen;
            log_file(recvLen.ToString());
            log_file("recv_size:"+recv_size.ToString());
            total_bags += 1;
            try
            {
                log_file("截取之前长度:"+recvData.Length);
                recvData=recvData.Skip(0).Take(recvLen).ToArray();
                log_file("截取之后长度:"+recvData.Length);
                byte_ls.AddRange(recvData);
                log_file("byte_ls长度:" + byte_ls.Count);
            }
            catch(Exception e)
            {
                log_file("未成功写入文件");
            }

            //log_file(total_bytes.ToString());
            //log_file(total_bags.ToString());
        }
        log_file("最终字节数:" + byte_ls.Count);
        log_file("最终字节数total_byte" + total_bytes);
        File.WriteAllBytes(filepath, byte_ls.ToArray());
        log_file("接受完毕");
       
    }
    int cut_byte(byte[] buffer)
    {
        int i = 0;
        for (; i < buffer.Length; i++)
        {
            if (buffer[i] == '\0')
            {
                break;
            }
        }
        return i;
    }
    void SocketQuit()
    {
        //关闭线程
        if (connectThread != null)
        {
            connectThread.Interrupt();
            connectThread.Abort();
        }
        //最后关闭服务器
        if (serverSocket != null)
            serverSocket.Close();
        print("diconnect");
    }


    // Start is called before the first frame update
    void Start()
    {
        init_log_file("Transfer_file_debug.txt");
        filename = "longdress.drc";
        filepath = Application.persistentDataPath + "/" + filename;
        //File.WriteAllText(filepath, "");//初始化文件
        InitSocket();
        

        

    }
    void OnDestroy()
    {
        //fs.Flush();
        //fs.Close();
        SocketQuit();
        
    }

    // Update is called once per frame
    void Update()
    {

    }
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Unity 的局域网传输文件可以通过网络编程的方式实现。以下是一种可能的实现方法: 首先,需要在项目中使用 Unity 的网络模块创建一个本地服务器,并与其他设备建立连接。Unity 提供了 NetworkServer 类和 NetworkClient 类来简化网络通信的配置和管理。可以使用 NetworkServer.Listen() 方法在本地创建一个服务器,并使用 NetworkClient.Connect() 方法从其他设备连接到该服务器。 其次,需要定义文件传输的协议和消息格式。可以使用自定义的消息类来定义传输文件的请求、进度和完成等状态信息。例如,可以定义一个名为 FileTransferMessage 的类,包含文件的名称、大小、传输进度等信息。 然后,需要在服务器端和客户端之间实现文件传输的逻辑。可以使用 Unity文件读写 API(如 File 类),将要传输文件拆分成小块,并在服务器端逐个块地发送给客户端,客户端则接收文件块并逐块存储成文件。 最后,可以根据需求添加一些额外的功能,如断点续、数据校验等。这些功能可以通过在传输过程中记录已传输文件块和校验和等方式实现。 需要注意的是,局域网传输文件涉及到网络通信和文件读写等操作,需要对异常情况进行处理,保证传输过程的稳定和安全。 总结起来,Unity 的局域网传输文件需要借助网络编程的知识和方法,在服务器和客户端之间建立连接,定义传输协议和消息格式,并实现文件的拆分、发送和接收等操作。这样,就可以实现局域网内的文件传输功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值