?? advanced.html
字號(hào):
<HTML><HEAD><TITLE>Slightly Advanced Techniques</TITLE><METANAME="GENERATOR"CONTENT="Modular DocBook HTML Stylesheet Version 1.70"><LINKREL="HOME"TITLE="Beej's Guide to Network Programming"HREF="index.html"><LINKREL="PREVIOUS"TITLE="Client-Server Background"HREF="clientserver.html"><LINKREL="NEXT"TITLE="More References"HREF="reference.html"><METAHTTP-EQUIV="Content-Type"CONTENT="text/html; charset=utf-8"></HEAD><BODYCLASS="sect1"BGCOLOR="#FFFFFF"TEXT="#000000"LINK="#0000FF"VLINK="#840084"ALINK="#0000FF"><DIVCLASS="NAVHEADER"><TABLESUMMARY="Header navigation table"WIDTH="100%"BORDER="0"CELLPADDING="0"CELLSPACING="0"><TR><THCOLSPAN="3"ALIGN="center">Beej's Guide to Network Programming</TH></TR><TR><TDWIDTH="10%"ALIGN="left"VALIGN="bottom"><AHREF="clientserver.html">Prev</A></TD><TDWIDTH="80%"ALIGN="center"VALIGN="bottom"></TD><TDWIDTH="10%"ALIGN="right"VALIGN="bottom"><AHREF="reference.html">Next</A></TD></TR></TABLE><HRALIGN="LEFT"WIDTH="100%"></DIV><DIVCLASS="sect1"><H1CLASS="sect1"><ANAME="advanced">6. Slightly Advanced Techniques</A></H1><P>These aren't <EM>really</EM> advanced, but they'regetting out of the more basic levels we've already covered. In fact, ifyou've gotten this far, you should consider yourself fairly accomplishedin the basics of Unix network programming! Congratulations!</P><P>So here we go into the brave new world of some of the moreesoteric things you might want to learn about sockets. Have atit!</P><DIVCLASS="sect2"><H2CLASS="sect2"><ANAME="blocking">6.1. Blocking</A></H2><P>Blocking. You've heard about it--now what the heck is it? In anutshell, "block" is techie jargon for "sleep". You probably noticedthat when you run <BCLASS="command">listener</B>, above, it just sits thereuntil a packet arrives. What happened is that it called<TTCLASS="function">recvfrom()</TT>, there was no data, and so<TTCLASS="function">recvfrom()</TT> is said to "block" (that is, sleepthere) until some data arrives.</P><P>Lots of functions block. <TTCLASS="function">accept()</TT> blocks.All the <TTCLASS="function">recv()</TT> functions block. The reason theycan do this is because they're allowed to. When you first create thesocket descriptor with <TTCLASS="function">socket()</TT>, the kernel sets itto blocking. If you don't want a socket to be blocking, you have tomake a call to <TTCLASS="function">fcntl()</TT>:</P><TABLEBORDER="0"BGCOLOR="#E0E0E0"WIDTH="100%"><TR><TD><PRECLASS="programlisting"> #include <unistd.h> #include <fcntl.h> . . sockfd = socket(AF_INET, SOCK_STREAM, 0); fcntl(sockfd, F_SETFL, O_NONBLOCK); . . </PRE></TD></TR></TABLE><P>By setting a socket to non-blocking, you can effectively "poll"the socket for information. If you try to read from a non-blockingsocket and there's no data there, it's not allowed to block--it willreturn <TTCLASS="constant">-1</TT> and <TTCLASS="parameter"><I>errno</I></TT> will beset to <TTCLASS="constant">EWOULDBLOCK</TT>.</P><P>Generally speaking, however, this type of polling is a bad idea.If you put your program in a busy-wait looking for data on the socket,you'll suck up CPU time like it was going out of style. A more elegantsolution for checking to see if there's data waiting to be read comes inthe following section on <TTCLASS="function">select()</TT>.</P></DIV><DIVCLASS="sect2"><H2CLASS="sect2"><ANAME="select">6.2. <TTCLASS="function">select()</TT>--Synchronous I/O Multiplexing</A></H2><P>This function is somewhat strange, but it's very useful. Take thefollowing situation: you are a server and you want to listen forincoming connections as well as keep reading from the connections youalready have.</P><P>No problem, you say, just an <TTCLASS="function">accept()</TT> and acouple of <TTCLASS="function">recv()</TT>s. Not so fast, buster! What ifyou're blocking on an <TTCLASS="function">accept()</TT> call? How are yougoing to <TTCLASS="function">recv()</TT> data at the same time? "Usenon-blocking sockets!" No way! You don't want to be a CPU hog. What,then?</P><P><TTCLASS="function">select()</TT> gives you the power to monitorseveral sockets at the same time. It'll tell you which ones are readyfor reading, which are ready for writing, and which sockets have raisedexceptions, if you really want to know that.</P><P>Without any further ado, I'll offer the synopsis of<TTCLASS="function">select()</TT>:</P><TABLEBORDER="0"BGCOLOR="#E0E0E0"WIDTH="100%"><TR><TD><PRECLASS="programlisting"> #include <sys/time.h> #include <sys/types.h> #include <unistd.h> int select(int numfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); </PRE></TD></TR></TABLE><P>The function monitors "sets" of file descriptors; in particular<TTCLASS="parameter"><I>readfds</I></TT>, <TTCLASS="parameter"><I>writefds</I></TT>, and<TTCLASS="parameter"><I>exceptfds</I></TT>. If you want to see if you can readfrom standard input and some socket descriptor,<TTCLASS="parameter"><I>sockfd</I></TT>, just add the file descriptors<TTCLASS="constant">0</TT> and <TTCLASS="parameter"><I>sockfd</I></TT> to the set<TTCLASS="parameter"><I>readfds</I></TT>. The parameter<TTCLASS="parameter"><I>numfds</I></TT> should be set to the values of the highestfile descriptor plus one. In this example, it should be set to<TTCLASS="parameter"><I>sockfd+1</I></TT>, since it is assuredly higher thanstandard input (<TTCLASS="constant">0</TT>).</P><P>When <TTCLASS="function">select()</TT> returns,<TTCLASS="parameter"><I>readfds</I></TT> will be modified to reflect which of thefile descriptors you selected which is ready for reading. You can testthem with the macro <TTCLASS="function">FD_ISSET()</TT>, below.</P><P>Before progressing much further, I'll talk about how to manipulatethese sets. Each set is of the type <TTCLASS="type">fd_set</TT>. The followingmacros operate on this type:</P><P></P><UL><LI><P><TTCLASS="function">FD_ZERO(fd_set *set)</TT> -- clears afile descriptor set</P></LI><LI><P><TTCLASS="function">FD_SET(int fd, fd_set *set)</TT> -- adds<TTCLASS="parameter"><I>fd</I></TT> to the set</P></LI><LI><P><TTCLASS="function">FD_CLR(int fd, fd_set *set)</TT> --removes <TTCLASS="parameter"><I>fd</I></TT> from the set</P></LI><LI><P><TTCLASS="function">FD_ISSET(int fd, fd_set *set)</TT> --tests to see if <TTCLASS="parameter"><I>fd</I></TT> is in theset</P></LI></UL><P>Finally, what is this weirded out <TTCLASS="type">struct timeval</TT>?Well, sometimes you don't want to wait forever for someone to send yousome data. Maybe every 96 seconds you want to print "Still Going..." tothe terminal even though nothing has happened. This time structureallows you to specify a timeout period. If the time is exceeded and<TTCLASS="function">select()</TT> still hasn't found any ready filedescriptors, it'll return so you can continue processing.</P><P>The <TTCLASS="type">struct timeval</TT> has the follow fields:</P><TABLEBORDER="0"BGCOLOR="#E0E0E0"WIDTH="100%"><TR><TD><PRECLASS="programlisting"> struct timeval { int tv_sec; // seconds int tv_usec; // microseconds }; </PRE></TD></TR></TABLE><P>Just set <TTCLASS="parameter"><I>tv_sec</I></TT> to the number of seconds towait, and set <TTCLASS="parameter"><I>tv_usec</I></TT> to the number ofmicroseconds to wait. Yes, that's <EM>micro</EM>seconds,not milliseconds. There are 1,000 microseconds in a millisecond, and1,000 milliseconds in a second. Thus, there are 1,000,000 microsecondsin a second. Why is it "usec"? The "u" is supposed to look like theGreek letter 渭 (Mu) that we use for "micro". Also, when the functionreturns, <TTCLASS="parameter"><I>timeout</I></TT> <EM>might</EM> beupdated to show the time still remaining. This depends on what flavorof Unix you're running.</P><P>Yay! We have a microsecond resolution timer! Well, don't counton it. Standard Unix timeslice is around 100 milliseconds, so you mighthave to wait that long no matter how small you set your <TTCLASS="type">structtimeval</TT>.</P><P>Other things of interest: If you set the fields in your<TTCLASS="type">struct timeval</TT> to <TTCLASS="constant">0</TT>,<TTCLASS="function">select()</TT> will timeout immediately, effectivelypolling all the file descriptors in your sets. If you set theparameter <TTCLASS="parameter"><I>timeout</I></TT> to NULL, it will never timeout,and will wait until the first file descriptor is ready. Finally, if youdon't care about waiting for a certain set, you can just set it to NULLin the call to <TTCLASS="function">select()</TT>.</P><P><AHREF="http://www.ecst.csuchico.edu/~beej/guide/net/examples/select.c"TARGET="_top">The following code snippet</A> waits 2.5 seconds forsomething to appear on standard input:</P><TABLEBORDER="0"BGCOLOR="#E0E0E0"WIDTH="100%"><TR><TD><PRECLASS="programlisting"> /* ** select.c -- a select() demo */ #include <stdio.h> #include <sys/time.h> #include <sys/types.h> #include <unistd.h> #define STDIN 0 // file descriptor for standard input int main(void) { struct timeval tv; fd_set readfds; tv.tv_sec = 2; tv.tv_usec = 500000; FD_ZERO(&readfds); FD_SET(STDIN, &readfds); // don't care about writefds and exceptfds: select(STDIN+1, &readfds, NULL, NULL, &tv); if (FD_ISSET(STDIN, &readfds)) printf("A key was pressed!\n"); else printf("Timed out.\n"); return 0; } </PRE></TD></TR></TABLE><P>If you're on a line buffered terminal, the key you hit should beRETURN or it will time out anyway.</P><P>Now, some of you might think this is a great way to wait for dataon a datagram socket--and you are right: it <EM>might</EM>be. Some Unices can use select in this manner, and some can't. Youshould see what your local man page says on the matter if you want toattempt it.</P><P>Some Unices update the time in your <TTCLASS="type">struct timeval</TT> toreflect the amount of time still remaining before a timeout. But othersdo not. Don't rely on that occurring if you want to be portable. (Use<TTCLASS="function">gettimeofday()</TT> if you need to track timeelapsed. It's a bummer, I know, but that's the way it is.)</P><P>What happens if a socket in the read set closes the connection?Well, in that case, <TTCLASS="function">select()</TT> returns with thatsocket descriptor set as "ready to read". When you actually do<TTCLASS="function">recv()</TT> from it, <TTCLASS="function">recv()</TT> willreturn <TTCLASS="constant">0</TT>. That's how you know the client hasclosed the connection.</P><P>One more note of interest about <TTCLASS="function">select()</TT>: ifyou have a socket that is <TTCLASS="function">listen()</TT>ing, you cancheck to see if there is a new connection by putting that socket's filedescriptor in the <TTCLASS="parameter"><I>readfds</I></TT> set.</P><P>And that, my friends, is a quick overview of the almighty<TTCLASS="function">select()</TT> function.</P><P>But, by popular demand, here is an in-depth example.Unfortunately, the difference between the dirt-simple example, above, andthis one here is significant. But have a look, then read thedescription that follows it.</P><P><AHREF="http://www.ecst.csuchico.edu/~beej/guide/net/examples/selectserver.c"TARGET="_top">This program</A> actslike a simple multi-user chat server. Start it running in one window,then <BCLASS="command">telnet</B> to it ("<BCLASS="command">telnet hostname9034</B>") from multiple other windows. When you type somethingin one <BCLASS="command">telnet</B> session, it should appear in all theothers.</P><TABLEBORDER="0"BGCOLOR="#E0E0E0"WIDTH="100%"><TR><TD><PRECLASS="programlisting"> /* ** selectserver.c -- a cheezy multiperson chat server */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #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 int addrlen; int i, j; FD_ZERO(&master); // clear the master and temp sets FD_ZERO(&read_fds); // get the listener if ((listener = socket(AF_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;
?? 快捷鍵說(shuō)明
復(fù)制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號(hào)
Ctrl + =
減小字號(hào)
Ctrl + -