Я пытаюсь отправить файлы по сокетам. Я создал программу, и она работает на типы файлов, такие как .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;
}
Во-первых, вы должны попробовать увеличить буфер до чего-то большего. Чем больше буфер, тем эффективнее транспорт (только не преувеличивайте и не потребляйте слишком много локальной памяти).
Во-вторых, вы должны проверить, что и используют длины, возвращаемые функциями чтения и записи. Во всех ваших операциях чтения и записи вы только проверяете, было ли что-то прочитано / записано, но вы должны использовать это количество байтов при следующей операции записи / чтения. Например, если клиент сообщает, что он прочитал 1 байт, вы должны записать на диск только 1 байт. Кроме того, если у вас есть 1024 байта, прочитанные с сервера, вы должны попытаться записать 1024 байта на диск, что может не произойти в этом вызове, и вам может потребоваться еще один вызов записи для завершения операции.
Я знаю, что это звучит как большая работа, но именно так это нужно делать, чтобы гарантировать операции ввода-вывода. Все операции чтения и записи в основном должны выполняться внутри их собственных циклов.
Для чтения текстовых файлов ascii допустим буфер char
.
Для чтения двоичных данных вам нужно будет использовать unsigned char
, иначе ваши данные будут сброшены, поскольку двоичные данные представляют собой байты без знака.
Проблема в том, что вы открываете файлы в режиме text с помощью fopen (..., "r")
и fopen (..., "ш")
. Вам необходимо использовать двоичный режим ( «rb»
и «wb»
) для нетекстовых файлов.
Знаете ли вы, что в вашем двоичном файле образа нет последовательности байтов 0x48 0x69
( «Привет»
)? Ваш цикл чтения завершится, как только он получит эту последовательность. Кроме того, вы вызываете strcmp ()
с символьным буфером длиной два байта и почти наверняка гарантируете, что он не имеет нулевого завершающего байта ( '\ 0'
).
Вы также можете изучить, чем файлы различаются? Инструмент cmp
может предоставить вам список байтов, которые различаются между источником и местом назначения.
Для правильности вы определенно хотите проверить результаты, возвращаемые read ()
, write ()
, send ()
и так далее. Вероятность коротких операций чтения и записи с помощью сокетов очень высока, поэтому жизненно важно, чтобы ваш код мог обрабатывать случаи, когда не все данные передаются. Поскольку вы не отслеживаете, сколько байтов возвращает recv ()
, возможно, что вы получили только один байт, но записали два байта в вызове write ()
после Это.
Для повышения производительности больший буфер поможет снизить накладные расходы на системные вызовы для перемещения данных, хотя допустимо выполнение меньших операций, если полоса пропускания и задержка не являются первоочередными задачами. И этого не должно быть, пока вы не научитесь правильно вести себя.
Наконец, для совместимости с менее продвинутыми операционными системами вы должны открывать файлы в двоичном режиме с помощью «rb»
и «wb»
, чтобы символы новой строки не искажались во время записи.
Другие указали на проблемы с вашим кодом, а именно:
read (2)
и write (2)
, strcmp (3)
для строк, которые могут не оканчиваться нулем (позвольте мне отметить, что использование функций, полагающихся на завершение нуля для данных, полученных от сеть, как правило, не является хорошей идеей и часто приводит к переполнению буфера.) Вам было бы намного лучше определить простой протокол для передачи файлов (прочтите "Окончательную страницу SO_LINGER, или: почему мой tcp ненадежно », если вы хотите знать, почему.) Сообщите стороне сервера заранее, сколько данных вы отправляете - перед передачей укажите заголовок фиксированного размера, содержащий длину файла (который также может включать имя файла, но тогда вам нужно будет передать и длину этого имени.) Обратите внимание на порядковый номер - всегда отправляйте номера в сети порядок байтов .
Поскольку вы работаете в Linux, позвольте мне также указать вам на sendfile (2)
, который является очень эффективным способом отправки файлов, поскольку он позволяет избежать копирования данных в / из пользовательского пространства.