?? advanced.html
字號:
myaddr.sin_addr.s_addr = INADDR_ANY; myaddr.sin_port = htons(PORT); memset(&(myaddr.sin_zero), '\0', 8); 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) { perror("select"); exit(1); } // run through the existing connections looking for data to read for(i = 0; i <= fdmax; 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)) <= 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 <= fdmax; 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; } </PRE></TD></TR></TABLE><P>Notice I have two file descriptor sets in the code:<TTCLASS="parameter"><I>master</I></TT> and <TTCLASS="parameter"><I>read_fds</I></TT>. Thefirst, <TTCLASS="parameter"><I>master</I></TT>, holds all the socket descriptorsthat are currently connected, as well as the socket descriptor that islistening for new connections.</P><P>The reason I have the <TTCLASS="parameter"><I>master</I></TT> set is that<TTCLASS="function">select()</TT> actually <EM>changes</EM> theset you pass into it to reflect which sockets are ready to read. SinceI have to keep track of the connections from one call of<TTCLASS="function">select()</TT> to the next, I must store these safelyaway somewhere. At the last minute, I copy the<TTCLASS="parameter"><I>master</I></TT> into the <TTCLASS="parameter"><I>read_fds</I></TT>,and then call <TTCLASS="function">select()</TT>.</P><P>But doesn't this mean that every time I get a new connection, Ihave to add it to the <TTCLASS="parameter"><I>master</I></TT> set? Yup! Andevery time a connection closes, I have to remove it from the<TTCLASS="parameter"><I>master</I></TT> set? Yes, it does.</P><P>Notice I check to see when the <TTCLASS="parameter"><I>listener</I></TT>socket is ready to read. When it is, it means I have a new connectionpending, and I <TTCLASS="function">accept()</TT> it and add it to the<TTCLASS="parameter"><I>master</I></TT> set. Similarly, when a client connectionis ready to read, and <TTCLASS="function">recv()</TT> returns<TTCLASS="constant">0</TT>, I know the client has closed the connection, andI must remove it from the <TTCLASS="parameter"><I>master</I></TT> set.</P><P>If the client <TTCLASS="function">recv()</TT> returns non-zero,though, I know some data has been received. So I get it, and then gothrough the <TTCLASS="parameter"><I>master</I></TT> list and send that data to allthe rest of the connected clients.</P><P>And that, my friends, is a less-than-simple overview of thealmighty <TTCLASS="function">select()</TT> function.</P></DIV><DIVCLASS="sect2"><H2CLASS="sect2"><ANAME="sendall">6.3. Handling Partial <TTCLASS="function">send()</TT>s</A></H2><P>Remember back in the <AHREF="syscalls.html#sendrecv">section about<TTCLASS="function">send()</TT></A>, above, when I said that<TTCLASS="function">send()</TT> might not send all the bytes you asked itto? That is, you want it to send 512 bytes, but it returns 412. Whathappened to the remaining 100 bytes?</P><P>Well, they're still in your little buffer waiting to be sent out.Due to circumstances beyond your control, the kernel decided not to sendall the data out in one chunk, and now, my friend, it's up to you to getthe data out there.</P><P>You could write a function like this to do it, too:</P><TABLEBORDER="0"BGCOLOR="#E0E0E0"WIDTH="100%"><TR><TD><PRECLASS="programlisting"> #include <sys/types.h> #include <sys/socket.h> 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 < *len) { 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 } </PRE></TD></TR></TABLE><P>In this example, <TTCLASS="parameter"><I>s</I></TT> is the socket you wantto send the data to, <TTCLASS="parameter"><I>buf</I></TT> is the buffer containingthe data, and <TTCLASS="parameter"><I>len</I></TT> is a pointer to an<TTCLASS="type">int</TT> containing the number of bytes in the buffer.</P><P>The function returns <TTCLASS="constant">-1</TT> on error (and<TTCLASS="parameter"><I>errno</I></TT> is still set from the call to<TTCLASS="function">send()</TT>.) Also, the number of bytes actually sentis returned in <TTCLASS="parameter"><I>len</I></TT>. This will be the same numberof bytes you asked it to send, unless there was an error.<TTCLASS="function">sendall()</TT> will do it's best, huffing and puffing,to send the data out, but if there's an error, it gets back to you rightaway.</P><P>For completeness, here's a sample call to the function:</P><TABLEBORDER="0"BGCOLOR="#E0E0E0"WIDTH="100%"><TR><TD><PRECLASS="programlisting"> char buf[10] = "Beej!"; int len; len = strlen(buf); if (sendall(s, buf, &len) == -1) { perror("sendall"); printf("We only sent %d bytes because of the error!\n", len); } </PRE></TD></TR></TABLE><P>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 onepacket ends and another begins? Yes, real-world scenarios are a royalpain in the donkeys. You probably have to<EM>encapsulate</EM> (remember that from the <AHREF="theory.html#lowlevel">data encapsulation section</A> way back there atthe beginning?) Read on for details!</P></DIV><DIVCLASS="sect2"><H2CLASS="sect2"><ANAME="sonofdataencap">6.4. Son of Data Encapsulation</A></H2><P>What does it really mean to encapsulate data, anyway? In thesimplest case, it means you'll stick a header on there with either someidentifying information or a packet length, or both.</P><P>What should your header look like? Well, it's just some binarydata that represents whatever you feel is necessary to complete yourproject.</P><P>Wow. That's vague.</P><P>Okay. For instance, let's say you have a multi-user chat programthat uses <TTCLASS="constant">SOCK_STREAM</TT>s. When a user types ("says")something, two pieces of information need to be transmitted to theserver: what was said and who said it.</P><P>So far so good? "What's the problem?" you're asking.</P><P>The problem is that the messages can be of varying lengths. Oneperson named "tom" might say, "Hi", and another person named"Benjamin" might say, "Hey guys what is up?"</P><P>So you <TTCLASS="function">send()</TT> all this stuff to the clientsas it comes in. Your outgoing data stream looks like this:</P><TABLEBORDER="0"BGCOLOR="#E0E0E0"WIDTH="100%"><TR><TD><PRECLASS="screen"> t o m H i B e n j a m i n H e y g u y s w h a t i s u p ?</PRE></TD></TR></TABLE><P>And so on. How does the client know when one message starts andanother stops? You could, if you wanted, make all messages the samelength and just call the <TTCLASS="function">sendall()</TT> we implemented,<AHREF="advanced.html#sendall">above</A>. But that wastes bandwidth! Wedon't want to <TTCLASS="function">send()</TT> 1024 bytes just so "tom" cansay "Hi".</P><P>So we <EM>encapsulate</EM> the data in a tiny headerand packet structure. Both the client and server know how to pack andunpack (sometimes referred to as "marshal" and "unmarshal") this data.Don't look now, but we're starting to define a<EM>protocol</EM> that describes how a client and servercommunicate!</P><P>In this case, let's assume the user name is a fixed length of 8characters, padded with <TTCLASS="constant">'\0'</TT>. And then let'sassume the data is variable length, up to a maximum of 128characters. Let's have a look a sample packet structure that we mightuse in this situation:</P><P></P><OLTYPE="1"><LI><P><TTCLASS="computeroutput">len</TT> (1 byte, unsigned)-- The total length of the packet, counting the 8-byte user name andchat data.</P></LI><LI><P><TTCLASS="computeroutput">name</TT> (8 bytes) -- Theuser's name, NUL-padded if necessary.</P></LI><LI><P><TTCLASS="computeroutput">chatdata</TT>(<EM>n</EM>-bytes) -- The data itself, no more than 128bytes. The length of the packet should be calculated as the length ofthis data plus 8 (the length of the name field,above).</P></LI></OL><P>Why did I choose the 8-byte and 128-byte limits for the fields? Ipulled them out of the air, assuming they'd be long enough. Maybe,though, 8 bytes is too restrictive for your needs, and you can have a30-byte name field, or whatever. The choice is up to you.</P><P>Using the above packet definition, the first packet would consistof the following information (in hex and ASCII):</P><TABLEBORDER="0"BGCOLOR="#E0E0E0"WIDTH="100%"><TR><TD><PRECLASS="screen"> 0A 74 6F 6D 00 00 00 00 00 48 69 (length) T o m (padding) H i</PRE></TD></TR></TABLE><P>And the second is similar:</P><TABLEBORDER="0"BGCOLOR="#E0E0E0"WIDTH="100%"><TR><TD><PRECLASS="screen"> 14 42 65 6E 6A 61 6D 69 6E 48 65 79 20 67 75 79 73 20 77 ... (length) B e n j a m i n H e y g u y s w ...</PRE></TD></TR></TABLE><P>(The length is stored in Network Byte Order, of course. In thiscase, it's only one byte so it doesn't matter, but generally speakingyou'll want all your binary integers to be stored in Network Byte Orderin your packets.)</P><P>When you're sending this data, you should be safe and use acommand similar to <AHREF="advanced.html#sendall"><TTCLASS="function">sendall()</TT></A>, above, so youknow all the data is sent, even if it takes multiple calls to<TTCLASS="function">send()</TT> to get it all out.</P><P>Likewise, when you're receiving this data, you need to do a bit of extrawork. To be safe, you should assume that you might receive a partialpacket (like maybe we receive "<TTCLASS="computeroutput">00 14 42 656E</TT>" from Benjamin, above, but that's all we get in thiscall to <TTCLASS="function">recv()</TT>). We need to call<TTCLASS="function">recv()</TT> over and over again until the packet iscompletely received.</P><P>But how? Well, we know the number of bytes we need to receive intotal for the packet to be complete, since that number is tacked on thefront of the packet. We also know the maximum packet size is 1+8+128,or 137 bytes (because that's how we defined the packet.)</P><P>What you can do is declare an array big enough for two packets.This is your work array where you will reconstruct packets as theyarrive.</P><P>Every time you <TTCLASS="function">recv()</TT> data, you'll feed itinto the work buffer and check to see if the packet is complete. Thatis, the number of bytes in the buffer is greater than or equal to thelength specified in the header (+1, because the length in the headerdoesn't include the byte for the length itself.) If the number of bytesin the buffer is less than 1, the packet is not complete, obviously.You have to make a special case for this, though, since the first byteis garbage and you can't rely on it for the correct packetlength.</P><P>Once the packet is complete, you can do with it what youwill. Use it, and remove it from your work buffer.</P><P>Whew! Are you juggling that in your head yet? Well, here's thesecond of the one-two punch: you might have read past the end of onepacket and onto the next in a single <TTCLASS="function">recv()</TT> call.That is, you have a work buffer with one complete packet, and anincomplete part of the next packet! Bloody heck. (But this is why youmade your work buffer large enough to hold <EM>two</EM>packets--in case this happened!)</P><P>Since you know the length of the first packet from the header, andyou've been keeping track of the number of bytes in the work buffer, youcan subtract and calculate how many of the bytes in the work bufferbelong to the second (incomplete) packet. When you've handled the firstone, you can clear it out of the work buffer and move the partial secondpacked down the to front of the buffer so it's all ready to go for thenext <TTCLASS="function">recv()</TT>.</P><P>(Some of you readers will note that actually moving the partialsecond packet to the beginning of the work buffer takes time, and theprogram can be coded to not require this by using a circular buffer.Unfortunately for the rest of you, a discussion on circular buffers isbeyond the scope of this article. If you're still curious, grab a datastructures book and go from there.)</P><P>I never said it was easy. Ok, I did say it was easy. And it is;you just need practice and pretty soon it'll come to you naturally. ByExcalibur I swear it!</P></DIV></DIV><DIVCLASS="NAVFOOTER"><HRALIGN="LEFT"WIDTH="100%"><TABLESUMMARY="Footer navigation table"WIDTH="100%"BORDER="0"CELLPADDING="0"CELLSPACING="0"><TR><TDWIDTH="33%"ALIGN="left"VALIGN="top"><AHREF="clientserver.html">Prev</A></TD><TDWIDTH="34%"ALIGN="center"VALIGN="top"><AHREF="index.html">Home</A></TD><TDWIDTH="33%"ALIGN="right"VALIGN="top"><AHREF="reference.html">Next</A></TD></TR><TR><TDWIDTH="33%"ALIGN="left"VALIGN="top">Client-Server Background</TD><TDWIDTH="34%"ALIGN="center"VALIGN="top"> </TD><TDWIDTH="33%"ALIGN="right"VALIGN="top">More References</TD></TR></TABLE></DIV></BODY></HTML>
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -