views:

145

answers:

3

I am new to both sockets and threads. I have this code:

listen(socket_fd, 20);

/* Looooop */
  while (1) {
    newsocket_fd = accept(socket_fd, 
                          (struct sockaddr *) &client_addr, 
                          &client_len);
    if (newsocket_fd < 0) {
      error("ERROR on accept");
    }
    pthread_t thread;
    pthread_create(&thread, NULL, run_thread, (void *) newsocket_fd);
    pthread_join(thread, NULL);
  }

How can I start a new thread for each new connection, rather than for each request? These threads should be started when a new connection comes in, and these threads should then wait for requests, handle those requests, and finally return when the connection is closed. There should be one thread for each connection. Here is the code for run_thread:

void
*run_thread(void *ptr) {
  char buffer[256];
  bzero(buffer, 256);
  int n;
  n = read((int) ptr, buffer, 255);
  if (n < 0) error("ERROR Reading from socket");
  printf("%s\n\n**********\n\n", buffer);

  /* Parse buffer and return result */
  char *result;
  {
    /* First, determine command, 4 characters */
    /* (much code) */
  }

  n = write((int) ptr, result, strlen(result));
  if (n < 0) error("ERROR Writing to socket");
}

Can anyone help me? Thanks.

+5  A: 

You almost got it right. The problem is, however, that you are joining the thread right after creation, and pthread_join is actually a blocking call which is waiting for the thread to finish. It means that you will not be able to accept any more connections while that one thread is running. To solve this problem, you might want to use detached threads. You don't have to join detached threads. For this purpose, you have to create thread attributes using pthread_attr_init function and pass those attributes to pthread_create.

Be aware that if you have too many client connections, your application may run out of resources. So, in real world, you have to manage a pool of threads. But the best case scenario for TCP/IP server applications is to use asynchronous I/O. I do not know about C, but there is a very good library in C++ for asynchronous I/O application called boost::asio.

Vlad Lazarenko
For C, libev and libevent look very cool. I personally favour libev.
Jack Kelly
A: 

Vlad has good advice.

Also note that your newsocket_fd variable is being reused for each new connection in your accept loop, and then a pointer to it is passed to every worker thread. This will cause problems when you start having multiple clients connected at the same time.

EDIT: Ignore this comment, I misread the mistake you were making. Others have given proper corrections for your handling of newsocket_fd.

Darron
He's not passing the address of `newsocket_fd`, he's passing the value and then casting the arg from `(void*)` to `int` in the thread func.
bstpierre
+4  A: 

There is also a different critical error.

You cast the int to (void*). This does not make sense. Also, you can't pass the address directly since the variable could be changed on the next accept() call before the thread can copy the variable to its local stack. One way to write it would be something like this:

while (1) {
    newsocket_fd = accept(socket_fd, 
                          (struct sockaddr *) &client_addr, 
                          &client_len);
    if (newsocket_fd < 0) {
      error("ERROR on accept");
    }
    pthread_t thread;
    int *newsock = malloc(sizeof(int));
    *newsock = newsocket_fd;
    pthread_create(&thread, NULL, run_thread, newsock);
    pthread_detach(thread);
  }

With this approach, the thread will make sure to free() the newsock. E.g a simple

void *handler(void *thread_data) {
   int fd = *(int *) thread_data;
   free(thread_data);
   ....
}

Also, I assume pthread_detach() is okay, if the main program doesn't care about syncing up with the thread later with pthread_join().

Maister
Size of the pointer is always equal or bigger than integer. So there is no need in dynamic memory allocation to pass integer.
Vlad Lazarenko
I still feel it's rather bad taste to cast integers to (void*) there. Does the standard say anything about int -> (void*) -> int conversion (or similar conversions) being guaranteed to work?
Maister
Casting an "int" to a "void*" and back again is guaranteed to work.
Steve Emmerson
I see. Then I learned something new today.
Maister
@Steve Emmerson: No, I don't believe it is. Converting an integer to a pointer type (other than an integer constant 0) has an implementation-defined result. Converting a pointer to an integer type has an implementation-defined result, unless the result is outside the range of the integer type, in which case the behaviour is undefined. It is also noted that *"The result need not be in the range of values of any integer type."*
caf
"An object of integral type may be explicitly converted to a pointer. The mapping always carries a sufficiently wide integer converted from a pointer back to the same pointer, but is otherwise implementation-dependent". The sense is reversed, but, in practice, this guarantees what I said. I know of no counterexamples. If you do, then please enlighten me.
Steve Emmerson
@Steve: I'm reading from the C standard - which document are you looking at? Also, your statement doesn't imply the reverse (even if you have a sufficiently-wide pointer type) - for example, with a segmented memory architecture the integer-to-pointer conversion may produce a canonicalised pointer, which converts back to a different integer.
caf
Steve Emmerson
@Steve: I can't recall how most DOS compilers converted between integers and (far) pointers, but two schemes seem reasonable to me: 1) Treat the integer as a linear address, so that bits 4 to 19 are placed in the segment and bits 0 to 3 are placed in the offset; 2) Treat the integer as a segment:offset, so that bits 16 to 31 are placed in the segment and bits 0 to 15 are placed in the offset. Scheme 1) clearly loses the top 12 bits of the original integer, and scheme 2) will map many integers onto few if the resulting pointer is canonicalised (which would be reasonable to do for comparisons).
caf
@caf: Like I said, the integer->pointer->integer idiom worked even on the segmented memory system I used. The combination of three things constrain the integer->pointer->integer sequence to recover the original integer: 1) the requirement that pointer->integer->pointer must recover the original pointer; 2) an "int" is the "native" word size; and 3) compiler writers and chip designers want to keep things simple. It's not in the standard, but it's dependable.
Steve Emmerson