Отправка изображений через сокеты C ++ (Linux) - PullRequest
6 голосов
/ 26 июня 2010

Я пытаюсь отправить файлы через сокеты. Я создал программу, и она работает для типов файлов, таких как .cpp, .txt и других текстовых файлов. Но двоичные файлы, изображения (.jpg, .png) и сжатые файлы, такие как .zip и .rar, отправляются неправильно. Я знаю, что это не связано с размером файлов, потому что я тестировал большие файлы .txt. Я не знаю проблему, я получаю все отправленные байты, но файл не может быть открыт. В большинстве случаев файл поврежден и не может быть просмотрен. Я искал в Google решение и просто нашел других с такой же проблемой и без решения. Так что, помогая мне, вы также помогаете кому-то еще, нуждающемуся в решении.

Код сервера:

#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 ]

4 голосов
/ 26 июня 2010

Во-первых, вы должны попытаться увеличить буфер до чего-то большего. Чем больше буфер, тем эффективнее транспорт (только не преувеличивайте и не занимайте слишком много локальной памяти).

Во-вторых, вы должны проверить и , чтобы использовать длины, возвращаемые функциями чтения и записи. Во всех ваших операциях чтения и записи вы только проверяете, было ли что-то прочитано / записано, но вы должны использовать этот счетчик байтов при следующей операции записи / чтения. Например, если клиент сообщает, что он прочитал 1 байт, вы должны записать только 1 байт на диск. Кроме того, если у вас есть 1024 байта, считанные с сервера, вы должны попытаться записать 1024 байта на диск, что может не произойти при этом вызове, и вам может потребоваться еще один вызов для записи для завершения операции.

Я знаю, это звучит как большая работа, но именно так это должно быть сделано, чтобы гарантировать операции ввода-вывода. Все операции чтения и записи в основном должны выполняться внутри их собственных циклов.

2 голосов
/ 26 июня 2010

Вы точно знаете, что в вашем двоичном файле образа нет последовательности байтов 0x48 0x69 ("Hi")?Ваш цикл чтения завершится, как только получит эту последовательность.Кроме того, вы вызываете strcmp() с символьным буфером, длина которого составляет два байта, и почти наверняка гарантирует, что у него нет нулевого завершающего байта ('\0').

Вы также можете исследовать, как отличаются файлы?Инструмент cmp может дать вам список байтов, которые различаются между источником и назначением.

Для правильности вы определенно хотите проверить возвращаемые результаты из read(), write(), send(),и так далее.Возможность короткого чтения и записи с использованием сокетов очень высока, поэтому жизненно важно, чтобы ваш код мог обрабатывать случаи, когда не все данные передаются.Поскольку вы не отслеживаете, сколько байтов возвращается recv(), возможно, вы получили только один байт, но записали два байта в вызове write() после него.

Для большей производительностиБуфер поможет снизить издержки системных вызовов для перемещения данных, хотя допустимо выполнять меньшие операции, если пропускная способность и задержка не являются основными проблемами.И так не должно быть до тех пор, пока у вас не будет правильного поведения.

Наконец, для совместимости с менее просвещенными операционными системами вы должны открывать файлы в двоичном режиме с помощью "rb" и "wb", чтобы символы новой строки не искажались во времязапись.

0 голосов
/ 27 июня 2010

Другие указали на проблемы с вашим кодом, а именно:

  • без использования возвращаемых значений вызовов read(2) и write(2),
  • смешивание двоичных данных и данных управления символами,
  • использование strcmp(3) для строк, которые могут не заканчиваться нулем (позвольте мне отметить, что использование функций, полагающихся на завершение нуля для данных, полученных из сети, как правило, не очень хорошая идея и часто приводит к переполнению буфера.)

Вам было бы намного лучше определить простой протокол для передачи файлов (прочитайте "Последняя страница SO_LINGER или: почему мой tcp ненадежен" , если вы хотитезнаю почему.) Пусть серверная сторона заранее знает, сколько данных вы отправляете - перед передачей должен быть заголовок фиксированного размера, содержащий длину файла (который может также включать имя файла, но тогда вам нужно будет передать длину этогоимя тоже.) Обратите внимание на номер порядковый номер - всегда отправляйте номера в сетевой порядок байтов .

, так как вы находитесь на Linux, позвольте мне также указать вам sendfile(2), который является очень эффективным способом отправки файлов, поскольку он позволяет избежать копирования данных в / из пользовательского пространства.

0 голосов
/ 26 июня 2010

Проблема в том, что вы открываете файлы в режиме text с fopen(..., "r") и fopen(..., "w").Вам необходимо использовать двоичный режим ("rb" и "wb") для нетекстовых файлов.

0 голосов
/ 26 июня 2010

Для чтения текстовых файлов ascii допустим буфер char.

Для чтения двоичных данных вам нужно будет использовать unsigned char, в противном случае ваши данные будут заблокированы, поскольку двоичные данные являются беззнаковыми байтами.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...