Search code examples
csocketsnetwork-programmingposix

Is there any way doing that when I forcefully exit from my server (entering clt + c), client also gets terminated at the same time?


Write two separate C program, one for TCP server (handles request for single user) and other one for client.

At server side- Creates a socket and listens on some specific port to process client request.

There is a default file present having n lines and the server should be able to process READX and WRITEX request from the client.

  1. The server process should tokenize string received from the client that may contain READX or WRITEX request in following format-
    • READX k- read kth line from the starting of file and return to client.
    • WRITEX msg- append msg string to the end of file present at server and return “SUCCESS!!” to the client as acknowledgement.

At client side-

  1. Client process should take input from the user whether to READ or WRITE on the server side.
  2. It then initiates connection to server and forwards the query to server.
  3. Receives output from server and displays it to the user.

I have almost handled all error handling. All is working.

  1. My first query if I forcefully kill server program (e.g. by entering clt+ c), I want client program to get terminated simultaneously. how can I do this?
  2. 2nd query: when I am forcefully kill client, my server program is getting terminated automatically. But, I am not finding why it happening? Generally. it does not happen. I am not finding which line of my code makes that happen.

Server code:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<sys/types.h> 
#include<sys/socket.h>
#include<netinet/in.h>
#include<netdb.h>
#include<arpa/inet.h>
#include<unistd.h>
#define MYPORT "5000"
#define BACKLOG 3
char space[] = "    ";


/*this funtion is counting total no of  line present in input.txt file   
whenever it is called */
 int count_line()
{
FILE *ptr = fopen("input.txt", "r");
char * line = NULL;
int i = 0;
size_t count =0;
if (ptr == NULL)
{
    perror("error: line no 21");
}
while(getline(&line,&count, ptr) > 0)
{
    i++;
}
fclose(ptr);
return i;

}

/* This funtion will  write the message what is given by client at the   
end of the file */
 int write_instruction(char *buffer,int max_length)
{
char final[255];
FILE *fp = fopen("input.txt", "a");

if(fseek(fp, 0, SEEK_END) != 0)
{
    perror("fseek:error");
    exit(EXIT_FAILURE);
}
if (count_line() == 250)
{
    sprintf(final,"%d%s%s",count_line() +1, space, buffer);
}
else
    sprintf(final,"%s%d%s%s","\n",count_line() +1, space, buffer);
fputs(final, fp);
fclose(fp);
return (1);

}

 /* This function will fetch the exact line from input.txt what is instructed by READX in client and will return to client */
void read_instruction(int line_no, int max_length, int server_new_fd)
 {
ssize_t no_read_byte;

/*error checking , if you enter out of bound line no*/
if ((line_no > count_line()) || (line_no <1))
{
    if( ( no_read_byte = write(server_new_fd, "you are entering out of bound line no: TRY AGAIN", strlen("you are entering out of bound line no: TRY AGAIN")+1)) == -1)
    {
        perror("write:error");
        exit(EXIT_FAILURE);
    } 
    return;
}


char *line = NULL, final[max_length];
size_t count =0;

FILE *stream = fopen("input.txt", "r");
if (stream == NULL)
    exit(EXIT_FAILURE);


 for (int i = 0;i < line_no; ++i)
 {
    if(getline(&line, &count, stream) == -1)
    {
        perror("getline: error");

    }
 }

if( ( no_read_byte = write(server_new_fd, line, strlen(line)+1)) == -1)
    {
        perror("write:error");
        exit(EXIT_FAILURE);
    } 
free(line);
fclose(stream);

return ;
}
 void *get_in_addr(struct sockaddr *sa)
{
if (sa->sa_family == AF_INET) {
    return &(((struct sockaddr_in*)sa)->sin_addr);
}

return &(((struct sockaddr_in6*)sa)->sin6_addr);
}

int main()
{
int status, server_sockfd, yes = 1, server_new_fd;
struct addrinfo hints, *ref, *p;
struct sockaddr_storage addr;
struct sockaddr_in m;
struct sockaddr_in6 n;
socklen_t addrlen;
ssize_t no_read_byte;
size_t count = 1024;
char ip_str[INET6_ADDRSTRLEN], buffer[1024], *readx, *writex;

memset(&hints, 0, sizeof hints);/* setting all bits of hints 0;*/

/* AF_UNSPEC mean any address family */
hints.ai_family = AF_UNSPEC;
hints.ai_flags = AI_PASSIVE;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = 0;

if ( (status = getaddrinfo(NULL, MYPORT, &hints, &ref)) != 0)
{
    fprintf(stderr, "getaddrinfo : %s\n", gai_strerror(status));
    return 2;
}


for ( p = ref; p != NULL; p = p->ai_next)
{
    /*creating socket where passing ai_family , ai_socktype, ai_protocol as domain, type, protocol.
    And chhecking one by one struct addrinfo from list returned by getaddrinfo(), to get first successful socket descriptor.*/
    if ( (server_sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1)
    {
        perror("socket:error");
        continue;
    }

    if (setsockopt(server_sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof (int)) == -1)
    {
        perror("setsockopt : error");
        exit(1);
    }

    if (bind(server_sockfd, p->ai_addr, p->ai_addrlen) == -1)
    {
        perror("bind : error");
        close(server_sockfd);
        continue;
    }
    /*  I am getting out of this for loop because I already got successful socket fd, and successful binding.
        I don't need to traverse the list anymore.
    */
    break;
}

freeaddrinfo(ref);// I am done with struct addrinfo list.

/* I am listening here.*/
if (listen(server_sockfd, BACKLOG) == -1)
{
    perror("listen : error");
    exit(1);
}

printf("server: waiting for connection...........\n");


addrlen = sizeof(addr); /* If there are any client trying to connect, I accept here its connect request. */
    if( (server_new_fd = accept(server_sockfd, (struct sockaddr *)&addr, &addrlen)) == -1)
    {
        perror("accept: error");
        exit(EXIT_FAILURE);
    }

    inet_ntop(addr.ss_family, get_in_addr((struct sockaddr *)&addr), ip_str, sizeof ip_str);
    printf("Server : %s is connected .\n", ip_str);


int i=0;

close(server_sockfd); /* WE  are here just hanldling one client, so we do need to listen anymore. so we are closing server_sockfd */

while(1)
{
memset(&buffer, 0, 1024); /* Begining of every loop, we shall set '\0' to every byte of buffer */


/*we read here first time for every loop from server_new_fd and save it into buffer*/
if( (no_read_byte = read(server_new_fd, &buffer, count)) == -1)
{
    perror("read failed");
    exit(EXIT_FAILURE);
}

writex = buffer;


/*we are checking error here. when you will give just empty string, that will be detected here*/    
if( (readx = strtok_r( writex, " ", &writex)) == NULL)
{
    if( ( no_read_byte = write(server_new_fd, "you are entering invalid input", strlen("you are entering invalid input")+1) == -1))
    {
        perror("write:error");
        exit(EXIT_FAILURE);
    }
    continue; 

}/* here we are checking for shutdown condition . if you want to shutdown just enter -1*/
else if (strcmp(readx, "-1") == 0)
{
    printf("we are terminating\n");
    if( (no_read_byte = write(server_new_fd,"-1", strlen("-1")+1) ) == -1)
        {
            perror("write: error");
            exit(EXIT_FAILURE);
        }
    exit(EXIT_SUCCESS);
}
/* if you enter just READX or WRITEX , then that will be detected here. */
else if ((atoi(writex) == 0) && (strcmp(readx, "READX") ==0) )
{

    if( ( no_read_byte = write(server_new_fd, "you are entering invalid input", strlen("you are entering invalid input")+1)) == -1)
    {
        perror("write:error");
        exit(EXIT_FAILURE);
    }
    continue;
}
else if ((writex[0] == 0) && (strcmp(readx, "WRITEX") ==0 ))
{
    if( ( no_read_byte = write(server_new_fd, "you are entering invalid input", strlen("you are entering invalid input")+1)) == -1)
    {
        perror("write:error");
        exit(EXIT_FAILURE);
    }
    continue;
}
/* this for READX formatted instruction */
else if( strcmp(readx, "READX") ==0)
{ 
    char * str = strtok_r( writex, " ", &writex);

    if (atoi(writex) != 0)
    {
        /* if you enter like READX 12 34 45, that will be detected here */
        if( ( no_read_byte = write(server_new_fd, "you are entering invalid input", strlen("you are entering invalid input")+1)) == -1)
        {
            perror("write:error");
            exit(EXIT_FAILURE);
        }
        continue; 
    }
    else /* This for correct formatted READX instruction */
    {

        printf("Client is instructing to read and return line.\n");
        read_instruction(atoi(str), 255, server_new_fd);
    }

} /* this for correct WRITEX instruction */
else if (strcmp(readx, "WRITEX") ==0)
{
    printf("Client is instructing to append given string to end of the file.\n");
    if(write_instruction(writex, 255) == 1) 
    {
        if( (no_read_byte = write(server_new_fd,"SUCCESS!!", strlen("SUCCESS!!")+1) ) == -1)
        {
            perror("write: error");
            exit(EXIT_FAILURE);
        }

    }   
}
else /* this for all other invalid error */
{
    if( ( no_read_byte = write(server_new_fd, "you are entering invalid input 1", strlen("you are entering invalid input 1  ")+1)) == -1)
        {
            perror("write:error");
            exit(EXIT_FAILURE);
        }
        continue;
}


}

close(server_new_fd);



}

client code:

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


void *convert(struct sockaddr *sa)
{
if ( sa->sa_family == AF_INET)
    return &(((struct sockaddr_in *)sa)->sin_addr);
return &(((struct sockaddr_in6*)sa)->sin6_addr);
}

int main(int argc, char *argv[])
{
if (argc != 2)
{
fprintf(stderr, "you are entering wrong number of string\n"   );
    exit(EXIT_FAILURE);
}

struct addrinfo hints, *ref, *p;
int status, client_sockfd, yes = 1;
ssize_t  no_read_byte;
size_t count=1024;
struct sockaddr_storage addr;
char ip_str[INET6_ADDRSTRLEN], buffer[1024];

memset(&hints, 0, sizeof hints);

if ( (status = getaddrinfo(argv[1], MYPORT, &hints, &ref)) != 0)
{
    fprintf(stderr, "getaddrinfo : %s\n", gai_strerror(status));
    return 2;
}


 for (p = ref;p != NULL; p = p->ai_next)
 {
if ( (client_sockfd = socket(p->ai_family, p->ai_socktype,  p->ai_protocol)) == -1)
    {
        perror("client_socket: error");
        continue;
    }



    if ( (connect(client_sockfd, p->ai_addr, p->ai_addrlen)) == -1)
    {
        perror("connect: error");
        close(client_sockfd);
        continue;
    }break;
 }
 if (p == NULL) {
    fprintf(stderr, "client: failed to connect\n");
    exit(EXIT_FAILURE);
}
//inet_ntop(p->ai_family, get_in_addr((struct sockaddr *)p->ai_addr), ip_str, sizeof ip_str);
inet_ntop(p->ai_family, convert((struct sockaddr *)p->ai_addr), ip_str, sizeof ip_str);
//printf("%s\n",convert((struct sockaddr *)p->ai_addr));
printf("client: connecting to %s\n", ip_str);

freeaddrinfo(ref);
while(1){
printf("please enter the instruction: ");
gets(buffer);

if ((no_read_byte = write(client_sockfd, buffer, strlen(buffer)+1)) == -1)
{
    perror("write:error");
    exit(EXIT_FAILURE);

}
bzero(buffer, 255);
if((no_read_byte = read(client_sockfd, buffer, 255)) == -1)
{

    perror("read:error");

    exit(EXIT_FAILURE);

}
else if (strcmp(buffer, "-1") == 0)
{
    exit(EXIT_SUCCESS);
}
else
    printf("%s\n",buffer );
bzero(buffer, 255);
}

close(client_sockfd);
}

Solution

  • I'm not sure I'm reading your code correctly, but it looks to me like no message is produced if zero bytes are read (by client or server). read(2) can return zero:

    RETURN VALUES If successful, the number of bytes actually read is returned. Upon reading end-of-file, zero is returned. Otherwise, a -1 is returned and the global variable errno is set to indicate the error.

    End of File is not considered an error. It is the normal indication that the remote has closed the connection, which automatically happens when a program terminates.

    Therefore, for any process with an outstanding read operation (blocked, waiting for data from the peer) read(2) will return zero when the peer terminates. If you kill your server and the client is reading, the client will see zero bytes read, and vice versa.

    Your client logic doesn't do anything special for that case. You test for -1. If not -1, you test for a bunch of stuff in the (empty) buffer. Then you exit.

    Check explicitly for zero, and write a message that the server closed the connection. I think you'll see the client is not "getting terminated automatically". It's just falling off the end.