我正在尝试通过套接字发送文件。我创建了一个程序,它适用于.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;
}
答案 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)
,这是一种非常有效的文件发送方式,因为它可以避免将数据复制到用户区域。