我想编写一个简单的TCP echo服务器应用程序。我设法做了回声部分,但在客户端和服务器之间发送文件时遇到了一些问题。这个想法很简单:尽管发送普通消息,客户端可以向服务器发送一个特殊命令(\ SENDFILE filename.txt),并且在收到这样的命令后,服务器应该向客户端询问该文件,并从客户端获取该文件。 (此外,我想从一个客户端获取一个文件,然后将其发送到另一个客户端)。
我认为"协议"这里很简单,但是在客户端输入\ SENDFILE之后,客户端挂断,并且不会从服务器接收任何进一步的消息。此外(服务器和客户端在不同的目录中)在服务器端,只有来自客户端的空文件,内部没有内容。
任何想法在这里可能出错?
client.c
#include<stdio.h> //printf
#include<string.h> //
#include <sys/stat.h>
#include<sys/socket.h> //socket
#include<arpa/inet.h> //inet_addr
#include <fcntl.h>
#define SERVER_PORT 9034
#define BUFF_SIZE 2000
int sendall(int s, char *buf, int len)
{
int total = 0;
int bytesleft = len;
int n;
while(total < len)
{
n = send(s, buf+total, bytesleft, 0);
if (n == -1)
break;
total += n;
bytesleft -= n;
}
return n==-1?-1:0;
}
void SendMsgToSender(char *msg, int connfd)
{
write(connfd, msg, strlen(msg));
memset(msg, 0, BUFF_SIZE);
}
int main(int argc , char *argv[])
{
int sock;
struct sockaddr_in server;
char bufferOUT[BUFF_SIZE] , bufferIN[BUFF_SIZE];
struct stat file_stat;
memset(bufferOUT, 0, BUFF_SIZE);
memset(bufferIN, 0, BUFF_SIZE);
//Create socket
sock = socket(AF_INET , SOCK_STREAM , 0);
if (sock == -1)
{
printf("Could not create socket");
}
// puts("Socket created");
server.sin_addr.s_addr = inet_addr("127.0.0.1");
server.sin_family = AF_INET;
server.sin_port = htons( SERVER_PORT );
//Connect to remote server
if (connect(sock , (struct sockaddr *)&server , sizeof(server)) < 0)
{
perror("Connect failed. Error");
return 1;
}
// puts("Connected\n");
int read_size = 10;
//keep communicating with server
while(1)
{
printf("> ");
fgets(bufferOUT, BUFF_SIZE, stdin);
//Send some data
if( send(sock , bufferOUT , BUFF_SIZE , 0) < 0)
{
perror("Send failed");
return 1;
}
//Receive a reply from the server
if( (read_size = recv(sock , bufferIN , BUFF_SIZE , 0)) < 0)
{
perror("Recv failed");
break;
}
if(read_size == 0)
break;
if(bufferIN[0] == '\\')
{
char tmp[BUFF_SIZE], filename[BUFF_SIZE], *param;
memset(filename, BUFF_SIZE, 0);
strcpy(tmp, bufferIN);
param = strtok(tmp, " ");
if(param != NULL)
{
if(!strcmp(param, "\\GIVEMEFILE"))
{
param = strtok(NULL, " ");
if(param != NULL)
{
strcpy(filename, param);
FILE * fp;
int nBytes;
char buffer[BUFF_SIZE], *s;
memset(buffer, 0, BUFF_SIZE);
fp = fopen(filename, "r");
if(fp == NULL)
{
perror("fopen");
fflush(stdout);
break;
}
int remain_data = file_stat.st_size;
do
{
s = fgets(buffer, BUFF_SIZE, fp);
if(s != NULL && buffer[0] != EOF)
{
nBytes = sendall(sock, buffer, BUFF_SIZE);
remain_data -= nBytes;
}
else
break;
}
while((s != NULL) && (nBytes > 0) && (remain_data > 0));
fclose(fp);
memset(bufferOUT, 0, BUFF_SIZE);
memset(bufferIN, 0, BUFF_SIZE);
continue;
}
}
}
}
else
{
printf("%s\n", bufferIN);
fflush(stdout);
}
memset(bufferOUT, 0, BUFF_SIZE);
memset(bufferIN, 0, BUFF_SIZE);
}
close(sock);
return 0;
}
server.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <time.h>
#include <fcntl.h>
#define SERVER_PORT 9034
#define BUFF_SIZE 2000
void StripNewline(char *s)
{
while(*s != '\0')
{
if(*s == '\r' || *s == '\n')
{
*s = '\0';
}
s++;
}
}
void SendMsgToSender(char *msg, int connfd)
{
write(connfd, msg, strlen(msg));
memset(msg, 0, BUFF_SIZE);
}
// get sockaddr, IPv4 or IPv6:
void *get_in_addr(struct sockaddr *sa)
{
if (sa->sa_family == AF_INET)
{
return &(((struct sockaddr_in*)sa)->sin_addr);
}
return &(((struct sockaddr_in6*)sa)->sin6_addr);
}
int GetFileFromClient(int connfd, char *filename)
{
FILE * fp = NULL;
int bytes;
char buffer[BUFF_SIZE];
memset(buffer, 0, BUFF_SIZE);
fp = fopen(filename, "w");
if(fp == NULL)
return 0;
memset(buffer, 0, BUFF_SIZE);
sprintf(buffer, "\\GIVEMEFILE %s \r\n", filename);
SendMsgToSender(buffer, connfd);
while(1)
{
memset(buffer ,0 , BUFF_SIZE);
if((bytes = recv(connfd , buffer , BUFF_SIZE , 0) ) <= 0)
return 0;
else
fprintf(fp, "%s\n", buffer);
}
fclose(fp);
sleep(1);
memset(buffer, 0, BUFF_SIZE);
sprintf(buffer, "\r\n");
SendMsgToSender(buffer, connfd);
return 1;
}
int main(void)
{
fd_set master;
fd_set read_fds;
int fdmax;
int listener;
int client_sock;
struct sockaddr_storage remoteaddr;
socklen_t addrlen;
char bufferIN[BUFF_SIZE], bufferOUT[BUFF_SIZE], tmp[BUFF_SIZE], *datetime;
int nbytes;
char remoteIP[INET6_ADDRSTRLEN];
int yes=1;
int i, j, rv;
struct addrinfo hints, *ai, *p;
FD_ZERO(&master);
FD_ZERO(&read_fds);
memset(bufferIN, 0, BUFF_SIZE);
memset(bufferOUT, 0, BUFF_SIZE);
memset(tmp, 0, BUFF_SIZE);
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
char port[16] = "9034";
if (getaddrinfo(NULL, port, &hints, &ai) < 0)
{
fprintf(stderr, "selectserver: %s\n", gai_strerror(rv));
exit(1);
}
for(p = ai; p != NULL; p = p->ai_next)
{
listener = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
if (listener < 0)
{
continue;
}
setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int));
if (bind(listener, p->ai_addr, p->ai_addrlen) < 0)
continue;
break;
}
if (p == NULL)
exit(2);
freeaddrinfo(ai);
if (listen(listener, 10) == -1)
{
perror("listen");
exit(3);
}
FD_SET(listener, &master);
fdmax = listener;
printf("Server is running ...\n\n");
for(;;)
{
read_fds = master;
if (select(fdmax+1, &read_fds, NULL, NULL, NULL) == -1)
{
perror("select");
exit(4);
}
for(i = 0; i <= fdmax; i++)
{
if (FD_ISSET(i, &read_fds))
{
if (i == listener)
{
addrlen = sizeof remoteaddr;
client_sock = accept(listener,
(struct sockaddr *)&remoteaddr,
&addrlen);
if (client_sock == -1)
{
perror("accept");
}
else
{
FD_SET(client_sock, &master);
if (client_sock > fdmax)
fdmax = client_sock;
}
}
else
{
if ((nbytes = recv(i, bufferIN, BUFF_SIZE, 0)) <= 0)
{
if (nbytes == 0)
close(i);
else if(nbytes == -1)
{
perror("recv");
fflush(stdout);
}
close(i);
FD_CLR(i, &master);
}
else
{
bufferIN[nbytes-1] = '\0';
StripNewline(bufferIN);
strcpy(tmp, bufferIN);
if(bufferIN[0] == '\\')
{
char *command, *param;
command = strtok(bufferIN, " ");
if(!strcmp(command, "\\QUIT"))
{
close(i);
FD_CLR(i, &master);
break;
}
else if(!strcmp(command, "\\SENDFILE"))
{
param = strtok(tmp, " ");
if(param != NULL)
{
param = strtok(NULL, " ");
if(param != NULL)
{
printf("Client is sending me a file '%s'...\n", param);
GetFileFromClient(i, param);
}
}
}
else
{
SendMsgToSender(bufferIN, i);
}
memset(bufferIN, 0, BUFF_SIZE);
memset(bufferOUT, 0, BUFF_SIZE);
}
else
{
SendMsgToSender(bufferIN, i);
}
}
} // END handle data from client
} // END got new incoming connection
} // END looping through file descriptors
} // END for(;;)
memset(bufferIN, 0, BUFF_SIZE);
memset(bufferOUT, 0, BUFF_SIZE);
return 0;
}
答案 0 :(得分:12)
strcpy(tmp, bufferIN);
这里假设读取的内容为空终止。
param = strtok(tmp, " ");
if(param != NULL)
{
if(!strcmp(param, "\\GIVEMEFILE"))
这里假设已收到整个邮件。
strcpy(filename, param);
同上。
memset(buffer, 0, BUFF_SIZE);
毫无意义的。删除。
do
{
s = fgets(buffer, BUFF_SIZE, fp);
这里假设文件由行组成。
if(s != NULL && buffer[0] != EOF)
测试buffer[0] !=EOF
毫无意义。如果你已经达到EOF,s
将为空,假设文件由行组成,但没有任何关于一行说明它的第一个字符可能是什么,除了它不是&#39; ta行终结者。
memset(bufferOUT, 0, BUFF_SIZE);
memset(bufferIN, 0, BUFF_SIZE);
两者毫无意义。删除。
memset(bufferOUT, 0, BUFF_SIZE);
memset(bufferIN, 0, BUFF_SIZE);
同上。
void StripNewline(char *s)
这种方法看起来毫无意义。删除。
void SendMsgToSender(char *msg, int connfd)
{
write(connfd, msg, strlen(msg));
在这里,您要向对等发送一个字符串,而没有尾随空,对等方正在strlen()
处查找该字符串。好好考虑一下你的应用程序协议实际需要什么。
memset(msg, 0, BUFF_SIZE);
毫无意义的。删除。
int GetFileFromClient(int connfd, char *filename)
{
FILE * fp = NULL;
int bytes;
char buffer[BUFF_SIZE];
memset(buffer, 0, BUFF_SIZE);
毫无意义的。删除。
memset(buffer, 0, BUFF_SIZE);
同上。
sprintf(buffer, "\\GIVEMEFILE %s \r\n", filename);
SendMsgToSender(buffer, connfd);
while(1)
{
memset(buffer ,0 , BUFF_SIZE);
毫无意义的。删除。
if((bytes = recv(connfd , buffer , BUFF_SIZE , 0) ) <= 0)
return 0;
在这里,您需要区分(1)bytes == 0
,这意味着对等方断开连接,以及(2)byte == -1
,它表示错误,您需要 log ,通过errno
,strerror()
和朋友。
else
fprintf(fp, "%s\n", buffer);
更改为fprintf(fp, "%.*s\n", bytes, buffer)
。您假设所有消息都由TCP以空值终止。他们不是。
sleep(1);
毫无意义的。删除。
memset(buffer, 0, BUFF_SIZE);
同上。
sprintf(buffer, "\r\n");
SendMsgToSender(buffer, connfd);
向对等点发送行终止符似乎完全没有意义。
memset(bufferIN, 0, BUFF_SIZE);
memset(bufferOUT, 0, BUFF_SIZE);
memset(tmp, 0, BUFF_SIZE);
一切都毫无意义。删除。
if (bind(listener, p->ai_addr, p->ai_addrlen) < 0)
continue;
在这里,您需要打印错误消息而不是忽略条件。
if (select(fdmax+1, &read_fds, NULL, NULL, NULL) == -1)
您还没有将侦听套接字置于非阻止模式。因此使用select()
毫无意义。
bufferIN[nbytes-1] = '\0';
StripNewline(bufferIN);
为什么?
strcpy(tmp, bufferIN);
为什么呢?继续使用bufferIN
会有什么问题?
if(bufferIN[0] == '\\')
{
char *command, *param;
command = strtok(bufferIN, " ");
这里再次假设您收到了一个完整的命令,并且尾随空格。
memset(bufferIN, 0, BUFF_SIZE);
memset(bufferOUT, 0, BUFF_SIZE);
两者毫无意义。去掉。这是一个突然的货物崇拜节目。 recv()
返回一个长度。使用它。
memset(bufferIN, 0, BUFF_SIZE);
memset(bufferOUT, 0, BUFF_SIZE);
同上,在黑桃中。
基本上你有一个应用程序协议问题。具体来说,您没有拥有应用程序协议。只是一大堆无根据的假设。如果你想要一个尾随空值,(a)发送一个尾随空值,(b)循环读数,直到你收到它。您还假设正在发送的文件的内容,这是完全没有必要的。只需从文件中读取字节并将其发送到服务器即可。没有关于线路或线路终结器的假设。如果您通过同一连接发送多个文件,则需要在文件之前发送 size 文件,因此接收方将确切知道要读取多少字节并复制到该文件。
实质上,你需要完全重新考虑这一点。
答案 1 :(得分:1)
在client.c中,您必须在获取文件file_stat
的大小之前初始化stat(filename, &file_stat);
由于此错误,remain_data
将始终具有错误的值。
在Server.c中 由于EJP指出的while循环中的错误,您将覆盖客户端发送的文件。基本上是空的。 使用“r”选项打开客户端文件名。 在服务器中打开另一个文件并接收该文件的数据。 接收BUFF_SIZE内文件数据的小例子。您可以使用某些逻辑并将其扩展为更大的文件,就像在Client.c中完成的那样。
fd = fopen(<new_file_path>, "w");
while(1)
{
memset(buffer ,0 , BUFF_SIZE);
if((bytes = recv(connfd , buffer , BUFF_SIZE , 0) ) == BUFF_SIZE)
break;
}
fprintf(fd, "%s\n", buffer);
fclose(fd);