Hadi
Hadi

Reputation: 995

Attaching a C/C++ thread to a socket

My code, which is taken from on-line sources looks like below:

//.. initialization and other things..
int connfd = 0;
connfd = accept(listenfd, (struct sockaddr*)&client_address,&client_address_len);
//... other code
 pthread_create(&tid, NULL, &foo, &connfd);

The code above, will call the function foo with argument id. Now each thread will have its own version of foo to run with the argument. In foo, I am able to send data to a client using the socket. However, I am failing to see how (although it works perfectly fine), what is linking the thread and the socket. I understand that id is passed, but it that is only an integer.

This is what the foo function looks like:

  void* SendFileToClient(void *var){
   int *connfd;
   connfd =(int*) var;
   std::cout << "Connection accepted and id:" << *connfd << std::endl;
   std::cout << "Connected to Client with IP: " <<   inet_ntoa(c_addr.sin_addr) << "Port:" << ntohs(c_addr.sin_port) << std::endl; // inet_ntoa converts binary form of IPv4 to IPv4 dotted numbers, ntohs does something silimar for ports
   int write_size = write(*connfd, fname,64);

   std::cout << write_size << "connfds: " <<  *connfd << std::endl;

   FILE *fp = fopen(fname,"rb");

   if(fp==NULL)
    {
        printf("File opern error");
        exit(EXIT_FAILURE);
    }

    /* Read data from file and send it */
    while(1)
    {
        /* First read file in chunks of 64 bytes */
        unsigned char buff[1024]={0};
        int nread = fread(buff,1,1024,fp);

        /* If read was success, send data. */
        if(nread > 0)
        {
            std::cout << "Sending..." << std::endl;
            write_size =  write(*connfd, buff, nread);
        }
        if (nread < 1024)
        {
            if (feof(fp)) //end of file indicator
            {
                std::cout << "End of file" << std::endl;
                std::cout << "File transfer completed for id:" << *connfd << std::endl;
            }
            if (ferror(fp))
                std::cout << "Error reading" << std::endl;
            break;
        }
    }
    std::cout << "Closing Connection for id:" << *connfd << std::endl;
    close(*connfd);
    shutdown(*connfd,SHUT_WR);
    sleep(2);
    return NULL;

}

In the code above, the call to

   write(*connfd, buff, nread);

knows to send data through a socket referenced by connfd.

Upvotes: 1

Views: 2658

Answers (4)

Serge
Serge

Reputation: 12344

It seems that you picture threads incorrectly. A thread is just a way to call and execute your function. The program calls the foo function, but allows it to run in somewhat concurrent way, sharing execution time (and multiple cpus, if you have them) with other calls to the same or to a different function. This manner of execution is the thread and it is managed by the OS, similarly to other processes.

Your program just calls a regular function 'foo' and passes an argument, which happens to be the confd file descriptor. This is the only thing the foo function needs to know about the socket in order to write data into it. The program also tells operating system that it wants to execute this function as a thread, by calling pthread_create. The function itself does not care how it gets executed.

When the function exits, operating system stops the thread execution.

So, there is no really any link between socket and a thread. Just the function...

Upvotes: 1

Lightness Races in Orbit
Lightness Races in Orbit

Reputation: 385088

what is linking the thread and the socket

connfd. It is the file descriptor for the socket. Yes, it is "only an integer", but your operating system (which handles all the socket-related function calls) knows what to do with it, and performs all the necessary mappings for you. That's why it gave you that value as a result from the accept call: as a handle to use with future socket function calls.

As mentioned elsewhere, do note that you are currently passing connfd to the thread unsafely; you should instead pass in a copy … either encoded into the thread argument, masquerading as a pointer (via uintptr_t) or in some fresh dynamically-allocated object to which you can then pass a pointer into the thread.

Or, ideally, drop pthreads and use std::thread, since it is now 2018 already and that facility has been available for quite some time. This allows for very expressive passing of as many arguments as you want to foo, so you'd just pass connfd by value and leave it at that.

Upvotes: 1

user207421
user207421

Reputation: 310850

int *connfd;
connfd =(int*) var;

You should not do this. You must copy the FD as quickly as possible:

int connfd = *(int*)var;

Otherwise it will change after the next accept. You are deferencing *connfd every time you use it. If accept() has returned again, it will have returned a different FD, and your thread will now be using the wrong FD; and you will have lost the original FD; so you will also have a socket leak.

Better still, pass it in a piece of dynamic memory that the thread is responsible for freeing.

Upvotes: 0

jwdonahue
jwdonahue

Reputation: 6659

The write function doesn't care whether the handle maps to a socket, a file or a laser gun, it only knows how to write content into a buffer and signal that the buffer is ready to be flushed. How it gets flushed was arranged when you created the socket, opened the file or the laser gun.

Upvotes: 0

Related Questions