if (FD_ISSET(sock,&socks))
handle_new_connection();
/* Now check connectlist for available data */
/* Run through our sockets and check to see if anything
happened with them, if so 'service' them. */
for (listnum = 0; listnum < 5; listnum++) {
if (FD_ISSET(connectlist[listnum],&socks))
deal_with_data(listnum);
}
}
My problem with this is the linear search it does. We know from our call to select how many sockets are ready to be read from, but isnt there a way to know specifically which ones they are off the bat without having to do a linear search? With this method, I'd have to search through every single connected player every time any of the clients send a packet to my server. Maybe theres something similar to select that returns a fd_set containing only the sockets ready for reading?
select without a linear search?
Im trying to make some changes to my server side code to make it more efficient, so Ive been reading up more on select(). The problem is, all the examples I find do something like this after calling select
Yes, there are much better alternatives, but they depend on your target OS and are not necessarily trivial to implement.
Seeing that your example has an entire 5 sockets, it is probably not worth to spend a lot of time worrying, because there is no really existent overhead with select(). Unless you plan for more than a hundred descriptors, select() works just as fine as everything else.
If you need several hundred or thousand descriptors, look into epoll() or kqueue() for unix-like system and IO completion ports for Windows.
Seeing that your example has an entire 5 sockets, it is probably not worth to spend a lot of time worrying, because there is no really existent overhead with select(). Unless you plan for more than a hundred descriptors, select() works just as fine as everything else.
If you need several hundred or thousand descriptors, look into epoll() or kqueue() for unix-like system and IO completion ports for Windows.
That example isnt from my code. I was simply showing an example of what the examples that are out there look like. But yeah, I'm on linux and want my game to have support for thousands of players.
Then you want epoll. Note that while epoll can be confusing (read the docs very carefully!), it is flipping awesome, you can for example create special file descriptors that fire notifications when your thread catches a signal, and catch those with the same epoll call as you catch your socket notifications and your disk ready notifications.
If you don't want to mess with the low-level stuff yourself, there is libevent which does that for you. As with all layered libraries, it adds a little overhead (hardly noticeable) and does not do all the cool things that you could possibly do otherwise, but it works and it takes away 99.5% of the pain implementing it.
If you don't want to mess with the low-level stuff yourself, there is libevent which does that for you. As with all layered libraries, it adds a little overhead (hardly noticeable) and does not do all the cool things that you could possibly do otherwise, but it works and it takes away 99.5% of the pain implementing it.
thanks for the helpful reply. I plan on implementing it all myself as the main reason Im doing this project is to learn. I'm currently reading the man pages on epoll because I cant seem to find decent documentation on it online. Do you know of any sites that might be helpfull in learning the ins and outs of epoll?
Boost has the "asio" library. It wraps the epoll/iocp calls into a single interface. Works the same on linux and windows.
It's not perfect, but it's portable, and event driven (no select search to do).
www.boost.org
It's not perfect, but it's portable, and event driven (no select search to do).
www.boost.org
I agree; the epoll API is better than the select API for certain heavy use cases.
However, have you ever profiled this loop to be a problem? If you start getting more load, what will happen is that any time spent looping over inactive sockets will be time that the other users can add more data to their buffers, which means there will be more active users in the next iteration. The system is self-balancing that way, and under heavy load, the looping time just won't show up on a profile. Meanwhile, why would you optimize the light load case? :-)
However, have you ever profiled this loop to be a problem? If you start getting more load, what will happen is that any time spent looping over inactive sockets will be time that the other users can add more data to their buffers, which means there will be more active users in the next iteration. The system is self-balancing that way, and under heavy load, the looping time just won't show up on a profile. Meanwhile, why would you optimize the light load case? :-)
Ok, so I've got everything set up right going by the man pages, but it seems to be stuck on epoll_wait. It always returns 0 and I cant figure out why for the life of me. Im going to assume its because Ive been up for over 24 hours and made a really simple mistake somewhere but heres the code I got so far for it.
The man page says this...
Do you see anything that I'm doing obviously wrong?
if((ReadFDs = epoll_wait(epFD, Events, MAX_PLAYERS, 0)) < -1) { perror("epoll_wait"); exit(EXIT_FAILURE); } // We've got some data to read // else if(ReadFDs > 0) { for(int i = 0; i < ReadFDs; i++) { // New Connection // if(Events.data.fd == Listener) { if((NewFD = accept(Listener, (struct sockaddr *)&Client, &CliLen))<0) { perror("accept"); exit(EXIT_FAILURE); } SetNonBlocking(NewFD); Event.events = EPOLLIN | EPOLLET; Event.data.fd = NewFD; if(epoll_ctl(epFD, EPOLL_CTL_ADD, NewFD, &Event) < 0) { perror("epoll_ctl"); exit(EXIT_FAILURE); } send(NewFD, "Welcome to Paveria...\n", 22, 0); } // Handle Input // else { }
The man page says this...
EXAMPLE FOR SUGGESTED USAGE While the usage of epoll when employed like a Level Triggered interface does have the same semantics of poll(2), an Edge Triggered usage requires more clarifiction to avoid stalls in the application event loop. In this example, listener is a non-blocking socket on which lis- ten(2) has been called. The function do_use_fd() uses the new ready file descriptor until EAGAIN is returned by either read(2) or write(2). An event driven state machine application should, after having received EAGAIN, record its current state so that at the next call to do_use_fd() it will continue to read(2) or write(2) from where it stopped before. struct epoll_event ev, *events; for(;;) { nfds = epoll_wait(kdpfd, events, maxevents, -1); for(n = 0; n < nfds; ++n) { if(events[n].data.fd == listener) { client = accept(listener, (struct sockaddr *) &local, &addrlen); if(client < 0){ perror("accept"); continue; } setnonblocking(client); ev.events = EPOLLIN | EPOLLET; ev.data.fd = client; if (epoll_ctl(kdpfd, EPOLL_CTL_ADD, client, &ev) < 0) { fprintf(stderr, "epoll set insertion error: fd=%d0, client); return -1; } } else do_use_fd(events[n].data.fd); } }
Do you see anything that I'm doing obviously wrong?
Quote:Original post by nethackpro
Do you see anything that I'm doing obviously wrong?
Umm, you're passing zero for the timeout, which make epoll_wait return immediately regardless of how many events are available? Do I win?
This topic is closed to new replies.
Advertisement
Popular Topics
Advertisement