Linux 使用套接字的文件传输服务器/客户端
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/11254447/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
File transfer server/client using socket
提问by AscaL
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.
问题是第一个文件(quotidiani.txt)只读取了一小部分,然后出现了一些错误。我希望有人帮助我理解和纠正我的错误。
Here's my code:
这是我的代码:
client.c:
客户端.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
服务器.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);
//}
}
}
采纳答案by sarnold
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.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.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 threepossible return values fromfork()
: failure, child, parent.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 cannotbe 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 Environmentbook'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
.)Having smaller functions that each do one thing will allow you to focus on smaller amounts of code at a time andwrite little tests for each that will help you narrow down which sections of code still need improvement.
您经常错过了解确切错误的机会:
if(listen(sockfd,BACKLOG) == -1) { printf("ERROR: Failed to listen Port %d.\n", PORT); return (0); }
这个块绝对应该包含 a
perror("listen")
或类似的东西。始终包括perror()
或strerror()
在每一个错误处理块时错误的详细信息将通过报告errno
。拥有确切的失败原因将在编程时为您节省数小时,并在未来无法按预期工作时为您和您的用户节省数小时。您的错误处理需要进一步标准化:
if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1 ) { printf("ERROR: Failed to obtain Socket Descriptor.\n"); return (0); }
这应该不是
return 0
,因为这将标志着该外壳的程序运行完成没有错误。您应该return 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 */
在前面的块中,您遇到了错误情况,但仍然继续执行。这是获得非常不受欢迎的行为的快速方法。这应该重新启动主服务器循环或退出子进程或类似的东西。(取决于您是否保留多进程服务器。)
if(!fork()) {
前面的块忘记考虑
fork()
失败。fork()
可以,也确实会失败——尤其是在大学常见的共享托管环境中——所以你应该为完整、复杂的三个可能的返回值做好准备fork()
:失败、子项、父项。看来你是
fork()
乱用的;您的客户端和服务器都非常简单,而且它们的运行方式意味着它们不能同时用于为多个客户端提供服务。您可能应该为每个进程坚持一个进程,至少在算法被完美调试之前,您应该找到同时运行多个客户端的方法。我希望这是您现在遇到的问题的根源。需要使用函数来封装细节;编写一个连接服务器的函数,一个发送文件的函数,一个写入文件的函数,等等。编写一个函数来处理复杂的部分写入。(我特别推荐
writen
从Unix Environment一书的源代码中的Advanced Programming 中窃取该函数。文件lib/writen.c
。)如果您正确编写了这些函数,您可以在客户端和服务器中重用它们。(就像将它们放入utils.c
并编译程序一样gcc -o server server.c utils.c
。)拥有每个只做一件事的较小函数将使您能够一次专注于较少量的代码,并为每个代码编写少量测试,以帮助您缩小仍需要改进的代码部分。
回答by Tanmoy Bandyopadhyay
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.
现在所有 3 个步骤都需要完成。连接建立是通过调用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).
所以在这段代码中,当客户端完成发送文件时,我使用了一个关闭函数,它从客户端发送 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.txtfor 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);