嗨使用套接字编程创建一个简单的ftp程序时,我遇到了以下问题。
关于我的申请的简介
服务器端:读取客户端请求的文件,然后将其写在客户端套接字上。
客户端:它读取客户端发送的数据并保存到磁盘。
当我从服务器传输普通文本文件时,我在客户端获取正确的文件。但是当我传输一些其他文件如pdfs或可执行文件时,当我比较两个文件时,它们具有相同的大小,但是我的客户端保存到磁盘的文件已损坏。
例如,如果我的服务器将4000字节的二进制文件写入客户端套接字。然后,当我的客户端将其保存到磁盘时,大小相同4000字节。但是当我使用chmod给它执行权限并尝试执行它时,我收到的错误就像:无法执行二进制文件。
同样,当我传输pdf文件时,当我双击打开时,没有显示任何内容。
在客户端,我也检查了读取调用是否正在读取整个数据,并且是从套接字读取整个数据。
这是否与序列化有关。我的客户端和服务器都运行在仅使用相同编译器编译的同一系统上。
我的程序很大,有很多错误检查,所以我在这里粘贴了一些修改过的代码,解释了问题。为了简单起见,我也使用了许多静态的东西:
server.c
int main(int argc, char* argv[])
{
// validate proper usage
if (argc != 4)
{
fprintf(stderr, "Usage %s <serverBindIP> <serverBindPort> <CredentialsFilePath>\n", argv[0]);
exit(-1);
}
// create signal hanlder's
// TODO
// store the command line arguments supplied
char* ip = argv[1];
int port = htons(atoi(argv[2]));
char* passwd_file = argv[3];
struct sockaddr_in server_addr, client_addr;
int server_fd, client_fd, result;
socklen_t length;
// Create an internet domain TCP socket
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd == -1)
{
fprintf(stderr, "Unable to create socket\n");
exit(-1);
}
server_addr.sin_family = AF_INET;
server_addr.sin_port = port;
server_addr.sin_addr.s_addr = inet_addr(ip);
// bind socket to an network interface
result = bind(server_fd, (struct sockaddr*) &server_addr, sizeof(server_addr));
if (result == -1)
{
fprintf(stderr, "Unable to bind socket\n");
exit(-1);
}
// mark the socket used for incoming requests
listen(server_fd, 5);
// accept an incoming connection
printf("Waiting for incoming connection\n");
length = sizeof(client_addr);
client_fd = accept(server_fd, (struct sockaddr*) &client_addr, &length);
if (client_fd == -1)
{
fprintf(stderr, "Unable to accept peer connection\n");
exit(-1);
}
// read and send one full file
struct stat stats;
stat("/home/xpansat/book.pdf", &stats);
int size = stats.st_size;
// send size of file to the client
write(client_fd, &size, sizeof(int));
FILE* in = fopen("/home/xpansat/book.pdf", "rb");
char *buffer = malloc(size);
fread(buffer, 1, size, in);
write(client_fd, buffer, size);
fclose(in);
返回0; }
client.c
int main(int argc, char* argv[])
{
// validate proper usage
if (argc != 3)
{
fprintf(stderr, "Usage: %s <serverIP> <serverPort>\n", argv[0]);
exit(-1);
}
// store the command line arguments
char *server_ip = argv[1];
int server_port = htons(atoi(argv[2]));
// stores address of remote server to connect
struct sockaddr_in server_addr;
int fd, option;
fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd == -1)
{
fprintf(stderr, "Error creating socket\n");
exit(-1);
}
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = inet_addr(server_ip);
server_addr.sin_port = server_port;
if (connect(fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1)
{
fprintf(stderr, "Error connecting to server\n");
exit(-1);
}
int size = 0;
// read file size first
read(fd, &size, sizeof(int));
int bytes_read = 0;
int to_read = size;
FILE* out = fopen("book2.pdf", "wb");
char *buffer = malloc(size);
do
{
bytes_read = read(fd, buffer, to_read);
printf("To read: %d\n", to_read);
printf("Data read: %d\n", bytes_read);
to_read = to_read - bytes_read;
// save content to disk
fwrite(buffer, 1, bytes_read, out);
} while (to_read != 0);
return 0;
}
虽然我对这段代码有很好的建议,但是我知道这个贴在我这里的代码实际上没有指明我的问题,因为我发现在填充缓冲区以发送给客户端时我正在复制数据使用strncpy函数进入它,这使得可执行文件损坏了一些方法(可能因为它放到了最后的\ 0但是我不确定为什么)。所以实际上解决了我的问题的是:用memcpy函数替换所有strncpy函数,现在我也可以正确地传输二进制文件。所以,这解决了我的问题。
答案 0 :(得分:0)
我对服务器进行了此更改,它突然开始工作。
// send size of file to the client
write(client_fd, &size, sizeof(int));
我还在顶部添加了一些#include
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/stat.h>
#include <string.h>
答案 1 :(得分:-2)
注意:对于fread和fwrite,参数的顺序是:
&amp; buffer,sizeOfElement,元素数量,fileDescriptor
对于发布的代码,指示是每个元素长度为1个字节
并且有&#39; fileSize&#39;元素数量
(通常)tcp / ip不会传送大于~1600字节的数据包 所以,通常情况下,发送数据的速度就越快。
正常的方法是在循环中使用select()和read(),直到select()的超时到期,其中传递的块号和第一个数据包的块长度字段指示输入缓冲区中的位置放置下一个读取数据块。
记得在每次调用select()之前总是重新设置超时变量。
TCP / IP中数据块大小的这种大小限制表明数据应该一次写入合理的块大小,比如每次调用写入1024个数据字节()
设置选择/读取循环时,应将读取套接字设置为非阻塞(特别是),因为最后一个块(可能)的长度不是完整的读取块。 客户端应该在每次read()之后检查读取的字节数,以确保收到完整的块。
最好发送一个初始块,其中包含文件名,要传输的实际字节数,整个文件的校验和以及数据块大小。
每个数据包都应有一个标头,指示数据包中的块编号和数据字节数。
(对于包含文件名和总文件大小的初始块,使用-1,以及&#39;此数据包大小)
如果没有先从客户端获得文件传输正确的指示,请不要关闭写入套接字。
确保客户端已读取所有数据, 让客户发送&#39; ack&#39;收到每个数据块后的数据包。 建议ack包包含接收包中的块号。
然后,在服务器收到最后一个ack数据包后,服务器可以关闭套接字。如果服务器收到nak数据包,则表示需要再次进行文件传输。
the client should be doing these things,
1) waiting for the select() to timeout,
2) assuring that all blocks were received
3) assuring the file checksum matches the passed checksum from data block 0
4) sending a ack for each packet received
5) sending a final ack if the checksum matches, else send a nak
注意: 虽然速度较慢,但我喜欢将select()/ read()循环设置为仅在一次读取单个字节,这意味着循环的迭代次数要多得多,但会使读取套接字设置为非阻塞a更安全的方法。
上述看起来似乎比单个写入和单个读取要复杂得多,但它会消除未发现的通信错误和未发现的损坏数据