-2

I am quite inexperienced with multithreading in C, so I would really appreciate some input on this piece of client-side code (extraneous parts have been stripped out for simplicity's sake).

// client_read(): read from server
void *client_read(int sockfd) {
    char text[MAX_TEXT_SIZE + 2];
    while (1) {
        // clean out buffer
        bzero(text, sizeof(text));
        // read from server
        if (read(sockfd, text, sizeof(text)) <= 0) {
            fprintf(stderr, "failed to read\n");
            exit(1);
        }
        // print message from server to stdout
        fprintf(stdout, "%s", text);
        // quit conversation
        if (strncmp(text, "bye\n", 4) == 0) {
            fprintf(stdout, "end to conversation\n");
            close(sockfd);
            exit(0);
        }
    }
}

// client_write(): write to server
void *client_write(int sockfd) {
    char text[MAX_TEXT_SIZE + 2];
    int c, d, i;
    size_t text_len;
    while (1) {
        // clean out buffer
        bzero(text, sizeof(text));
        i = 0;
        // read from stdin
        while ((c = getchar()) != '\n' && c != EOF && i < MAX_TEXT_SIZE) {
            text[i++] = c;
        }
        // clean out stdin if MAX_TEXT_SIZE exceeded
        if (i == MAX_TEXT_SIZE && c != '\n') {
            while ((d = getchar()) != EOF && d != '\n') {
    }
        }
        text[i++] = '\n';
        text[i] = '\0';
        text_len = strlen(text);
        // write to server
        if (write(sockfd, text, text_len) != text_len) {
            fprintf(stderr, "sent wrong number of bytes\n");
            exit(1);
         }
        // quit conversation
        if (strncmp(text, "bye\n", 4) == 0) {
            fprintf(stdout, "end to conversation\n");
            close(sockfd);
            exit(0);
        }
    }
}

int main(int argc, char **argv) {
    char keyword[MAX_KEY_SIZE + 1], *server_IP;
    in_port_t server_port = 6000;
    int r_val, rtn_val, sockfd, w_val;
    pthread_t th_r, th_w;
    struct sockaddr_in server_addr;

    // checking number of command line arguments
    if (argc != 3 && argc != 5) {
        fprintf(stderr, "Usage: ./client server_IP [-p server_port] key\n");
        exit(1);
    }
    // Usage: ./client server_IP key
    else if (argc == 3) {
        if (strlen(argv[2]) > MAX_KEY_SIZE) {
            fprintf(stderr, "maximum key length exceeded\n");
            exit(1);
        }
        else {
            strcpy(keyword, argv[2]);
        }
    }
    // Usage: ./client server_IP -p server_port key
    else {
        if (strcmp("-p", argv[2]) != 0) {
            fprintf(stderr, "client: illegal option %s\n", argv[2]);
            exit(1);
        }
        else {
            server_port = atoi(argv[3]);
        }
        if (strlen(argv[4]) > MAX_KEY_SIZE) {
            fprintf(stderr, "maximum key length exceeded\n");
            exit(1);
        }
        else {
            strcpy(keyword, argv[4]);
        }
    }
    server_IP = argv[1];
    // creating reliable stream socket using TCP
    if ((sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
        fprintf(stderr, "unable to create TCP socket\n");
        exit(1);
    }
    // Constructing server address structure
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    // Converting address into proper format
    if ((rtn_val = inet_pton(AF_INET, server_IP, &server_addr.sin_addr.s_addr)) == 0) {
        fprintf(stderr, "invalid IP address\n");
        exit(1);
    }
    else if (rtn_val < 0) {
        fprintf(stderr, "failed to convert IP string\n");
        exit(1);
    }
    // server port
    server_addr.sin_port = htons(server_port);
    // connecting to server
    if (connect(sockfd, (struct sockaddr *) &server_addr, sizeof(server_addr)) < 0) {
        fprintf(stderr, "failed to connect to server\n");
        exit(1);
    }
    // beginning chat
    else {
        fprintf(stdout, "successfully connected to server\n");
        // starting separate thread for reading from server
        if ((r_val = pthread_create(&th_r, NULL, (void *) client_read, (void *) sockfd)) != 0) {
            fprintf(stderr, "failed to create read thread\n");
            exit(1);
        }
        // starting separate thread for writing to server
        if ((w_val = pthread_create(&th_w, NULL, (void *) client_write, (void *) sockfd)) != 0) {
            fprintf(stderr, "failed to create write thread\n");
            exit(1);
        }
        // Waiting until threads close to return resources
        printf("hello1\n");
        pthread_join(th_r, NULL);
        printf("hello2\n");
        pthread_join(th_w, NULL);
        printf("hello3\n");
    }
    return 0;
}

For those of you familiar with client-server programming, you should see that besides the use of POSIX threads there is nothing out of the ordinary about the client code in terms of processing user input and reading and writing to a server. I decided to use a separate thread for reading and writing; otherwise, the client would have to write something, wait for the server to reply and then write something again.

I use the pthread_join() methods at the very end of main(). These functions are supposed to wait until the read (th_r) and write (th_w) threads to finish and relinquish the resources they use. The problem I have is that these methods are apparently never called.

When I start the client, I see it reaches "hello1" right away, but when either the client or server terminates the communication stream, I see neither "hello2" or "hello3," which means neither is ever reached.

Could someone please shed some light on what I am missing?

AK-33
  • 167
  • 2
  • 10
  • Side note: converting the `sockfd` to a `void *`, and then declaring the argument to the thread function as `int` is not safe, and will fail on systems where pointers and `int`s are different sizes. You're better off passing the _address of `socked`_ so that you're passing a _pointer to an `int`_ as the argument to the thread function. – user3386109 Jun 11 '14 at 00:57
  • @user3386109 In fact it's undefined behaviour regardless of the size of an `int`. – Tavian Barnes Jun 11 '14 at 01:09
  • No it's not UB. The conversion is implementation-defined, not undefined, and works as expected on basically all real implementations. – R.. GitHub STOP HELPING ICE Jun 11 '14 at 05:20
  • Moreover passing the address is **unsafe** without synchronization. – R.. GitHub STOP HELPING ICE Jun 11 '14 at 05:21
  • @R.. Are you sure? I'm talking about calling a function through a function pointer with the wrong type, not just casting a pointer to an int. – Tavian Barnes Jun 11 '14 at 14:20
  • Oh, maybe I misread. Indeed you're right. Calling a function via the wrong function type is always UB; I just missed that that's what OP was doing. – R.. GitHub STOP HELPING ICE Jun 11 '14 at 15:04

1 Answers1

2

You aren't supposed to call exit in your threads, you are supposed to return NULL when the thread is finished. Calling exit just terminates the whole program.

user3386109
  • 34,287
  • 7
  • 49
  • 68
  • Thank you. When I tried this, it introduced another problem. When either the read or write thread terminated, the other still kept going. What I am trying to do is make when either the client or server terminate the communication, the entire communication session ends for both parties. So, both the client and server threads are supposed to end and pthread_join() for all four threads is to be called. I can't seem to find a way to do this, so I guess I'll have to settle for exit(). – AK-33 Jun 11 '14 at 02:45
  • As an alternative solution, I guess I can call pthread_detach(pthread_self()) in both client_read() and client_write() as well as server_read() and server_write() to make sure resources will be relinquished as soon as anything terminates. – AK-33 Jun 11 '14 at 02:47