通过TCP套接字发送文件视窗

如何解决通过TCP套接字发送文件视窗

我想在Windows上的C ++中通过TCP套接字发送文件,一切工作都很好,但是我不能发送这样的大文件,我知道TCP不受任何协议的限制,就像我无法发送每个数据包超过64KB,我的方法适用于较小的文件大小(经测试,最大可达12KB),但是我想发送LARGE文件(例如ubuntu或Windows的iso映像),这些文件肯定大于12个完全打包的数据包等。

服务器

int filesize = 0;
int err = recv(conn,(char*)&filesize,sizeof(filesize),0);
if (err <= 0)
{
    printf("recv: %d\n",WSAGetLastError());
    clean(conn);
}
printf("recv %d bytes [OK]\n",err);

char* buffer = new char[filesize];
ZeroMemory(buffer,filesize);
err = recv(conn,buffer,filesize,MSG_WAITALL);
if (err <= 0)
{
    printf("recv: %d\n",err);

ofstream file("a.txt",ios::binary);
file.write(buffer,filesize);
delete[] buffer;
file.close();

客户

ifstream file("a.txt",ios::binary);
file.seekg(0,ios::end);
int size = file.tellg();
file.seekg(0,ios::beg);
char* buffer = new char[size];
file.read(buffer,size);
file.close();

int* fsize = &size;
int err = send(client,(char*)fsize,sizeof(int),0);
if (err <= 0)
{
    printf("send: %d\n",WSAGetLastError());
}
printf("send %d bytes [OK]\n",err);

err = send(client,size,err);
delete[] buffer;

双方的所有值都已初始化,并且错误处理做得很好,如果我有问题,那我会说的。我决定使用MSG_WAITALL,因为我认为这适合这种情况,请更正我的代码以进行接收/发送,如果可能的话,对其进行重构,最好附有说明,以便evrybody可以更好地学习编码,谢谢)))

解决方法

应该从问题下方的注释中删除一个要点,即sendrecv善变。仅仅因为您写send(buffer with 100 bytes)并不意味着它将发送100个字节。它可以发送25个字节或99个字节,或者完全失败。由您决定返回值并计算仍需要发送的内容。

recv也一样。如果由于期望100个字节而写recv(buffer with 100 bytes),则它只能捕获25个字节或99个字节,或者完全失效。同样,由您决定使用该返回值并计算仍需要接收的内容。

文件I / O完全不同。如果要向文件写入100个字节,则在方法未失败的情况下,保证可以写入这100个字节。因此,当使用文件I / O的人们转移到套接字I / O时,通常会感到困惑,为什么事情不能正确发送或接收。

套接字编程中比较棘手的部分之一就是知道您将需要接收多少数据。您通过首先发送文件的长度来解决此问题。服务器将知道要读取该值,然后继续读取直到满足该值。

某些协议(例如HTTP)将使用定界符(在HTTP的情况下\r\n\r\n)来指示数据包何时结束。因此,作为套接字程序员,您将在循环中recv直到读取这4个字节。

我整理了一个示例,说明如何完成大文件的发送和接收(这将处理最大长度为9,223,372,036,854,775,807的文件)。这不是纯C ++,由于时间不足,我在某些地方作弊。出于相同的原因,我使用了一些仅Windows的结构。

所以让我们来看一下:

int64_t GetFileSize(const std::string& fileName) {
    // no idea how to get filesizes > 2.1 GB in a C++ kind-of way.
    // I will cheat and use Microsoft's C-style file API
    FILE* f;
    if (fopen_s(&f,fileName.c_str(),"rb") != 0) {
        return -1;
    }
    _fseeki64(f,SEEK_END);
    const int64_t len = _ftelli64(f);
    fclose(f);
    return len;
}

///
/// Recieves data in to buffer until bufferSize value is met
///
int RecvBuffer(SOCKET s,char* buffer,int bufferSize,int chunkSize = 4 * 1024) {
    int i = 0;
    while (i < bufferSize) {
        const int l = recv(s,&buffer[i],__min(chunkSize,bufferSize - i),0);
        if (l < 0) { return l; } // this is an error
        i += l;
    }
    return i;
}

///
/// Sends data in buffer until bufferSize value is met
///
int SendBuffer(SOCKET s,const char* buffer,int chunkSize = 4 * 1024) {

    int i = 0;
    while (i < bufferSize) {
        const int l = send(s,0);
        if (l < 0) { return l; } // this is an error
        i += l;
    }
    return i;
}

//
// Sends a file
// returns size of file if success
// returns -1 if file couldn't be opened for input
// returns -2 if couldn't send file length properly
// returns -3 if file couldn't be sent properly
//
int64_t SendFile(SOCKET s,const std::string& fileName,int chunkSize = 64 * 1024) {

    const int64_t fileSize = GetFileSize(fileName);
    if (fileSize < 0) { return -1; }

    std::ifstream file(fileName,std::ifstream::binary);
    if (file.fail()) { return -1; }

    if (SendBuffer(s,reinterpret_cast<const char*>(&fileSize),sizeof(fileSize)) != sizeof(fileSize)) {
        return -2;
    }

    char* buffer = new char[chunkSize];
    bool errored = false;
    int64_t i = fileSize;
    while (i != 0) {
        const int64_t ssize = __min(i,(int64_t)chunkSize);
        if (!file.read(buffer,ssize)) { errored = true; break; }
        const int l = SendBuffer(s,buffer,(int)ssize);
        if (l < 0) { errored = true; break; }
        i -= l;
    }
    delete[] buffer;

    file.close();

    return errored ? -3 : fileSize;
}

//
// Receives a file
// returns size of file if success
// returns -1 if file couldn't be opened for output
// returns -2 if couldn't receive file length properly
// returns -3 if couldn't receive file properly
//
int64_t RecvFile(SOCKET s,int chunkSize = 64 * 1024) {
    std::ofstream file(fileName,std::ofstream::binary);
    if (file.fail()) { return -1; }

    int64_t fileSize;
    if (RecvBuffer(s,reinterpret_cast<char*>(&fileSize),sizeof(fileSize)) != sizeof(fileSize)) {
        return -2;
    }

    char* buffer = new char[chunkSize];
    bool errored = false;
    int64_t i = fileSize;
    while (i != 0) {
        const int r = RecvBuffer(s,(int)__min(i,(int64_t)chunkSize));
        if ((r < 0) || !file.write(buffer,r)) { errored = true; break; }
        i -= r;
    }
    delete[] buffer;

    file.close();

    return errored ? -3 : fileSize;
}

发送和接收缓冲区

在顶部,我们有两种方法可用于内存中的缓冲区。您可以将其发送到任何大小的任何缓冲区(在这里保持合理),并且这些方法将发送和接收,直到传递进来的所有字节都已发送为止。

这就是我上面所说的。它占用缓冲区并循环直到成功发送或接收所有字节为止。这些方法完成后,可以保证所有数据都已传输(只要返回值是零或正)。

您可以定义“块大小”,这是方法将用于发送或接收数据的数据块的默认大小。我确信可以通过使用比当前设置的值更合适的值来优化这些值,但是我不知道这些值是什么。将它们保留为默认设置是安全的。我不认为,以当今计算机的速度,如果将其更改为其他内容,您不会注意到太多差异。

发送和接收文件

用于执行文件的代码在本质上与缓冲区代码几乎相同。相同的想法,除了现在我们可以假设,如果缓冲区方法的返回值大于零,则说明成功。因此,代码要简单一些。出于特殊原因,我使用64KB的块大小。这次块大小决定了从文件I / O操作而不是套接字I / O读取多少数据。

测试服务器和客户端

为了完整起见,我使用下面的代码对磁盘上的5.3 GB文件进行了测试。我基本上只是以非常精简的方式重写了Microsoft的client / server示例。

#pragma comment(lib,"Ws2_32.lib")
#include <iostream>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <fstream>

DWORD __stdcall ClientProc(LPVOID param) {

    struct addrinfo hints = { 0 },* result,* ptr;
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;

    if (getaddrinfo("127.0.0.1","9001",&hints,&result) != 0) {
        return ~0;
    }

    SOCKET client = INVALID_SOCKET;
    for (ptr = result; ptr != NULL; ptr = ptr->ai_next) {
        client = socket(ptr->ai_family,ptr->ai_socktype,ptr->ai_protocol);
        if (client == SOCKET_ERROR) {
            // TODO: failed (don't just return,cleanup)
        }
        if (connect(client,ptr->ai_addr,(int)ptr->ai_addrlen) == SOCKET_ERROR) {
            closesocket(client);
            client = INVALID_SOCKET;
            continue;
        }
        break;
    }
    freeaddrinfo(result);

    if (client == SOCKET_ERROR) {
        std::cout << "Couldn't create client socket" << std::endl;
        return ~1;
    }

    int64_t rc = SendFile(client,"D:\\hugefiletosend.bin");
    if (rc < 0) {
        std::cout << "Failed to send file: " << rc << std::endl;
    }

    closesocket(client);

    return 0;
}

int main()
{
    WSADATA wsaData;
    WSAStartup(MAKEWORD(2,2),&wsaData);

    {
        struct addrinfo hints = { 0 };
        hints.ai_family = AF_INET;
        hints.ai_socktype = SOCK_STREAM;
        hints.ai_protocol = IPPROTO_TCP;
        hints.ai_flags = AI_PASSIVE;

        struct addrinfo* result = NULL;
        if (0 != getaddrinfo(NULL,&result)) {
            // TODO: failed (don't just return,clean up)
        }

        SOCKET server = socket(result->ai_family,result->ai_socktype,result->ai_protocol);
        if (server == INVALID_SOCKET) {
            // TODO: failed (don't just return,clean up)
        }

        if (bind(server,result->ai_addr,(int)result->ai_addrlen) == INVALID_SOCKET) {
            // TODO: failed (don't just return,clean up)
        }
        freeaddrinfo(result);

        if (listen(server,SOMAXCONN) == SOCKET_ERROR) {
            // TODO: failed (don't just return,clean up)
        }

        // start a client on another thread
        HANDLE hClientThread = CreateThread(NULL,ClientProc,NULL,0);

        SOCKET client = accept(server,NULL);

        const int64_t rc = RecvFile(client,"D:\\thetransmittedfile.bin");
        if (rc < 0) {
            std::cout << "Failed to recv file: " << rc << std::endl;
        }

        closesocket(client);
        closesocket(server);

        WaitForSingleObject(hClientThread,INFINITE);
        CloseHandle(hClientThread);
    }
    WSACleanup();
    return 0;
}

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 &lt;select id=&quot;xxx&quot;&gt; SELECT di.id, di.name, di.work_type, di.updated... &lt;where&gt; &lt;if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 &lt;property name=&quot;dynamic.classpath&quot; value=&quot;tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-