ARBY ARBY - 2 months ago 7
C Question

clients connecting to TCP iterative server even after backlog queue is full

Here's an iterative server I've created to handle basic client-server chat application.

I am trying to run TCPserver on a terminal window and TCPclient on multiple terminal windows.

More than 5 the clients are getting connected(neither getting blocked nor failing. They immediately

connect
successfully) despite of the fact that I set the
backlog
value (
listen
system call) in server socket to be 5.

I expected that no more than 5 clients could be connected(only 1 would be accepted at a time).

Is my understanding of the backlog value set in the
listen
system call wrong?
Please clarify.


int listen(int sockfd, int backlog);

The backlog argument defines the maximum length to which the queue of pending connections for sockfd may grow.


Here are the actual programs for reference.

TCPserver.c



#include<stdio.h>
#include<stdlib.h>

#include<sys/types.h>
#include<sys/socket.h>

#include<netinet/in.h>
#include<unistd.h>
#define BACKLOG 5

#include <netinet/in.h>
#include <arpa/inet.h>

#include<string.h>

int main()
{
//create the server socket
int sd;
sd=socket(AF_INET,SOCK_STREAM,0);
if(sd==-1)
{
perror("Some error occured in creating the socket: ");
//Interprets the value of errno as an error message, and prints it to stderr
exit(EXIT_FAILURE);
}
else
printf("Socket created!\n");

//define the server address
struct sockaddr_in server_address;

server_address.sin_family = AF_INET;
server_address.sin_port = htons(9002);
server_address.sin_addr.s_addr = INADDR_ANY;
//inet_addr("192.168.137.163");//INADDR_ANY;

//bind the socket to our specified IP and port
int bind_status = bind(sd, (struct sockaddr *) &server_address, sizeof(server_address));
if(bind_status == -1)
{
perror("An error occurred in binding the socket: " );
exit(EXIT_FAILURE);
}
else
printf("Bind Successful!\n");


//listen for connections
int listen_status = listen(sd, BACKLOG);
if(listen_status == -1)
{
perror("Error occured in listening: ");
exit(EXIT_FAILURE);
}
else
printf("Server is listening!\n");

while(1)
{
//Accept a connection and create a new socket for this connection
int new_sd;
struct sockaddr_in client_address;
int client_address_size = sizeof(client_address);

new_sd= accept(sd, (struct sockaddr*) &client_address, &client_address_size);
if(new_sd==-1)
{
perror("Can't accept connection: ");
exit(EXIT_FAILURE);
}
else
printf("Accept successful!\nA new client has connected. He'll soon send you a message.\n (You can chat or say \"exit\" to stop chatting)\n\n");


//send a message to the client
char buffer[256] = "Welcome to the server, lets chat! \n (You can chat or say \"exit\" to stop chatting)\n";
send(new_sd,buffer,sizeof(buffer),0);

//start chat
while(1)
{
memset(buffer,0,256);
int n = recv(new_sd, buffer, sizeof(buffer),0);
if(n==-1 || strcmp(buffer,"exit\n")==0 || strcmp(buffer,"exit")==0)
break;
printf("\nclient said: %s\n",buffer);

memset(buffer,0,256);
printf("Say something: ");
fgets(buffer,256,stdin);
n = send(new_sd, buffer, sizeof(buffer), 0);
if(n==-1 || strcmp(buffer,"exit\n")==0)
break;
}

//close the sockets
close(new_sd);
printf("\nConnection ended. waiting for new connection now . . .\n");
}

close(sd);

return 0;
}


and Here's the client

TCPclient.c



#include<stdio.h>
#include<stdlib.h>

#include<sys/types.h>
#include<sys/socket.h>

#include<netinet/in.h>

#include<unistd.h>

#include <netinet/in.h>
#include <arpa/inet.h>
#include<string.h>
int main()
{
//create the socket
int sd = socket(AF_INET,SOCK_STREAM,0);

if(sd==-1)
{
perror("Some error occurred in creating the socket: ");
exit(EXIT_FAILURE);
}
else
printf("Socket created!\n");

//specify an address for the socket
struct sockaddr_in server_address;

server_address.sin_family = AF_INET;
server_address.sin_port = htons(9002);
server_address.sin_addr.s_addr = inet_addr("127.0.0.1");//INADDR_ANY;

//Connect to the server
int connection_status = connect(sd,(struct sockaddr *) &server_address, sizeof(server_address));

if(connection_status == -1)
{
perror("There was an error connecting to the remote socket: ");
exit(EXIT_FAILURE);
}
else
printf("Connected to the server! Waiting in the queue for the server to accept the connection...\n");

char buffer[256];
//get the connection message from the server
recv(sd, &buffer, sizeof(buffer), 0);
printf("Server Said: %s\n", buffer);

//start chat
while(1)
{
memset(buffer,0,256);
printf("Say something: ");
fgets(buffer,256,stdin);
int n = send(sd,buffer,sizeof(buffer),0);
if(n==-1 || strcmp(buffer,"exit\n")==0)
break;

memset(buffer,0,256);
n = recv(sd,buffer,sizeof(buffer),0);
if(n==-1 || strcmp(buffer,"exit\n")==0)
break;

printf("\nServer said: %s\n",buffer);
}

//close the socket
close(sd);

return 0;
}


Someone suggested I check if
syncookies
have been enabled on my system. When I perform
cat /proc/sys/net/ipv4/tcp_syncookies
, I get
1
.

jxh jxh
Answer

Your system has enabled SYN cookies, which allows the TCP stack to behave as if it has a very large listen queue. It was designed to mitigate DOS via SYN flooding.

The listen man page states about the backlog argument:

When syncookies are enabled there is no logical maximum length and this setting is ignored.

If you really want no more than 5 pending clients to wait on the server, you will have to manually maintain your own queue, and close new connections if your queue is full.

Note that this solution does not actually affect the behavior of the operating systems listen queue. The solution is to continually clear the listen queue of any backlog and close those connections if your server already has 5 pending connections in its own queue.

Probably in your case, the easiest way to achieve that is with two threads. One doing the accepting, the other handling connections off the queue.

The fragment below illustrates this in pseudo-code. It assumes proper thread mutual exclusion and signaling is performed by the queue operations.

accepting_thread () {
    int queue_count = 0;

    for (;;) {
        new_conn = accept();
        if (q_size(q) < 5) {
            q_enqueue(q, new_conn);
        } else {
            close(new_conn);
        }
    }
}

handling_thread () {
    for (;;) {
        new_conn = q_dequeue(q);
        /* ... */
        close(new_conn);
     }
}

To get clients that are terminated early to see a reset, you can enable the linger option with a 0 timeout value. Most TCP stacks will cause a RST to be generated when the socket is closed.

Comments