PDA

View Full Version : select system call usage doubt



vlrk
06-26-2011, 04:21 AM
i have a tcp concurrent server program as below.

my doubt is in the for loop , I have written my doubt near the for loop with in comments

If any body can share there knowledge it would give me complete understanding of the select call usage.




#include <stdio.h>

#include <sys/types.h>

#include <sys/socket.h>

#include <netinet/in.h>

#include <arpa/inet.h>

#include <stdlib.h>

#include <string.h>



#define SRV_TCP_PORT 8000

#define MAX_MSG 100



void errExit(char *str)
{
puts(str);
exit(0);
}

int main()
{
int srvSockFd,newSockFd,fd;
struct sockaddr_in srvAdr, cliAdr;
int cliLen,n;
fd_set readfds, testfds;
int stat;
char mesg[MAX_MSG];

if((srvSockFd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
errExit("Can't open datagram socket\n");

memset(&srvAdr, 0, sizeof(srvAdr));
srvAdr.sin_family = AF_INET;
srvAdr.sin_addr.s_addr = htonl(INADDR_ANY);
srvAdr.sin_port = htons(SRV_TCP_PORT);

if(bind(srvSockFd,(struct sockaddr*)&srvAdr, sizeof(srvAdr)) < 0)
errExit("Can't bind local address \n");

listen(srvSockFd,5);

FD_ZERO(&readfds);
FD_SET(srvSockFd,&readfds);

printf("Server waiting for new connection :\n");

while(1)
{
testfds = readfds;

stat = select(FD_SETSIZE, &testfds, (fd_set *)0, (fd_set *)0, (struct timeval *)0);

if(stat < 1)
errExit("select error \n");

for(fd = 0; fd < FD_SETSIZE; fd++)
{


if(FD_ISSET(fd,&testfds))
{


/* here once any fd is ready it will come in to loop .
why do we need to compare is it srvSockFd or not ? */
if(fd == srvSockFd)
{
newSockFd = accept(fd, (struct sockaddr *)0, NULL);
FD_SET(newSockFd, &readfds);
/* why cant we directly use the testfd set it selft to set the newSockFD
why do we need the second fd set readfd here ?
here is readfds and testfds are reference based , i mean any change made to
readfds will take effect on testfds? */


printf("Adding client fd: %d to readFdSet \n",newSockFd);
}
else
{

/* This is my fundamental doubt like when will it come to else part
once fd is ready it will go the above block so when and how it comes to this
block */
n = recv(fd, mesg, MAX_MSG, 0);
if(n < 0)
errExit("recv error \n");
else if(n == 0)
{
close(fd);
FD_CLR(fd, &readfds);
printf("Removing client fd: %d from readFdSet \n",fd);
}
else
{
if(send(fd,mesg,n,0) != n)
errExit("send error \n");
}
}
}
}
}
}

nonpuz
06-26-2011, 05:46 AM
....
....
for(fd = 0; fd < FD_SETSIZE; fd++)
{


if(FD_ISSET(fd,&testfds))
{


/* here once any fd is ready it will come in to loop .
why do we need to compare is it srvSockFd or not ? */
if(fd == srvSockFd)
{
newSockFd = accept(fd, (struct sockaddr *)0, NULL);
FD_SET(newSockFd, &readfds);
/* why cant we directly use the testfd set it selft to set the newSockFD
why do we need the second fd set readfd here ?
here is readfds and testfds are reference based , i mean any change made to
readfds will take effect on testfds? */


printf("Adding client fd: %d to readFdSet \n",newSockFd);
}


What is happening is here is that they are testing the current file descriptor that is ready to read from and checking to see if it is the "main" server socket; the socket that is handling NEW connections. If it is the "server" socket then it opens/accepts the new connection coming in and adds this NEW file descriptor to the SAME test set "readfds" you see earlier that testfds has been set to readfds.

So if its adding new connections to the same read set as the "server" socket..now you see why it has to test the descriptor to see if its the server or a normal client connection...another term i could call the "server" socket would be the listening socket, in this case and probably a better name for it..



else
{
/* This is my fundamental doubt like when will it come to else part
once fd is ready it will go the above block so when and how it comes to this
block */
n = recv(fd, mesg, MAX_MSG, 0);
if(n < 0)
errExit("recv error \n");
else if(n == 0)
{
close(fd);
FD_CLR(fd, &readfds);
printf("Removing client fd: %d from readFdSet \n",fd);
}
else
{
if(send(fd,mesg,n,0) != n)
errExit("send error \n");
}
}
}
}
....
....


The else block here is happening when the current socket "fd" is NOT the "server" socket that I talked about above. Meaning its one of the clients that we add to the read set in the FIRST block I explained above...

So that is why it tries to read data from this socket, because it is a client...whats odd is that it just sends the data back again...kind of pointless no?

make sense?

EDIT: BTW please check out "man select" and "man select_tut" on any *nix/POSIX system
you need to re-initialize your fd_sets BEFORE EACH select() call you do...
particularly these rules:


Select Law
Many people who try to use select() come across behavior that is difficult to understand and
produces nonportable or borderline results. For instance, the above program is carefully
written not to block at any point, even though it does not set its file descriptors to non-
blocking mode. It is easy to introduce subtle errors that will remove the advantage of using
select(), so here is a list of essentials to watch for when using select().

1. You should always try to use select() without a timeout. Your program should have noth-
ing to do if there is no data available. Code that depends on timeouts is not usually
portable and is difficult to debug.

2. The value nfds must be properly calculated for efficiency as explained above.

3. No file descriptor must be added to any set if you do not intend to check its result
after the select() call, and respond appropriately. See next rule.

4. After select() returns, all file descriptors in all sets should be checked to see if they
are ready.

5. The functions read(2), recv(2), write(2), and send(2) do not necessarily read/write the
full amount of data that you have requested. If they do read/write the full amount, it's
because you have a low traffic load and a fast stream. This is not always going to be
the case. You should cope with the case of your functions only managing to send or
receive a single byte.

6. Never read/write only in single bytes at a time unless you are really sure that you have
a small amount of data to process. It is extremely inefficient not to read/write as much
data as you can buffer each time. The buffers in the example below are 1024 bytes
although they could easily be made larger.

7. The functions read(2), recv(2), write(2), and send(2) as well as the select() call can
return -1 with errno set to EINTR, or with errno set to EAGAIN (EWOULDBLOCK). These
results must be properly managed (not done properly above). If your program is not going
to receive any signals, then it is unlikely you will get EINTR. If your program does not
set nonblocking I/O, you will not get EAGAIN.

8. Never call read(2), recv(2), write(2), or send(2) with a buffer length of zero.

9. If the functions read(2), recv(2), write(2), and send(2) fail with errors other than
those listed in 7., or one of the input functions returns 0, indicating end of file, then
you should not pass that descriptor to select() again. In the example below, I close the
descriptor immediately, and then set it to -1 to prevent it being included in a set.

10. The timeout value must be initialized with each new call to select(), since some operat-
ing systems modify the structure. pselect() however does not modify its timeout struc-
ture.

11. Since select() modifies its file descriptor sets, if the call is being used in a loop,
then the sets must be reinitialized before each call.

vlrk
06-26-2011, 07:12 AM
Thank you very much for your detailed comments, it is really very very informative.

vlrk
06-26-2011, 08:20 AM
nonpuz,

what i grasp here is following points

1) the server sockfd would be read set when select call comes out from waiting .

2) we compare from all the fds our sockfd and then accept the connection to recive data on that connection from the new fd.

3) we add that client fd to the readset ,now in the for loop when the fd is not of server fd it will goto else case and read the data from that fd.

another confustion here is in select system call we say testfds is for reading i.e if somebody is writing on to that we would be getting out of the select system call to read the data hope iam right here

i.e the client is writing on to the server sock fd .

here accept system call would accept the connection and give fd , so total 2 fds one is server socket fd and then accept result client fd am i right?

in that case client is writing through the client sock fd , and server is reading throught server sock fd .

is my conclustions are right

thanks for your detailed comments and expecting the your comments on above.

MK27
06-26-2011, 08:46 AM
nonpuz,

what i grasp here is following points

1) the server sockfd would be read set when select call comes out from waiting.


It might be. If it was read set before, but there are no connections waiting on the server (aka, listen) socket, it will not be. The only purpose of the listen socket is to accept() connections. If the listen socket is ready to read, you call accept() on it. Do not actually write() or read() on it.



2) we compare from all the fds our sockfd and then accept the connection to recive data on that connection from the new fd.


Beware that you have to keep a list separate from the fdset you submit to select(), because select will alter it. Also, you cannot find out what's in an fdset easily -- you can only ask if a particular fd ISSET. So you need a separate array of ints that you are going to check for when select() returns.



3) we add that client fd to the readset ,now in the for loop when the fd is not of server fd it will goto else case and read the data from that fd.

Yes.



another confustion here is in select system call we say testfds is for reading i.e if somebody is writing on to that

I would not be passing the fds on for concurrent operations and keeping them in the select() call, that is just going to create a mess. If you want to write to a client, remove it from the readset until you are done writing.



i.e the client is writing on to the server sock fd .

The only reason a client will be waiting on the server listen socket is because it is not yet connected. After that it waits on its own socket. Clients cannot write data to the listen socket.



here accept system call would accept the connection and give fd , so total 2 fds one is server socket fd and then accept result client fd am i right?

Yes.



in that case client is writing through the client sock fd , and server is reading throught server sock fd .

Wrong. After accept(), the server reads from and writes to the client via the same socket, the new one returned by accept(). Again: never read() or write() to the listening socket. There is no one there to read or write to.