0

I have tried everything in order to get my server class to close the sockets when it gets a "CLOSE" message from the client, or run AutoTest() when it gets an "AUTOTEST" message, etc. Every single time though, it does not register the message and I can't figure out why. To keep it short, I made the AutoTest() function just print out a message saying it tested it, as the main problem is just getting the messages to register. The output when ran is as follows:

./PhotoLab_server: Preparing the server address...
./PhotoLab_server: Assigning the server name to the socket...
./PhotoLab_server: Listening on port 2004...
./PhotoLab_server: Accepted connection from client
./PhotoLab_server: Received message: AUTOTEST
./PhotoLab_server: Sending response: OK.
./PhotoLab_server: Received message: CLOSE
ST
./PhotoLab_server: Sending response: OK.

CLIENT:

./PhotoLab_client: Using port 2004...
./PhotoLab_client: Creating a socket...
./PhotoLab_client: Preparing the server address...
./PhotoLab_client: Connecting to the server...
./PhotoLab_client: Sending a request AUTOTEST
./PhotoLab_client: Waiting for response...
./PhotoLab_client: Received response: OK.
./PhotoLab_client: Sending a request CLOSE
./PhotoLab_client: Waiting for response...
./PhotoLab_client: Received response: OK.
./PhotoLab_client: Sending a request 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>

/*** global definitions ***/
#define SLEN    80      /* maximum length of file names */
#define MAX_PIXEL 255   /* maximum pixel intesity value */

/* test all functions */
int AutoTest(void);

/* Displays an error message to the command line */
void FatalError(const char *Program, const char *ErrorMsg);

/* Prints out the available command flags */
void PrintUsage();

int main(int argc, char *argv[]) {
    int server_fd, new_socket;
    struct sockaddr_in address;
    int addrlen = sizeof(address);
    int port;

    /* Parse command line arguments */
    if (argc != 3 || strcmp(argv[1], "-p") != 0) {
        FatalError(argv[0], "Usage: %s -p <port>\n");
    }
    port = atoi(argv[2]);

    /* Validate the port number */
    if (port < 2000) {
        FatalError(argv[0], "Error: Port number must be greater than or equal to 2000\n");
    }

    printf("%s: Preparing the server address...\n", argv[0]);

    /* Create a socket */
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
        FatalError(argv[0], "Error creating socket\n");
    }

    printf("%s: Assigning the server name to the socket...\n", argv[0]);

    /* Bind the socket to a specific IP address and port */
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(port);

    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
        FatalError(argv[0], "Error binding socket\n");
    }

    printf("%s: Listening on port %d...\n", argv[0], port);

    /* Listen for incoming connections */
    if (listen(server_fd, 3) < 0) {
        FatalError(argv[0], "Error listening on socket\n");
    }

    /* Accept incoming connections and serve client requests */
    while (1) {
    if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {
        FatalError(argv[0], "Error accepting client connection\n");
    }

    printf("%s: Accepted connection from client\n", argv[0]);

    char buffer[256] = {0};
    int valread = 0; 
    while ((valread = read(new_socket, buffer, 256)) > 0) {
        printf("%s: Received message: %s", argv[0], buffer);

        /* Check if the client sent a CLOSE request */
        if (strcmp(buffer, "CLOSE") == 0) {
            break;
        }
        /* Check if the client sent an AUTOTEST request */
        else if (strcmp(buffer, "AUTOTEST") == 0) {
            AutoTest();
            const char *response = "Autotest completed";
            send(new_socket, response, strlen(response), 0);
        }
        /* Handle other client requests */
        else {
            /* Handle the client request */
            const char *response = "OK.";
            send(new_socket, response, strlen(response), 0);
            printf("%s: Sending response: %s\n", argv[0], response);
        }
    }

    /* Close the client connection */
    close(new_socket);
    printf("%s: Closed connection with client\n", argv[0]);

    /* Break out of the loop if a CLOSE request was received */
    if (valread == 0 || strcmp(buffer, "CLOSE") == 0) {
        printf("%s: Server exiting...\n", argv[0]);
        break;
    }
}
    /* Close the server socket */
    close(server_fd);
    printf("%s: Server socket closed\n", argv[0]);

    return 0;
}

/* test all functions */
int AutoTest(void) {
    printf("tested");
    return 1;
}

/* Displays an error message to the command line and exits the program */
void FatalError(const char *Program, const char *ErrorMsg) {
    fprintf(stderr, "%s: %s\n", Program, ErrorMsg);
    exit(EXIT_FAILURE);
}

/* Prints out the available command flags */
void PrintUsage() {
    printf("Usage:  PhotoLab_server -p <port_no>");
    printf("Options:");
    printf("-p                  Specify the server port number");
    printf("-h                  Display this usage information");
}

CLIENT CLASS:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>

/* Displays an error message to the command line */
void FatalError(const char *Program, const char *ErrorMsg);

/* Sends a request to the specified socket */
int SendRequest(const int SocketFD, const char *ReqMsg);

/* Prints out the available command flags */
void PrintUsage();

int main(int argc, char *argv[]) {
    int x;
    char *server_ip;
    int port_number = -1;

    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) {
        perror("./PhotoLab_client: Error creating a socket...");
        exit(1);
    }

    while (x < argc) {
        /* the server ip address option */
        if (strcmp(argv[x], "-ip") == 0) {
            if (x < argc - 1) {
                server_ip = argv[x+1];
            } else {
                printf("Missing argument for server IP address!\n");
                return 5;
            }
            x += 2;
            continue;
        }

        /* the port number option */
        if (strcmp(argv[x], "-p") == 0) {
            if (x < argc - 1) {
                if (sscanf(argv[x+1], "%d", &port_number) == 1) {
                    x += 2;
                    continue;
                } else {
                    printf("Invalid port number argument!\n");
                    return 5;
                }
            } else {
                printf("Missing argument for port number!\n");
                return 5;
            }
        }

        /* the help option */
        if (strcmp(argv[x], "-h") == 0) {
            PrintUsage();
            return 0;
        }

        /* the print option */
        if (strcmp(argv[x], "-print") == 0) {
            SendRequest(sockfd, "print");
            return 0;
        }

        /* the autotest option */
        if (strcmp(argv[x], "-autotest") == 0) {
            SendRequest(sockfd, "AUTOTEST");
            printf("autotest");
            return 0;
        }
        x++;
    }

    if (server_ip == NULL || port_number == -1) {
        printf("Missing required arguments!\n");
        return 5;
    }

    printf("%s: Using port %d...\n", argv[0], port_number);

    struct sockaddr_in serv_addr;
    bzero((char *) &serv_addr, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = inet_addr(server_ip);
    serv_addr.sin_port = htons(port_number);

    printf("./PhotoLab_client: Creating a socket...\n");
    printf("%s: Preparing the server address...\n", argv[0]);


    if (connect(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) {
        perror("./PhotoLab_client: Error connecting to server...");
        exit(1);
    }

     printf("%s: Connecting to the server...\n", argv[0]);

    while (1) {
        char input[100];
        printf("%s: Sending a request ", argv[0]);
        fgets(input, sizeof(input), stdin);
        printf("%s: Waiting for response...\n", argv[0]);
        SendRequest(sockfd, input);

        char buffer[256] = {0};
        int valread = read(sockfd, buffer, 256);
        if (valread < 0) {
            FatalError(argv[0], "Error receiving response from server");
            exit(EXIT_FAILURE);
        }
        printf("%s: Received response: %s\n", argv[0], buffer);
        }

    return 0;
}

/* Displays an error message to the command line and exits the program */
void FatalError(const char *Program, const char *ErrorMsg) {
    fprintf(stderr, "%s: %s\n", Program, ErrorMsg);
    exit(EXIT_FAILURE);
}

/* Sends a request of what to do to the server */
int SendRequest(const int SocketFD, const char *ReqMsg) {
    int n = send(SocketFD, ReqMsg, strlen(ReqMsg), 0);
    if (n < 0) {
        FatalError("SendRequest", "Failed to send request message.");
    }
    return n;
}

/* Print the usage information */
void PrintUsage() {
    printf("-------------------------------------------------------------------------\n");
    printf("Usage: PhotoLab_client -ip <server_ip> -p <port_no> -print -autotest\n");
    printf("Options:\n");
    printf("-ip     Specify the server IP address\n");
    printf("-p      Specify the server port number\n");
    printf("-print          Send request to server to print supported DIP operations\n");
    printf("-autotest   Send request to server to run AutoTest functionality\n");
    printf("-h              Display this usage information\n");
    printf("-------------------------------------------------------------------------\n");
}

I tried to make a function in order to receive messages and changed code on both server/client ends multiple times just to get it to register the messages, but I cannot figure out why it wont' work.

yano
  • 4,827
  • 2
  • 23
  • 35
  • 1
    Simplify to the barest minimum needed for testing the basic functionality. You don't need argument parsing or handling for example. Or any input processing. Hard-code everything, from the settings to the "input". In short, create a [mre]. That will make it much easier to test and debug your program. – Some programmer dude May 12 '23 at 03:37
  • Also please show us what happens when you run the programs. What is printed by the server? What is printed by the client? Copy-paste that (as text!) into your question. – Some programmer dude May 12 '23 at 03:40
  • I tried to hardcode it and the same thing happened - the "CLOSE" message still wouldn't get registered. – user20602600 May 12 '23 at 04:12
  • what do you mean by "registered"? – yano May 12 '23 at 04:36
  • i mean like recognized - the client will send out a "CLOSE" request, which is sent to the server, but the server won't recognize that it sent the "CLOSE" request so it won't close the program – user20602600 May 12 '23 at 04:45
  • Why do you need to send a "CLOSE" request, when the server can just detect that the connection is closed by checking that the return value of `recv()` is 0 ? – Jeremy Friesner May 12 '23 at 04:50
  • it's for an assignment for school, and one of the requirements is to have it keep running until a "CLOSE" request is sent by the client – user20602600 May 12 '23 at 05:00
  • The [`fgets`](https://en.cppreference.com/w/c/io/fgets) function keeps the trailing newline in the buffer. You send the string *with* the newline. This newline is not checked for in your `strcmp` calls. I recommend looking at [Removing trailing newline character from fgets() input](https://stackoverflow.com/questions/2693776/removing-trailing-newline-character-from-fgets-input) – Some programmer dude May 12 '23 at 05:08
  • `printf("%s: Received response: %s\n", argv[0], buffer)` should be `printf("%s: Received response: %.*s\n", argv[0], valread, buffer)`, and similarly in the server. Please indent your code properly. At the moment it looks like you are never getting out of your `accept()` loop, but you are. – user207421 May 12 '23 at 05:28
  • 1
    `if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) { FatalError(argv[0], "Error creating socket\n"); }` ====> Seems like one bug. `socket()` returns -1 on failure. – Harith May 12 '23 at 07:48

1 Answers1

-1

You need to can zero out your buffer each iteration. Look at what you're getting:

"AUTOTEST"  // this is the first message, then you get
"CLOSE_ST"  // ST left over from "AUTOTEST"

I put an _ there just so it was one character and you could see the comparison, but this is really '\n' or something similar, sent from the client presumably, so the strcmp with "CLOSE" fails, as it's really comparing "CLOSE\nST". Maybe not the most efficient solution, but you can zero out the buffer each iteration so you always get NUL termination when the message is read from the wire into buffer. Something like

while ((valread = read(new_socket, buffer, 256)) > 0) {
   // do all you work, then clear the buffer
   // ... 
   memset(buffer, 0, sizeof(buffer));
}

will do it.

As discussed in the comments, you also need to consider the newline that fgets adds to the sent command. You can eliminate it on the client side, server side, or take it into account on the strcmp. My preference is eliminate it on the client side using strcspan:

fgets(input, sizeof(input), stdin);
buffer[strcspn(buffer, "\n")] = 0; // writes 0 over the newline
printf("%s: Waiting for response...\n", argv[0]);
SendRequest(sockfd, input);
yano
  • 4,827
  • 2
  • 23
  • 35
  • thank you! i tested that out and it got rid of the "ST" thing, but it still won't register the "CLOSE". – user20602600 May 12 '23 at 04:59
  • @user20602600 probably still has the `'\n'` on it, so it's comparing `"CLOSE"` with `"CLOSE\n"`. Still no match. See [here](https://stackoverflow.com/questions/2693776/removing-trailing-newline-character-from-fgets-input) to remove the newline from `fgets`. This woudl go in the client, but you can send it with the newline and remove it on the server side instead if you like. – yano May 12 '23 at 05:05
  • @user20602600 also highly recommend learning how to use a debugger. You would quickly see these what's actually happening. In addition, using a network sniffer like wireshark and a known, working network client/server like netcat (nc) go long ways towards debugging network software. – yano May 12 '23 at 05:10
  • im seeing that it is saying to use buffer[strcspn(buffer, "\n")] = 0; but where would i put that line? – user20602600 May 12 '23 at 05:10
  • yes - after this I definitely will be using more debuggers I'm learning how helpful they can be – user20602600 May 12 '23 at 05:11
  • @user20602600 Your choice. I'd put it in your client code, in between `fgets` and `SendRequest`. `fgets` is saving the newline from when you hit Enter. You need to get rid of that at some point before comparing to `"CLOSE"`. You could also do it in server before the `strcmp`. – yano May 12 '23 at 05:13
  • He doesn't need to zero the buffer. He just needs to make sure not to use any data in it beyond the length returned by the last read. – user207421 May 12 '23 at 05:28
  • @user207421 zeroing the buffer is a way to do that. Another way is to check `valread` for the number of bytes read and NUL terminate in the appropriate place. `memset`ing is an easy one liner that trades code brevity for a bit more work,, which is hardly a problem when reading slow asynchronous text commands. – yano May 12 '23 at 05:41
  • And *another* way is not to use either `memset()` *or* inserting a NUL but merely to use the return value correctly. This is what good networking code does. And `fgets()` does not 'add' a newline. It *copies* one if there is one in the input, but not otherwise. – user207421 May 12 '23 at 09:46
  • @user207421 since you want to split hairs, `fgets` doesn't _copy_ a newline either, it _reads_ it from `stdin`, with the end effect of moving/transferring it to the user buffer, and removing it from `stdin`. Your only valid criticism of my answer is I should've mentioned checking the return value from `read` to properly handle errors. I correctly answered the OP's immediate concern (why `strcmp` wasn't working) without looking much further. Your actions and comments suggest that `memset` is a wholly wrong approach to solving the issue, which is an incorrect assessment. – yano May 12 '23 at 15:05
  • Furthermore, you're incorrect about not needing to insert a NUL terminator. OP's client code isn't sending one, at least not in the right spot,, it needs to get added somehow. That's the whole reason `strcmp` is failing, `"CLOSE"` is getting written into `buffer` without proper NUL termination, so OP is comparing `"CLOSE"` to `"CLOSE\nST"`, where the ST is clearly left over from the last command. How do you propose using `strcmp` to compare stings without proper NUL termination? – yano May 12 '23 at 15:22
  • Yet another option would be for OP to send the NUL terminator from the client .. After the `strcspn` fix, do `send(... strlen(ReqMsg) + 1)`, then the `valread` value would be the command length plus the NUL terminator. That would alleviate the need for the server to NUL terminate the command from the wire. But then you're counting on the client to send you properly formatted data. Making the server enforce NUL termination sanitizes the input to a degree and is coding defensively. – yano May 12 '23 at 15:38