JB0x2D1
JB0x2D1

Reputation: 191

C pthreads send()ing and recv()ing on a socket. Worked separately but not together. Won't quit

In order to slake my thirst for C knowledge, on two linux boxes connected to my home network, I'm writing kind of a skeleton telnet that send()s and recv()s strings (just for some experience with sockets and threads). The server listens and the client connects and sends strings from stdin. I got those to work then I changed them to implement pthreads and the threaded versions worked. Last, I put the two together into one program so that either end of the connection could (in theory) send and receive strings. Both the client and server use strstr() to watch for "quit" and then quit. As the title of this post implies, when I put it all together, the combined version will send strings but it doesn't quit when it is supposed to. I'm not sure what went wrong. I tried to step through it with gdb but I'm just too inexperienced with gdb and couldn't tell what is happening.

So, why won't it quit?

To kind of take a step back, is there a better way to implement what I'm trying to do?

Thanks for any help.

clientserver.c

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <errno.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>

int sockfd = 0, send_running = 1, recv_running = 1, status = 0, acptsockfd = 0;
char str_to_send[200], str_rcvd[200];
char *remote_host_addr_str = NULL;
struct sockaddr_in remote_addr, listening_addr;

void *sender(void *threadid);

void *receiver(void *threadid);

int main(int argc, char *argv[])
{
pthread_t threads[2];
long t = 0;

memset(&remote_addr, 0, sizeof remote_addr);
memset(&listening_addr, 0, sizeof listening_addr);
str_to_send[0] = '\0';
str_rcvd[0] = '\0';
if(argc != 2)
{
    fprintf(stderr, "\n Usage: %s <IP of host to connect to> \n", argv[0]);
    return 1;
}
if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
    fprintf(stderr, "\n Socket Error %s\n", strerror(errno));
    return 1;
}
remote_addr.sin_family = AF_INET;
remote_addr.sin_port = htons(1234);
remote_host_addr_str = argv[1];
if(inet_pton(AF_INET, argv[1], &remote_addr.sin_addr)<=0)
{
    fprintf(stderr, "\n inet_pton error \n");
    return 1;
}
listening_addr.sin_addr.s_addr = htonl(INADDR_ANY);
listening_addr.sin_port = htons(1234);
status = pthread_create(&threads[t], NULL, receiver, (void *)t);
if(status)
{
    fprintf(stderr, "Error: pthread_create(receiver) returned %d\n", status);
    exit(-1);
}
status = pthread_create(&threads[t+1], NULL, sender, (void *)t);
if(status)
{
    fprintf(stderr, "Error: pthread_create(sender) returned %d\n", status);
    exit(-1);
}
while(send_running && recv_running)
    continue;
pthread_exit(NULL);
return 0;
}

void *sender(void *threadid)
{
if (connect(sockfd, (struct sockaddr *)&remote_addr, sizeof remote_addr) == -1)
{
    fprintf(stderr, "socket error %s", strerror(errno));
    send_running = 0;
}

while(1)
{
    fgets(str_to_send, sizeof str_to_send, stdin);
    send(sockfd, str_to_send, sizeof str_to_send, 0);
    if((strstr(str_to_send, "quit")) || strstr(str_rcvd, "quit"))
    {
        send_running = 0;
        recv_running = 0;
        pthread_exit(NULL);
        break;
    }
}

send_running = 0;
}

void *receiver(void *threadid)
{
bind(sockfd, (struct sockaddr*)&listening_addr, sizeof listening_addr);
listen(sockfd, 5);
acptsockfd = accept(sockfd, (struct sockaddr *)NULL, NULL);
while(1)
{
    recv(acptsockfd, str_rcvd, sizeof str_rcvd, 0);
    if(str_rcvd[0] != '\0') 
        printf("%s", str_rcvd);
    if(strstr(str_rcvd, "quit"))
    {
        close(acptsockfd);
        recv_running = 0;
        send_running = 0;
        pthread_exit(NULL);
        break;
    }
}   
recv_running = 0;
}

Upvotes: 1

Views: 6824

Answers (3)

JB0x2D1
JB0x2D1

Reputation: 191

This is the fixed code for what I was trying to do.

/*
* clientserver.c -- send and receive strings over a socket using threads
*/

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <errno.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>

const long MYSENDER = 0;    // send thread ID
const long MYRECVR = 1;     // recv thread ID
int sockfd = 0, out_sockfd = 0, status = 0, acptsockfd = 0, fdmax = 0;  // socket file descriptors, exit status, largest file descriptor
char str_to_send[200], str_rcvd[200];   // send and receive buffers
char *remote_host_addr_str = NULL;  // IP address of host to connect to from command line argument
struct sockaddr_in remote_addr, listening_addr; // remote host and listening socket params
fd_set master_fdset;    // file descriptor set for select()
unsigned char flags = 0;    // operating conditions
const unsigned char ACCEPTED_CONNECTION = 1;    // the receive function has accepted a connection
const unsigned char SEND_RUNNING = 1<<1;    // the send function is running
const unsigned char RECV_RUNNING = 1<<2;    // the receive function is running
pthread_mutex_t flag_mutex; // so all threads can safely read & write the flags variable

void *sender(void *threadid);

void *receiver(void *threadid);

int main(int argc, char *argv[])
{
    FD_ZERO(&master_fdset); // initialize file descriptor set
    pthread_t threads[2];   // two threads: send and receive

    pthread_mutex_init(&flag_mutex, NULL);  // initialize flags mutex
    memset(&remote_addr, 0, sizeof remote_addr);    // initialize to zero
    memset(&listening_addr, 0, sizeof listening_addr);  // initialize to zero
    str_to_send[0] = '\0';  // initialize to NULL char
    str_rcvd[0] = '\0';     // initialize to NULL char
    if(argc != 2)   // expecting an IP address
    {
        fprintf(stderr, "\n Usage: %s <IP of host to connect to> \n", argv[0]);
        return 1;
    }
    if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)  // create listening socket and check for error
    {
        fprintf(stderr, "\n socket() error %s\n", strerror(errno));
        return 1;
    }
    if((out_sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)  // create sending socket and check for error
    {
        fprintf(stderr, "\n socket() Error %s\n", strerror(errno));
        return 1;
    }
    /* fill in details about remote host socket */
    remote_addr.sin_family = AF_INET;
    remote_addr.sin_port = htons(1234);
    remote_host_addr_str = argv[1];
    if(inet_pton(AF_INET, argv[1], &remote_addr.sin_addr)<=0)
    {
        fprintf(stderr, "\n inet_pton error \n");
        return 1;
    }
    /* fill in details about listening socket */
    listening_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    listening_addr.sin_port = htons(1234);
    status = pthread_create(&threads[MYRECVR], NULL, receiver, (void *)MYRECVR);    // start the server thread and check for error
    if(status)
    {
        fprintf(stderr, "Error: pthread_create(receiver) returned %d\n", status);
        exit(-1);
    }
    pthread_mutex_lock(&flag_mutex);
    flags |= RECV_RUNNING;  // server thread is running
    pthread_mutex_unlock(&flag_mutex);
    sleep(1); // wait to see if an incoming connection was accepted
    pthread_mutex_lock(&flag_mutex);
    if(flags & ACCEPTED_CONNECTION) //received an incoming connection
        out_sockfd = acptsockfd;
    pthread_mutex_unlock(&flag_mutex);
    status = pthread_create(&threads[MYSENDER], NULL, sender, (void *)MYSENDER);    // start the client thread and check for error
    if(status)
    {
        fprintf(stderr, "Error: pthread_create(sender) returned %d\n", status);
        exit(-1);
    }
    pthread_mutex_lock(&flag_mutex);
    flags |= SEND_RUNNING;  // client thread is running
    pthread_mutex_unlock(&flag_mutex);
    pthread_join(threads[MYRECVR], NULL);   // main() will wait for the server thread to complete
    pthread_join(threads[MYSENDER], NULL);  // main() will wait for the client thread to complete

    return 0;
}

void *sender(void *threadid)
{
    int c;  // loop counter
    fprintf(stderr, "Connecting to %s\n", remote_host_addr_str);
    for(c = 0; c < 12; ++c)
    {
        if (connect(out_sockfd, (struct sockaddr *)&remote_addr, sizeof remote_addr) == -1) // connect to the remote host.  Retry every 5 sec for 1 min
        {
            fprintf(stderr, "Send socket error: %s\nRetrying in 5 seconds.  %d tries remaining.\n", strerror(errno), (11 - c));
            int d;
            /* show the user a countdown to next retry on the screen */
            fprintf(stderr, " ");
            for(d=5; d>0; --d)
            {
                fprintf(stderr, "\b%d", d);
                sleep(1);
            }
            fprintf(stderr, "\b \b");
            if(c < 11)
                continue;
            else    // failed to connect to remote host.  Shutdown client thread
            {
                pthread_mutex_lock(&flag_mutex);
                flags &= !SEND_RUNNING;
                pthread_mutex_unlock(&flag_mutex);
                return (int*)1;
            }
        }
        else
        {
            fprintf(stderr, "Connected!\n");
            c += 12;
        }
    }

    while(1)
    {
        if(fgets(str_to_send, sizeof str_to_send, stdin) == NULL)   // get input from stdin.  Shutdown client thread on error
            goto shutdown_send_function;
        if((status = send(out_sockfd, str_to_send, strlen(str_to_send)+2, 0)) == -1)    // send the input from stdin and check for error
            fprintf(stderr, "send() error : %s\n", strerror(errno));
        pthread_mutex_lock(&flag_mutex);
        status = (flags & RECV_RUNNING);    // make sure the server thread is still running
        pthread_mutex_unlock(&flag_mutex);
        if((strstr(str_to_send, "quit")) || !status)    // shutdown if the message contains "quit" or the server thread stopped
        {
shutdown_send_function:
            pthread_mutex_lock(&flag_mutex);
            flags &= !SEND_RUNNING;
            pthread_mutex_unlock(&flag_mutex);
            if(out_sockfd != acptsockfd)    // if the sending socket is different than the accepted socket
                if((status = close(sockfd)) == -1)  // close the sending socket
                    fprintf(stderr, "close() error : %s\n", strerror(errno));
            break;
        }
    }
    return 0;
}

void *receiver(void *threadid)
{
    int opt = 1;

    setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
    if(bind(sockfd, (struct sockaddr*)&listening_addr, sizeof listening_addr) == -1)    // bind the listening socket and check for error
        fprintf(stderr, "bind() error : %s\n", strerror(errno));
    fprintf(stderr, "Waiting for incoming connection\n");
    if(listen(sockfd, 5) == -1) // listen for incoming connections
        fprintf(stderr, "listen() error : %s\n", strerror(errno));
    FD_SET(sockfd, &master_fdset);  // add the listening socket to the file descriptor set
    fdmax = sockfd; // keep track of the largest file descriptor for select()
    if((acptsockfd = accept(sockfd, (struct sockaddr *)NULL, NULL)) == -1)  // accept incoming connection request and check for error
        fprintf(stderr, "accept() error : %s\n", strerror(errno));
    FD_SET(acptsockfd, &master_fdset);  // add accepted socket to file descriptor set
    if(acptsockfd > fdmax)  // keep track of the largest file descriptor for select()
        fdmax = acptsockfd;
    pthread_mutex_lock(&flag_mutex);
    flags |= ACCEPTED_CONNECTION;   // a connection has been accepted
    pthread_mutex_unlock(&flag_mutex);
    fprintf(stderr, "Incoming connection detected\n");
    while(1)
    {
        if((status = select(fdmax+1, &master_fdset, 0, 0, NULL)) > 0) // there is data available to be read
        {
            if(recv(acptsockfd, str_rcvd, sizeof str_rcvd, 0) == -1)    // receive the data and check for error
                fprintf(stderr, "recv() error : %s\n", strerror(errno));
            if(str_rcvd[0] != '\0') 
                printf("%s", str_rcvd); // print the message received
            pthread_mutex_lock(&flag_mutex);
            status = (flags & SEND_RUNNING);    // check if the client thread is still running
            pthread_mutex_unlock(&flag_mutex);
            if((strstr(str_rcvd, "quit")) || !status)   // shutdown the server thread if message contains "quit" or client thread stopped
            {
                if((status = close(acptsockfd)) == -1)  // close the accepted socket
                    fprintf(stderr, "close() error : %s\n", strerror(errno));
                pthread_mutex_lock(&flag_mutex);
                flags &= !RECV_RUNNING;
                pthread_mutex_unlock(&flag_mutex);
                break;
            }
        }
        if(status == -1)
            fprintf(stderr, "select() error : %s\n", strerror(errno));
    }
    return 0;
}

Upvotes: 0

Wu Yongzheng
Wu Yongzheng

Reputation: 1707

You shouldn't use the same socket to do listen and connect. Use two sockets.

Upvotes: 0

Anthony
Anthony

Reputation: 12397

From the pthread_exit synopsis

An implicit call to pthread_exit() is made when a thread other than the thread in which main() was first invoked returns from the start routine that was used to create it. The function's return value serves as the thread's exit status.

You are calling pthread_exit() unnecessarily. If you're able to return from your function normally, then the thread will finish correctly. I would prefer to just return from the function if you can.

I think you'll find that the send_running and recv_running flags are superfluous. Basically, if both the send and receive functions loop until they reach their exit condition ("quit" was sent or received), then they return, then the main function should be able to wait on the other two threads. Look at pthread_join. This will eliminate the busy-waiting (looping on send_running && recv_running) in your main function.

As to why the process doesn't end? I don't think the receiver function is ever exiting, so the process won't end until all threads are finished. The receiver function is only checking to see if "quit" was received. If you send "quit", the sender function will quit normally, as will main, but receiver will continually wait to receive the value "quit".

Upvotes: 1

Related Questions