我的 c 套接字程序在成功后在命令上吐出垃圾,不知道为什么

My c socket program spitting out garbage on the command following a successful one, no clue why

我什么都试过了。不知道我的程序发生了什么。我写了一个 tcp 服务器和一个客户端。我可以从客户端向服务器发出一些命令,它们是:ls-local、ls-remote、get filename、put filename(我还没有实现put,因为我卡在了get上)。

一切正常,直到我发出 get 命令。 server.c 和 client.c 文件位于不同的文件夹中。当您发出 'get filename' 时,其中 filename 是您要获取的文件的名称,服务器会检查该文件是否存在于其当前目录中,如果存在,则将其发送给客户端。

在第一次迭代中,这非常有效。该文件被发送到客户端,客户端创建该文件的副本。但是,除了 'ls-local' 之外的任何命令都会导致将纯垃圾打印到客户端,我不知道为什么。我认为这可能与我需要清除缓冲区有关,但我试过了但它仍然没有用,所以我显然做错了。 这是我在获取文件后发出命令时发生的情况: 当我再次发出 get filename 时,我得到一行垃圾,即使文件存在于服务器中,也没有发送任何内容。

如果我发出命令 ls-remote,这就是疯狂地不停地向我的客户端打印乱码的地方。

我已经包含了这两个文件,以防您想测试它们以了解我的意思。用法是:'get filename',没有单引号。请记住,它在第一次后中断,第一次工作正常。对不起,文件太长了,发送文件的代码在名为 sendfile 的方法中,我试着组织它以便于阅读。

在此方面需要一些重要帮助,在此先感谢!!

server.c

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <dirent.h> 


/**
 * Structure used to contain the type of command: 'get' or 'put'
 * as well as the name of the file
 */
struct command{
    char* type;
    char* filename;
};

void syserr(char *msg) { perror(msg); exit(-1); }

/* function prototypes */
void handleConnection(int);
void sendDirectories(int);
struct command getCommand(char*); 
int fileExists(char*);
void sendFile(char*, int);

int main(int argc, char *argv[]){

    int sockfd, newsockfd, portno, processId;
    struct sockaddr_in serv_addr, clt_addr;
    socklen_t addrlen;

    switch(argc) {
        case 1:
                    portno = 5555;
                    break;
            case 2 :
                    portno = atoi(argv[1]);
                    break;
         default :
                    fprintf(stderr,"Usage: %s <port>\n", argv[0]);
            return 1;
    }

    sockfd = socket(AF_INET, SOCK_STREAM, 0); 
    if(sockfd < 0) syserr("can't open socket"); 
    printf("create socket...\n");

    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = INADDR_ANY;
    serv_addr.sin_port = htons(portno);

    if(bind(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) {
        syserr("can't bind");
    }
    printf("bind socket to port %d...\n", portno);

    listen(sockfd, 5); 

    for(;;) {
        printf("wait on port %d...\n", portno);
        addrlen = sizeof(clt_addr); 
        newsockfd = accept(sockfd, (struct sockaddr*)&clt_addr, &addrlen);
        if(newsockfd < 0) syserr("can't accept"); 
        printf("connected to client....\n");

        signal(SIGCHLD,SIG_IGN);//prevent zombie process
        processId = fork();
        if(processId < 0){
            // printf("process id < 0, nothing closed\n");
            syserr("ERROR while attempting to fork");
        }
        if(processId == 0){//child process
            // printf("process id == 0, close(sockfd)\n");
            close(sockfd);
            handleConnection(newsockfd);
            exit(0);
        }
        else{//parent process
            // printf("else statement, close(newsockfd)\n");
            close(newsockfd);
        }
    }
    // printf("outside of loop, close(sockfd)\n");
    // close(sockfd); 
    return 0;
}

/**
 * Method that handles the commands received and routes them to the proper methods
 * @param newsockfd the file descriptor created when the connection was accepted
 */
void handleConnection(int newsockfd){
    int n;
    char buffer[256];

    while(1){

        bzero(buffer,256);
        printf("waiting for client's command.....\n");
        n = recv(newsockfd, buffer, 255, 0); 
        if(n < 0) syserr("can't receive from client"); 
        else buffer[n] = '[=10=]';
        printf("SERVER GOT MESSAGE: %s\n", buffer);


        /**
         * exit command
         */
        if (strcmp(buffer, "exit") == 0){
            printf("Ending session with client....\n");
            break;
        }
        /*
        * ls-remote command
         */
        if (strcmp(buffer, "ls-remote") == 0){
            sendDirectories(newsockfd);
        }else{

            struct command userCommand = getCommand(buffer);
            //USER SENDS VALID COMMAND
            if(strcmp(userCommand.type, "invalid") != 0){
                printf("Command %s %s is valid\n", userCommand.type, userCommand.filename);

                /**
                 *  'get' command
                 */
                if(strcmp(userCommand.type, "get") == 0){
                    //File exists
                    if(fileExists(userCommand.filename) == 1){
                        sendFile(userCommand.filename, newsockfd);
                    }
                    //File does not exist
                    else{
                        n = send(newsockfd, "ERROR: FILE DOES NOT EXIST IN REMOTE DIRECTORY", 255, 0);
                        if(n < 0) syserr("Unable to send FILE DOES NOT EXIST ERROR to client"); 
                        printf("Sending FILE DOES NOT EXIST ERROR..\n");
                    }
                }
                /**
                 *  'put' command
                 *  no check needed for 'put' because only possible options at this point are 'get' and 'put'
                 *  since invalid commands are filtered out in getCommand method
                 */
                else{

                }





            }
            //USER SENDS INVALID COMMAND
            else{
                n = send(newsockfd, "INVALID COMMAND: you may only send 'ls-remote' OR  ' get ' or ' put ' followed by a filename", 255, 0);
                if(n < 0) syserr("Unable to send INVALID COMMAND ERROR' to client"); 
                printf("Sending INVALID COMMAND ERROR...\n");
            }
        }

    }
    // printf("in handleConnection, close(sockfd)\n");
    // close(newsockfd); 
}

/**
 * Method that executes when the command 'ls-remote' is received, sends entire file listing in working directory
 * @param  newsockfd the file  descriptor created when the connection was accepted, will be used to send file listing
 */
void sendDirectories(int newsockfd){
    DIR* directory;
    struct dirent *dir;
    directory = opendir(".");
    int n;
    if (directory){
        int count = 0;
        while ((dir = readdir(directory)) != NULL){
            //ignore current dir and parent dir names
            if(strcmp(dir->d_name, ".") != 0 && strcmp(dir->d_name, "..") != 0){
                // // printf("%s\n", dir->d_name);
                // strcat(dir->d_name, "\n");//concatenates new line char to end of dir name, consider sending without new line and handle new line in client
                // n = send(newsockfd, dir->d_name,255, 0);
                // if(n < 0) syserr("can't send file list to client"); 
                // printf("sending file name: '%s' to client...\n", dir->d_name);
                count++;
            }
        }
        int32_t convCount = htonl(count);
        n = send(newsockfd, &convCount, sizeof(convCount), 0);
        if(n < 0) syserr("can't send file list count"); 
        printf("sending file list count: '%d' to client...\n", count);
        rewinddir(directory);
        while ((dir = readdir(directory)) != NULL){
            //ignore current dir and parent dir names
            if(strcmp(dir->d_name, ".") != 0 && strcmp(dir->d_name, "..") != 0){
                // printf("%s\n", dir->d_name);
                // strcat(dir->d_name, "\n");//concatenates new line char to end of dir name, consider sending without new line and handle new line in client
                n = send(newsockfd, dir->d_name,255, 0);
                if(n < 0) syserr("can't send file list to client"); 
                printf("sending file name: '%s' to client...\n", dir->d_name);
            }
        }
        closedir(directory);
    }
}

/**
 * Method that extracts a valid command from a given string
 * @param  string  the string containing the commands
 * @return   the valid command if found, a command with type 'invalid' if not found
 */
struct command getCommand(char* string){
    char* temp;
    int count;
    struct command userCommand;

    count = 0;
    //Split the string on spaces, if more than one space then # of arguments is > 2, thus invalid command
    temp = strtok(string, " ");
    while(temp != NULL){
        if(count == 0){
            userCommand.type = temp;
        }else if(count == 1){
            userCommand.filename = temp;
        }else{
            userCommand.type = "invalid";
            break;
        }
        temp = strtok(NULL, " ");
        count++;
    }
    //We test count ==1 because this means only one space in string but also only one word because count did not increment again
    //which is still an invalid command
    if(strcmp(userCommand.type, "get") != 0 && strcmp(userCommand.type, "put") != 0 || count == 1){
        userCommand.type = "invalid";
    }
    return userCommand;
}

/**
 * Method that searches server directory for a given file name
 * @param  filename the name of the file to search for
 * @return   1 if the file is found, 0 if not found
 */
int fileExists(char* filename){
    DIR* directory;
    struct dirent *dir;
    directory = opendir(".");
    int n;
    if (directory){
        while ((dir = readdir(directory)) != NULL){
            //ignore current dir and parent dir names
            if(strcmp(dir->d_name, ".") != 0 && strcmp(dir->d_name, "..") != 0){

                if(strcmp(dir->d_name, filename) == 0) {
                    printf("File: '%s' was FOUND!\n", filename);
                    return 1;
                }
            }
        }
        closedir(directory);
    }
    printf("Sorry file: '%s' was NOT found!\n", filename);
    return 0;
}

/**
 * Method that sends the specified file using the file descriptor passed
 * @param filename  the name of the file to be sent
 * @param newsockfd the file descriptor to use to send the file
 */
void sendFile(char* filename, int newsockfd){
    int n, fd;
    struct stat file_stat;
    char buffer[256];
    char* file_buffer;
    char file_chunk[BUFSIZ];

    fd = open(filename, O_RDONLY);

    if(fd < 0){
        n = send(newsockfd, "ERROR: File could not be opened by server....",255, 0);
        if(n < 0) syserr("Error opening file client has requested"); 
    }

    if (fstat(fd, &file_stat) < 0){
        n = send(newsockfd, "ERROR:File stats could not be obtained by server....",255, 0);
        if(n < 0) syserr("Error getting stats of file client has requested"); 
    }

    n = send(newsockfd, "Server found file and successfully opened...",255, 0);
    if(n < 0) syserr("can't send file open confirmation to client"); 
    printf("File found and successfully opened....\n");

    // int data_remaining = file_stat.st_size;
    // int32_t convSize = htonl(data_remaining);
    // n = send(newsockfd, &convSize, sizeof(convSize), 0);
    // if(n < 0) syserr("can't send file size "); 
    // printf("N IS: %d\n", n);
    // printf("Sending file size to client, size is : %d bytes....\n", file_stat.st_size);

    int data_remaining = file_stat.st_size;
    int sent_bytes = 0;
    char str[255];//store file size in string to send, for some reason the above code was sending fine but client could not receive, even though it works perfectly for ls-remote
    sprintf(str, "%d", data_remaining);

    file_buffer = (char*) malloc (sizeof(char)*data_remaining);

    n = send(newsockfd,str,255, 0);
    if(n < 0) syserr("can't send file size to client..."); 
    printf("Sending file size to client, size is : %d bytes....\n", file_stat.st_size);


    int read_bytes;

    read_bytes = read(fd, file_buffer, data_remaining);
    while((sent_bytes = send(newsockfd, file_buffer, file_stat.st_size, 0)) > 0 && data_remaining > 0){
        data_remaining -= sent_bytes;
        printf("Sent %d bytes of file, %d bytes remain\n", sent_bytes, data_remaining);
    }

    printf("File %s has finished sending....\n", filename);

    close(fd);






}

client.c

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <dirent.h> 


/**
 * Structure used to contain the type of command: 'get' or 'put'
 * as well as the name of the file
 */
struct command{
    char* type;
    char* filename;
};

void syserr(char* msg) { perror(msg); exit(-1); }

/*function prototypes*/
void listDirectories();
struct command getCommand(char*);

int main(int argc, char *argv[]){

    int sockfd, portno, n;
    int32_t *convCount, *convSize;
    struct hostent* server;
    struct sockaddr_in serv_addr;
    FILE *received_file;

    char buffer[256];

    if(argc != 3){
        fprintf(stderr, "Usage: %s <hostname> <port>\n", argv[0]);
        return 1;
    }
    server = gethostbyname(argv[1]);

    if(!server){
        fprintf(stderr, "ERROR: no such host: %s\n", argv[1]);
        return 2;
    }
    portno = atoi(argv[2]);

    sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if(sockfd < 0) syserr("Error opening socket.");
    printf("create socket...\n");

    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr = *((struct in_addr*)server->h_addr);
    serv_addr.sin_port = htons(portno);

    if(connect(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0){
        syserr("can't connect to server");
    }
    printf("connection to %s:%s established. Now awaiting commands...\n", argv[1], argv[2]);



    while(1){
        // printf("PLEASE ENTER MESSAGE: ");
        printf("%s:%s> ", argv[1], argv[2]);

        fgets(buffer, 255, stdin);
        n = strlen(buffer); 
        if(n>0 && buffer[n-1] == '\n'){
            buffer[n-1] = '[=11=]'; 
        }

        if(strcmp(buffer, "exit") == 0){
            n = send(sockfd, buffer, strlen(buffer), 0);
            if(n < 0) syserr("can't send 'exit' command to server");
            printf("send...\n");
            break;
        }else if(strcmp(buffer, "ls-local") == 0){
            listDirectories();
        }else if(strcmp(buffer, "ls-remote") == 0){

            n = send(sockfd, buffer, strlen(buffer), 0);
            if(n < 0) syserr("can't send to server");
            printf("send...\n");


            n = recv(sockfd, convCount, sizeof(int32_t), 0);
            if(n < 0) syserr("can't receive from server");

            int fileCount = ntohl(*convCount);
            // printf("CLIENT RECEIVED FILE COUNT: %d\n", fileCount);
            printf("Files at server (%s:%s):\n", argv[1], argv[2]);

            int i = 0;
            while(i < fileCount){
                n = recv(sockfd, buffer, 255, 0);
                if(n < 0) syserr("can't receive from server");
                printf("%s\n", buffer);
                i++;
            }
        }else{

            char cmdCopy[256];
            strcpy(cmdCopy, buffer);
            struct command userCommand = getCommand(cmdCopy);
            if(strcmp(userCommand.type, "invalid") != 0){
                printf("Command %s %s is valid\n", userCommand.type, userCommand.filename);

                /**
                 *  'get' command
                 */
                if(strcmp(userCommand.type, "get") == 0){
                    //Re-append the entire command since
                    n = send(sockfd, buffer, strlen(buffer), 0);
                    if(n < 0) syserr("can't send to server");
                    printf("send GET request for file: %s\n", userCommand.filename);

                    n = recv(sockfd, buffer, 255, 0);
                    if(n < 0) syserr("can't receive from server");
                    else buffer[n] = '[=11=]';

                    if(strcmp(buffer, "ERROR: FILE DOES NOT EXIST IN REMOTE DIRECTORY") != 0 
                        && strcmp(buffer, "ERROR: File could not be opened by server....") != 0
                        && strcmp(buffer, "ERROR:File stats could not be obtained by server....") != 0){

                        printf("%s\n", buffer);

                        //// Not working for some reason, must receive as char*
                        // n = recv(sockfd, convSize, sizeof(int32_t), 0);
                        // printf("N IS: %d\n", n);
                        // if(n < 0) syserr("can't receive file size from server");

                        // int fileSize = ntohl(*convSize);
                        // printf("Size of file: %s to be received is %d\n bytes",buffer, fileSize);
                        // printf("Receiving file: %s \n....", userCommand.filename);

                        n = recv(sockfd, buffer, 255, 0);
                        if(n < 0) syserr("can't receive size from server");
                        else buffer[n] = '[=11=]';

                        int data_remaining = atoi(buffer);
                        printf("file size is %d\n", data_remaining);


                        received_file = fopen(userCommand.filename, "w");
                        if (received_file == NULL){
                            syserr("Failed to open file.");
                        }else{
                            char file_buffer[BUFSIZ];
                            int bytes_received;
                            while((bytes_received = recv(sockfd, file_buffer, BUFSIZ, 0)) >0  && data_remaining > 0){
                                fwrite(file_buffer, 1, bytes_received, received_file);
                                    data_remaining -= bytes_received;
                                    printf("Received %d bytes of file, %d bytes remain\n", bytes_received, data_remaining);
                            }

                            printf("File receive complete\n");
                            fclose(received_file);





                        }



                    }else{
                        printf("%s\n", buffer);
                    }

                }
                /**
                 *  'put' command
                 *  no check needed for 'put' because only possible options at this point are 'get' and 'put'
                 *  since invalid commands are filtered out in getCommand method
                 */
                else{
                    // //File exists
                    // if(fileExists(userCommand.filename) == 1){
                    //  sendFile(userCommand.filename, newsockfd);
                    // }
                    // //File does not exist
                    // else{
                    //  printf( "ERROR: FILE DOES NOT EXIST IN LOCAL DIRECTORY\n");
                    // }


                }
            }
            //USER TYPES INVALID COMMAND
            else{
                printf("INVALID COMMAND: you may only send 'ls-remote' OR  ' get ' or ' put ' followed by a filename");
            }

        }

        // n = send(sockfd, buffer, strlen(buffer), 0);
        // if(n < 0) syserr("can't send to server");
        // printf("send...\n");

        // while( (n = recv(sockfd , buffer , 255 , 0)) > 0 ){
        //  printf("in while and n is: %d\n", n);
        //      printf("%s", buffer);
        // }
        // if(n < 0){
        //  printf("in if and n is: %d\n", n);
        //  syserr("can't receive from server");
        // }else{
        //  printf("in else and n is: %d\n", n);
        //  buffer[n] = '[=11=]';
        // }
        // printf("in no loop and n is: %d\n", n);

    }

    close(sockfd);

    return 0;
}

    // printf("connection to %s:%s established. Now awaiting commands...\n", argv[1], argv[2]);


    // do{

    // printf("%s:%s> ", argv[1], argv[2]);
    // fgets(buffer, 255, stdin);
    // n = strlen(buffer); 
    // if(n>0 && buffer[n-1] == '\n'){
    //  buffer[n-1] = '[=11=]'; 
    // }

    // n = send(sockfd, buffer, strlen(buffer), 0);
    // if(n < 0) syserr("can't send to server");
    // printf("send...\n");

    // n = recv(sockfd, buffer, 255, 0);
    // if(n < 0) syserr("can't receive from server");
    // else buffer[n] = '[=11=]';
    // printf("CLIENT RECEIVED MESSAGE: %s\n", buffer);


    // }while(strcmp(buffer, "exit") != 0);

    // close(sockfd);
    // return 0;


/**
 * Method that extracts a valid command from a given string
 * @param  string  the string containing the commands
 * @return   the valid command if found, a command with type 'invalid' if not found
 */
struct command getCommand(char* string){
    char* temp;
    int count;
    struct command userCommand;

    count = 0;
    //Split the string on spaces, if more than one space then # of arguments is > 2, thus invalid command
    temp = strtok(string, " ");
    while(temp != NULL){
        if(count == 0){
            userCommand.type = temp;
        }else if(count == 1){
            userCommand.filename = temp;
        }else{
            userCommand.type = "invalid";
            break;
        }
        temp = strtok(NULL, " ");
        count++;
    }
    //We test count ==1 because this means only one space in string but also only one word because count did not increment again
    //which is still an invalid command
    if(strcmp(userCommand.type, "get") != 0 && strcmp(userCommand.type, "put") != 0 || count == 1){
        userCommand.type = "invalid";
    }
    return userCommand;
}


/**
 * Method that executes when the command 'ls-local' is received, sends entire file listing in working directory
 */
void listDirectories(){
    DIR* directory;
    struct dirent *dir;
    directory = opendir(".");
    if (directory){
        printf("Files at the client:\n");
        while ((dir = readdir(directory)) != NULL){
            //ignore current dir and parent dir names
            if(strcmp(dir->d_name, ".") != 0 && strcmp(dir->d_name, "..") != 0){
                printf("%s\n", dir->d_name);
            }
        }
        closedir(directory);
    }
}
    printf("SERVER GOT MESSAGE: %s\n", buffer);

没有,您没有收到消息。你有一些字节。 TCP 不是消息协议。如果要收发消息,就得写代码收发消息,你没做过

"TCP is a reliable, byte-stream protocol that does not preserve application message boundaries."记住这个。在理解其中的所有内容之前,不要编写任何使用 TCP 的代码,否则您的代码注定会失败。

另一个要点:在您对在 TCP 之上使用的协议有规范之前,不要编写任何使用 TCP 的代码。谁什么时候发送?有超时吗?哪一方实施它们?连接是如何关闭的?谁发起的?是否存在应用程序消息边界?它们是如何标记的? 等等。花时间详细记录这一点真的很值得。您可以查看使用 TCP(例如 HTTP、SMTP、IRC 等)的现有协议的规范,以了解协议规范应该是什么样子的示例。

除了 David Schwartz 指出的代码中的基本谬误之外,您还犯了所有常见错误。

bzero(buffer,256);

冗余。移除。

n = recv(newsockfd, buffer, 255, 0);
if(n < 0) syserr("can't receive from client");
else

此处缺少 n == 0break 的测试。

    buffer[n] = '[=12=]';

仅当数据完全是文本时才有效。在整个过程中,包括以下 printf(),您应该做的是使用 n 作为您从缓冲区中检索内容的唯一上限。

如前所述,您没有理由相信这是一条完整的消息,或者只有一条消息。

解决您眼前的问题,有两个问题:

server.c第一名:

while((sent_bytes = send(newsockfd, file_buffer, file_stat.st_size, 0)) > 0 && data_remaining > 0){

每次发送时,您将发送 file_stat.st_size 个字节,而不是 data_remaining 个字节。

此外,您应该先检查 data_remaining > 0。由于 && 运算符是短路运算符,因此除非第一部分的计算结果为真,否则它不会计算第二部分。因此,如果您还剩下 0 个字节要发送,它将尝试发送它们。

所以上面一行应该是:

while((data_remaining > 0) && ((sent_bytes = send(newsockfd, file_buffer, file_stat.st_size, 0)) > 0)) {

与 client.c 类似,您有:

while((bytes_received = recv(sockfd, file_buffer, BUFSIZ, 0)) >0  && data_remaining > 0){

如果您收到了整个文件,它会再尝试一次 recv,然后才意识到 data_remaining > 0 是错误的。由于服务器已完成发送,它会一直等待。

和以前一样,调换 && 个操作数的顺序:

while((data_remaining > 0) && ((bytes_received = recv(sockfd, file_buffer, BUFSIZ, 0)) > 0)){

这至少应该让你 运行。

除此之外还有其他问题。正如 David Schwartz 所提到的,TCP 的流媒体特性不会保留消息边界。

这意味着仅仅因为您尝试读取 255 个字节并不意味着您实际上会得到那么多。在您期望特定大小的消息的任何地方,请确保您实际获得了那么多字节。如果您还没有读够,请继续阅读直到读完。