Reputation: 191
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
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
Reputation: 1707
You shouldn't use the same socket to do listen and connect. Use two sockets.
Upvotes: 0
Reputation: 12397
From the pthread_exit
synopsis
An implicit call to
pthread_exit()
is made when a thread other than the thread in whichmain()
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