通过C ++套接字发送图像(Linux)

时间:2010-06-26 18:39:29

标签: sockets

我正在尝试通过套接字发送文件。我创建了一个程序,它适用于.cpp,.txt和其他文本文件等文件类型。但是二进制文件,图像(.jpg,.png)和压缩文件(如.zip和.rar)未正确发送。我知道这与文件的大小无关,因为我测试了大的.txt文件。我不知道问题,我收到所有发送的字节,但文件无法打开。大多数情况下文件已损坏且无法查看。我已经通过谷歌搜索了一个解决方案,并且发现其他人遇到了同样的问题并没有解决方案。所以通过帮助我,你也可以帮助其他需要解决方案的人。

服务器代码:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>

int main ( int agrc, char *argv[] )
{
    /******** Program Variable Define & Initialize **********/
    int Main_Socket;    // Main Socket For Server
    int Communication_Socket; // Socket For Special Clients
    int Status; // Status Of Function
    struct sockaddr_in Server_Address; // Address Of Server
    struct sockaddr_in Client_Address;// Address Of Client That Communicate with Server
    int Port;
    char Buff[100] = "";
    Port = atoi(argv[2]);
    printf ("Server Communicating By Using Port %d\n", Port);
    /******** Create A Socket To Communicate With Server **********/
    Main_Socket = socket ( AF_INET, SOCK_STREAM, 0 );
    if ( Main_Socket == -1 )
    {
            printf ("Sorry System Can Not Create Socket!\n");
    }
    /******** Create A Address For Server To Communicate **********/
    Server_Address.sin_family = AF_INET;
    Server_Address.sin_port = htons(Port);
    Server_Address.sin_addr.s_addr = inet_addr(argv[1]);
    /******** Bind Address To Socket **********/
    Status = bind ( Main_Socket, (struct sockaddr*)&Server_Address, sizeof(Server_Address) );
    if ( Status == -1 )
    {
            printf ("Sorry System Can Not Bind Address to The Socket!\n");
    }
    /******** Listen To The Port to Any Connection **********/        
    listen (Main_Socket,12);    
    socklen_t Lenght = sizeof (Client_Address);
    while (1)
    {
        Communication_Socket = accept ( Main_Socket, (struct sockaddr*)&Client_Address, &Lenght );

        if (!fork())
        {

            FILE *fp=fopen("recv.jpeg","w");
            while(1)
            {
                char Buffer[2]="";
                if (recv(Communication_Socket, Buffer, sizeof(Buffer), 0))
                {
                    if ( strcmp (Buffer,"Hi") == 0  )
                    {
                        break;
                    }
                    else
                    {
                        fwrite(Buffer,sizeof(Buffer),1, fp);
                    }
                }
            }
            fclose(fp);
            send(Communication_Socket, "ACK" ,3,0);
            printf("ACK Send");
            exit(0);
        }
    }
    return 0;
}

客户代码:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>

int main ( int agrc, char *argv[] )
{
    int Socket;

    struct sockaddr_in Server_Address;  
    Socket = socket ( AF_INET, SOCK_STREAM, 0 );
    if ( Socket == -1 )
    {   
        printf ("Can Not Create A Socket!");    
    }
    int Port ;
    Port = atoi(argv[2]);   
    Server_Address.sin_family = AF_INET;
    Server_Address.sin_port = htons ( Port );
    Server_Address.sin_addr.s_addr = inet_addr(argv[1]);
    if ( Server_Address.sin_addr.s_addr == INADDR_NONE )
    {
        printf ( "Bad Address!" );
    }   
    connect ( Socket, (struct sockaddr *)&Server_Address, sizeof (Server_Address) );


    FILE *in = fopen("background.jpeg","r");
    char Buffer[2] = "";
    int len;
    while ((len = fread(Buffer,sizeof(Buffer),1, in)) > 0)
    {            
        send(Socket,Buffer,sizeof(Buffer),0);            
    }
    send(Socket,"Hi",sizeof(Buffer),0);

    char Buf[BUFSIZ];
    recv(Socket, Buf, BUFSIZ, 0);
    if ( strcmp (Buf,"ACK") == 0  )
    {
         printf("Recive ACK\n");
    }        
    close (Socket);
    fclose(in);
    return 0;   
}

5 个答案:

答案 0 :(得分:4)

首先,您应该尝试将缓冲区增加到更大的范围。缓冲区越大,传输效率越高(只是不要夸大和消耗过多的本地内存)。

其次,您应该检查使用读写函数返回的长度。在所有读写操作中,您只检查是否读取/写入了某些内容,但是您应该在下一次写入/读取操作时使用此字节计数。例如,如果客户端报告它已读取1个字节,则只应向磁盘写入1个字节。此外,如果从服务器读取1024字节,则应尝试将1024字节写入磁盘,这可能不会发生在该调用中,您可能需要再次调用write才能完成操作。

我知道这听起来像是很多工作,但这就是如何保证I / O操作。所有的读写操作基本上都是在自己的循环中完成的。

答案 1 :(得分:2)

您确定您的二进制图像文件中没有字节序列0x48 0x69"Hi")吗?您的读取循环将在收到该序列后立即终止。另外,使用两个字节长的字符缓冲区调用strcmp(),几乎可以肯定它保证它没有空终止字节('\0')。

您还可以调查文件的不同之处? cmp工具可以为您提供源和目标之间不同的字节列表。

为了正确起见,您肯定要查看来自read()write()send()等的返回结果。使用套接字进行短读取和写入的可能性非常高,因此,您的代码能够处理并非所有数据都被传输的情况至关重要。由于您不跟踪recv()返回的字节数,因此您可能只收到一个字节,但在其后的write()调用中写了两个字节。

对于性能,较大的缓冲区将有助于减少系统调用移动数据的开销,但如果带宽和延迟不是主要问题,则可以执行较小的操作。除非你有正确的行为,否则他们不应该这样做。

最后,为了与不太开明的操作系统兼容,您应该使用"rb""wb"以二进制模式打开文件,以便在写入期间不会损坏换行符。

答案 2 :(得分:0)

对于读取ascii文本文件,可以使用char缓冲区。

为了读取二进制数据,您需要使用unsigned char,否则您的数据将被剔除,因为二进制数据是无符号字节。

答案 3 :(得分:0)

问题是您使用fopen(..., "r")fopen(..., "w")文本模式打开文件。您需要对非文本文件使用二进制模式("rb""wb")。

答案 4 :(得分:0)

其他人指出你的代码存在问题,即:

  • 未使用read(2)write(2)来电的返回值,
  • 混合二进制数据和字符控制数据,
  • 对可能不是零终止的字符串使用strcmp(3)(请注意,使用依赖于从网络接收的数据终止零的函数通常不是一个好主意,并且经常会导致缓冲区溢出。)

为文件传输定义简单协议会好得多(如果你想知道原因,请阅读"The ultimate SO_LINGER page, or: why is my tcp not reliable"。)让服务器端预先知道你要发送多少数据 - 在传输之前修复包含文件长度的-size标头(也可能包含文件名,但是您也需要传输该名称的长度。)注意号码endianness - 始终在network byte order中发送号码

由于您使用的是Linux,我还要指向sendfile(2),这是一种非常有效的文件发送方式,因为它可以避免将数据复制到用户区域。