Link to home
Start Free TrialLog in
Avatar of phirephly
phirephly

asked on

using select() with Berkeley sockets

I'm writing a client/server pair in C. The clients (naturally) connect to the server, but then I want a message sent to the server from any client to be sent to all the clients. It looks like select is supposed to do this, but I'm not seeing how that would exactly play out. I've found a bunch of web examples that look quite similar to the examples I have here in the W Richard Stevens book, and they're not helping me. Like I'm not even sure where to put the select in my program, etc. Also, I'm forking processes, so I don't understand how a forked child process from the server would know that whatever structure maintains the fds in the parent process has changed the list of fds since the new ones would be past the number sent to the child process. Wouldn't a child process only be able to multicast to all the other fds that had joined before it (and not even see any of the ones that joined it)? I'll believe it works without explanation is someone could show me. I'm using TCP (for now, will be switching to UDP eventually). Code would really really really help here. thanks,

mich
Avatar of MacTruck
MacTruck

Are you wanting to write something similar to, say, a chat room?

If so, then you should look at 'man -k pthread'.  Specifically, the section on mutex'es.  You should probably be using LinuxThreads (pthread's) for what you want to do instead of relying on something primitive and not very portable to other platforms/OSes.

MacTruck
If you fork, you cannot share memory other than have their own IPC mechanisms.

you don't HAVE to use select. There are various models of servers. As MacTruck pointed out you can use threads that run in parallel and process incoming messages. On single threaded servers select is good and if you're carefult about structuring your server sometimes you can really get a lot out of just pure select without incurring the overhead of threads. If you're feeling wild, you can try signal driven servers as well. I wouldn't recommend it if you're just starting out with sockets, however.

here's a typical tcp server

get socket descriptor
bind
listen
accept
read

accept is blocking, so it's usually not very desirable to have accpet be blocking when you want to be handling more than one requests at a time. read is also blocking ( although you can do non-blocking reads, that wastes a lot of CPU cycles ). This is where select comes in ( single-threaded scenario ).

you can call select with a timeout of 0. this means select will check if there are any file descriptors awaiting io it'll set the masks for it if not, it'll just fall through. after it falls through you can read from only the
ones that have io waiting, and in the remaining portion of
your loop you can do other processing. you have to really
plan your core loop carefully to make the most out of the use of select.

ASKER CERTIFIED SOLUTION
Avatar of djbusychild
djbusychild

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of phirephly

ASKER

I suppose it could be thought of kinda of like a chat room, because whatever someone says, everyone else should see. Actually what i'm trying to do it write the networking part of a virtual reality simulation. The server will be sitting somewhere, and the clients will connect to it. The clients will tell the server where they are and other useful infomation such as velocities for dead reckoning, but the point is that whatever is in this packet should be broadcast to everyone else somehow. I know that once a process is forked, you can't really easily update the data that it has from the outside (tell it someone else has joined), but maybe I can just be clever that way and have the latest process do the broadcasting. Processes would just assume they are the latest until they see someone else join, then shut up until they see that client leave. I dunno - I'll figure out how to be clever later, I just wanna get select working. Like right now here's what I have that's locking my server:

fd_set readmes, writemes;  /*  global so they don't have to be passed  */

void sock_check(int sock)  {

        int cnt, i;
        FD_SET((unsigned int) sock,&readmes);
        fprintf(stderr, "past the FD_SET\t");
        cnt = select(sock+1,&readmes,NULL,NULL,NULL);
        fprintf(stderr,"cnt: %d\n",cnt);
        return;
}

void add_ints2(int sockfd, int *fds)  {

        int     n, i, sum, nums[MAXSIZE], *ptr;
        char    *message = "Please enter some more integers to add: ";

        while (1)  {

            sum = 0;
            n = read(sockfd, (char *)nums, sizeof(int));

            sum = nums[0];
            sock_check(sockfd);

            if (writen(sockfd, (char *)&sum, sizeof(int)) != sizeof(int))   {
                exit_add_ints(1, fds);
                return;
            }

            if (writen(sockfd, message, strlen(message)) != strlen(message))   {
                exit_add_ints(2, fds);
                return;
            }

        }
        exit_add_ints(0, fds);
        return;
}

void main()  {

        int fd, newfd, clilen, childpid, addrsize, i;
        struct sockaddr_in cli_addr, serv_addr;

        FD_ZERO(&readmes);
        FD_ZERO(&writemes);

        /*  socket, bind, getsockname, listen */

        for ( ; ; )
        {
            clilen = sizeof(cli_addr);
            newfd = accept(fd, (struct sockaddr *) &cli_addr, &clilen);

            if (newfd < 0)   {
                perror("server: accept error");
                return;
            }
            if ( (childpid = fork()) < 0)   {
                perror("server: fork error");
                return;
            }
            else if (childpid == 0)   {
                close(fd);
                FD_SET(newfd, &readmes);
                add_ints2(newfd, &fds);
                return;
            }
            close(newfd);
        }
}

All I want select to do right now is tell me how many fds are readable each time the loop in add_ints2, then I'll work on ISSETing them. Shouldn't this do that? Doesn't select return the number of readables? Is setting the timeout (arg5) to NULL different than setting it to 0? Right now I'm only connecting with one client and the select call is hanging up the server. If I set the timeout to 0, it should just poll all the fds, and give me a count, right? If no one is readable, then it should just return 0, shouldn't it? Even if select just gives me some number (any number) back that I can work with with the ISSETs, that's all I want right now. When I tried declaring a struct timeval timeout in sock_check, then assigning timeout.tv_sec = 0; amd timeout.tv_usec = 0; the compiler kept saying "storage size of 'timeout' isn't known". Which then failed the compile.

Does this help anyone help me?  thanks and ttyl,

mich
oh yeah, sorry I forgot to address this before. I'm not supposed to use threads. I got in trouble for that yesterday. Since this select is supposed to be so simple, that's the way I'm supposed to go.
alright... got the timeout size error fixed. needed to add #include <sys/time.h>
here's what sock_check looks like now:

void sock_check(int sock)  {

        int cnt, i,cnt2;
        struct timeval timeout;

        FD_SET(sock,&readmes);

        memset((char *)&timeout,0,sizeof(timeout));
        timeout.tv_sec = 0;
        timeout.tv_usec = 0;

        cnt = select(sock+1,&readmes,&writemes,NULL,&timeout);

        fprintf(stderr,"sock_passed: %d\t",sock);
        fprintf(stderr,"cnt: %d\t",cnt);
        fprintf(stderr,"read? %d\t",(FD_ISSET(sock,&readmes)));
        fprintf(stderr,"write? %d\t",(FD_ISSET(sock,&writemes)));
        fprintf(stderr,"read2? %d\t",(FD_ISSET(sock+1,&readmes)));
        fprintf(stderr,"write2? %d\n",(FD_ISSET(sock+1,&writemes)));

        return;
}

as you can see from the above, this is called right before I write out to the connected client, so I should see that someone's writeable, shouldn't I? I always seem to get the same thing: sock_passed is 4, and all the rest are 0s. Since the client is waiting to be written to, shouldn't it show up as writeable, and therefore, shouldn't cnt = 1, and write2 = 1 (true)? thanks and ttyl,

mich
sock_passed: 4  cnt: 0  read? 0 write? 0        read2? 0        write2? 0
something that might have something to do with it... when I have close(newfd) in main() as shown above, every new socket comes in as 4 when I make multiple connections. This isn't right, is it? If I move it up into the exit_add_ints() function which just prints a string based on the first arg and closes the fd that is now passed to it), then I get sequential sockfds. eg. 4, 5, 6, when multiple connections are made. I want them to number up, right? When I close 4, the next one still comes in at the next number, but that's ok, right? I'm still getting the same output, tho, as shown above, except, the sock matches the one that it's supposed to be. I dunno if this helps at all or even has anything to do with anything, but thought I'd letcha know.
sock_check now starts like this:

void sock_check(int sock)  {

        int cnt, i,cnt2;
        struct timeval timeout;

        FD_ZERO(&readmes);
        FD_ZERO(&writemes);

        FD_SET(sock,&readmes);
        FD_SET(sock,&writemes);

...

and I get outputs like this:

sum: 6546543    sock_passed: 10 cnt: 1  read? 0 write? 1024     read2? 0        write2? 0

the write val is 2^(sockfd), so no prob there, but I'm not seeing the other sockets showing up in here. the count variable (cnt), is always 1. Shouldn't the other connections always show up as readable or writable?
you're bombarding with new comments... I'll take a look at all of them when I get home tonight. =)
there really are two parts to the server's socket descriptor. the main socket descriptor is what you socket is bound to, and every type of accept a connection a new socket descriptor is returned.

don't worry too much about the highest fd, just set it to something like 32 for the time being

if you're select is returning number bigger than 0, than that simply means you can do an accept on the socket and get a new descriptor to establish a connection.
pseudo code helped a bunch. thanks!