Unable to receive data after connection getting Ba

2019-08-13 14:11发布

I am working on a Multi-threaded Server application in C using Pthreads(Linux). My system is dual boot.Windows 7 as well as Ubuntu. I restarted my PC and booted into Windows from Ubuntu, Before restarting my server application was working Fine.After i again booted to ubuntu from windows. and started my server i start getting following Error when a client connects.

recv failed: Bad file descriptor

Here is My code:

Main.C

#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h> //for socket,AF_INET,SOCK_STREAM
#include <arpa/inet.h> //for sockaddr_in
#include <string.h>   //for memset()  
#include <pthread.h>
#include <fcntl.h>
#include <unistd.h>
#include "extern.h" 

#define MAX_CONNECTIONS  5

int main(int argc, char** argv) {

    int sock_desc = 0, connfd = 0,listenfd =0;
    struct sockaddr_in serv_addr;
    int clntSock; 
    struct sockaddr_in echoClntAddr; 
    unsigned int clntLen; 
    char sendBuff[1025];
    char recvBuff[10025];
    int n = 0;
    pthread_t thr;



    sock_desc = socket(AF_INET, SOCK_STREAM, 0); 

    if(sock_desc < 0 )
       dieWithError("Unable to open Socket\n");  

    memset(&serv_addr,0,sizeof(serv_addr)); 

    serv_addr.sin_family = AF_INET ;
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_addr.sin_port = htons(7024);

    if(bind(sock_desc, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) 
       dieWithError("bind failed\n");

    if(listen(sock_desc,MAX_CONNECTIONS) < 0)
       dieWithError("listen failed\n");  

     file = fopen("testServer.txt", "w");

      clntSock = sizeof(struct sockaddr);
      int i =0;
      fcntl(sock_desc, F_SETFL, fcntl(sock_desc, F_GETFL, 0) | O_NONBLOCK);

      while(1)
    {

        connfd = accept(sock_desc, (struct sockaddr *)&echoClntAddr,(socklen_t*)&clntSock);

      if(connfd > 0){  
         puts("Connection accepted");

        if( pthread_create( &thr, NULL ,  connection_handler , (void*) &connfd) < 0)
        {
            perror("could not create thread");
            return 1;
        }

        puts("Handler assigned");
        }    
    }

    if (connfd < 0)
    {
        perror("accept failed");
        return 1;
    }


       return (EXIT_SUCCESS);
}

void dieWithError(char *errormsg){
     printf("%s", errormsg);  
}

HandleConnection.c

#include <stdio.h> 
#include <sys/socket.h> 
#include <unistd.h> 
#include <string.h>
#include "extern.h"



void *connection_handler(void *socket_desc)
{

    int sock = *(int*)socket_desc;
    t_data.t_id = sock;

    int read_size;
    char *message , client_message[2000];

    while( (read_size = recv(sock , client_message , 2000 , 0)) > 0 )
    {

       client_message[read_size] = '\0';
           t_data.msg = client_message; 


           printf("%s",client_message); 

        memset(client_message, 0, 2000);
    }

    if(read_size == 0)
    {
        puts("Client disconnected");
        fflush(stdout);
    }
    else if(read_size == -1)
    {
        perror("recv failed");
    }

    return 0;
} 

I tried by restarting my PC but same problem persists.Please Help.

1条回答
贼婆χ
2楼-- · 2019-08-13 14:54

(The issue you observe has nothing to do with running on a dual-boot machine.)

The code as shown introduces a race by using the address of the same variable for each incoming connection to pass the socket descriptor to the thread function.

The race occures if accept() would return faster a second time. To fast for the thread-handler to store what the address it got passed pointed to via executing:

int sock = *(int*)socket_desc;

In your specfic case with the listening socket set to non-blocking this most likley is the case, when only one connection came in, the handler got the address of connfd passed in and in parallel accept() gets called again on the (non-blocking) listening socket, no incoming connection is waiting and accept() returns immediately and sets connfd to -1. This happens faster then the handler for the previous connection could have called

int sock = *(int*)socket_desc;

and with this store the -1.

To observer this effect (not to fix the issue) modify your code as follows:

int result = accept(sock_desc, (struct sockaddr *)&echoClntAddr, &clntSock);
if (0 > result)
{
  perror("accept() failed");
}
else
{
  connfd = result;

  puts("Connection accepted");

  if( pthread_create( &thr, NULL ,  connection_handler , (void*) &connfd) < 0)
  {
    ...

The handler then would begin like this:

    void *connection_handler(void * pv)
    {
      int sock = *((int*) pv);
      fprintf("sock = %d\n", sock);
      ...

To fix your issue there are two approaches:

  1. Misuse the thread-function's void* argument to pass an int.

    socklen_t clntSock = ...;
    ...
    connfd = accept(sock_desc, (struct sockaddr *)&echoClntAddr, &clntSock);
    
    if (0 <= connfd)
    {
      if (pthread_create( &thr, NULL, connection_handler, (void *) connfd) < 0)
      {
        ...
    

    The handler then would begin like this:

    void *connection_handler(void * pv)
    {
      int sock = (int) pv;
    
      ...
    

    This is a common, but somewhat "hacky" solution, which relies on a pointer being at least as wide as an int.

  2. Use a seperate instance of an int to store the result of accept().

    socklen_t clntSock = ...;
    ...
    connfd = accept(sock_desc, (struct sockaddr *)&echoClntAddr, &clntSock);
    if (0 <= connfd)
    {
      int * pconnfd = malloc(sizeof *pconnfd);
      if (NULL == pconnfd)
        ... // fail and leave here
      *pconnfd = connfd;
      if (pthread_create(&thr, NULL, connection_handler, pconnfd) < 0)
      {
        ...
    

    The handler then would begin like this:

    void *connection_handler(void * pv)
    {
      int connfd = *((int *) pv);
      free(pv);
    
      ...
    

    This is a clean and portable approach, with the cost of an additional pair of calls to malloc()/free().

Other corrections in the code shown above:

  • Define the variable whichs address is passed to accept() to be socklen_t as the 3rd parameter to accept() is defined as socklen_t *. If passing a pointer to something else and this "something" is of different size than socklen_t accept() would run into undefined behaviour.

  • Test the outcome of accept() against <0 for error as 0 is a valid value for a file/socket descriptor.

查看更多
登录 后发表回答