C TCP套接字,带有文件发送的回显服务器,发送文件后挂断

Posted

技术标签:

【中文标题】C TCP套接字,带有文件发送的回显服务器,发送文件后挂断【英文标题】:C TCP sockets, echo server with file sending, hangs up after sending a file 【发布时间】:2017-01-30 19:17:26 【问题描述】:

我想编写一个简单的 TCP 回显服务器应用程序。我设法完成了回显部分,但是在客户端和服务器之间发送文件时遇到了一些问题。这个想法很简单:尽管发送普通消息,客户端可以向服务器发送一个特殊的命令(\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;

【问题讨论】:

file_stat 似乎没有初始化。你的调试器应该警告你。 你知道你的StripNewline函数发生了什么吗? @red0ct:我猜它去掉了换行符?将 \r 和 \n 替换为 \0。当我将其注释掉时,代码也不起作用。 您想在调试器中运行代码以单步执行以查看实际情况。 【参考方案1】:
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 将是空的,假设文件由行组成,但是没有任何关于行说明它的第一个字符可以是什么的任何内容,除了它不是行终止符。

                        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));

在这里,您正在向对等方发送一个字符串没有尾随的 null,对等方正在上面的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,表示错误,需要记录,来自errnostrerror() 和朋友。

        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) send 一个尾随空值,(b) 循环 读取直到你收到它。您还对正在发送的文件的内容有一个假设,这是完全没有必要的。只需从文件中读取字节并将它们发送到服务器。无需对线路或线路终结器进行假设。如果您通过同一连接发送多个文件,则需要在文件之前发送文件 size,以便接收方确切知道要读取和复制到文件的字节数。

本质上,您需要彻底重新考虑这一点。

【讨论】:

我已经改变了你所说的一切,但仍然 - 没有效果。我添加了尾随空值,但仍然... gist.github.com/anonymous/a2e93bdae5e9f147eb8f50505ae6ab15 @AntiiHaapala 它被定义为-1。那可以是char。关键是测试已经没有意义了。 这应该被标记为答案。发布的代码有很多问题,这篇文章重点介绍了其中的大部分。我要补充一点,让另一个函数归零一个猜测任意大小的缓冲区完全超出了将代码放入多个函数的目的。 @yak 从头开始​​重写,遵循建议,您的直接问题将消失。这是作业吗? @yak 你什么都没改变。快速检查后,我评论的所有内容都还在。【参考方案2】:

在client.c中你必须初始化file_statbefore得到文件的大小stat(filename, &amp;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);

【讨论】:

您不必初始化file_statstat() 将其全部填写。这里或 OP 的代码中没有任何内容会覆盖客户端发送的文件,除非您指的是 fopen(&lt;new_file_path&gt;, "w");,这是必需的。并重新出现在您的代码中。你的回答没有意义。

以上是关于C TCP套接字,带有文件发送的回显服务器,发送文件后挂断的主要内容,如果未能解决你的问题,请参考以下文章

使用 C 中的套接字通过 TCP 发送音频文件

C - 填充 TCP 套接字发送缓冲区

Qt - 一个简单的回显服务器

tcp服务器和客户端交互

重定向 Windows 批处理文件命令的回显

Python 将 http post body 扭曲转发到 tcp 端口