First time using select(), maybe a basic question? - c

i've been working for a few days with this server using select(). What it does, is that, i have two arrays of clients (one is "suppliers", and the other is "consumers"), and the mission of the server is to check whether the suppliers have something to send to the consumers, and in case affirmative, send it.
The second part of the server is that, when the consumers have received the suppliers' info, they send a confirmation message to the same suppliers that sent the info.
When a client connects, it gets recognized as "undefined", until it sends a message with the word "supplier" or "consumer" (in Spanish, as i'm from there), when the server puts it in the correct clients array.
Well, what the server does is not very important here. What's important is that, i'm doing both parts with two different "for" loops, and that's where i'm getting the problems. When the first user connects to the server (be it a supplier or a consumer), the server gets stuck in the first or second loop, instead of just continuing its execution. As it's the first time i'm using select(), i may be missing something. Could you guys give me any sort of help?
Thanks a lot in advance.
for(;;)
{
rset=allset;
nready=select(maxfd+1,&rset,NULL,NULL,NULL);
if (FD_ISSET(sockfd, &rset))
{
clilen=sizeof(cliente);
if((connfd=accept(sockfd,(struct sockaddr *)&cliente,&clilen))<0)
{
printf("Error");
}
IP=inet_ntoa(cliente.sin_addr);
for(i=0;i<COLA;i++)
{
if(indef[i]<0)
{
indef[i]=connfd;
IPind[i]=IP;
break;
}
}
FD_SET(connfd,&allset);
if(connfd > maxfd)
{
maxfd=connfd;
}
if(i>maxii)
{
maxii=i;
}
if(--nready<=0)
{ continue; }
}// Fin ISSET(sockfd)
for(i=0;i<=maxii;i++)
{
if((sockfd1=indef[i])<0)
{ continue; } //!
if(FD_ISSET(sockfd1,&rset))
{
if((n=read(sockfd1,comp,MAXLINE))==0)
{
close(sockfd1);
FD_CLR(sockfd1,&allset);
indef[i]=-1;
printf("Cliente indefinido desconectado \n");
}
else
{
comp[n]='\0';
if(strcmp(comp,"suministrador")==0)
{
for(j=0;j<=limite;j++)
{
if(sumi[j]<0)
{
IPsum[j]=IPind[i];
sumi[j]=indef[i];
indef[i]=-1;
if(j>maxis)
{
maxis=j;
}
break;
}
}
}
else if(strcmp(comp,"consumidor")==0)
{
for(o=0;j<=limite;j++)
{
if(consum[o]<0)
{
IPcons[o]=IPind[i];
consum[o]=indef[i];
indef[o]=-1;
if(o>maxic)
{
maxic=o;
}
break;
}
}
}
if(--nready <=0)
{
break;
}
}
}
}//fin bucle for maxii
for(i=0;i<=maxis;i++)
{
if((sockfd2=sumi[i])<0)
{ continue; }
if(FD_ISSET(sockfd2,&rset))
{
if((n=read(sockfd2,buffer2,MAXLINE))==0)
{
close(sockfd2);
FD_CLR(sockfd2,&allset);
sumi[i]=-1;
printf("Suministrador desconectado \n");
}
else
{
buffer2[n]='\0';
for(j=0;j<=maxic;j++)
{
if((sockfd3=consum[j])<0)
{ continue; }
else
{
strcpy(final,IPsum[i]);
strcat(final,":");
strcat(final,buffer2);
write(sockfd3,final,sizeof(final));
respuesta[i]=1;
}
}
break; // ?
}
}
}//fin for maxis
for(i=miniic;i<=maxic;i++)
{
if((sockfd4=consum[i])<0)
{ continue; }
if(FD_ISSET(sockfd4,&rset))
{
if((n=read(sockfd4,buffer3,MAXLINE))==0)
{
close(sockfd4);
FD_CLR(sockfd4,&allset);
consum[i]=-1;
printf("Consumidor desconectado \n");
}
else
{
buffer3[n]='\0';
IP2=strtok(buffer3,":");
obj=strtok(NULL,":");
for(j=0;j<100;j++)
{
if((strcmp(IPsum[j],IP2)==0) && (respuesta[j]==1))
{
write(sumi[j],obj,sizeof(obj));
miniic=i+1;
respuesta[j]=0;
break;
}
}
}
}
}

Hmm, I think your logic is all wrong. It should look something more like this (warning, untested pseudo-code):
for (;;)
{
// First, set up the fd_sets to specify the sockets we want to be notified about
fd_set readSet; FD_CLR(&readSet);
fd_set writeSet; FD_CLR(&writeSet);
int maxFD = -1;
for (int i=0; i<num_consumers; i++)
{
if (consumer_sockets[i] > maxFD) maxFD = consumer_sockets[i];
FD_SET(consumer_sockets[i], &readSet);
if (consumer_has_data_he_wants_to_send[i]) FD_SET(consumer_sockets[i], &writeSet);
}
for (int i=0; i<num_producers; i++)
{
if (producer_sockets[i] > maxFD) maxFD = producer_sockets[i];
FD_SET(producer_sockets[i], &readSet);
if (producer_has_data_he_wants_to_send[i]) FD_SET(producer_sockets[i], &writeSet);
}
// Now we block in select() until something is ready to be handled on a socket
int selResult = select(maxFD+1, &readSet, &writeSet, NULL, NULL);
if (selResult < 0) {perror("select"); exit(10);}
for (int i=0; i<num_consumers; i++)
{
if (FD_ISSET(consumer_sockets[i], &readSet)
{
// There is some incoming data ready to be read from consumer_socket[i], so recv() it now
[...]
}
if (FD_ISSET(consumer_sockets[i], &writeSet)
{
// There is buffer space in consumer_socket[i] to hold more outgoing
// data for consumer_socket[i], so send() it now
[...]
}
}
for (int i=0; i<num_producers; i++)
{
if (FD_ISSET(&producer_sockets[i], &readSet)
{
// There is some data ready to be read from producer_socket[i], so recv() it now
[...]
}
if (FD_ISSET(producer_sockets[i], &writeSet)
{
// There is buffer space in producer_socket[i] to hold more outgoing
// data for producer_socket[i], so send() it now
[...]
}
}
}
Note that to really do this properly, you'd need to set all of your sockets to non-blocking I/O and be able to handle partial reads and writes (by storing the partial data into a local memory-buffer associated with that consumer/producer, until you have enough data to act on), otherwise you risk having a call to recv() or send() block, which would prevent the event loop from being able to service any of the other consumers or producers. Ideally the only place you should ever block in is select()... every other call should be non-blocking. But if you want to keep things simple to start out with, you may be able to get away with using blocking I/O for a while.

You might read the introductory tutorial
Look at blocking vs non-blocking connections

Related

Is it safe to do SSL_shutdown again if socket returns SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE

I am trying perform SSL_shutdown on a non-blocking socket. In the code below, i am trying poll after trying SSL_shutdown.
do {
err = SSL_shutdown();
if (err == 0) {
// unidirectional shutdown success, will try SSL_shutdown once more and exit
} else if (err == 1) {
//shutdown complete. Exit
} else {
ec = SSL_get_error(ctx, err);
if (ec == SSL_ERROR_WANT_READ) {
rc = poll(fd, POLLIN|POLLPRI, timeout);
if (rc > 0) {
continue;
} else {
break;
}
} else if (ec == SSL_ERROR_WANT_WRITE) {
rc = poll(fd, POLLOUT, timeout);
if (rc > 0) {
continue;
} else {
break;
}
} else {
break;
}
}
} while(1);
After a successful poll is it possible that the socket is writeable or readable and triggering the shutdown will WANT_WRITE or WANT_READ again. Can the above code get stuck in an infinite loop?
From the official documentation:
If the underlying BIO is nonblocking, SSL_shutdown() will also return when the underlying BIO could not satisfy the needs of SSL_shutdown() to continue the handshake. In this case a call to SSL_get_error() with the return value of SSL_shutdown() will yield SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE. The calling process then must repeat the call after taking appropriate action to satisfy the needs of SSL_shutdown().
In other words, it is not only safe but it is expected.
But note the "... after taking appropriate action to satisfy the needs of SSL_shutdown()...". If your SSL object is implicitly backed by a file descriptor then it will automatically do the necessary reads and writes. If it is only backed by a memory BIO these read and writes must be done in your code. It is unclear from your code if it is backed by a memory BIO or not though.

How to remove an extra cycle?

I need to make the code more readable, but it has a lot of cycle, can I fix this somehow?
The variable i constantly jumps there and there, it is very inconvenient to watch in debugging.
for(;;)
{
//
// For connection orientated protocols, we will handle the
// packets received from a connection collectively. For datagram
// protocols, we have to handle each datagram individually.
//
//
// Check to see if we have any sockets remaining to be served
// from previous time through this loop. If not, call select()
// to wait for a connection request or a datagram to arrive.
//
for (i = 0; i < numSocket; i++)
{
if (FD_ISSET(servSock[i], &SockSet))
break;
}
if (i == numSocket)
{
for (i = 0; i < numSocket; i++)
{
FD_SET(servSock[i], &SockSet);
}
if (select(numSocket, &SockSet, 0, 0, 0) == SOCKET_ERROR)
continue;
}
for (i = 0; i < numSocket; i++)
{
if (FD_ISSET(servSock[i], &SockSet))
{
FD_CLR(servSock[i], &SockSet);
break;
}
}
...
}
To make the code more readable (and testable and maintainable) you can encapsulate some of the logic into separate functions with meaningful names. Consider this version of your loop
for(;;)
{
int i = find_socket_to_be_served(numSocket, servSock, &SockSet);
if (i == numSocket)
{
set_all_sockets(numSocket, servSock, &SockSet);
if (select(numSocket, &SockSet, 0, 0, 0) == SOCKET_ERROR)
continue;
}
// ...
}
Where the two functions used are defined as
int find_socket_to_be_served(int n_sockets, int *fds, fd_set *fdset)
{
int i = 0;
for (; i < n_sockets; i++)
{
if (FD_ISSET(fds[i], fdset))
{
FD_CLR(fds[i], fdset);
break;
}
}
return i;
}
void set_all_sockets(int n_sockets, int *fds, fd_set *fdset)
{
for (int i = 0; i < n_sockets; i++)
{
FD_SET(fds[i], fdset);
}
}
As far as I understand your code, you don't need FD_CLEAR() your sockets one by one.
Just FD_ZERO() all of them at once, then use a single loop to FD_SET() them again.
Note that after select(), many sockets could be ready to use; you don't have to break when one has been used.
Note also that the first parameter of select() is unused on Windows but should be 1+ the max of your socket file descriptors (not the number of them) everywhere else.
for(;;)
{
FD_ZERO(&SockSet);
for (int i = 0; i < numSocket; i++)
{
FD_SET(servSock[i], &SockSet);
}
if (select(numSocket, &SockSet, 0, 0, 0) == SOCKET_ERROR)
continue; // or probably break...
}
for (int i = 0; i < numSocket; i++)
{
if (FD_ISSET(servSock[i], &SockSet))
{
// use servSock[i]
}
}
}

esp32 idf multi-socket-server

It's my first post so ask for remotely anything if it can help and I didn't provide it.
My application requires multiple sockets being opened at once from Master, then the slaves connect to WiFi, and then to the sockets
Problem is: I have to make it "bulletproof" against constant reconnecting from slaves and i get Accept error:
E (23817) TCP SOCKET: accept error: -1 Too many open files in system
It appears when I reconnect client for 5th time, when Max Number of Open Sockets = 5 in menuconfig,
I disconnect clients from the server when they don't send anything in 1second -> then i assume they got DC-d.
I do it with close() procedure.
void closeOvertimedTask(void * ignore)
{
while(1)
{
for(int i = 0; i < openedSockets;)
{
if(needsRestart[i] == 1)
{
ESP_LOGI("RESTARTING", " task#%d",i);
//lwip_close_r(clientSock[i]);
//closesocket(clientSock[i]);
//ESP_LOGI("closing result", "%d", close(clientSock[i]));
stopSocketHandler(i);
needsRestart[i] = 0;
//if(isSocketOpened[i])
{
}
ESP_LOGI("close", "%d", lwip_close_r(clientSock[i]));
isSocketOpened[i] = 0;
xTaskCreate( handleNthSocket, "TCP_HANDLER", 10*1024, &(sockNums[i]) , tskIDLE_PRIORITY, &socketHandlerHandle[i]);
configASSERT(socketHandlerHandle[i]);
needsRestart[i] = 0;
}
if(isSocketOpened[i])
{
int diff = ((int)((uint64_t)esp_timer_get_time()) - lastWDT[i]) - 2*TCPWDT;
if(diff > 0)
{
if(isSocketOpened[i])
{
ESP_LOGI("I FOUND OUT HE DC-d","");
//closesocket(clientSock[i]);
}
ESP_LOGI("close", "%d", close(clientSock[i]));
stopSocketHandler(i);
isSocketOpened[i] = 0;
xTaskCreate( handleNthSocket, "TCP_HANDLER", 10*1024, &(sockNums[i]) , tskIDLE_PRIORITY, &socketHandlerHandle[i]);
configASSERT(socketHandlerHandle[i]);
}
}
}
}
}
For each socket I run 1 task that is supposed to receive from that socket and act further.
For all of them I have an other task that checks last time a message arrived and restarts tasks when time has exceeded (it's 2 seconds)
I need around 16 sockets opened in the final version so there is no room to have sockets that are still closing after Slave has restarted whole connection
How to properly close a Task with running recv() procedure in it to properly close Socket.
Is there a way to read from Server side that socket has been closed if WiFi hasn't realized STA DC-d
Is this about TIME_WAIT from tcp stack ?
Socket read code:
void handleNthSocket(void * param) // 0 <= whichSocket < openedSockets
{
int whichSocket = *((int *) param);
ESP_LOGI("TCP SOCKET", "%s #%d", getSpaces(whichSocket), whichSocket);
struct sockaddr_in clientAddress;
while (1)
{
if(needsRestart [whichSocket] == 0)
{
socklen_t clientAddressLength = sizeof(clientAddress);
clientSock[whichSocket] = accept(sock[whichSocket], (struct sockaddr *)&clientAddress, &clientAddressLength);
if (clientSock[whichSocket] < 0)
{
ESP_LOGE("TCP SOCKET", "accept error: %d %s", clientSock[whichSocket], strerror(errno)); //HERE IT FLIPS
//E (232189) TCP SOCKET: accept error: -1 Too many open files in system
isSocketOpened[whichSocket] = 0;
needsRestart[whichSocket] = 1;
continue;
}
//isSocketOpened[whichSocket] = 1;
// We now have a new client ...
int total = 1000;
char dataNP[1000];
char *data;
data = &dataNP[0];
for(int z = 0; z < total; z++)
{
dataNP[z] = 0;
}
ESP_LOGI("TCP SOCKET", "%snew client",getSpaces(whichSocket));
ESP_LOGI(" ", "%s#%d connected",getSpaces(whichSocket), whichSocket);
lastWDT[whichSocket] = (uint64_t)esp_timer_get_time() + 1000000;
isSocketOpened[whichSocket] = 1;
// Loop reading data.
while(isSocketOpened[whichSocket])
{
/*
if (sizeRead < 0)
{
ESP_LOGE(tag, "recv: %d %s", sizeRead, strerror(errno));
goto END;
}
if (sizeRead == 0)
{
break;
}
sizeUsed += sizeRead;
*/
ssize_t sizeRead = recv(clientSock[whichSocket], data, total, 0);
/*for (int k = 0; k < sizeRead; k++)
{
if(*(data+k) == '\n')
{
ESP_LOGI("TCP DATA ", "%sthere was enter", getSpaces(whichSocket));
//ESP_LOGI("TIME ", "%d", (int)esp_timer_get_time());
}
//ESP_LOGI("last wdt", "%d", (int)lastWDT[whichSocket]);
}*/
lastWDT[whichSocket] = (uint64_t)esp_timer_get_time();
int diff = ((int)((uint64_t)esp_timer_get_time()) - lastWDT[whichSocket]) - 2*TCPWDT;
ESP_LOGI("last wdt", "%d, data = %s", (int)lastWDT[whichSocket], data);
if(diff > 0)
{
ESP_LOGI("last wdt", "too long - %d", diff);
isSocketOpened[whichSocket] = 0;
}
if (sizeRead < 0)
{
isSocketOpened[whichSocket] = 0;
}
//TODO: all RX from slave routine
for(int k = 0; k < sizeRead; k++)
{
*(data+k) = 0;
}
// ESP_LOGI("lol data", "clientSock[whichSocket]=%d,
/*if(sizeRead > -1)
{
ESP_LOGI("TCP DATA: ", "%c", *(data + sizeRead-1));
}
else
{
ESP_LOGI("TCP DC ", "");
goto END;
}*/
}
if(isSocketOpened[whichSocket])
{
ESP_LOGI("closing result", "%d", close(clientSock[whichSocket]));
}
}
}
}
I don't see you closing your sockets anywhere?
Sockets, no matter the platform, is usually a limited resource, and a resource that will be reused. If you don't close the sockets then the system will think that you still use then, and can't reuse those sockets for new connections (and on POSIX systems even opening files will be affected).
So close connections immediately when they are not needed any more.
Usually this is done by checking what recv and send returns: If they return a value less than zero an error occured and in most cases it's a non-recoverable errors, so connection should be closed. Even if it is a recoverable error, it's easier to close the connection and let the client reconnect.
For recv there's also the special case when it returns zero. That means the other end has closed the connection. That of course you need to close your end as well.
this post solved all my problems
https://www.esp32.com/viewtopic.php?t=911

Unknown condition satisfaction in pthread cond timedwait

There's something going on in my code that I didn't intend.
I've got a program with several threads. One thread reads a rfid chip and saves the rfid code string into a global struct variable. There are also some other threads that are opened when a client, which is supposed to get the string, connects to the program on a certain socket. They are on a timed waiting (pthread_cond_timedwait) for the reading thread to broadcast a pthread condition variable (thread->done) that signals that a rfid chip was read and there is a new string to be sent to the clients. Now to the problem: the pthread_cond_timedwait in the sending thread shall only be satisfied when a new string is ready to be sent, but it also triggers when a new client thread is opened. The client threads start with the function below. I hope I explained my problem clearly and anyone can help me with the problem.
code of the sending function:
void* send_data(void* arg)
{
int client_id;
int status_timeout;
struct timespec timeout;
struct Thread_parameter *thread = (struct Thread_parameter*)arg;
pthread_condattr_t attr;
pthread_condattr_init(&attr);
pthread_condattr_setclock(&attr, CLOCK_MONOTONIC);
pthread_cond_init(&thread->done, &attr);
// read the client id from struct
pthread_mutex_lock(&thread->mutex);
client_id = thread->t_client_id;
pthread_mutex_unlock(&thread->mutex);
while (1)
{
// wait for string
while (1)
{
pthread_mutex_lock(&thread->mutex);
status_timeout = ETIMEDOUT;
while (1)
{
clock_gettime(CLOCK_MONOTONIC, &timeout);
timeout.tv_sec += 1;
status_timeout = pthread_cond_timedwait(&thread->done, &thread->mutex, &timeout);
// Timeout
if (status_timeout == ETIMEDOUT)
{
if (is_connected(client_id))
continue;
else
{
log_output("Lost connection to client '%i'", client_id);
pthread_mutex_unlock(&thread->mutex);
pthread_exit(NULL);
}
}
else if (status_timeout == 0)
{
// condition satisfied
break;
}
}
pthread_mutex_unlock(&thread->mutex);
break;
}
// send the string to the client when the condition is satisfied
while (1)
{
pthread_mutex_lock(&thread->mutex);
if (send(client_id, thread->t_chip_id, CHIP_ID_LENGTH, MSG_NOSIGNAL) >= 0)
{
pthread_mutex_unlock(&thread->mutex);
log_output("Successfully sent data to client '%i'", client_id);
break;
}
else
{
pthread_mutex_unlock(&thread->mutex);
// sending not successful, check if client is still connected
if (is_connected(client_id))
continue;
else
{
close(client_id);
log_output("Lost connection to client '%i'", client_id);
pthread_exit(NULL);
}
}
}
}
}

Why is this socket/ file descriptor assignment invalid?

I'm trying to write a simple server in c that plays a two player game. It checks for incoming connections, and if there is no player1, it saves player1's file descriptor (to be used later for sending and receiving) and if there is no player2, it does the same. I have this loop set up that I modified from Here. My problem is that I want to receive from one, and send to the other, but it seems that my assignments are invalid. When I try to send to player2, it fails or it sends garbage. Sometimes, sending to player1 sends back to the server(?). Am I using select correctly and looping through the file descriptor set correctly? Any feedback would be appreciated.
// add the listener to the master set
FD_SET(listener, &master);
// keep track of the biggest file descriptor
fdmax = listener; // so far, it's this one
// main loop
while (1) {
read_fds = master; // copy it
if (select(fdmax+1, &read_fds, NULL, NULL, NULL) == -1) {
error("select");
}
// run through the existing connections looking for data to read
for(i = 0; i <= fdmax; i++) {
//This indicates that someone is trying to do something
if (FD_ISSET(i, &read_fds)) {
if (i == listener) {
addrlen = sizeof remoteaddr;
newfd = accept(listener, (struct sockaddr *)&remoteaddr, &addrlen);
if (newfd == -1) {
error("accept");
} else {
FD_SET(newfd, &master);
if (newfd > fdmax) {
fdmax = newfd;
}
/* If we have the maximum number of players, we tell if that it's busy */
if (players >= 2) {
toobusy(fdmax); close(fdmax); FD_CLR(fdmax, &master);
} else {
//Problem here?
if (player1_fd == -1) {
player1_fd = newfd;
}
if ((player1_fd != -1) && (player2_fd == -1)) {
player2_fd = newfd;
}
players++;
if (players == 2) {
sendhandles(); //says two players exist
}
}
}
} else {
//Possible problems here
if (i == player1_fd || i == player2_fd) {
receive(i); //Processes the messages
}
}
}
}
}
The toobusy part should use newfd, not fdmax. Otherwise there's no easy spotted error in this code.
Your comment "Sometimes, sending to player1 sends back to the server(?)" makes me think that player1_fd and player2_fd might be uninitialized or perhaps initialized to 0 instead of -1. You should double check that you set them to -1 before the loop.
A few additionally notes:
Are you sure master is 0 initialized? Have you called FD_ZERO on it?
You should use FD_COPY to copy master to read_fds.
Finally, I'd recommend to use a library for event handling, such as libevent or libev.

Resources