这适用于C系统中的Linux系统。它涉及网络编程。这是一个文件传输程序。
我已经遇到了这个问题,这段代码无法预测。它要么完全成功,要么客户端中的while循环永远不会结束。我发现这是因为fileLength变量有时会是一个巨大的(负面或正面)值,我认为这是因为ntohl犯了一些错误。当我输入一个印刷声明时,它似乎完美无缺,没有错误。
以下是客户端代码:
//...here includes relevant header files
int main (int argc, char *argv[]) {
//socket file descriptor
int sockfd;
if (argc != 2) {
fprintf (stderr, "usage: client hostname\n");
exit(1);
}
//...creates socket file descriptor, connects to server
//create buffer for filename
char name[256];
//recieve filename into name buffer, bytes recieved stored in numbytes
if((numbytes = recv (sockfd, name, 255 * sizeof (char), 0)) == -1) {
perror ("recv");
exit(1);
}
//Null terminator after the filename
name[numbytes] = '\0';
//length of the file to recieve from server
long fl;
memset(&fl, 0, sizeof fl);
//recieve filelength from server
if((numbytes = recv (sockfd, &fl, sizeof(long), 0)) == -1) {
perror ("recv");
exit(1);
}
//convert filelength to host format
long fileLength = ntohl(fl);
//check to make sure file does not exist, so that the application will not overwrite exisitng files
if (fopen (name, "r") != NULL) {
fprintf (stderr, "file already present in client directory\n");
exit(1);
}
//open file called name in write mode
FILE *filefd = fopen (name, "wb");
//variable stating amount of data recieved
long bytesTransferred = 0;
//Until the file is recieved, keep recieving
while (bytesTransferred < fileLength) {
printf("transferred: %d\ntotal: %d\n", bytesTransferred, fileLength);
//set counter at beginning of unwritten segment
fseek(filefd, bytesTransferred, SEEK_SET);
//buffer of 256 bytes; 1 byte for byte-length of segment, 255 bytes of data
char buf[256];
//recieve segment from server
if ((numbytes = recv (sockfd, buf, sizeof buf, 0)) == -1) {
perror ("recv");
exit(1);
}
//first byte of buffer, stating number of bytes of data in recieved segment
//converting from char to short requires adding 128, since the char ranges from -128 to 127
short bufLength = buf[0] + 128;
//write buffer into file, starting after the first byte of the buffer
fwrite (buf + 1, 1, bufLength * sizeof (char), filefd);
//add number of bytes of data recieved to bytesTransferred
bytesTransferred += bufLength;
}
fclose (filefd);
close (sockfd);
return 0;
}
这是服务器代码:
//...here includes relevant header files
int main (int argc, char *argv[]) {
if (argc != 2) {
fprintf (stderr, "usage: server filename\n");
exit(1);
}
//socket file descriptor, file descriptor for specific client connections
int sockfd, new_fd;
//...get socket file descriptor for sockfd, bind sockfd to predetermined port, listen for incoming connections
//...reaps zombie processes
printf("awaiting connections...\n");
while(1) {
//...accepts any incoming connections, gets file descriptor and assigns to new_fd
if (!fork()) {
//close socket file discriptor, only need file descriptor for specific client connection
close (sockfd);
//open a file for reading
FILE *filefd = fopen (argv[1], "rb");
//send filename to client
if (send (new_fd, argv[1], strlen (argv[1]) * sizeof(char), 0) == -1)
{ perror ("send"); }
//put counter at end of selected file, and find length
fseek (filefd, 0, SEEK_END);
long fileLength = ftell (filefd);
//convert length to network form and send it to client
long fl = htonl(fileLength);
//Are we sure this is sending all the bytes??? TEST
if (send (new_fd, &fl, sizeof fl, 0) == -1)
{ perror ("send"); }
//variable stating amount of data unsent
long len = fileLength;
//Until file is sent, keep sending
while(len > 0) {
printf("remaining: %d\ntotal: %d\n", len, fileLength);
//set counter at beginning of unread segment
fseek (filefd, fileLength - len, SEEK_SET);
//length of the segment; 255 unless last segment
short bufLength;
if (len > 255) {
len -= 255;
bufLength = 255;
} else {
bufLength = len;
len = 0;
}
//buffer of 256 bytes; 1 byte for byte-length of segment, 255 bytes of data
char buf[256];
//Set first byte of buffer as the length of the segment
//converting short to char requires subtracting 128
buf[0] = bufLength - 128;
//read file into the buffer starting after the first byte of the buffer
fread(buf + 1, 1, bufLength * sizeof(char), filefd);
//Send data too client
if (send (new_fd, buf, sizeof buf, 0) == -1)
{ perror ("send"); }
}
fclose (filefd);
close (new_fd);
exit (0);
}
close (new_fd);
}
return 0;
}
注意:我已经简化了一些代码,希望它更清晰。 以//开头的任何内容代表一堆代码
答案 0 :(得分:2)
您似乎假设每个send()
将传输指定的完整字节数或将错误输出,并且每个recv()
将与另一侧的recv()
完美配对,例如send()
完全接收send()
发送的字节数(或错误输出),不多也不少。这些都不是安全的假设。
您不会显示设置网络连接的代码。如果您正在使用基于数据报的协议(即UDP),那么您更有可能获得预期的发送/接收边界匹配,但您需要考虑数据包丢失或损坏的可能性。如果您正在使用基于流的协议(即TCP),那么您不必过于担心数据丢失或损坏,但您根本没有理由期望边界匹配行为。
您至少需要三件事:
网络层之上的应用程序级协议。您已经拥有了部分内容,例如首先如何传输文件长度以向客户提供有关预期的内容,但是您需要对所有未预先确定的固定长度的数据进行类似操作。或者,发明另一种传递数据边界的方法。
每个旨在传输多个字节的write()
/ recv()
必须在循环中执行,以便将传输分成多个部分。返回值告诉您传输了多少请求的字节(或者至少传输了多少个字节),如果这个字节少于请求,则必须循环返回以尝试传输剩余的字节。
每个旨在传输多个字节的read()
/ send()
必须在循环中执行,以便将传输分成多个部分。我建议按照string TheText = (serializer.ConvertToType<string>(dictionary["TheText"]))?.Trim();
所述的相同方式构建它,但您也可以选择接收数据,直到看到预先安排的分隔符。但是,基于分隔符的方法更复杂,因为它需要在接收方进行额外的缓冲。
如果没有这些措施,您的服务器和客户端很容易失去同步。其中可能的结果是客户端将文件名的一部分或文件内容的一部分解释为文件长度。
答案 1 :(得分:0)
即使您从该代码中删除了它,我也会做出有根据的猜测,并假设您在此处使用TCP或其他流协议。这意味着服务器发送的数据是一个字节流,recv
次调用与send
次调用所获得的数据量不一致。
对于第一次recv
调用只获取一个字节的数据同样合法,因为它是获取文件名,文件大小和文件的一半。
你说
当我输入打印声明时,
但你不知道在哪里。我在这里做了另一个有根据的猜测,猜测你是在发送文件长度之前在服务器上做的。这恰好让事情变得足够震撼,以至于连接上发送的数据量偶然发生了与客户期望的相符。
您需要定义协议。也许从文件名的长度开始,然后是文件名,然后是文件的长度。或者总是为文件名发送256个字节,无论它有多长。或者将文件名作为以0结尾的字符串发送,并尝试从中找出数据。但是你永远不能假设只是因为你用{X}字节调用send
而recv
调用将得到X字节。
答案 2 :(得分:0)
好吧,经过一些测试,我发现导致问题的问题确实与htonl()有关,尽管我在开始时仍然没有错误地读取数据。并不是说htonl()根本不起作用,但是我没有意识到长期的&#39;根据系统架构有不同的长度(感谢@tofro)。也就是说“长”的长度。 32位和64位操作系统上的整数分别为4个字节和8个字节。和htonl()函数(来自arpa / inet.h)用于4字节整数。我使用的是64位操作系统,这解释了为什么这个价值被捏造了。我通过使用int32_t变量(来自stdint.h)来修复该问题来存储文件长度。因此,本案中的主要问题不是它变得不同步(我认为)。但至于每个人对制定实际协议的建议,我想我知道你究竟是什么意思,我当然明白为什么它很重要,我现在正在努力解决它。谢谢大家的帮助。
答案 3 :(得分:0)
我认为这个问题实际上是你和其他人所说的一切。在服务器代码中,您发送文件的名称,如下所示:
send (new_fd, argv[1], strlen (argv[1]) * sizeof(char), 0);
并在客户端收到它:
recv (sockfd, name, 255 * sizeof (char), 0);
当文件名长度小于255时,这将导致问题。由于TCP是流协议(如@Art所述),send
和{{1}之间没有真正的界限} s,这可能会导致您在奇怪的地方收到您不期望的数据。
我的建议是首先发送文件名的长度,例如:
recv
这将确保您始终了解文件名的确切时间,并确保您不会意外地从文件中间的某个位置读取文件长度(这是我期望目前正在发生的事情)。
修改。
另外,在发送大小的号码时要小心。如果您使用// server
long namelen = htonl(strlen(argv[1]));
send (new_fd, &namelen, 4, 0);
send (new_fd, argv[1], strlen (argv[1]) * sizeof(char), 0);
// client
long namelen;
recv (sockfd, &namelen, 4, 0);
namelen = ntohl(namelen);
recv (sockfd, name, namelen * sizeof (char), 0);
电话,则可能会发送和接收不同的尺寸。这就是为什么我将sizeof
和send
中的大小硬编码为名称长度,以便双方都不会混淆。