[ Pobierz całość w formacie PDF ]

/*
** selectserver.c -- a cheezy multiperson chat server
*/
#include
#include
#include
#include
#include
18. http://beej.us/guide/bgnet/examples/selectserver.c
Beej's Guide to Network Programming 29
#include
#include
#include
#define PORT 9034 // port we're listening on
int main(void)
{
fd_set master; // master file descriptor list
fd_set read_fds; // temp file descriptor list for select()
struct sockaddr_in myaddr; // server address
struct sockaddr_in remoteaddr; // client address
int fdmax; // maximum file descriptor number
int listener; // listening socket descriptor
int newfd; // newly accept()ed socket descriptor
char buf[256]; // buffer for client data
int nbytes;
int yes=1; // for setsockopt() SO_REUSEADDR, below
socklen_t addrlen;
int i, j;
FD_ZERO(&master); // clear the master and temp sets
FD_ZERO(&read_fds);
// get the listener
if ((listener = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
perror("socket");
exit(1);
}
// lose the pesky "address already in use" error message
if (setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &yes, \
sizeof(int)) == -1) {
perror("setsockopt");
exit(1);
}
// bind
myaddr.sin_family = AF_INET;
myaddr.sin_addr.s_addr = INADDR_ANY;
myaddr.sin_port = htons(PORT);
memset(myaddr.sin_zero, '\0', sizeof myaddr.sin_zero);
if (bind(listener, (struct sockaddr *)&myaddr, sizeof myaddr) == -1) {
perror("bind");
exit(1);
}
// listen
if (listen(listener, 10) == -1) {
perror("listen");
exit(1);
}
// 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
for(;;) {
read_fds = master; // copy it
if (select(fdmax+1, &read_fds, NULL, NULL, NULL) == -1) {
Beej's Guide to Network Programming 30
perror("select");
exit(1);
}
// run through the existing connections looking for data to read
for(i = 0; i
if (FD_ISSET(i, &read_fds)) { // we got one!!
if (i == listener) {
// handle new connections
addrlen = sizeof remoteaddr;
if ((newfd = accept(listener, \
(struct sockaddr *)&remoteaddr, &addrlen)) == -1) {
perror("accept");
} else {
FD_SET(newfd, &master); // add to master set
if (newfd > fdmax) { // keep track of the maximum
fdmax = newfd;
}
printf("selectserver: new connection from %s on " \
"socket %d\n", \
inet_ntoa(remoteaddr.sin_addr), newfd);
}
} else {
// handle data from a client
if ((nbytes = recv(i, buf, sizeof buf, 0))
// got error or connection closed by client
if (nbytes == 0) {
// connection closed
printf("selectserver: socket %d hung up\n", i);
} else {
perror("recv");
}
close(i); // bye!
FD_CLR(i, &master); // remove from master set
} else {
// we got some data from a client
for(j = 0; j
// send to everyone!
if (FD_ISSET(j, &master)) {
// except the listener and ourselves
if (j != listener && j != i) {
if (send(j, buf, nbytes, 0) == -1) {
perror("send");
}
}
}
}
}
} // it's SO UGLY!
}
}
}
return 0;
}
Notice I have two file descriptor sets in the code: master and read_fds. The first,
master, holds all the socket descriptors that are currently connected, as well as the socket
descriptor that is listening for new connections.
The reason I have the master set is that select() actually changes the set you pass into
it to reflect which sockets are ready to read. Since I have to keep track of the connections from
Beej's Guide to Network Programming 31
one call of select() to the next, I must store these safely away somewhere. At the last minute,
I copy the master into the read_fds, and then call select().
But doesn't this mean that every time I get a new connection, I have to add it to the master
set? Yup! And every time a connection closes, I have to remove it from the master set? Yes, it
does.
Notice I check to see when the listener socket is ready to read. When it is, it means I
have a new connection pending, and I accept() it and add it to the master set. Similarly,
when a client connection is ready to read, and recv() returns 0, I know the client has closed the
connection, and I must remove it from the master set.
If the client recv() returns non-zero, though, I know some data has been received. So I
get it, and then go through the master list and send that data to all the rest of the connected
clients.
And that, my friends, is a less-than-simple overview of the almighty select() function.
In addition, here is a bonus afterthought: there is another function called poll() which
behaves much the same way select() does, but with a different system for managing the file
descriptor sets. Check it out!
6.3. Handling Partial send()s
Remember back in the section about send(), above, when I said that send() might not
send all the bytes you asked it to? That is, you want it to send 512 bytes, but it returns 412. What
happened to the remaining 100 bytes?
Well, they're still in your little buffer waiting to be sent out. Due to circumstances beyond
your control, the kernel decided not to send all the data out in one chunk, and now, my friend,
it's up to you to get the data out there.
You could write a function like this to do it, too:
#include
#include
int sendall(int s, char *buf, int *len)
{
int total = 0; // how many bytes we've sent
int bytesleft = *len; // how many we have left to send
int n;
while(total
n = send(s, buf+total, bytesleft, 0);
if (n == -1) { break; }
total += n;
bytesleft -= n;
}
*len = total; // return number actually sent here
return n==-1?-1:0; // return -1 on failure, 0 on success
}
In this example, s is the socket you want to send the data to, buf is the buffer containing
the data, and len is a pointer to an int containing the number of bytes in the buffer.
The function returns -1 on error (and errno is still set from the call to send().) Also,
the number of bytes actually sent is returned in len. This will be the same number of bytes you
asked it to send, unless there was an error. sendall() will do it's best, huffing and puffing, to
send the data out, but if there's an error, it gets back to you right away.
For completeness, here's a sample call to the function:
char buf[10] = "Beej!";
int len;
Beej's Guide to Network Programming 32
len = strlen(buf);
if (sendall(s, buf, &len) == -1) {
perror("sendall");
printf("We only sent %d bytes because of the error!\n", len);
}
What happens on the receiver's end when part of a packet arrives? If the packets are
variable length, how does the receiver know when one packet ends and another begins?
Yes, real-world scenarios are a royal pain in the donkeys. You probably have to encapsulate
(remember that from the data encapsulation section way back there at the beginning?) Read on
for details!
6.4. Serialization How to Pack Data
It's easy enough to send text data across the network, you're finding, but what happens if
you want to send some  binary data like ints or floats? It turns out you have a few options.
1. Convert the number into text with a function like sprintf(), then send the text. The
receiver will parse the text back into a number using a function like strtol().
2. Just send the data raw, passing a pointer to the data to send().
3. Encode the number into a portable binary form. The receiver will decode it.
Sneak preview! Tonight only!
[Curtain raises]
Beej says,  I prefer Method Three, above! [ Pobierz całość w formacie PDF ]

  • zanotowane.pl
  • doc.pisz.pl
  • pdf.pisz.pl
  • tibiahacks.keep.pl