I am trying to make a file transfer between server and client, but is working very badly. Basically what needs to happen is:
1) The client send a txt file to the server (I called it "quotidiani.txt")
2) The server saves it in another txt file ("receive.txt")
3) The server runs a script on it that modifies it and saves it with another name ("output.txt")
4) The server send the file back to the client that saves it (on the same socket) with the name (final.txt)
我正在尝试在服务器和客户端之间进行文件传输,但是工作得很糟糕。基本上需要发生什么:1)客户端向服务器发送一个txt文件(我把它称之为“quotidiani.txt”)2)服务器保存在另一个txt文件(“receive.txt”)3)服务器运行一个脚本,修改并保存它另一个名字(“output.txt”)4)服务器将文件发送回客户机保存它(在相同的套接字)的名字(final.txt)
The problem is that the first file (quotidiani.txt) is read just for a little part, and then there are some errors. I'd like someone to help me understand and correct my errors.
问题是第一个文件(common diani.txt)只读取了一小部分,然后出现了一些错误。我希望有人能帮助我理解和改正我的错误。
Here's my code:
这是我的代码:
client.c:
client.c:
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <signal.h>
#include <ctype.h>
#include <arpa/inet.h>
#include <netdb.h>
#define PORT 20000
#define LENGTH 512
void error(const char *msg)
{
perror(msg);
exit(1);
}
int main(int argc, char *argv[])
{
/* Variable Definition */
int sockfd;
int nsockfd;
char revbuf[LENGTH];
struct sockaddr_in remote_addr;
/* Get the Socket file descriptor */
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
fprintf(stderr, "ERROR: Failed to obtain Socket Descriptor! (errno = %d)\n",errno);
exit(1);
}
/* Fill the socket address struct */
remote_addr.sin_family = AF_INET;
remote_addr.sin_port = htons(PORT);
inet_pton(AF_INET, "127.0.0.1", &remote_addr.sin_addr);
bzero(&(remote_addr.sin_zero), 8);
/* Try to connect the remote */
if (connect(sockfd, (struct sockaddr *)&remote_addr, sizeof(struct sockaddr)) == -1)
{
fprintf(stderr, "ERROR: Failed to connect to the host! (errno = %d)\n",errno);
exit(1);
}
else
printf("[Client] Connected to server at port %d...ok!\n", PORT);
/* Send File to Server */
//if(!fork())
//{
char* fs_name = "/home/aryan/Desktop/quotidiani.txt";
char sdbuf[LENGTH];
printf("[Client] Sending %s to the Server... ", fs_name);
FILE *fs = fopen(fs_name, "r");
if(fs == NULL)
{
printf("ERROR: File %s not found.\n", fs_name);
exit(1);
}
bzero(sdbuf, LENGTH);
int fs_block_sz;
while((fs_block_sz = fread(sdbuf, sizeof(char), LENGTH, fs)) > 0)
{
if(send(sockfd, sdbuf, fs_block_sz, 0) < 0)
{
fprintf(stderr, "ERROR: Failed to send file %s. (errno = %d)\n", fs_name, errno);
break;
}
bzero(sdbuf, LENGTH);
}
printf("Ok File %s from Client was Sent!\n", fs_name);
//}
/* Receive File from Server */
printf("[Client] Receiveing file from Server and saving it as final.txt...");
char* fr_name = "/home/aryan/Desktop/progetto/final.txt";
FILE *fr = fopen(fr_name, "a");
if(fr == NULL)
printf("File %s Cannot be opened.\n", fr_name);
else
{
bzero(revbuf, LENGTH);
int fr_block_sz = 0;
while((fr_block_sz = recv(sockfd, revbuf, LENGTH, 0)) > 0)
{
int write_sz = fwrite(revbuf, sizeof(char), fr_block_sz, fr);
if(write_sz < fr_block_sz)
{
error("File write failed.\n");
}
bzero(revbuf, LENGTH);
if (fr_block_sz == 0 || fr_block_sz != 512)
{
break;
}
}
if(fr_block_sz < 0)
{
if (errno == EAGAIN)
{
printf("recv() timed out.\n");
}
else
{
fprintf(stderr, "recv() failed due to errno = %d\n", errno);
}
}
printf("Ok received from server!\n");
fclose(fr);
}
close (sockfd);
printf("[Client] Connection lost.\n");
return (0);
}
server.c
server.c
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <signal.h>
#include <ctype.h>
#include <arpa/inet.h>
#include <netdb.h>
#define PORT 20000
#define BACKLOG 5
#define LENGTH 512
void error(const char *msg)
{
perror(msg);
exit(1);
}
int main ()
{
/* Defining Variables */
int sockfd;
int nsockfd;
int num;
int sin_size;
struct sockaddr_in addr_local; /* client addr */
struct sockaddr_in addr_remote; /* server addr */
char revbuf[LENGTH]; // Receiver buffer
/* Get the Socket file descriptor */
if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1 )
{
fprintf(stderr, "ERROR: Failed to obtain Socket Descriptor. (errno = %d)\n", errno);
exit(1);
}
else
printf("[Server] Obtaining socket descriptor successfully.\n");
/* Fill the client socket address struct */
addr_local.sin_family = AF_INET; // Protocol Family
addr_local.sin_port = htons(PORT); // Port number
addr_local.sin_addr.s_addr = INADDR_ANY; // AutoFill local address
bzero(&(addr_local.sin_zero), 8); // Flush the rest of struct
/* Bind a special Port */
if( bind(sockfd, (struct sockaddr*)&addr_local, sizeof(struct sockaddr)) == -1 )
{
fprintf(stderr, "ERROR: Failed to bind Port. (errno = %d)\n", errno);
exit(1);
}
else
printf("[Server] Binded tcp port %d in addr 127.0.0.1 sucessfully.\n",PORT);
/* Listen remote connect/calling */
if(listen(sockfd,BACKLOG) == -1)
{
fprintf(stderr, "ERROR: Failed to listen Port. (errno = %d)\n", errno);
exit(1);
}
else
printf ("[Server] Listening the port %d successfully.\n", PORT);
int success = 0;
while(success == 0)
{
sin_size = sizeof(struct sockaddr_in);
/* Wait a connection, and obtain a new socket file despriptor for single connection */
if ((nsockfd = accept(sockfd, (struct sockaddr *)&addr_remote, &sin_size)) == -1)
{
fprintf(stderr, "ERROR: Obtaining new Socket Despcritor. (errno = %d)\n", errno);
exit(1);
}
else
printf("[Server] Server has got connected from %s.\n", inet_ntoa(addr_remote.sin_addr));
/*Receive File from Client */
char* fr_name = "/home/aryan/Desktop/receive.txt";
FILE *fr = fopen(fr_name, "a");
if(fr == NULL)
printf("File %s Cannot be opened file on server.\n", fr_name);
else
{
bzero(revbuf, LENGTH);
int fr_block_sz = 0;
while((fr_block_sz = recv(nsockfd, revbuf, LENGTH, 0)) > 0)
{
int write_sz = fwrite(revbuf, sizeof(char), fr_block_sz, fr);
if(write_sz < fr_block_sz)
{
error("File write failed on server.\n");
}
bzero(revbuf, LENGTH);
if (fr_block_sz == 0 || fr_block_sz != 512)
{
break;
}
}
if(fr_block_sz < 0)
{
if (errno == EAGAIN)
{
printf("recv() timed out.\n");
}
else
{
fprintf(stderr, "recv() failed due to errno = %d\n", errno);
exit(1);
}
}
printf("Ok received from client!\n");
fclose(fr);
}
/* Call the Script */
system("cd ; chmod +x script.sh ; ./script.sh");
/* Send File to Client */
//if(!fork())
//{
char* fs_name = "/home/aryan/Desktop/output.txt";
char sdbuf[LENGTH]; // Send buffer
printf("[Server] Sending %s to the Client...", fs_name);
FILE *fs = fopen(fs_name, "r");
if(fs == NULL)
{
fprintf(stderr, "ERROR: File %s not found on server. (errno = %d)\n", fs_name, errno);
exit(1);
}
bzero(sdbuf, LENGTH);
int fs_block_sz;
while((fs_block_sz = fread(sdbuf, sizeof(char), LENGTH, fs))>0)
{
if(send(nsockfd, sdbuf, fs_block_sz, 0) < 0)
{
fprintf(stderr, "ERROR: Failed to send file %s. (errno = %d)\n", fs_name, errno);
exit(1);
}
bzero(sdbuf, LENGTH);
}
printf("Ok sent to client!\n");
success = 1;
close(nsockfd);
printf("[Server] Connection with Client closed. Server will wait now...\n");
while(waitpid(-1, NULL, WNOHANG) > 0);
//}
}
}
2 个解决方案
#1
7
Some comments in no particular order:
一些评论没有特别的顺序:
-
You're passing up the opportunity to know exact errors too often:
你太频繁地错过了知道准确错误的机会:
if(listen(sockfd,BACKLOG) == -1) { printf("ERROR: Failed to listen Port %d.\n", PORT); return (0); }
This block should definitely include a
perror("listen")
or something similar. Always includeperror()
orstrerror()
in every error handling block when the error details will be reported viaerrno
. Having exact failure reasons will save you hours when programming and will save you and your users hours when things don't work as expected in the future.这个模块应该包含一个perror(“listen”)或类似的东西。当错误细节将通过errno报告时,始终在每个错误处理块中包含perror()或strerror()。有明确的失败原因将在编程时为您节省时间,并将为您和您的用户节省时间,当事情在未来不像预期的那样工作时。
-
Your error handling needs some further standardizing:
您的错误处理需要进一步标准化:
if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1 ) { printf("ERROR: Failed to obtain Socket Descriptor.\n"); return (0); }
This should not
return 0
because that will signal to the shell that the program ran to completion without error. You shouldreturn 1
(or useEXIT_SUCCESS
andEXIT_FAILURE
) to signal an abnormal exit.这不应该返回0,因为这将向shell发出信号,表明程序运行到完成时没有出现错误。您应该返回1(或使用EXIT_SUCCESS和EXIT_FAILURE)以发出异常退出的信号。
else printf("[Server] Server has got connected from %s.\n", inet_ntoa(addr_remote.sin_addr)); /*Receive File from Client */
In this preceding block you've gotten an error condition but continue executing anyway. That's a quick way to get very undesirable behavior. This should either re-start the main server loop or exit the child process or something similar. (Depends if you keep the multi-process server.)
在前面的代码块中,您得到了一个错误条件,但是仍然继续执行。这是一种让人产生非常不希望的行为的快速方法。这应该重新启动主服务器循环或退出子进程或类似的操作。(这取决于您是否保留多进程服务器。)
if(!fork()) {
The preceding block forgot to account for
fork()
failing.fork()
can, and does fail -- especially in shared hosting environments common at universities -- so you should be prepared for the full, complicated three possible return values fromfork()
: failure, child, parent.前面的块忘记解释fork()失败。fork()可能会失败,而且确实会失败——尤其是在大学常见的共享托管环境中——所以您应该为fork()的完整、复杂的三个可能的返回值做好准备:失败、子节点、父节点。
-
It appears you're using
fork()
indiscriminately; your client and server are both very simple and the way they are designed to run means they cannot be used to service multiple clients simultaneously. You should probably stick to exactly one process for each, at least until the algorithm is perfectly debugged and you figure out some way to run multiple clients simultaneously. I expect this is the source of the problem you're encountering now.看起来你在用叉();您的客户机和服务器都非常简单,它们的运行方式意味着它们不能同时用于服务多个客户机。您可能应该对每个进程只执行一个进程,至少在算法被完美地调试并找到某种同时运行多个客户机的方法之前是这样。我想这就是你现在遇到的问题的根源。
-
You need to use functions to encapsulate details; write a function to connect to the server, a function to send the file, a function to write the file, etc. Write a function to handle the complicated partial writes. (I especially recommend stealing the
writen
function from the Advanced Programming in the Unix Environment book's source code. Filelib/writen.c
.) If you write the functions correctly you can re-use them in both the client and server. (Something like placing them inutils.c
and compiling the programs likegcc -o server server.c utils.c
.)您需要使用函数来封装细节;编写连接服务器的函数、发送文件的函数、编写文件的函数等。编写处理复杂的部分写入的函数。(我特别推荐在Unix环境书籍的源代码中,从高级编程中窃取writen函数。文件lib / writen.c)。如果您正确地编写函数,您可以在客户端和服务器中重用它们。比如把它们放在utils。c和编译程序,如gcc -o服务器服务器。c utils.c。)
Having smaller functions that each do one thing will allow you to focus on smaller amounts of code at a time and write little tests for each that will help you narrow down which sections of code still need improvement.
让每个函数都做一件事的小函数可以让您一次专注于更小的代码量,并为每个函数编写小测试,这将帮助您缩小仍然需要改进的代码部分。
#2
6
One discussion point seems was missing here, So I thought to mention it here.
这里似乎缺少一个讨论点,所以我想在这里提一下。
Let us very quicky understand TCP's data transfer. There are three steps a)Connection Establishment, b)Data Transfer, c)Connection Termination
让我们快速理解TCP的数据传输。a)建立连接,b)数据传输,c)终止连接
Now here a client is sending a file to a server, over a TCP socket.
现在,客户机通过TCP套接字将文件发送到服务器。
The server does some processing on the file and sends it back to the client.
服务器对文件进行一些处理并将其发送回客户端。
Now all the 3 steps need to done. Connection Establishment is done by calling connect. Data reading/writing is done by recv/send here and connection termination is done by close.
现在需要完成这三个步骤。连接建立是通过调用connect来完成的。数据读写由recv/send完成,连接终止由close完成。
The server here is reading data in a loop using recv. Now when the loop will come to an end? When the recv returns 0 or may be less than 0 on error. When recv returns 0? -> When the other side has closed the connection. (When the TCP FIN segement has been recived by this side).
这里的服务器正在使用recv在循环中读取数据。当循环结束的时候?当recv返回0或在错误时可能小于0时。当recv返回0 ?-当另一方已关闭连接时,>。(当TCP的FIN分割被这方面接受时)。
So in this code when the client has finished sending the file,I have used a shutdown function, which sends the FIN segement from the client side and the server's recv can now return 0 and the program continues.(Half way close, since the client also needs to read data subsequently).
因此,在这段代码中,当客户端完成文件发送后,我使用了一个shutdown函数,它从客户端发送FIN片段,服务器的recv现在可以返回0,程序继续。(由于客户端随后也需要读取数据,所以接近了一半)。
(Just for understanding please note TCP's connection Establisment is a 3 way Handshake and connection termination is a 4 way handshake.)
(仅仅为了理解,请注意TCP连接的建立是一个3路握手和连接终止是一个4路握手。)
Similarly if you forget closing the connection socket on the server side, the client's recv will also block for ever. I think that was the reason for using ctrl c to stop the client sometimes which you have mentioned.
类似地,如果忘记关闭服务器端上的连接套接字,客户端的recv也将永远阻塞。我想这就是使用ctrl c来阻止客户的原因。
You may pl. refer any standard Networking book or the rfc http://www.ietf.org/rfc/rfc793.txt for learning more about TCP
您可以参考任何标准的网络书籍或rfc http://www.ietf.org/rfc/rfc793.txt了解TCP的更多信息
I have pasted the modified code and also little added some comments,
我粘贴了修改后的代码,也没有添加任何注释,
Hope this explanation will help.
希望这个解释能有所帮助。
Modified client code:
修改客户端代码:
while((fs_block_sz = fread(sdbuf, sizeof(char), LENGTH, fs)) > 0)
{
if(send(sockfd, sdbuf, fs_block_sz, 0) < 0)
{
fprintf(stderr, "ERROR: Failed to send file %s. (errno = %d)\n", fs_name, errno);
exit(1);
}
bzero(sdbuf, LENGTH);
}
/*Now we have sent the File's data, what about server's recv?
Recv is blocked and waiting for data to arrive or if the protocol
stack receives a TCP FIN segment ..then the recv will return 0 and
the server code can continue */
/*Sending the TCP FIN segment by shutdown and this is half way
close, since the client also needs to read data subsequently*/
shutdown(sockfd, SHUT_WR);
printf("Ok File %s from Client was Sent!\n", fs_name);
#1
7
Some comments in no particular order:
一些评论没有特别的顺序:
-
You're passing up the opportunity to know exact errors too often:
你太频繁地错过了知道准确错误的机会:
if(listen(sockfd,BACKLOG) == -1) { printf("ERROR: Failed to listen Port %d.\n", PORT); return (0); }
This block should definitely include a
perror("listen")
or something similar. Always includeperror()
orstrerror()
in every error handling block when the error details will be reported viaerrno
. Having exact failure reasons will save you hours when programming and will save you and your users hours when things don't work as expected in the future.这个模块应该包含一个perror(“listen”)或类似的东西。当错误细节将通过errno报告时,始终在每个错误处理块中包含perror()或strerror()。有明确的失败原因将在编程时为您节省时间,并将为您和您的用户节省时间,当事情在未来不像预期的那样工作时。
-
Your error handling needs some further standardizing:
您的错误处理需要进一步标准化:
if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1 ) { printf("ERROR: Failed to obtain Socket Descriptor.\n"); return (0); }
This should not
return 0
because that will signal to the shell that the program ran to completion without error. You shouldreturn 1
(or useEXIT_SUCCESS
andEXIT_FAILURE
) to signal an abnormal exit.这不应该返回0,因为这将向shell发出信号,表明程序运行到完成时没有出现错误。您应该返回1(或使用EXIT_SUCCESS和EXIT_FAILURE)以发出异常退出的信号。
else printf("[Server] Server has got connected from %s.\n", inet_ntoa(addr_remote.sin_addr)); /*Receive File from Client */
In this preceding block you've gotten an error condition but continue executing anyway. That's a quick way to get very undesirable behavior. This should either re-start the main server loop or exit the child process or something similar. (Depends if you keep the multi-process server.)
在前面的代码块中,您得到了一个错误条件,但是仍然继续执行。这是一种让人产生非常不希望的行为的快速方法。这应该重新启动主服务器循环或退出子进程或类似的操作。(这取决于您是否保留多进程服务器。)
if(!fork()) {
The preceding block forgot to account for
fork()
failing.fork()
can, and does fail -- especially in shared hosting environments common at universities -- so you should be prepared for the full, complicated three possible return values fromfork()
: failure, child, parent.前面的块忘记解释fork()失败。fork()可能会失败,而且确实会失败——尤其是在大学常见的共享托管环境中——所以您应该为fork()的完整、复杂的三个可能的返回值做好准备:失败、子节点、父节点。
-
It appears you're using
fork()
indiscriminately; your client and server are both very simple and the way they are designed to run means they cannot be used to service multiple clients simultaneously. You should probably stick to exactly one process for each, at least until the algorithm is perfectly debugged and you figure out some way to run multiple clients simultaneously. I expect this is the source of the problem you're encountering now.看起来你在用叉();您的客户机和服务器都非常简单,它们的运行方式意味着它们不能同时用于服务多个客户机。您可能应该对每个进程只执行一个进程,至少在算法被完美地调试并找到某种同时运行多个客户机的方法之前是这样。我想这就是你现在遇到的问题的根源。
-
You need to use functions to encapsulate details; write a function to connect to the server, a function to send the file, a function to write the file, etc. Write a function to handle the complicated partial writes. (I especially recommend stealing the
writen
function from the Advanced Programming in the Unix Environment book's source code. Filelib/writen.c
.) If you write the functions correctly you can re-use them in both the client and server. (Something like placing them inutils.c
and compiling the programs likegcc -o server server.c utils.c
.)您需要使用函数来封装细节;编写连接服务器的函数、发送文件的函数、编写文件的函数等。编写处理复杂的部分写入的函数。(我特别推荐在Unix环境书籍的源代码中,从高级编程中窃取writen函数。文件lib / writen.c)。如果您正确地编写函数,您可以在客户端和服务器中重用它们。比如把它们放在utils。c和编译程序,如gcc -o服务器服务器。c utils.c。)
Having smaller functions that each do one thing will allow you to focus on smaller amounts of code at a time and write little tests for each that will help you narrow down which sections of code still need improvement.
让每个函数都做一件事的小函数可以让您一次专注于更小的代码量,并为每个函数编写小测试,这将帮助您缩小仍然需要改进的代码部分。
#2
6
One discussion point seems was missing here, So I thought to mention it here.
这里似乎缺少一个讨论点,所以我想在这里提一下。
Let us very quicky understand TCP's data transfer. There are three steps a)Connection Establishment, b)Data Transfer, c)Connection Termination
让我们快速理解TCP的数据传输。a)建立连接,b)数据传输,c)终止连接
Now here a client is sending a file to a server, over a TCP socket.
现在,客户机通过TCP套接字将文件发送到服务器。
The server does some processing on the file and sends it back to the client.
服务器对文件进行一些处理并将其发送回客户端。
Now all the 3 steps need to done. Connection Establishment is done by calling connect. Data reading/writing is done by recv/send here and connection termination is done by close.
现在需要完成这三个步骤。连接建立是通过调用connect来完成的。数据读写由recv/send完成,连接终止由close完成。
The server here is reading data in a loop using recv. Now when the loop will come to an end? When the recv returns 0 or may be less than 0 on error. When recv returns 0? -> When the other side has closed the connection. (When the TCP FIN segement has been recived by this side).
这里的服务器正在使用recv在循环中读取数据。当循环结束的时候?当recv返回0或在错误时可能小于0时。当recv返回0 ?-当另一方已关闭连接时,>。(当TCP的FIN分割被这方面接受时)。
So in this code when the client has finished sending the file,I have used a shutdown function, which sends the FIN segement from the client side and the server's recv can now return 0 and the program continues.(Half way close, since the client also needs to read data subsequently).
因此,在这段代码中,当客户端完成文件发送后,我使用了一个shutdown函数,它从客户端发送FIN片段,服务器的recv现在可以返回0,程序继续。(由于客户端随后也需要读取数据,所以接近了一半)。
(Just for understanding please note TCP's connection Establisment is a 3 way Handshake and connection termination is a 4 way handshake.)
(仅仅为了理解,请注意TCP连接的建立是一个3路握手和连接终止是一个4路握手。)
Similarly if you forget closing the connection socket on the server side, the client's recv will also block for ever. I think that was the reason for using ctrl c to stop the client sometimes which you have mentioned.
类似地,如果忘记关闭服务器端上的连接套接字,客户端的recv也将永远阻塞。我想这就是使用ctrl c来阻止客户的原因。
You may pl. refer any standard Networking book or the rfc http://www.ietf.org/rfc/rfc793.txt for learning more about TCP
您可以参考任何标准的网络书籍或rfc http://www.ietf.org/rfc/rfc793.txt了解TCP的更多信息
I have pasted the modified code and also little added some comments,
我粘贴了修改后的代码,也没有添加任何注释,
Hope this explanation will help.
希望这个解释能有所帮助。
Modified client code:
修改客户端代码:
while((fs_block_sz = fread(sdbuf, sizeof(char), LENGTH, fs)) > 0)
{
if(send(sockfd, sdbuf, fs_block_sz, 0) < 0)
{
fprintf(stderr, "ERROR: Failed to send file %s. (errno = %d)\n", fs_name, errno);
exit(1);
}
bzero(sdbuf, LENGTH);
}
/*Now we have sent the File's data, what about server's recv?
Recv is blocked and waiting for data to arrive or if the protocol
stack receives a TCP FIN segment ..then the recv will return 0 and
the server code can continue */
/*Sending the TCP FIN segment by shutdown and this is half way
close, since the client also needs to read data subsequently*/
shutdown(sockfd, SHUT_WR);
printf("Ok File %s from Client was Sent!\n", fs_name);