﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>C++博客-洗尘斋-随笔分类-socket网络编程</title><link>http://www.cppblog.com/lmlf001/category/1601.html</link><description>三悬明镜垂鸿韵，九撩清泉洗尘心
</description><language>zh-cn</language><lastBuildDate>Tue, 20 May 2008 01:07:16 GMT</lastBuildDate><pubDate>Tue, 20 May 2008 01:07:16 GMT</pubDate><ttl>60</ttl><item><title>epoll实现的net_echo程序</title><link>http://www.cppblog.com/lmlf001/archive/2007/09/08/31854.html</link><dc:creator>芥之舟</dc:creator><author>芥之舟</author><pubDate>Sat, 08 Sep 2007 12:49:00 GMT</pubDate><guid>http://www.cppblog.com/lmlf001/archive/2007/09/08/31854.html</guid><wfw:comment>http://www.cppblog.com/lmlf001/comments/31854.html</wfw:comment><comments>http://www.cppblog.com/lmlf001/archive/2007/09/08/31854.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/lmlf001/comments/commentRss/31854.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/lmlf001/services/trackbacks/31854.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 这是我前两天所做的一个小练习，用epoll写个echo程序，里面用共享内存存储访问信息，贴在这里，哪天生疏了还可以过来查查~~&nbsp; 更多内容请访问： http://lmlf001.blog.sohu.com/Code highlighting produced by Actipro CodeHighlighter (freeware)http://www.CodeHighlighter...&nbsp;&nbsp;<a href='http://www.cppblog.com/lmlf001/archive/2007/09/08/31854.html'>阅读全文</a><img src ="http://www.cppblog.com/lmlf001/aggbug/31854.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/lmlf001/" target="_blank">芥之舟</a> 2007-09-08 20:49 <a href="http://www.cppblog.com/lmlf001/archive/2007/09/08/31854.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Linux网络编程--8. 套接字选项（ZZ）</title><link>http://www.cppblog.com/lmlf001/archive/2006/04/27/6372.html</link><dc:creator>芥之舟</dc:creator><author>芥之舟</author><pubDate>Thu, 27 Apr 2006 05:12:00 GMT</pubDate><guid>http://www.cppblog.com/lmlf001/archive/2006/04/27/6372.html</guid><wfw:comment>http://www.cppblog.com/lmlf001/comments/6372.html</wfw:comment><comments>http://www.cppblog.com/lmlf001/archive/2006/04/27/6372.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/lmlf001/comments/commentRss/6372.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/lmlf001/services/trackbacks/6372.html</trackback:ping><description><![CDATA[
		<p align="center">
				<font size="4">Linux网络编程--8. 套接字选项</font>
				<br />
				<br />http://linuxc.51.net 作者:hoyt<br /></p>
		<br />有时候我们要控制套接字的行为(如修改缓冲区的大小),这个时候我们就要控制套接字的选项了. 
<br /><br /><br />8.1 getsockopt和setsockopt 
<br /><br />int getsockopt(int sockfd,int level,int optname,void *optval,socklen_t *optlen)
<br />int setsockopt(int sockfd,int level,int optname,const void *optval,socklen_t *optlen)
<br /><br />level指定控制套接字的层次.可以取三种值: 1)SOL_SOCKET:通用套接字选项. 2)IPPROTO_IP:IP选项. 3)IPPROTO_TCP:TCP选项. 
<br />optname指定控制的方式(选项的名称),我们下面详细解释 
<br /><br />optval获得或者是设置套接字选项.根据选项名称的数据类型进行转换 
<br /><br /><br />选项名称                说明                                    数据类型
<br />========================================================================
<br />                        SOL_SOCKET
<br />------------------------------------------------------------------------
<br />SO_BROADCAST            允许发送广播数据                        int
<br />SO_DEBUG                允许调试                                int
<br />SO_DONTROUTE            不查找路由                              int
<br />SO_ERROR                获得套接字错误                          int
<br />SO_KEEPALIVE            保持连接                                int
<br />SO_LINGER               延迟关闭连接                            struct linger
<br />SO_OOBINLINE            带外数据放入正常数据流                  int
<br />SO_RCVBUF               接收缓冲区大小                          int
<br />SO_SNDBUF               发送缓冲区大小                          int
<br />SO_RCVLOWAT             接收缓冲区下限                          int
<br />SO_SNDLOWAT             发送缓冲区下限                          int
<br />SO_RCVTIMEO             接收超时                                struct timeval
<br />SO_SNDTIMEO             发送超时                                struct timeval
<br />SO_REUSERADDR           允许重用本地地址和端口                  int
<br />SO_TYPE                 获得套接字类型                          int
<br />SO_BSDCOMPAT            与BSD系统兼容                           int
<br />==========================================================================
<br />                        IPPROTO_IP
<br />--------------------------------------------------------------------------
<br />IP_HDRINCL              在数据包中包含IP首部                    int
<br />IP_OPTINOS              IP首部选项                              int
<br />IP_TOS                  服务类型
<br />IP_TTL                  生存时间                                int
<br />==========================================================================
<br />                        IPPRO_TCP
<br />--------------------------------------------------------------------------
<br />TCP_MAXSEG              TCP最大数据段的大小                     int
<br />TCP_NODELAY             不使用Nagle算法                         int
<br />=========================================================================
<br /><br />关于这些选项的详细情况请查看 Linux Programmer's Manual 
<br /><br />8.2 ioctl 
<br />ioctl可以控制所有的文件描述符的情况,这里介绍一下控制套接字的选项. 
<br /><br /> int ioctl(int fd,int req,...)
<br /><br />==========================================================================
<br />                        ioctl的控制选项
<br />--------------------------------------------------------------------------
<br />SIOCATMARK              是否到达带外标记                        int
<br />FIOASYNC                异步输入/输出标志                       int
<br />FIONREAD                缓冲区可读的字节数                      int
<br />==========================================================================
<br /><br />详细的选项请用 man ioctl_list 查看. 
<img src ="http://www.cppblog.com/lmlf001/aggbug/6372.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/lmlf001/" target="_blank">芥之舟</a> 2006-04-27 13:12 <a href="http://www.cppblog.com/lmlf001/archive/2006/04/27/6372.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>SOCKET常用函数简介（ZZ）</title><link>http://www.cppblog.com/lmlf001/archive/2006/04/27/6370.html</link><dc:creator>芥之舟</dc:creator><author>芥之舟</author><pubDate>Thu, 27 Apr 2006 04:11:00 GMT</pubDate><guid>http://www.cppblog.com/lmlf001/archive/2006/04/27/6370.html</guid><wfw:comment>http://www.cppblog.com/lmlf001/comments/6370.html</wfw:comment><comments>http://www.cppblog.com/lmlf001/archive/2006/04/27/6370.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/lmlf001/comments/commentRss/6370.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/lmlf001/services/trackbacks/6370.html</trackback:ping><description><![CDATA[什么是 socket？ <br />你始终听到人们谈论着 "socket"，而你不知道他的确切含义。那么，现在我告诉你： 他是使用 Unix 文件描述符 (fiel descriptor) 和其他程序通讯的方式。 <br />什么？ <br />Ok
--你也许听到一些 Unix 高手 (hacker) 这样说：“呀，Unix 中所有的东西就是文件！”那个家伙也许正在说到一个事实：Unix
程序在执行任何形式的 I/O
的时候，程序是在读或者写一个文件描述符。一个文件描述符只是一个和打开的文件相关联的整数。但是(注意后面的话)，这个文件可能是一个网络连接，
FIFO，管道，终端，磁盘上的文件 或者什么其他的东西。Unix 中所有的东西是文件！因此，你想和 Internet 上别
的程序通讯的时候，你将要通过文件描述符。最好相信刚才的话。 <br />现在你脑海中或许冒出这样的念头：“那么我从哪里得到网络通讯的文件描述符呢，
聪明 人？”无论如何，我要回答这个问题：你利用系统调用 socket()。他返回套接口描 述符 (socket
descriptor)，然后你再通过他来调用 send() 和 recv()。 <br />“但是...”，你可能现在叫起来，“如果他是个文件描述
符，那么为什么不用一般的调用 read() 和 write() 来通过套接口通讯？”简单的答案是：“你可以使用
一般的函数！”。详细的答案是：“你可以，但是使用 send() 和 recv() 让你更好的控制数据传输。” <br />有这样一个事实：在我们的
世界上，有很多种套接口。有 DARPA Internet 地址 (Internet 套接口)，本地节点的路径名 (Unix
套接口)，CCITT X.25 地址 (你可以完全忽略 X.25 套接口)。 也许在你的 Unix
机器上还有其他的。我们在这里只讲第一种：Internet 套接口。 <br />----------------------------------------------------------------------------<br />Internet 套接口的两种类型 <br />什
么意思？有两种 Internet 套接口？是的。不，我在撒谎。其实还有很多，但是我可不想 吓着你。我们这里只讲两种。 Except for
this sentence, where I'm going to tell you that "Raw Sockets" are also
very powerful and you should look them up. <br />好了，好了。那两种类型是什么呢？一种是
"Stream Sockets"，另外一种是 "Datagram Sockets"。我们以后谈到他们的时候也会用到 "SOCK_STREAM"
和 "SOCK_DGRAM"。数据报套接口有时也叫“无连接套接口”(如果你确实要连接的时候用 connect()。) <br />流式套接口是可靠的双向通讯的数据流。如果你向套接口安顺序输出“1，2”，那么他们 将安顺序“1，2”到达另一边。他们也是无错误的传递的，有自己的错误控制。 <br />有
谁在使用流式套接口？你可能听说过 telnet，不是吗？他就使用流式套接口。你需要你所输入的字符按顺序到达，不是 吗？同样，WWW
浏览器使用的 HTTP 协议也使用他们。实际上，当你通过端口80 telnet 到一个 WWW 站点，然后输入 “GET pagename”
的时候，你也可以得到 HTML 的内容。 <br />为什么流式套接口可以达到高质量的数据传输？他使用了“传输控制协议 (The
Transmission Control Protocol)”，也叫 “TCP” (请参考 RFC-793 获得详细资料。)TCP
控制你的数据 按顺序到达并且没有错误。你也许听到 “TCP” 是因为听到过 “TCP/IP”。这里的 IP 是指 “Internet
协议”(请参考 RFC-791.) IP 只是处理 Internet 路由而已。 <br />那么数据报套接口呢？为什么他叫无连接呢？为什么他是不可靠的呢？恩，有这样的事实：如果你发送一个数据报，他可能到达，他可能次序颠倒了。如果他到达，那么在这个包的内部是无错误的。 <br />数据报也使用 IP 作路由，但是他不选择 TCP。他使用“用户数据报协议 (User Datagram Protocol)”，也叫 “UDP” (请参考 RFC-768.) <br />为什么他们是无连接的呢？主要原因是因为他并不象流式套接口那样维持一个连接。 你只要建立一个包，在目标信息中构造一个 IP 头，然后发出去。不需要连接。应用程序有： tftp, bootp 等等。 <br />“够
了！”你也许会想，“如果数据丢失了这些程序如何正常工作？”我的朋友，每个程序在 UDP 上有自己的协议。例如，tftp
协议每发出一个包，收到者发回一个包来说“我收到了！” (一个“命令正确应答”也叫“ACK”
包)。如果在一定时间内(例如5秒)，发送方没有收到应答， 他将重新发送，直到得到 ACK。这一点在实现 SOCK_DGRAM
应用程序的时候非常重要。 <br />----------------------------------------------------------------------------<br />网络理论 <br />既然我刚才提到了协议层，那么现在是讨论网络究竟如何工作和演示 SOCK_DGRAM 的工作。当然，你也可以跳过这一段，如果你认为 已经熟悉的话。 <br />朋
友们，现在是学习 数据封装 (Data Encapsulation) 的时候了！ 这非常非常重要。It's so important that
you might just learn about it if you take the networks course here at
Chico State <img src="http://www.osprg.org/uploads/smil3dbd4e398ff7b.gif" alt="" />. 主要的内容是：一个包，先是被第一个协议(在这里是 TFTP )包装(“封装”)， 然后，整个数据(包括 TFTP 头)被另外一个协议(在这里是 UDP )封装，然后下 一个( IP )，一直重复下去，直到硬件(物理)层( Ethernet )。 <br />当另外一台机器接收到包，硬件先剥去 Ethernet 头，内核剥去 IP 和 UDP 头，TFTP 程序再剥去 TFTP 头，最后得到数据。 <br />现
在我们终于讲到臭名远播的 网络分层模型 (Layered Network
Model)。这种网络模型在描述网络系统上相对其他模型有很多优点。例如，你可以写一个套接口
程序而不用关心数据的物理传输(串行口，以太网，连接单元接口 (AUI) 还是其他介质。
因为底层的程序为你处理他们。实际的网络硬件和拓扑对于程序员来说是透明的。 <br />不说其他废话了，我现在列出整个层次模型。如果你要参加网络考试，可一定要记住： <br />应用层 (Application) <br />表示层 (Presentation) <br />会话层 (Session) <br />传输层 (Transport) <br />网络层 (Network) <br />数据链路层 (Data Link) <br />物理层 (Physical) <br />物理层是硬件(串口，以太网等等)。应用层是和硬件层相隔最远的--他是用户和网络 交互的地方。 <br />这个模型如此通用，如果你想，你可以把他作为修车指南。把他应用到 Unix，结果是: <br />应用层 (Application Layer) (telnet, ftp, 等等) <br />传输层 (Host-to-Host Transport Layer) (TCP, UDP) <br />Internet 层 (Internet Layer) (IP 和路由) <br />网络访问层 (Network Access Layer) (网络层，数据链路层和物理层) <br />现在，你可能看到这些层次如何协调来封装原始的数据了。 <br />看
看建立一个简单的数据包有多少工作？哎呀，你将不得不使用 "cat" 来完成 他们！简直是笑话。对于流式套接口你要作的是 send()
发送数据。对于数据报 式套接口你按照你选择的方式封装数据然后用 sendto()。内核将为你建立传输 层和 Internet
层，硬件完成网络访问层。这就是现代科技。 <br />现在结束我们的网络理论速成班。哦，忘记告诉你关于路由的事情了。但是我不准备谈他。 如果你真的想知道，那么参考 IP RFC。如果你从来不曾了解他，也没有 关系，你还活着不是吗。 <br />----------------------------------------------------------------------------<br />structs <br />终于到达这里了，终于谈到编程了。在这章，我将谈到被套接口用到的各种数据类型。因为 他们中的一些太重要了。 <br />首先是简单的一个：socket descriptor。他是下面的类型： <br />  int <br />仅仅是一个常见的 int。 <br />从
现在起，事情变得不可思议了。请跟我一起忍受苦恼吧。注意这样的事实： 有两种字节排列顺序：重要的字节在前面(有时叫
"octet")，或者不重要的字节在前面。 前一种叫“网络字节顺序 (Network Byte
Order)”。有些机器在内部是按照这个顺序储存数据，而另外一些则不然。当我说某数据必须按照 NBO 顺序，那么你要调用函数(例 如
htons() )来将他从本机字节顺序 (Host Byte Order) 转换过来。如果我 没有提到 NBO， 那么就让他是本机字节顺序吧。
<br />我的第一个结构(TM)--struct sockaddr. 这个数据结构 为许多类型的套接口储存套接口地址信息： <br />struct sockaddr { <br />    unsigned short  sa_family;  /* address family, AF_xxx    */ <br />    char       sa_data[14]; /* 14 bytes of protocol address */ <br />}; <br />sa_family 能够是各种各样的事情，但是在这篇文章中是 "AF_INET"。 sa_data 为套接口储存目标地址和端口信息。看上去很笨拙，不是吗。 <br />为了对付 struct sockaddr，程序员创造了一个并列的结构： struct sockaddr_in ("in" 代表 "Internet".) <br />struct sockaddr_in { <br />    short int     sin_family; /* Address family        */ <br />    unsigned short int sin_port;  /* Port number         */ <br />    struct in_addr   sin_addr;  /* Internet address       */ <br />    unsigned char   sin_zero[8]; /* Same size as struct sockaddr */ <br /><br />}; <br />这
个数据结构让可以轻松处理套接口地址的基本元素。注意 sin_zero (他 被加入到这个结构，并且长度和 struct sockaddr
一样) 应该使用函数 bzero() 或 memset() 来全部置零。 Also, and this is the important
bit, a pointer to a struct sockaddr_in can be cast to a pointer to a
struct sockaddr and vice-versa. 这样的话 即使 socket() 想要的是 struct sockaddr
*， 你仍然可以使用 struct sockaddr_in，and cast it at the last minute! 同时，注意
sin_family 和 struct sockaddr 中的 sa_family 一致并能够设置为 "AF_INET"。最后，
sin_port 和 sin_addr 必须是网络字节顺序 (Network Byte Order)！ <br />你也许会反对道："但是，怎么让整个数据结构 struct in_addr sin_addr 按照网络字节顺序呢?" 要知道这个问题的答案，我们就要仔细的看一 看这个数据结构： struct in_addr, 有这样一个联合 (unions)： <br />/* Internet address (a structure for historical reasons) */ <br />  struct in_addr { <br />    unsigned long s_addr; <br />  }; <br />他
曾经是个最坏的联合，但是现在那些日子过去了。如果你声明 "ina" 是 数据结构 struct sockaddr_in 的实例，那么
"ina.sin_addr.s_addr" 就储存4字节的 IP 地址(网络字节顺序)。如果你不幸的 系统使用的还是恐怖的联合 struct
in_addr ，你还是可以放心4字 节的 IP 地址是和上面我说的一样(这是因为 #define。) <br />----------------------------------------------------------------------------<br />Convert the Natives! <br />我们现在到达下个章节。我们曾经讲了很多网络到本机字节顺序，现在是采取行动的时刻了！ <br />你
能够转换两种类型： short (两个字节)和 long (四个字节)。这个 函数对于变量类型 unsigned 也适用。假设你想将
short 从本机字节顺序 转换为网络字节顺序。用 "h" 表示 "本机 (host)"，接着是 "to"，然后用 "n" 表示 "网络
(network)"，最后用 "s" 表示 "short"： h-to-n-s, 或者 htons() ("Host to Network
Short")。 <br />太简单了... <br />如果不是太傻的话，你一定想到了组合 "n"，"h"，"s"，和 "l"。但是这里没有 stolh() ("Short to Long Host") 函数，但是这里有： <br />htons()--"Host to Network Short" <br />htonl()--"Host to Network Long" <br />ntohs()--"Network to Host Short" <br />ntohl()--"Network to Host Long" <br />现
在，你可能想你已经知道他们了。你也可能想："如果我改变 char 的顺序会 怎么样呢? 我的 68000
机器已经使用了网络字节顺序，我没有必要去调用 htonl() 转换 IP 地址。"
你可能是对的，但是当你移植你的程序到别的机器上的时候，你的程序将 失败。可移植性！这里是 Unix
世界！记住：在你将数据放到网络上的时候，确信他们是网络字 节顺序。 <br />最后一点：为什么在数据结构 struct sockaddr_in
中， sin_addr 和 sin_port 需要转换为网络字节顺序，而 sin_family 不需要呢? 答案是：sin_addr 和
sin_port 分别封装在包的 IP 和 UDP 层。因此，他们必须要是网络字节顺序。 但是 sin_family 域只是被内核
(kernel) 使用来决定在数据结构中包含什么类型的地址，所以他应该是本机字节顺序。也即 sin_family 没有 发
送到网络上，他们可以是本机字节顺序。 <br />----------------------------------------------------------------------------<br />IP 地址和如何处理他们 <br />现在我们很幸运，因为我们有很多的函数来方便地操作 IP 地址。没有必要用手工计算 他们，也没有必要用 &lt;&lt; 操作符来操作 long。 <br />首
先，假设你用 struct sockaddr_in ina，你想将 IP 地址 "132.241.5.10" 储存到其中。你要用的函数是
inet_addr()，转换 numbers-and-dots 格式的 IP 地址到 unsigned long。这个工作可以这样来做： <br />  ina.sin_addr.s_addr = inet_addr("132.241.5.10"); <br />注意：inet_addr() 返回的地址已经是按照网络字节顺序的，你没有必要再去调用 htonl()。 <br />上
面的代码可不是很健壮 (robust)，因为没有错误检查。inet_addr() 在发生错误 的时候返回-1。记得二进制数吗? 在 IP
地址为 255.255.255.255 的时候返回的是 (unsigned)-1！这是个广播地址！记住正确的使用错误检查。 <br />好了，你现
在可以转换字符串形式的 IP 地址为 long 了。那么你有一个数据结构 struct in_addr，该如何按照
numbers-and-dots 格式打印呢? 在这个 时候，也许你要用函数 inet_ntoa() ("ntoa" 意思是 "network
to ascii")： <br />  printf("%s",inet_ntoa(ina.sin_addr)); <br />他将打印 IP
地址。注意的是：函数 inet_ntoa() 的参数是 struct in_addr，而不是
long。同时要注意的是他返回的是一个指向字符的指针。 在 inet_ntoa 内部的指针静态地储存字符数组，因此每次你调用
inet_ntoa() 的时候他将覆盖以前的内容。例如： <br />  char *a1, *a2; <br />  . <br />  . <br />  a1 = inet_ntoa(ina1.sin_addr); /* this is 198.92.129.1 */ <br />  a2 = inet_ntoa(ina2.sin_addr); /* this is 132.241.5.10 */ <br />  printf("address 1: %s\n",a1); <br />  printf("address 2: %s\n",a2); <br />运行结果是： <br />  address 1: 132.241.5.10 <br />  address 2: 132.241.5.10 <br />如果你想保存地址，那么用 strcpy() 保存到自己的字符数组中。 <br />这就是这章的内容了。以后，我们将学习转换 "whitehouse.gov" 形式的字符串到正确 的 IP 地址(请看后面的 DNS 一章。) <br />----------------------------------------------------------------------------<br />socket()--得到文件描述符！ <br />我猜我不会再扯远了--我必须讲 socket() 这个系统调用了。这里是详细的定义： <br />  #include &lt;sys/types.h&gt; <br />  #include &lt;sys/socket.h&gt; <br />  int socket(int domain, int type, int protocol); <br />但
是他们的参数怎么用? 首先，domain 应该设置成 "AF_INET"，就象上面的 数据结构 struct sockaddr_in
中一样。然后，参数 type 告诉内核是 SOCK_STREAM 类型还是 SOCK_DGRAM 类型。最后，把 protocol 设置为
"0"。(注意：有很多种 domain、type， 我不可能一一列出了，请看 socket() 的 man
page。当然，还有一个"更好"的方式 去得到 protocol。请看 getprotobyname() 的 man page。) <br />socket() 只是返回你以后在系统调用种可能用到的 socket 描述符，或者在错误 的时候返回-1。全局变量 errno 中储存错误值。(请参考 perror() 的 man page。) <br />----------------------------------------------------------------------------<br />bind()--我在哪个端口? <br />一
旦你得到套接口，你可能要将套接口和机器上的一定的端口关联起来。(如果你想用 listen() 来侦听一定端口的数据，这是必要一步--MUD
经常告诉你说用命令 "telnet x.y.z 6969".)如果你只想用
connect()，那么这个步骤没有必要。但是无论如何，请继续读下去。 <br />这里是系统调用 bind() 的大略： <br />  #include &lt;sys/types.h&gt; <br />  #include &lt;sys/socket.h&gt; <br />  int bind(int sockfd, struct sockaddr *my_addr, int addrlen); <br />sockfd 是调用 socket 返回的文件描述符。my_addr 是指向 数据结构 struct sockaddr 的指针，他保存你的地址(即端口和 IP 地址) 信息。addrlen 设置为 sizeof(struct sockaddr)。 <br />简单得很不是吗? 再看看例子： <br />  #include &lt;string.h&gt; <br />  #include &lt;sys/types.h&gt; <br />  #include &lt;sys/socket.h&gt; <br />  #define MYPORT 3490 <br />  main() <br />  { <br />    int sockfd; <br />    struct sockaddr_in my_addr; <br />    sockfd = socket(AF_INET, SOCK_STREAM, 0); /* do some error checking! */ <br />    my_addr.sin_family = AF_INET;   /* host byte order */ <br />    my_addr.sin_port = htons(MYPORT); /* short, network byte order */ <br />    my_addr.sin_addr.s_addr = inet_addr("132.241.5.10"); <br />    bzero(&amp;(my_addr.sin_zero), 8);  /* zero the rest of the struct */ <br />    /* don't forget your error checking for bind(): */ <br />    bind(sockfd, (struct sockaddr *)&amp;my_addr, sizeof(struct sockaddr)); <br />   . <br />   . <br />   . <br />这里也有要注意的几件事情。my_addr.sin_port 是网络字节顺序，my_addr.sin_addr.s_addr 也是的。另外要注意到的事情是因系统的不同， 包含的头文件也不尽相同，请查阅自己的 man page。 <br />在 bind() 主题中最后要说的话是，在处理自己的 IP 地址和/或端口的时候，有些工作 是可以自动处理的。 <br />    my_addr.sin_port = 0; /* choose an unused port at random */ <br />    my_addr.sin_addr.s_addr = INADDR_ANY; /* use my IP address */ <br />通过将0赋给 my_addr.sin_port，你告诉 bind() 自己选择合适的端口。同样， 将 y_addr.sin_addr.s_addr 设置为 INADDR_ANY，你告诉他自动填上 他所运行的机器的 IP 地址。 <br />如果你一向小心谨慎，那么你可能注意到我没有将 INADDR_ANY 转换为网络字节顺序！这是因为我知道内部的东西：INADDR_ANY 实际上就是 0！即使你 改变字节的顺序，0依然是0。但是完美主义者说安全第一，那么看下面的代码： <br />    my_addr.sin_port = htons(0); /* choose an unused port at random */ <br />    my_addr.sin_addr.s_addr = htonl(INADDR_ANY); /* use my IP address */ <br />你可能不相信，上面的代码将可以随便移植。 <br />bind() 在错误的时候依然是返回-1，并且设置全局变量 errno。 <br />在你调用 bind() 的时候，你要小心的另一件事情是：不要采用小于1024的端口号。所有小于1024的端口号都 被系统保留！你可以选择从1024到65535(如果他们没有被别的程序使用的话)。 <br />你
要注意的另外一件小事是：有时候你根本不需要调用他。如果你使用 connect() 来和远程机器通讯，你不要关心你的本地端口号(就象你在使用
telnet 的时候)，你只要 简单的调用 connect() 就够可，他会检查套接口是否绑定，如果没有，他会自己绑定
一个没有使用的本地端口。 <br />----------------------------------------------------------------------------<br />connect()--Hello！ <br />现
在我们假设你是个 telnet 程序。你的用户命令你(就象电影 TRON 中一样)得到套接口 的文件描述符。你听从命令调用了
socket()。下一步，你的用户告诉你通过端口23(标 准 telnet 端口)连接到"132.241.5.10"。你该怎么做呢? <br />幸运的是，你正在疯狂地阅读 connect()--如何连接到远程主机这一章。你可不想让 你的用户失望。 <br />connect() 系统调用是这样的： <br />  #include &lt;sys/types.h&gt; <br />  #include &lt;sys/socket.h&gt; <br />  int connect(int sockfd, struct sockaddr *serv_addr, int addrlen); <br />sockfd 是系统调用 socket() 返回的套接口文件描述符。serv_addr 是保存着目的地端口和 IP 地址的数据结构 struct sockaddr。addrlen 设置为 sizeof(struct sockaddr)。 <br />让我们来看个例子： <br />  #include &lt;string.h&gt; <br />  #include &lt;sys/types.h&gt; <br />  #include &lt;sys/socket.h&gt; <br />  #define DEST_IP  "132.241.5.10" <br />  #define DEST_PORT 23 <br />  main() <br />  { <br />    int sockfd; <br />    struct sockaddr_in dest_addr;  /* will hold the destination addr */ <br />   sockfd = socket(AF_INET, SOCK_STREAM, 0); /* do some error checking! */ <br />    dest_addr.sin_family = AF_INET;    /* host byte order */ <br />    dest_addr.sin_port = htons(DEST_PORT); /* short, network byte order */ <br />    dest_addr.sin_addr.s_addr = inet_addr(DEST_IP); <br />    bzero(&amp;(dest_addr.sin_zero), 8);    /* zero the rest of the struct */ <br />    /* don't forget to error check the connect()! */ <br />    connect(sockfd, (struct sockaddr *)&amp;dest_addr, sizeof(struct sockaddr)); <br />    . <br />    . <br />    . <br />再一次，你应该检查 connect() 的返回值--他在错误的时候返回-1，并 设置全局变量 errno。 <br />同时，你可能看到，我没有调用 bind()。另外，我也没有管本地的端口号。我只关心 我在连接。内核将为我选择一个合适的端口号，而我们所连接的地方也自动地获得这些信息。 <br />----------------------------------------------------------------------------<br />listen()--Will somebody please call me? <br />Ok,
time for a change of pace. What if you don't want to connect to a
remote host. Say, just for kicks, that you want to wait for incoming
connections and handle them in some way.
处理过程分两步：首先，你听--listen()，然后，你接受--accept() (请看 下面的内容)。 <br />除了要一点解释外，系统调用 listen 相当简单。 <br /> int listen(int sockfd, int backlog); <br />sockfd
是调用 socket() 返回的套接口文件描述符。backlog 是 在进入队列中允许的连接数目。是什么意思呢?
进入的连接是在队列中一直等待直到你接受 (accept()
请看下面的文章)的连接。他们的数目限制于队列的允许。大多数系统的允许数目是20，你也可以设置为5到10。 <br />和别的函数一样，在发生错误的时候返回-1，并设置全局变量 errno。 <br />你可能想象到了，在你调用 listen() 前你或者要调用 bind() 或者让 内核随便选择一个端口。如果你想侦听进入的连接，那么系统调用的顺序可能是这样的： <br />  socket(); <br />  bind(); <br />  listen(); <br />  /* accept() goes here */ <br />因为他相当的明了，我将在这里不给出例子了。(在 accept() 那一章的代码将更加 完全。)真正麻烦的部分在 accept()。 <br />----------------------------------------------------------------------------<br />accept()--"Thank you for calling port 3490." <br />准
备好了，系统调用 accept() 会有点古怪的地方的！你可以想象发生这样的事情： 有人从很远的地方通过一个你在侦听 (listen())
的端口连接 (connect()) 到你的机器。他的连接将加入到等待接受 (accept()) 的队列中。你调用 accept()
告诉他你有空闲的连接。他将返回一个新的套接口文件描述符！ 原来的一个还在侦听你的那个端口，新的最后在准备发送 (send()) 和接收 (
recv()) 数据。这就是这个过程！ <br />函数是这样定义的： <br />  #include &lt;sys/socket.h&gt; <br />  int accept(int sockfd, void *addr, int *addrlen); <br />sockfd
相当简单，是和 listen() 中一样的套接口描述符。addr 是个指向局部的数据结构 struct sockaddr_in
的指针。This is where the information about the incoming connection will go
(and you can determine which host is calling you from which port).
在他的地址传递给 accept 之前，addrlen 是个局部的整形变量，设置为 sizeof(struct
sockaddr_in)。accept 将 不会将多余的字节给 addr。如果你放入的少些，那么在 addrlen 的值中反映 出来。 <br />同样，在错误时返回-1并设置全局变量 errno。 <br />现在是你应该熟悉的代码片段。 <br />  #include &lt;string.h&gt; <br />  #include &lt;sys/types.h&gt; <br />  #include &lt;sys/socket.h&gt; <br />  #define MYPORT 3490  /* the port users will be connecting to */ <br />  #define BACKLOG 10   /* how many pending connections queue will hold */ <br />  main() <br />  { <br />    int sockfd, new_fd; /* listen on sock_fd, new connection on new_fd */ <br />   struct sockaddr_in my_addr;  /* my address information */ <br />    struct sockaddr_in their_addr; /* connector's address information */ <br />    int sin_size; <br />    sockfd = socket(AF_INET, SOCK_STREAM, 0); /* do some error checking! */ <br />    my_addr.sin_family = AF_INET;     /* host byte order */ <br />    my_addr.sin_port = htons(MYPORT);   /* short, network byte order */ <br />    my_addr.sin_addr.s_addr = INADDR_ANY; /* auto-fill with my IP */ <br />    bzero(&amp;(my_addr.sin_zero), 8);    /* zero the rest of the struct */ <br />    /* don't forget your error checking for these calls: */ <br />    bind(sockfd, (struct sockaddr *)&amp;my_addr, sizeof(struct sockaddr)); <br />    listen(sockfd, BACKLOG); <br />   sin_size = sizeof(struct sockaddr_in); <br />    new_fd = accept(sockfd, &amp;their_addr, &amp;sin_size); <br />    . <br />    . <br />    . <br />注意，在系统调用 send() 和 recv() 中你应该使用新的文件描述符。 如果你只想让一个连接进来，那么你可以使用 close() 去关闭原来的文件描述 符 sockfd 来避免同一个端口更多的连接。 <br />----------------------------------------------------------------------------<br />send() and recv()--Talk to me, baby! <br />这两个函数用于流式套接口和数据报套接口的通讯。如果你喜欢使用无连接的数据报 套接口，你应该看一看下面关于 sendto() 和 recvfrom() 的章节。 <br />send() 是这样的： <br />  int send(int sockfd, const void *msg, int len, int flags); <br />sockfd
是你想发送数据的套接口描述符(或者是调用 socket() 或者是 accept() 返回的。)msg 是指向你想发送的数据的指针。len 是
数据的长度。把 flags 设置为 0 就可以了。(详细的资料请看 send() 的 man page)。 <br />这里是一些可能的例子： <br />  char *msg = "Beej was here!"; <br />  int len, bytes_sent; <br />  . <br />  . <br />  len = strlen(msg); <br />  bytes_sent = send(sockfd, msg, len, 0); <br /> . <br />. <br />  . <br />send()
返回实际发送的数据的字节数--他可能小于你要求发送的数目！也即你告诉他要发送一堆数据可是他不能处理成功。他只是发送他可能发送的数据，然后
希望你以后能够发送其他的数据。记住，如果 send() 返回的数据和 len 不
匹配，你应该发送其他的数据。但是这里也有个好消息：如果你要发送的包很小(小于大约
1K)，他可能处理让数据一次发送完。最后，在错误的时候返回-1，并设置 errno。 <br />recv() 函数很相似： <br />  int recv(int sockfd, void *buf, int len, unsigned int flags); <br />sockfd 是要读的套接口描述符。buf 是要读的信息的缓冲。len 是 缓冲的最大长度。flags 也可以设置为0。(请参考recv() 的 man page。) <br />recv() 返回实际读入缓冲的数据的字节数。或者在错误的时候返回-1，同时设置 errno。 <br />很简单，不是吗? 你现在可以在流式套接口上发送数据和接收数据了。你现在是 Unix 网络程序员了！ <br />----------------------------------------------------------------------------<br />sendto() 和 recvfrom()--Talk to me, DGRAM-style <br />"这很不错啊"，我听到你说，"但是你还没有讲无连接数据报套接口呢。"没问题，现在我们开始 这个内容。 <br />既然数据报套接口不是连接到远程主机的，那么在我们发送一个包之前需要什么信息呢? 不错，是目标地址！看下面的： <br />  int sendto(int sockfd, const void *msg, int len, unsigned int flags, <br />     const struct sockaddr *to, int tolen); <br />你
已经看到了，除了另外的两个信息外，其余的和函数 send() 是一样的。 to 是个指向数据结构 struct sockaddr
的指针，他包含了目的地的 IP 地址和断口信息。tolen 可以简单地设置为 sizeof(struct sockaddr)。 <br />和函数 send() 类似，sendto() 返回实际发送的字节数(他也可能小于你 想要发送的字节数！)，或者在错误的时候返回 -1。 <br />相似的还有函数 recv() 和 recvfrom()。recvfrom() 的定义是 这样的： <br />  int recvfrom(int sockfd, void *buf, int len, unsigned int flags <br />        struct sockaddr *from, int *fromlen); <br />又
一次，除了一点多余的参数外，这个函数和 recv() 也是一样的。from 是 一个指向局部数据结构 struct sockaddr
的指针，他的内容是源机器 的 IP 地址和端口信息。fromlen 是个 int 型的局部指针，他的初始值 为 sizeof(struct
sockaddr)。函数调用后，fromlen 保存着 实际储存在 from 中的地址的长度。 <br />recvfrom() 返回收到的字节长度，或者在发生错误后返回 -1。 <br />记住，如果你是用 connect() 连接一个数据报套接口，你可以简单的调用 send() 和 recv() 来满足你的要求。这个时候依然是数据报套接口，依然使用 UDP，系统 自动的加上了目标和源的信息。 <br />----------------------------------------------------------------------------<br />close() 和 shutdown()--Get outta my face! <br />你已经整天都在发送 (send()) 和接收 (recv()) 数据了，现在你准备 关闭你的套接口描述符了。这很简单，你可以使用一般的 Unix 文件描述符的 close() 函 数： <br />  close(sockfd); <br />他将防止套接口上更多的数据的读写。任何在另一端读写套接口的企图都将返回错误信息。 <br />如果你想在如何关闭套接口上有多一点的控制，你可以使用函数 shutdown()。他能够让 你将一定方向的通讯或者双向的通讯(就象 close() 一样)关闭，你可以使用： <br />  int shutdown(int sockfd, int how); <br />sockfd 是你想要关闭的套接口文件描述复。how 的值是下面的其中之一： <br />0 - Further receives are disallowed <br />1 - Further sends are disallowed <br />2 - Further sends and receives are disallowed (和 close() 一样 <br />shutdown() 成功时返回 0，失败时返回 -1(同时设置 errno。) <br />如果在无连接的数据报套接口中使用 shutdown()，那么只不过是让 send() 和 recv() 不能使用(记得你在数据报套接口中使用了 connect 后是可以 使用他们的吗?) <br />----------------------------------------------------------------------------<br />getpeername()--Who are you? <br />这个函数太简单了。 <br />他太简单了，以至我都不想单列一章。但是我还是这样做了。 <br />函数 getpeername() 告诉你在连接的流式套接口上谁在另外一边。函数是这样的： <br />  #include &lt;sys/socket.h&gt; <br />  int getpeername(int sockfd, struct sockaddr *addr, int *addrlen); <br />sockfd
是连接的流式套接口的描述符。addr 是一个指向结构 struct sockaddr (或者是 struct sockaddr_in)
的指针，他保存着 连接的另一边的信息。addrlen 是一个 int 型的指针，他初始化为 sizeof(struct sockaddr)。 <br />函数在错误的时候返回 -1，设置相应的 errno。 <br />一
旦你获得他们的地址，你可以使用 inet_ntoa() 或者 gethostbyaddr()
来打印或者获得更多的信息。但是你不能得到他的帐号。(如果他运行着愚蠢的守护进程，这是 可能的，但是他的讨论已经超出了本文的范围，请参考
RFC-1413 以获得更多的信息。) <br />------------------------------------------------------------------------------- <br />gethostname()--Who am I? <br />甚至比 getpeername() 还简单的函数是 gethostname()。他返回你程序 所运行的机器的主机名字。然后你可以使用 gethostbyname() 以获得你的机器的 IP 地址。 <br />下面是定义： <br />  #include &lt;unistd.h&gt; <br />  int gethostname(char *hostname, size_t size); <br />参数很简单：hostname 是一个字符数组指针，他将在函数返回时保存 主机名。size 是 hostname 数组的字节长度。 <br />函数调用成功时返回 0，失败时返回 -1，并设置 errno。 <br />-------------------------------------------------------------------------------- <br />DNS--You say "whitehouse.gov", I say "198.137.240.100" <br />如
果你不知道 DNS 的意思，那么我告诉你，他代表"域名服务 (Domain Name
Service)"。他主要的功能是：你给他一个容易记忆的某站点的地址，他给你 IP 地址(然后你就可以 使用 bind(),
connect(), sendto() 或者其他函数。)当一个人 输入： <br />  $ telnet whitehouse.gov <br />telnet 能知道他将连接 (connect()) 到 "198.137.240.100"。 <br />但是这是如何工作的呢? 你可以调用函数 gethostbyname()： <br />  #include &lt;netdb.h&gt; <br />  struct hostent *gethostbyname(const char *name); <br />很明白的是，他返回一个指向 struct hostent 的指针。这个数据结构是 这样的： <br />  struct hostent { <br />    char  *h_name; <br />    char  **h_aliases; <br />    int   h_addrtype; <br />    int   h_length; <br />   char  **h_addr_list; <br />  }; <br />  #define h_addr h_addr_list[0] <br />这里是这个数据结构的详细资料： struct hostent: <br />h_name - Official name of the host. <br />h_aliases - A NULL-terminated array of alternate names for the host. <br />h_addrtype - The type of address being returned; usually AF_INET. <br />h_length - The length of the address in bytes. <br />h_addr_list - A zero-terminated array of network addresses for the host. Host addresses are in Network Byte Order. <br />h_addr - The first address in h_addr_list. <br />gethostbyname() 成功时返回一个指向 struct hostent 的 指针，或者是个空 (NULL) 指针。(但是和以前不同，errno 不设置，h_errno 设置错误信息。请看下面的 herror()。) <br />但是如何使用呢? 这个函数可不象他看上去那么难用。 <br />这里是个例子： <br />  #include &lt;stdio.h&gt; <br />  #include &lt;stdlib.h&gt; <br />  #include &lt;errno.h&gt; <br />  #include &lt;netdb.h&gt; <br />  #include &lt;sys/types.h&gt; <br />  #include &lt;netinet/in.h&gt; <br />  int main(int argc, char *argv[]) <br />  { <br />    struct hostent *h; <br />    if (argc != 2) { /* error check the command line */ <br />      fprintf(stderr,"usage: getip address\n"); <br />      exit(1); <br />    } <br />    if ((h=gethostbyname(argv[1])) == NULL) { /* get the host info */ <br />      herror("gethostbyname"); <br />      exit(1); <br />    } <br />    printf("Host name : %s\n", h-&gt;h_name); <br />    printf("IP Address : %s\n",inet_ntoa(*((struct in_addr *)h-&gt;h_addr))); <br />    return 0; <br />  } <br />在使用 gethostbyname() 的时候，你不能用 perror() 打印错误信息(因 为 errno 没有使用)，你应该调用 herror()。 <br />相当简单，你只是传递一个保存机器名的自负串(例如 "whitehouse.gov") 给 gethostbyname()，然后从返回的数据结构 struct hostent 中 收集信息。 <br />唯一让人迷惑的是打印 IP 地址信息。h-&gt;h_addr 是一个 char *， 但是 inet_ntoa() 需要的是 struct in_addr。因此，我 转换 h-&gt;h_addr 成 struct in_addr *，然后得到数据。 <br />-------------------------------------------------------------------------------- <br />Client-Server Background <br />这
里是个客户--服务器的世界。在网络上的所有东西都是在处理客户进程和服务器进程的交谈。 举个 telnet 的例子。当你用 telnet
(客户)通过 23 号端口登陆到主机，主机上运行 的一个程序(一般叫 telnetd，服务器)激活。他处理这个连接，显示登陆界面，等等。 <br />Figure 2. The Client-Server Relationship. <br />图 2 说明了客户和服务器之间的信息交换。 <br />注
意，客--服务器之间可以使用SOCK_STREAM、SOCK_DGRAM 或者其他(只要他们采用相同的)。一些很好的客户--服务器的例子有
telnet/telnetd、 ftp/ftpd 和 bootp/bootpd。每次你使用 ftp 的 时候，在远端都有一个 ftpd
为你服务。 <br />一般，在服务端只有一个服务器，他采用 fork() 来处理多个客户的连接。基本的 程序是：服务器等待一个连接，接受 (accept()) 连接，然后 fork() 一个 子进程处理他。这是下一章我们的例子中会讲到的。 <br />-------------------------------------------------------------------------------- <br />简单的服务器 <br />这个服务器所做的全部工作是在留式连接上发送字符串 "Hello, World!\n"。你要 测试这个程序的话，可以在一台机器上运行该程序，然后在另外一机器上登陆： <br />  $ telnet remotehostname 3490 <br />remotehostname 是该程序运行的机器的名字。 <br />服务器代码： <br />  #include &lt;stdio.h&gt; <br />  #include &lt;stdlib.h&gt; <br />  #include &lt;errno.h&gt; <br />  #include &lt;string.h&gt; <br />  #include &lt;sys/types.h&gt; <br />  #include &lt;netinet/in.h&gt; <br />  #include &lt;sys/socket.h&gt; <br />  #include &lt;sys/wait.h&gt; <br />  #define MYPORT 3490  /* the port users will be connecting to */ <br />  #define BACKLOG 10   /* how many pending connections queue will hold */ <br />  main() <br />  { <br />    int sockfd, new_fd; /* listen on sock_fd, new connection on new_fd */ <br />    struct sockaddr_in my_addr;  /* my address information */ <br />    struct sockaddr_in their_addr; /* connector's address information */ <br />    int sin_size; <br />    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { <br />      perror("socket"); <br />      exit(1); <br />    } <br />    my_addr.sin_family = AF_INET;     /* host byte order */ <br />    my_addr.sin_port = htons(MYPORT);   /* short, network byte order */ <br />    my_addr.sin_addr.s_addr = INADDR_ANY; /* auto-fill with my IP */ <br />    bzero(&amp;(my_addr.sin_zero), 8);    /* zero the rest of the struct */ <br />    if (bind(sockfd, (struct sockaddr *)&amp;my_addr, sizeof(struct sockaddr)) \ <br />          == -1) { <br />      perror("bind"); <br />      exit(1); <br />    } <br />    if (listen(sockfd, BACKLOG) == -1) { <br />      perror("listen"); <br />      exit(1); <br />    } <br />    while(1) { /* main accept() loop */ <br />      sin_size = sizeof(struct sockaddr_in); <br />      if ((new_fd = accept(sockfd, (struct sockaddr *)&amp;their_addr, \ <br />          &amp;sin_size)) == -1) { <br />       perror("accept"); <br />        continue; <br />      } <br />      printf("server: got connection from %s\n", \ <br />                       inet_ntoa(their_addr.sin_addr)); <br />      if (!fork()) { /* this is the child process */ <br />       if (send(new_fd, "Hello, world!\n", 14, 0) == -1) <br />          perror("send"); <br />       close(new_fd); <br />       exit(0); <br />     } <br />      close(new_fd); /* parent doesn't need this */ <br />      while(waitpid(-1,NULL,WNOHANG) &gt; 0); /* clean up child processes */ <br />    } <br />  } <br />如果你很挑剔的话，一定不满意我所有的代码都在一个很大的 main() 函数 中。如果你不喜欢，可以划分得更细点。 <br />你也可以用我们下一章中的程序得到服务器端发送的字符串。 <br />-------------------------------------------------------------------------------- <br />简单的客户程序 <br />这个程序比服务器还简单。这个程序的所有工作是通过 3490 端口连接到命令行中制定的主机， 然后得到服务器的字符串。 <br />客户代码: <br />  #include &lt;stdio.h&gt; <br />  #include &lt;stdlib.h&gt; <br />  #include &lt;errno.h&gt; <br />  #include &lt;string.h&gt; <br />  #include &lt;netdb.h&gt; <br />  #include &lt;sys/types.h&gt; <br />  #include &lt;netinet/in.h&gt; <br />  #include &lt;sys/socket.h&gt; <br />  #define PORT 3490  /* the port client will be connecting to */ <br />  #define MAXDATASIZE 100 /* max number of bytes we can get at once */ <br />  int main(int argc, char *argv[]) <br />  { <br />   int sockfd, numbytes;  <br />    char buf[MAXDATASIZE]; <br />    struct hostent *he; <br />    struct sockaddr_in their_addr; /* connector's address information */ <br />    if (argc != 2) { <br />      fprintf(stderr,"usage: client hostname\n"); <br />      exit(1); <br />    } <br />    if ((he=gethostbyname(argv[1])) == NULL) { /* get the host info */ <br />      herror("gethostbyname"); <br />     exit(1); <br />    } <br />    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { <br />      perror("socket"); <br /><br />      exit(1); <br /><br />    } <br /><br />    their_addr.sin_family = AF_INET;   /* host byte order */ <br /><br />    their_addr.sin_port = htons(PORT);  /* short, network byte order */ <br /><br />    their_addr.sin_addr = *((struct in_addr *)he-&gt;h_addr); <br /><br />    bzero(&amp;(their_addr.sin_zero), 8);   /* zero the rest of the struct */ <br /><br />    if (connect(sockfd, (struct sockaddr *)&amp;their_addr, \ <br />                      sizeof(struct sockaddr)) == -1) { <br />      perror("connect"); <br />     exit(1); <br />   } <br />    if ((numbytes=recv(sockfd, buf, MAXDATASIZE, 0)) == -1) { <br />      perror("recv"); <br />     exit(1); <br />    } <br />    buf[numbytes] = '\0'; <br />    printf("Received: %s",buf); <br />   close(sockfd); <br />    return 0; <br />  } <br />注意，如果你在运行服务器之前运行客户程序，connect() 将返回 "Connection refused" 信息。 <br />-------------------------------------------------------------------------------- <br />数据报 Sockets <br />我不想讲更多了，所以我给出代码 talker.c 和 listener.c。 <br />listener 在机器上等待在端口 4590 来的数据包。talker 发送数据包到一定的 机器，他包含用户在命令行输入的东西。 <br />这里就是 listener.c： <br />  #include &lt;stdio.h&gt; <br />  #include &lt;stdlib.h&gt; <br />  #include &lt;errno.h&gt; <br />  #include &lt;string.h&gt; <br />  #include &lt;sys/types.h&gt; <br />  #include &lt;netinet/in.h&gt; <br />  #include &lt;sys/socket.h&gt; <br />  #include &lt;sys/wait.h&gt; <br />  #define MYPORT 4950  /* the port users will be sending to */ <br />  #define MAXBUFLEN 100 <br />  main() <br />  { <br />    int sockfd; <br />    struct sockaddr_in my_addr;  /* my address information */ <br />    struct sockaddr_in their_addr; /* connector's address information */ <br />   int addr_len, numbytes; <br />   char buf[MAXBUFLEN]; <br />    if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) { <br />      perror("socket"); <br />     exit(1); <br />   } <br />    my_addr.sin_family = AF_INET;     /* host byte order */ <br />    my_addr.sin_port = htons(MYPORT);   /* short, network byte order */ <br />    my_addr.sin_addr.s_addr = INADDR_ANY; /* auto-fill with my IP */ <br />    bzero(&amp;(my_addr.sin_zero), 8);    /* zero the rest of the struct */ <br />    if (bind(sockfd, (struct sockaddr *)&amp;my_addr, sizeof(struct sockaddr)) \ <br />                                   == -1) { <br />      perror("bind"); <br />      exit(1); <br />   } <br />    addr_len = sizeof(struct sockaddr); <br />    if ((numbytes=recvfrom(sockfd, buf, MAXBUFLEN, 0, \ <br />             (struct sockaddr *)&amp;their_addr, &amp;addr_len)) == -1) { <br />      perror("recvfrom"); <br />     exit(1); <br />    } <br />    printf("got packet from %s\n",inet_ntoa(their_addr.sin_addr)); <br />    printf("packet is %d bytes long\n",numbytes); <br />    buf[numbytes] = '\0'; <br />    printf("packet contains \"%s\"\n",buf); <br />    close(sockfd); <br />  } <br />注意在我们的调用 socket()，我们最后使用了 SOCK_DGRAM。同时，没有 必要去使用 listen() 或者 accept()。我们在使用无连接的数据报套接口！ <br />下面是 talker.c： <br />  #include &lt;stdio.h&gt; <br />  #include &lt;stdlib.h&gt; <br />  #include &lt;errno.h&gt; <br />  #include &lt;string.h&gt; <br />  #include &lt;sys/types.h&gt; <br />  #include &lt;netinet/in.h&gt; <br /> #include &lt;netdb.h&gt; <br />  #include &lt;sys/socket.h&gt; <br />  #include &lt;sys/wait.h&gt; <br />  #define MYPORT 4950  /* the port users will be sending to */ <br />  int main(int argc, char *argv[]) <br />  { <br />    int sockfd; <br />    struct sockaddr_in their_addr; /* connector's address information */ <br />    struct hostent *he; <br />    int numbytes; <br />    if (argc != 3) { <br />      fprintf(stderr,"usage: talker hostname message\n"); <br />      exit(1); <br />    } <br />    if ((he=gethostbyname(argv[1])) == NULL) { /* get the host info */ <br />      herror("gethostbyname"); <br />      exit(1); <br />    } <br />    if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) { <br />     perror("socket"); <br />      exit(1); <br />    } <br />   their_addr.sin_family = AF_INET;   /* host byte order */ <br />    their_addr.sin_port = htons(MYPORT); /* short, network byte order */ <br />    their_addr.sin_addr = *((struct in_addr *)he-&gt;h_addr); <br />    bzero(&amp;(their_addr.sin_zero), 8);   /* zero the rest of the struct */ <br />   if ((numbytes=sendto(sockfd, argv[2], strlen(argv[2]), 0, \ <br />      (struct sockaddr *)&amp;their_addr, sizeof(struct sockaddr))) == -1) { <br />      perror("sendto"); <br />     exit(1); <br />    } <br />    printf("sent %d bytes to %s\n",numbytes,inet_ntoa(their_addr.sin_addr)); <br />    close(sockfd); <br />    return 0; <br />  } <br />这就是所有的了。在一台机器上运行 listener，然后在另外一台机器上运行 talker。观察他们的通讯！ <br />Except
for one more tiny detail that I've mentioned many times in the past:
connected datagram sockets. I need to talk about this here, since we're
in the datagram section of the document. Let's say that talker calls
connect() and specifies the listener's address. From that point on,
talker may only sent to and receive from the address specified by
connect(). For this reason, you don't have to use sendto() and
recvfrom(); you can simply use send() and recv(). <br />-------------------------------------------------------------------------------- <br />阻塞 <br />阻
塞，你也许早就听说了。"阻塞"是 "sleep" 的科技行话。你可能注意到前面运行的 listener
程序，他在那里不停地运行，等待数据包的到来。实际在运行的是 他调用 recvfrom()，然后没有数据，因此 recvfrom() 说"阻塞
(block)" 直到数据的到来。 <br />很多函数都利用阻塞。accept() 阻塞，所有的 recv*() 函数阻塞。他们之所以能这样做是因为他们被允许这样做。当你第一次调用 socket() 建立套接口描述符的时候，内核就将他设置为阻塞。如果你不想套接口阻塞，你就要调用函数 fcntl()： <br />  #include &lt;unistd.h&gt; <br />  #include &lt;fcntl.h&gt; <br /> . <br /> . <br />  sockfd = socket(AF_INET, SOCK_STREAM, 0); <br />  fcntl(sockfd, F_SETFL, O_NONBLOCK); <br />  . <br />  . <br />通过设置套接口为非阻塞，你能够有效地"询问"套接口以获得信息。如果你尝试着 从一个非阻塞的套接口读信息并且没有任何数据，他不会变成阻塞--他将返回 -1 并 将 errno 设置为 EWOULDBLOCK。 <br />但是一般说来，这种轮询不是个好主意。如果你让你的程序在忙等状态查询套接口的数据， 你将浪费大量的 CPU 时间。更好的解决之道是用下一章讲的 select() 去查询 是否有数据要读进来。 <br />-------------------------------------------------------------------------------- <br />select()--多路同步 I/O <br />虽然这个函数有点奇怪，但是他很有用。假设这样的情况：你是个服务器，你一边在不停地 从连接上读数据，一边在侦听连接上的信息。 <br />没
问题，你可能会说，不就是一个 accept() 和两个 recv() 吗? 这么容易 吗，朋友? 如果你在调用 accept()
的时候阻塞呢? 你怎么能够同时接受 recv() 数据? "用非阻塞的套接口啊！" 不行！你不想耗尽所有的 CPU，不是吗?
那么，该如何是好? <br />select() 让你可以同时监视多个套接口。如果你想知道的话，那么他就会告诉你哪个套接口准备读，哪个又 准备好了写，哪个套接口又发生了例外 (exception)。 <br />闲话少说，下面是 select()： <br />   #include &lt;sys/time.h&gt; <br />   #include &lt;sys/types.h&gt; <br />   #include &lt;unistd.h&gt; <br />   int select(int numfds, fd_set *readfds, fd_set *writefds, <br />         fd_set *exceptfds, struct timeval *timeout); <br />这
个函数监视一系列文件描述符，特别是 readfds、writefds 和 exceptfds。如果你想知道你是否能够从标准输入和套接口描述符
sockfd 读 入数据，你只要将文件描述符 0 和 sockfd 加入到集合 readfds 中。 参数 numfds
应该等于最高的文件描述符的值加1。在这个例子中，你应该设置该值 为 sockfd+1。因为他一定大于标准输入的文件描述符 (0)。 <br />当函数 select() 返回的时候，readfds 的值修改为反映你选择的哪个文件 描述符可以读。你可以用下面讲到的宏 FD_ISSET() 来测试。 <br />在我们继续下去之前，让我来讲讲如何对这些集合进行操作。每个集合类型都是 fd_set。 下面有一些宏来对这个类型进行操作： <br />FD_ZERO(fd_set *set) - clears a file descriptor set <br />FD_SET(int fd, fd_set *set) - adds fd to the set <br />FD_CLR(int fd, fd_set *set) - removes fd from the set <br />FD_ISSET(int fd, fd_set *set) - tests to see if fd is in the set <br />最
后，是有点古怪的数据结构 struct
timeval。有时你可不想永远等待别人发送数据过来。也许什么事情都没有发生的时候你也想每隔96秒在终端 上打印字符串 "Still
Going..."。这个数据结构允许你设定一个时间，如果时间到了， 而 select()
还没有找到一个准备好的文件描述符，他将返回让你继续处理。 <br />数据结构 struct timeval 是这样的： <br />  struct timeval { <br />    int tv_sec;   /* seconds */ <br />    int tv_usec;  /* microseconds */ <br />  }; <br />只
要将 tv_sec 设置为你要等待的秒数，将 tv_usec
设置为你要等待的微秒数就可以了。是的，是微秒而不是毫秒。1,000微秒等于1豪秒，1,000毫秒等于1秒。也就是说，1秒等于1,000,000微
秒。为什么用符号 "usec" 呢? 字母 "u" 很象希腊字母 Mu， 而 Mu 表示 "微" 的意思。当然，函数返回的时候 timeout
可能是剩余的 时间，之所以是可能，是因为他依赖于你的 Unix 操作系统。 <br />哈！我们现在有一个微秒级的定时器！不要计算了，标准的 Unix 系统的时间片是100毫秒，所以 无论你如何设置你的数据结构 struct timeval，你都要等待那么长的 时间。 <br />还
有一些有趣的事情：如果你设置数据结构 struct timeval 中的 数据为 0，select()
将立即超时，这样就可以有效地轮询集合中的 所有的文件描述符。如果你将参数 timeout 赋值为
NULL，那么将永远不会发生超时，即一直等到第一个文件描述符就绪。最后，如果你不是很关心等待多长时间，那么 就把他赋为 NULL 吧。 <br />下面的代码演示了在标准输入上等待 2.5 秒： <br />   #include &lt;sys/time.h&gt; <br />   #include &lt;sys/types.h&gt; <br />   #include &lt;unistd.h&gt; <br />   #define STDIN 0 /* file descriptor for standard input */ <br />   main() <br />   { <br />     struct timeval tv; <br />     fd_set readfds; <br />     tv.tv_sec = 2; <br />     tv.tv_usec = 500000; <br />     FD_ZERO(&amp;readfds); <br />     FD_SET(STDIN, &amp;readfds); <br />     /* don't care about writefds and exceptfds: */ <br />     select(STDIN+1, &amp;readfds, NULL, NULL, &amp;tv); <br />     if (FD_ISSET(STDIN, &amp;readfds)) <br />       printf("A key was pressed!\n"); <br />     else <br />       printf("Timed out.\n"); <br />  } <br />如果你是在一个 line buffered 终端上，那么你敲的键应该是回车 (RETURN)，否则无论如何 他都会超时。 <br />现在，你可能回认为这就是在数据报套接口上等待数据的方式--你是对的：他可能是。 有些 Unix 系统可以按这种方式，而另外一些则不能。你在尝试以前可能要先看看本系统 的 man page 了。 <br />最后一件关于 select() 的事情：如果你有一个正在侦听 (listen()) 的套接口，你可以通过将该套接口的文件描述符加入到 readfds 集合中来看 是否有新的连接。 <br />这就是我关于函数 select() 要讲的所有的东西。<img src ="http://www.cppblog.com/lmlf001/aggbug/6370.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/lmlf001/" target="_blank">芥之舟</a> 2006-04-27 12:11 <a href="http://www.cppblog.com/lmlf001/archive/2006/04/27/6370.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Linux网络编程-- 服务器模型（ZZ）</title><link>http://www.cppblog.com/lmlf001/archive/2006/04/27/6369.html</link><dc:creator>芥之舟</dc:creator><author>芥之舟</author><pubDate>Thu, 27 Apr 2006 03:58:00 GMT</pubDate><guid>http://www.cppblog.com/lmlf001/archive/2006/04/27/6369.html</guid><wfw:comment>http://www.cppblog.com/lmlf001/comments/6369.html</wfw:comment><comments>http://www.cppblog.com/lmlf001/archive/2006/04/27/6369.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/lmlf001/comments/commentRss/6369.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/lmlf001/services/trackbacks/6369.html</trackback:ping><description><![CDATA[
		<p align="left">
				<font size="4">原文地址：http://study.pay500.com/1/s12212.htm<br /></font>
		</p>
		<p align="center">
				<font size="4">Linux网络编程--9. 服务器模型</font>
				<br />
				<br />
		</p>
		<br /> 学习过《软件工程》吧.软件工程可是每一个程序员"必修"的课程啊.如果你没有学习过, 建议你去看一看.
在这一章里面,我们一起来从软件工程的角度学习网络编程的思想.在我们写程序之前,
我们都应该从软件工程的角度规划好我们的软件,这样我们开发软件的效率才会高.
在网络程序里面,一般的来说都是许多客户机对应一个服务器.为了处理客户机的请求,
对服务端的程序就提出了特殊的要求.我们学习一下目前最常用的服务器模型. <br /><br />循环服务器:循环服务器在同一个时刻只可以响应一个客户端的请求 
<br /><br />并发服务器:并发服务器在同一个时刻可以响应多个客户端的请求 
<br /><br /><br />9.1 循环服务器:UDP服务器 
<br />UDP循环服务器的实现非常简单:UDP服务器每次从套接字上读取一个客户端的请求,处理, 然后将结果返回给客户机. 
<br /><br />可以用下面的算法来实现. 
<br /><br />   socket(...);
<br />   bind(...);
<br />   while(1)
<br />    {
<br />         recvfrom(...);
<br />         process(...);
<br />         sendto(...);
<br />   }
<br /><br />因为UDP是非面向连接的,没有一个客户端可以老是占住服务端. 只要处理过程不是死循环, 服务器对于每一个客户机的请求总是能够满足. 
<br />9.2 循环服务器:TCP服务器 
<br />TCP循环服务器的实现也不难:TCP服务器接受一个客户端的连接,然后处理,完成了这个客户的所有请求后,断开连接. 
<br /><br />算法如下: 
<br /><br />        socket(...);
<br />        bind(...);
<br />        listen(...);
<br />        while(1)
<br />        {
<br />                accept(...);
<br />                while(1)
<br />                {
<br />                        read(...);
<br />                        process(...);
<br />                        write(...);
<br />                }
<br />                close(...);
<br />        }
<br /><br />TCP循环服务器一次只能处理一个客户端的请求.只有在这个客户的所有请求都满足后, 服务器才可以继续后面的请求.这样如果有一个客户端占住服务器不放时,其它的客户机都不能工作了.因此,TCP服务器一般很少用循环服务器模型的. 
<br /><br />9.3 并发服务器:TCP服务器 
<br />为了弥补循环TCP服务器的缺陷,人们又想出了并发服务器的模型. 并发服务器的思想是每一个客户机的请求并不由服务器直接处理,而是服务器创建一个 子进程来处理. 
<br /><br />算法如下: 
<br /><br />  socket(...);
<br />  bind(...);
<br />  listen(...);
<br />  while(1)
<br />  {
<br />        accept(...);
<br />        if(fork(..)==0)
<br />          {
<br />              while(1)
<br />               {        
<br />                read(...);
<br />                process(...);
<br />                write(...);
<br />               }
<br />           close(...);
<br />           exit(...);
<br />          }
<br />        close(...);
<br />  }     
<br /><br />TCP并发服务器可以解决TCP循环服务器客户机独占服务器的情况. 不过也同时带来了一个不小的问题.为了响应客户机的请求,服务器要创建子进程来处理. 而创建子进程是一种非常消耗资源的操作. 
<br /><br />9.4 并发服务器:多路复用I/O 
<br />为了解决创建子进程带来的系统资源消耗,人们又想出了多路复用I/O模型. 
<br /><br />首先介绍一个函数select 
<br /><br /> int select(int nfds,fd_set *readfds,fd_set *writefds,
<br />                fd_set *except fds,struct timeval *timeout)
<br /> void FD_SET(int fd,fd_set *fdset)
<br /> void FD_CLR(int fd,fd_set *fdset)
<br /> void FD_ZERO(fd_set *fdset)
<br /> int FD_ISSET(int fd,fd_set *fdset)
<br /><br />一般的来说当我们在向文件读写时,进程有可能在读写出阻塞,直到一定的条件满足.
比如我们从一个套接字读数据时,可能缓冲区里面没有数据可读(通信的对方还没有
发送数据过来),这个时候我们的读调用就会等待(阻塞)直到有数据可读.如果我们不 希望阻塞,我们的一个选择是用select系统调用.
只要我们设置好select的各个参数,那么当文件可以读写的时候select回"通知"我们 说可以读写了.
readfds所有要读的文件文件描述符的集合 <br />writefds所有要的写文件文件描述符的集合 
<br /><br />exceptfds其他的服要向我们通知的文件描述符 
<br /><br />timeout超时设置. 
<br /><br />nfds所有我们监控的文件描述符中最大的那一个加1 
<br /><br />在我们调用select时进程会一直阻塞直到以下的一种情况发生. 1)有文件可以读.2)有文件可以写.3)超时所设置的时间到. 
<br /><br />为了设置文件描述符我们要使用几个宏. FD_SET将fd加入到fdset 
<br /><br />FD_CLR将fd从fdset里面清除 
<br /><br />FD_ZERO从fdset中清除所有的文件描述符 
<br /><br />FD_ISSET判断fd是否在fdset集合中 
<br /><br />使用select的一个例子 
<br /><br />int use_select(int *readfd,int n)
<br />{
<br />   fd_set my_readfd;
<br />   int maxfd;
<br />   int i;
<br /><br />   maxfd=readfd[0];
<br />   for(i=1;i<br />    if(readfd[i]&gt;maxfd) maxfd=readfd[i];
<br />   while(1)
<br />   {
<br />        /*   将所有的文件描述符加入   */
<br />        FD_ZERO(&amp;my_readfd);
<br />        for(i=0;i<br />            FD_SET(readfd[i],*my_readfd);
<br />        /*     进程阻塞                 */
<br />        select(maxfd+1,&amp; my_readfd,NULL,NULL,NULL); 
<br />        /*        有东西可以读了       */
<br />        for(i=0;i<br />          if(FD_ISSET(readfd[i],&amp;my_readfd))
<br />              {
<br />                  /* 原来是我可以读了  */ 
<br />                        we_read(readfd[i]);
<br />              }
<br />   }
<br />}
<br /><br />使用select后我们的服务器程序就变成了. 
<br /><br /><br />        初始话(socket,bind,listen);
<br /><br />    while(1)
<br />        {
<br />        设置监听读写文件描述符(FD_*);   
<br /><br />        调用select;
<br /><br />        如果是倾听套接字就绪,说明一个新的连接请求建立
<br />             { 
<br />                建立连接(accept);
<br />                加入到监听文件描述符中去;
<br />             }
<br />       否则说明是一个已经连接过的描述符
<br />                {
<br />                    进行操作(read或者write);
<br />                 }
<br /><br />        }               
<br /><br />多路复用I/O可以解决资源限制的问题.着模型实际上是将UDP循环模型用在了TCP上面. 这也就带来了一些问题.如由于服务器依次处理客户的请求,所以可能会导致有的客户 会等待很久. 
<br /><br />9.5 并发服务器:UDP服务器 
<br />人们把并发的概念用于UDP就得到了并发UDP服务器模型. 并发UDP服务器模型其实是简单的.和并发的TCP服务器模型一样是创建一个子进程来处理的 算法和并发的TCP模型一样. 
<br /><br />除非服务器在处理客户端的请求所用的时间比较长以外,人们实际上很少用这种模型. 
<br /><br /><br />9.6 一个并发TCP服务器实例 
<br /><br />#include 
<br />#include 
<br />#include 
<br />#include 
<br />#include 
<br />#define MY_PORT         8888
<br /><br />int main(int argc ,char **argv)
<br />{
<br /> int listen_fd,accept_fd;
<br /> struct sockaddr_in     client_addr;
<br /> int n;
<br /><br /> if((listen_fd=socket(AF_INET,SOCK_STREAM,0))&lt;0)
<br />  {
<br />        printf("Socket Error:%s\n\a",strerror(errno));
<br />        exit(1);
<br />  }
<br /><br /> bzero(&amp;client_addr,sizeof(struct sockaddr_in));
<br /> client_addr.sin_family=AF_INET;
<br /> client_addr.sin_port=htons(MY_PORT);
<br /> client_addr.sin_addr.s_addr=htonl(INADDR_ANY);
<br /> n=1;
<br /> /* 如果服务器终止后,服务器可以第二次快速启动而不用等待一段时间  */
<br /> setsockopt(listen_fd,SOL_SOCKET,SO_REUSEADDR,&amp;n,sizeof(int));
<br /> if(bind(listen_fd,(struct sockaddr *)&amp;client_addr,sizeof(client_addr))&lt;0)
<br />  {
<br />        printf("Bind Error:%s\n\a",strerror(errno));
<br />        exit(1);
<br />  }
<br />  listen(listen_fd,5);
<br />  while(1)
<br />  {
<br />   accept_fd=accept(listen_fd,NULL,NULL);
<br />   if((accept_fd&lt;0)&amp;&amp;(errno==EINTR))
<br />          continue;
<br />   else if(accept_fd&lt;0)
<br />    {
<br />        printf("Accept Error:%s\n\a",strerror(errno));
<br />        continue;
<br />    }
<br />  if((n=fork())==0)
<br />   {
<br />        /* 子进程处理客户端的连接 */
<br />        char buffer[1024];
<br /><br />        close(listen_fd);
<br />        n=read(accept_fd,buffer,1024);
<br />        write(accept_fd,buffer,n);
<br />        close(accept_fd);
<br />        exit(0);
<br />   }
<br />   else if(n&lt;0)
<br />        printf("Fork Error:%s\n\a",strerror(errno));
<br />   close(accept_fd);
<br />  }
<br />} 
<br /><br />你可以用我们前面写客户端程序来调试着程序,或者是用来telnet调试 
<img src ="http://www.cppblog.com/lmlf001/aggbug/6369.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/lmlf001/" target="_blank">芥之舟</a> 2006-04-27 11:58 <a href="http://www.cppblog.com/lmlf001/archive/2006/04/27/6369.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>一个简单聊天室的两种实现 (fcntl 和 select)(ZZ）</title><link>http://www.cppblog.com/lmlf001/archive/2006/04/27/6368.html</link><dc:creator>芥之舟</dc:creator><author>芥之舟</author><pubDate>Thu, 27 Apr 2006 03:51:00 GMT</pubDate><guid>http://www.cppblog.com/lmlf001/archive/2006/04/27/6368.html</guid><wfw:comment>http://www.cppblog.com/lmlf001/comments/6368.html</wfw:comment><comments>http://www.cppblog.com/lmlf001/archive/2006/04/27/6368.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.cppblog.com/lmlf001/comments/commentRss/6368.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/lmlf001/services/trackbacks/6368.html</trackback:ping><description><![CDATA[
		<div class="blogbody">
				<h2 class="title">一个简单聊天室的两种实现 (fcntl 和 select)(转)</h2>
		</div>
		<br />
		<font size="2">
在互联网相当普及的今天，在互联网上聊天对很多“网虫”来说已经是家常便饭了。聊天室程序可以说是网上最简单的多点通信程序。聊天室的实现方法有很多，但
都是利用所谓的“多用户空间”来对信息进行交换，具有典型的多路I/O的架构。一个简单的聊天室,
从程序员的观点来看就是在多个I/O端点之间实现多对多的通信。其架构如图一所示。这样的实现在用户的眼里就是聊天室内任何一个人输入一段字符之后,其他
用户都可以得到这一句话。这种“多用户空间”的架构在其他多点通信程序中应用的非常广泛，其核心就是多路I/O通信。多路I/O通信又被称为I/O多路复
用（I/O Multiplexing）一般被使用在以下的场合： <br />
客户程序需要同时处理交互式的输入和同服务器之间的网络连接时需要处理I/O多路复用问题； <br />
客户端需要同时对多个网络连接作出反应（这种情况很少见）； <br />
TCP服务器需要同时处理处于监听状态和多个连接状态的socket； <br />
服务器需要处理多个网络协议的socket; <br />
服务器需要同时处理不同的网络服务和协议。 <br />
聊天室所需要面对的情况正是第一和第三两种情况。我们将通过在TCP/IP协议之上建立一个功能简单的聊天室让大家更加了解多路I/O以及它的实现方法。 <br />
我们要讨论的聊天室功能非常简单, 感兴趣的朋友可以将其功能扩展, 发展成一个功能比较完整的聊天室, 如加上用户认证, 用户昵称, 秘密信息, semote 等功能. <br />
首先它是一个 client/server 结构的程序, 首先启动 server, 然后用户使用 client 进行连接. client/server 结构的优点是速度快, 缺点是当 server 进行更新时, client 也必需更新. <br />
　 <br />
网络初始化 <br />
　 <br />
首先是初始化 server, 使server 进入监听状态: (为了简洁起见,以下引用的程序与实际程序略有出入, 下同) <br />
sockfd = socket( AF_INET,SOCK_STREAM, 0); <br />
// 首先建立一个 socket, 族为 AF_INET, 类型为 SOCK_STREAM. <br />
// AF_INET = ARPA Internet protocols 即使用 TCP/IP 协议族 <br />
// SOCK_STREAM 类型提供了顺序的, 可靠的, 基于字节流的全双工连接. <br />
// 由于该协议族中只有一个协议, 因此第三个参数为 0 <br />
　 <br />
bind( sockfd, ( struct sockaddr *)&amp;serv_addr, sizeof( serv_addr)); <br />
// 再将这个 socket 与某个地址进行绑定. <br />
// serv_addr 包括 sin_family = AF_INET 协议族同 socket <br />
// sin_addr.s_addr = htonl( INADDR_ANY) server 所接受的所有其他 <br />
// 地址请求建立的连接. <br />
// sin_port = htons( SERV_TCP_PORT) server 所监听的端口 <br />
// 在本程序中, server 的 IP和监听的端口都存放在 config 文件中. <br />
listen( sockfd, MAX_CLIENT); <br />
// 地址绑定之后, server 进入监听状态. <br />
// MAX_CLIENT 是可以同时建立连接的 client 总数. <br />
server 进入 listen 状态后, 等待 client 建立连接。 <br />
Client端要建立连接首先也需要初始化连接： <br />
sockfd = socket( AF_INET,SOCK_STREAM,0)); <br />
// 同样的, client 也先建立一个 socket, 其参数与 server 相同. <br />
connect( sockfd, ( struct sockaddr *)&amp;serv_addr, sizeof( serv_addr)); <br />
// client 使用 connect 建立一个连接. <br />
// serv_addr 中的变量分别设置为: <br />
// sin_family = AF_INET 协议族同 socket <br />
// sin_addr.s_addr = inet_addr( SERV_HOST_ADDR) 地址为 server <br />
// 所在的计算机的地址. <br />
// sin_port = htons( SERV_TCP_PORT) 端口为 server 监听的端口. <br />
当 client 建立新连接的请求被送到Server端时, server 使用 accept 来接受该连接: <br />
accept( sockfd, (struct sockaddr*)&amp;cli_addr, &amp;cli_len); <br />
// 在函数返回时, cli_addr 中保留的是该连接对方的信息 <br />
// 包括对方的 IP 地址和对方使用的端口. <br />
// accept 返回一个新的文件描述符. <br />
在 server 进入 listen 状态之后, 由于已有多个用户在线，所以程序需要同时对这些用户进行操作，并在它们之间实现信息交换。这在实现上称为I/O多路复用技术。多路复用一般有以下几种方法： <br />
非阻塞通信方法：将文件管道通过fcntl()设为非阻塞通信方式，每隔一端时间对他们实行一次轮询，以判断是否可以进行读写操作。这种方式的缺点是费用太高，大部分资源浪费在轮询上。 <br />
子进程方法：应用多个子进程，每一个对一个单工阻塞方式通信。所有子进程通过IPC和父进程进行通信。父进程掌管所有信息。这种方式的缺点是实现复杂，而且由于IPC在各个操作系统平台上并不完全一致，会导致可移植性降低。 <br />
信号驱动（SIGIO）的异步I/O方法：首先，异步I/O是基于信号机制的，并不可靠。其次单一的信号不足以提供更多的信息来源。还是需要辅助以其他的手段，实现上有很高的难度。 <br />
select
()方法：在BSD中提供了一种可以对多路I/O进行阻塞式查询的方法——select()。它提供同时对多个I/O描述符进行阻塞式查询的方法，利用
它，我们可以很方便的实现多路复用。根据统一UNIX规范的协议,POSIX也采用了这种方法，因此，我们可以在大多数操作系统中使用select方法。
<br />
使用专门的I/O多路复用器：在“UNIX? SYSTEM V Programmer's Guide: STREAMS”一书中详细的说明了构造和使用多路复用器的方法。这里就不再详述了。 <br />
　 <br />
我们下面分别讨论多路I/O的两种实现方法: <br />
1. 非阻塞通信方法 <br />对
一个文件描述符指定的文件或设备, 有两种工作方式: 阻塞与非阻塞。所谓阻塞方式的意思是指, 当试图对该文件描述符进行读写时,
如果当时没有东西可读,或者暂时不可写, 程序就进入等待状态, 直到有东西可读或者可写为止。而对于非阻塞状态, 如果没有东西可读, 或者不可写,
读写函数马上返回, 而不会等待。缺省情况下, 文件描述符处于阻塞状态。在实现聊天室时, server 需要轮流查询与各client 建立的
socket, 一旦可读就将该 socket 中的字符读出来并向所有其他client 发送。并且, server 还要随时查看是否有新的
client 试图建立连接,这样, 如果 server 在任何一个地方阻塞了, 其他 client
发送的内容就会受到影响,得不到服务器的及时响应。新 client
试图建立连接也会受到影响。所以我们在这里不能使用缺省的阻塞的文件工作方式，而需要将文件的工作方式变成非阻塞方式。在UNIX下，函数fcntl()
可以用来改变文件I/O操作的工作方式，函数描述如下： <br />
fcntl( sockfd, F_SETFL, O_NONBLOCK); <br />
// sockfd 是要改变状态的文件描述符. <br />
// F_SETFL 表明要改变文件描述符的状态 <br />
// O_NONBLOCK 表示将文件描述符变为非阻塞的. <br />
为了节省篇幅我们使用自然语言描述聊天室 server : <br />
while ( 1) { <br />
if 有新连接 then 建立并记录该新连接; <br />
for ( 所有的有效连接) <br />
begin <br />
if 该连接中有字符可读 then <br />
begin <br />
读入字符串； <br />
for ( 所有其他的有效连接) <br />
begin <br />
将该字符串发送给该连接; <br />
end; <br />
end; <br />
end; <br />
end. <br />
由于判断是否有新连接, 是否可读都是非阻塞的, 因此每次判断,不管有还是没有, 都会马上返回. 这样,任何一个 client 向 server 发送字符或者试图建立新连接, 都不会对其他 client 的活动造成影响。 <br />
对 client 而言, 建立连接之后, 只需要处理两个文件描述符, 一个是建立了连接的 socket 描述符, 另一个是标准输入. 和
server 一样, 如果使用阻塞方式的话, 很容易因为其中一个暂时没有输入而影响另外一个的读入.. 因此将它们都变成非阻塞的,
然后client 进行如下动作: <br />
while ( 不想退出) <br />
begin <br />
if ( 与 server 的连接有字符可读) <br />
begin <br />
从该连接读入, 并输出到标准输出上去. <br />
End; <br />
if ( 标准输入可读) <br />
Begin <br />
从标准输入读入, 并输出到与 server 的连接中去. <br />
End; <br />
End. <br />
上面的读写分别调用这样两个函数: <br />
read( userfd[i], line, MAX_LINE); <br />
// userfd[i] 是指第 i 个 client 连接的文件描述符. <br />
// line 是指读出的字符存放的位置. <br />
// MAX_LINE 是一次最多读出的字符数. <br />
// 返回值是实际读出的字符数. <br />
write( userfd[j], line, strlen( line)); <br />
// userfd[j] 是第 j 个 client 的文件描述符. <br />
// line 是要发送的字符串. <br />
// strlen( line) 是要发送的字符串长度. <br />
分析上面的程序可以知道, 不管是 server 还是 client, 它们都不停的轮流查询各个文件描述符, 一旦可读就读入并进行处理.
这样的程序, 不停的在执行, 只要有CPU 资源, 就不会放过。因此对系统资源的消耗非常大。server 或者 client 单独执行时,
CPU 资源的 98% 左右都被其占用。极大的消耗了系统资源。 <br />
select 方法 <br />
因此，虽然我们不希望在某一个用户没有反应时阻塞其他的用户，但我们却应该在没有任何用户有反应的情况之下停止程序的运行，让出抢占的系统资源，进入阻塞状态。有没有这种方法呢？现在的UNIX系统中都提供了select方法，具体实现方式如下： <br />
select 方法中, 所有文件描述符都是阻塞的. 使用 select 判断一组文件描述符中是否有一个可读(写), 如果没有就阻塞, 直到有一个的时候就被唤醒. 我们先看比较简单的 client 的实现: <br />
由于 client 只需要处理两个文件描述符, 因此, 需要判断是否有可读写的文件描述符只需要加入两项: <br />
FD_ZERO( sockset); <br />
// 将 sockset 清空 <br />
FD_SET( sockfd, sockset); <br />
// 把 sockfd 加入到 sockset 集合中 <br />
FD_SET( 0, sockset); <br />
// 把 0 (标准输入) 加入到 sockset 集合中 <br />
然后 client 的处理如下: <br />
while ( 不想退出) { <br />
select( sockfd+1, &amp;sockset, NULL, NULL, NULL); <br />
// 此时该函数将阻塞直到标准输入或者 sockfd 中有一个可读为止 <br />
// 第一个参数是 0 和 sockfd 中的最大值加一 <br />
// 第二个参数是 读集, 也就是 sockset <br />
// 第三, 四个参数是写集和异常集, 在本程序中都为空 <br />
// 第五个参数是超时时间, 即在指定时间内仍没有可读, 则出错 <br />
// 并返回. 当这个参数为NULL 时, 超时时间被设置为无限长. <br />
// 当 select 因为可读返回时, sockset 中包含的只是可读的 <br />
// 那些文件描述符. <br />
if ( FD_ISSET( sockfd, &amp;sockset)) { <br />
// FD_ISSET 这个宏判断 sockfd 是否属于可读的文件描述符 <br />
从 sockfd 中读入, 输出到标准输出上去. <br />
} <br />
if ( FD_ISSET( 0, &amp;sockset)) { <br />
// FD_ISSET 这个宏判断 sockfd 是否属于可读的文件描述符 <br />
从标准输入读入, 输出到 sockfd 中去. <br />
} <br />
重新设置 sockset. (即将 sockset 清空, 并将 sockfd 和 0 加入) <br />
} <br />
下面看 server 的情况: <br />
设置 sockset 如下: <br />
FD_ZERO( sockset); <br />
FD_SET( sockfd, sockset); <br />
for ( 所有有效连接) <br />
FD_SET( userfd[i], sockset); <br />
} <br />
maxfd = 最大的文件描述符号 + 1; <br />
server 处理如下: <br />
while ( 1) { <br />
select( maxfd, &amp;sockset, NULL, NULL, NULL); <br />
if ( FD_ISSET( sockfd, &amp;sockset)) { <br />
// 有新连接 <br />
建立新连接, 并将该连接描述符加入到 sockset 中去了. <br />
} <br />
for ( 所有有效连接) { <br />
if ( FD_ISSET ( userfd[i], &amp;sockset)) { <br />
// 该连接中有字符可读 <br />
从该连接中读入字符, 并发送到其他有效连接中去. <br />
} <br />
} <br />
重新设置 sockset; <br />
} <br />
性能比较 <br />
由于采用 select 机制, 因此当没有字符可读时, 程序处于阻塞状态,最小程度的占用CPU 资源, 在同一台机器上执行一个 server
和若干个client 时, 系统负载只有 0.1 左右, 而采用原来的非阻塞通信方法, 只运行一个 server, 系统负载就可以达到 1.5
左右. 因此我们推荐使用 select. </font>
<img src ="http://www.cppblog.com/lmlf001/aggbug/6368.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/lmlf001/" target="_blank">芥之舟</a> 2006-04-27 11:51 <a href="http://www.cppblog.com/lmlf001/archive/2006/04/27/6368.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Linux环境下的Socket编程（ZZ）</title><link>http://www.cppblog.com/lmlf001/archive/2006/04/27/6367.html</link><dc:creator>芥之舟</dc:creator><author>芥之舟</author><pubDate>Thu, 27 Apr 2006 03:46:00 GMT</pubDate><guid>http://www.cppblog.com/lmlf001/archive/2006/04/27/6367.html</guid><wfw:comment>http://www.cppblog.com/lmlf001/comments/6367.html</wfw:comment><comments>http://www.cppblog.com/lmlf001/archive/2006/04/27/6367.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/lmlf001/comments/commentRss/6367.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/lmlf001/services/trackbacks/6367.html</trackback:ping><description><![CDATA[
		<span class="title">Linux环境下的Socket编程</span>
		<br />
		<br />
		<br />
		<!---->
		<br />
		<table style="table-layout: fixed;" align="center" bgcolor="#ffffff" cellpadding="5" cellspacing="0">
				<tbody>
						<tr>
								<td valign="top">
										<span class="pg" id="xydwtext"> 什么是Socket <br />　　Socket接口是TCP/IP网络的API，Socket接口定义了许多函数或例程，程序员可以用它们来开发TCP/IP网络上的应用程序。要学Internet上的TCP/IP网络编程，必须理解Socket接口。 <br />　
　Socket接口设计者最先是将接口放在Unix操作系统里面的。如果了解Unix系统的输入和输出的话，就很容易了解Socket了。网络的
Socket数据传输是一种特殊的I/O，Socket也是一种文件描述符。Socket也具有一个类似于打开文件的函数调用Socket()，该函数返
回一个整型的Socket描述符，随后的连接建立、数据传输等操作都是通过该Socket实现的。常用的Socket类型有两种：流式Socket
（SOCK_STREAM）和数据报式Socket（SOCK_DGRAM）。流式是一种面向连接的Socket，针对于面向连接的TCP服务应用；数据
报式Socket是一种无连接的Socket，对应于无连接的UDP服务应用。 <br /><br />Socket建立 <br />　　为了建立Socket，程序可以调用Socket函数，该函数返回一个类似于文件描述符的句柄。socket函数原型为： <br />　　int socket(int domain, int type, int protocol); <br />　
　domain指明所使用的协议族，通常为PF_INET，表示互联网协议族（TCP/IP协议族）；type参数指定socket的类型：
SOCK_STREAM
或SOCK_DGRAM，Socket接口还定义了原始Socket（SOCK_RAW），允许程序使用低层协议；protocol通常赋值"0"。
Socket()调用返回一个整型socket描述符，你可以在后面的调用使用它。 <br />　　Socket描述符是一个指向内部数据结构的指针，它指向描述符表入口。调用Socket函数时，socket执行体将建立一个Socket，实际上"建立一个Socket"意味着为一个Socket数据结构分配存储空间。Socket执行体为你管理描述符表。 <br />　　两个网络程序之间的一个网络连接包括五种信息：通信协议、本地协议地址、本地主机端口、远端主机地址和远端协议端口。Socket数据结构中包含这五种信息。 <br /><br />Socket配置 <br />　
　通过socket调用返回一个socket描述符后，在使用socket进行网络传输以前，必须配置该socket。面向连接的socket客户端通过
调用Connect函数在socket数据结构中保存本地和远端信息。无连接socket的客户端和服务端以及面向连接socket的服务端通过调用
bind函数来配置本地信息。 <br />Bind函数将socket与本机上的一个端口相关联，随后你就可以在该端口监听服务请求。Bind函数原型为： <br />　　int bind(int sockfd,struct sockaddr *my_addr, int addrlen); <br />　　Sockfd是调用socket函数返回的socket描述符,my_addr是一个指向包含有本机IP地址及端口号等信息的sockaddr类型的指针；addrlen常被设置为sizeof(struct sockaddr)。 <br />　　struct sockaddr结构类型是用来保存socket信息的： <br />　　struct sockaddr { <br />　　 unsigned short sa_family; /* 地址族， AF_xxx */ <br />char sa_data[14]; /* 14 字节的协议地址 */ <br />}; <br />　　sa_family一般为AF_INET，代表Internet（TCP/IP）地址族；sa_data则包含该socket的IP地址和端口号。 <br />　　另外还有一种结构类型： <br />　　struct sockaddr_in { <br />　　 short int sin_family; /* 地址族 */ <br />　　 unsigned short int sin_port; /* 端口号 */ <br />　　 struct in_addr sin_addr; /* IP地址 */ <br />　　 unsigned char sin_zero[8]; /* 填充0 以保持与struct sockaddr同样大小 */ <br />　　}; <br />　
　这个结构更方便使用。sin_zero用来将sockaddr_in结构填充到与struct
sockaddr同样的长度，可以用bzero()或memset()函数将其置为零。指向sockaddr_in
的指针和指向sockaddr的指针可以相互转换，这意味着如果一个函数所需参数类型是sockaddr时，你可以在函数调用的时候将一个指向
sockaddr_in的指针转换为指向sockaddr的指针；或者相反。 <br />　　使用bind函数时，可以用下面的赋值实现自动获得本机IP地址和随机获取一个没有被占用的端口号： <br />　　my_addr.sin_port = 0; /* 系统随机选择一个未被使用的端口号 */ <br />　　my_addr.sin_addr.s_addr = INADDR_ANY; /* 填入本机IP地址 */ <br />通过将my_addr.sin_port置为0，函数会自动为你选择一个未占用的端口来使用。同样，通过将my_addr.sin_addr.s_addr置为INADDR_ANY，系统会自动填入本机IP地址。 <br />注意在使用bind函数是需要将sin_port和sin_addr转换成为网络字节优先顺序；而sin_addr则不需要转换。 <br />　　计算机数据存储有两种字节优先顺序：高位字节优先和低位字节优先。Internet上数据以高位字节优先顺序在网络上传输，所以对于在内部是以低位字节优先方式存储数据的机器，在Internet上传输数据时就需要进行转换，否则就会出现数据不一致。 <br />　　下面是几个字节顺序转换函数： <br />·htonl()：把32位值从主机字节序转换成网络字节序 <br />·htons()：把16位值从主机字节序转换成网络字节序 <br />·ntohl()：把32位值从网络字节序转换成主机字节序 <br />·ntohs()：把16位值从网络字节序转换成主机字节序 <br />　　Bind()函数在成功被调用时返回0；出现错误时返回"-1"并将errno置为相应的错误号。需要注意的是，在调用bind函数时一般不要将端口号置为小于1024的值，因为1到1024是保留端口号，你可以选择大于1024中的任何一个没有被占用的端口号。 <br /><br />连接建立 <br />　　面向连接的客户程序使用Connect函数来配置socket并与远端服务器建立一个TCP连接，其函数原型为： <br />　　int connect(int sockfd, struct sockaddr *serv_addr,int addrlen); <br />Sockfd
是socket函数返回的socket描述符；serv_addr是包含远端主机IP地址和端口号的指针；addrlen是远端地质结构的长度。
Connect函数在出现错误时返回-1，并且设置errno为相应的错误码。进行客户端程序设计无须调用bind()，因为这种情况下只需知道目的机器
的IP地址，而客户通过哪个端口与服务器建立连接并不需要关心，socket执行体为你的程序自动选择一个未被占用的端口，并通知你的程序数据什么时候到
打断口。 <br />　　Connect函数启动和远端主机的直接连接。只有面向连接的客户程序使用socket时才需要将此socket与远端主机相连。无连接协议从不建立直接连接。面向连接的服务器也从不启动一个连接，它只是被动的在协议端口监听客户的请求。 <br />　　Listen函数使socket处于被动的监听模式，并为该socket建立一个输入数据队列，将到达的服务请求保存在此队列中，直到程序处理它们。 <br />　　int listen(int sockfd， int backlog); <br />Sockfd
是Socket系统调用返回的socket
描述符；backlog指定在请求队列中允许的最大请求数，进入的连接请求将在队列中等待accept()它们（参考下文）。Backlog对队列中等待
服务的请求的数目进行了限制，大多数系统缺省值为20。如果一个服务请求到来时，输入队列已满，该socket将拒绝连接请求，客户将收到一个出错信息。
<br />当出现错误时listen函数返回-1，并置相应的errno错误码。 <br />　　accept()函数让服务器接收客户的连接请求。在建立好输入队列后，服务器就调用accept函数，然后睡眠并等待客户的连接请求。 <br />　　int accept(int sockfd, void *addr, int *addrlen); <br />　
　sockfd是被监听的socket描述符，addr通常是一个指向sockaddr_in变量的指针，该变量用来存放提出连接请求服务的主机的信息
（某台主机从某个端口发出该请求）；addrten通常为一个指向值为sizeof(struct
sockaddr_in)的整型指针变量。出现错误时accept函数返回-1并置相应的errno值。 <br />　　首先，当accept函数监视的
socket收到连接请求时，socket执行体将建立一个新的socket，执行体将这个新socket和请求连接进程的地址联系起来，收到服务请求的
初始socket仍可以继续在以前的 socket上监听，同时可以在新的socket描述符上进行数据传输操作。 <br /><br />数据传输 <br />　　Send()和recv()这两个函数用于面向连接的socket上进行数据传输。 <br />　　Send()函数原型为： <br />　　int send(int sockfd, const void *msg, int len, int flags); <br />Sockfd是你想用来传输数据的socket描述符；msg是一个指向要发送数据的指针；Len是以字节为单位的数据的长度；flags一般情况下置为0（关于该参数的用法可参照man手册）。 <br />　　Send()函数返回实际上发送出的字节数，可能会少于你希望发送的数据。在程序中应该将send()的返回值与欲发送的字节数进行比较。当send()返回值与len不匹配时，应该对这种情况进行处理。 <br />char *msg = "Hello!"; <br />int len, bytes_sent; <br />…… <br />len = strlen(msg); <br />bytes_sent = send(sockfd, msg,len,0); <br />…… <br />　　recv()函数原型为： <br />　　int recv(int sockfd,void *buf,int len,unsigned int flags); <br />　　Sockfd是接受数据的socket描述符；buf 是存放接收数据的缓冲区；len是缓冲的长度。Flags也被置为0。Recv()返回实际上接收的字节数，当出现错误时，返回-1并置相应的errno值。 <br />Sendto()和recvfrom()用于在无连接的数据报socket方式下进行数据传输。由于本地socket并没有与远端机器建立连接，所以在发送数据时应指明目的地址。 <br />sendto()函数原型为： <br />　　int sendto(int sockfd, const void *msg,int len,unsigned int flags,const struct sockaddr *to, int tolen); <br />　　该函数比send()函数多了两个参数，to表示目地机的IP地址和端口号信息，而tolen常常被赋值为sizeof (struct sockaddr)。Sendto 函数也返回实际发送的数据字节长度或在出现发送错误时返回-1。 <br />　　Recvfrom()函数原型为： <br />　　int recvfrom(int sockfd,void *buf,int len,unsigned int flags,struct sockaddr *from,int *fromlen); <br />　
　from是一个struct sockaddr类型的变量，该变量保存源机的IP地址及端口号。fromlen常置为sizeof (struct
sockaddr)。当recvfrom()返回时，fromlen包含实际存入from中的数据字节数。Recvfrom()函数返回接收到的字节数或
当出现错误时返回-1，并置相应的errno。 <br />如果你对数据报socket调用了connect()函数时，你也可以利用send()和recv()进行数据传输，但该socket仍然是数据报socket，并且利用传输层的UDP服务。但在发送或接收数据报时，内核会自动为之加上目地和源地址信息。 <br /><br />结束传输 <br />　　当所有的数据操作结束以后，你可以调用close()函数来释放该socket，从而停止在该socket上的任何数据操作： <br />close(sockfd); <br />　　你也可以调用shutdown()函数来关闭该socket。该函数允许你只停止在某个方向上的数据传输，而一个方向上的数据传输继续进行。如你可以关闭某socket的写操作而允许继续在该socket上接受数据，直至读入所有数据。 <br />　　int shutdown(int sockfd,int how); <br />　　Sockfd是需要关闭的socket的描述符。参数 how允许为shutdown操作选择以下几种方式： <br />　　·0-------不允许继续接收数据 <br />　　·1-------不允许继续发送数据 <br />·2-------不允许继续发送和接收数据， <br />·均为允许则调用close () <br />　　shutdown在操作成功时返回0，在出现错误时返回-1并置相应errno。 <br /><br />面向连接的Socket实例 <br />　　代码实例中的服务器通过socket连接向客户端发送字符串"Hello, you are connected!"。只要在服务器上运行该服务器软件，在客户端运行客户软件，客户端就会收到该字符串。 <br />　　该服务器软件代码如下： <br />#include &lt;stdio.h&gt; <br />#include &lt;stdlib.h&gt; <br />#include &lt;errno.h&gt; <br />#include &lt;string.h&gt; <br />#include &lt;sys/types.h&gt; <br />#include &lt;netinet/in.h&gt; <br />#include &lt;sys/socket.h&gt; <br />#include &lt;sys/wait.h&gt; <br />#define SERVPORT 3333 /*服务器监听端口号 */ <br />#define BACKLOG 10 /* 最大同时连接请求数 */ <br />main() <br />{ <br />int sockfd,client_fd; /*sock_fd：监听socket；client_fd：数据传输socket */ <br />　struct sockaddr_in my_addr; /* 本机地址信息 */ <br />　struct sockaddr_in remote_addr; /* 客户端地址信息 */ <br />if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { <br />　　perror("socket创建出错！"); exit(1); <br />} <br />my_addr.sin_family=AF_INET; <br />　my_addr.sin_port=htons(SERVPORT); <br />　my_addr.sin_addr.s_addr = INADDR_ANY; <br />bzero(&amp;(my_addr.sin_zero),8); <br />　if (bind(sockfd, (struct sockaddr *)&amp;my_addr, sizeof(struct sockaddr)) \ <br />　　 == -1) { <br />perror("bind出错！"); <br />exit(1); <br />} <br />　if (listen(sockfd, BACKLOG) == -1) { <br />perror("listen出错！"); <br />exit(1); <br />} <br />while(1) { <br />　　sin_size = sizeof(struct sockaddr_in); <br />　　if ((client_fd = accept(sockfd, (struct sockaddr *)&amp;remote_addr, \ <br />　　&amp;sin_size)) == -1) { <br />perror("accept出错"); <br />continue; <br />} <br />　　printf("received a connection from %s\n", inet_ntoa(remote_addr.sin_addr)); <br />　 if (!fork()) { /* 子进程代码段 */ <br />　　 if (send(client_fd, "Hello, you are connected!\n", 26, 0) == -1) <br />　　 perror("send出错！"); <br />close(client_fd); <br />exit(0); <br />} <br />　　close(client_fd); <br />　　} <br />　} <br />} <br />　
　服务器的工作流程是这样的：首先调用socket函数创建一个Socket，然后调用bind函数将其与本机地址以及一个本地端口号绑定，然后调用
listen在相应的socket上监听，当accpet接收到一个连接服务请求时，将生成一个新的socket。服务器显示该客户机的IP地址，并通过
新的socket向客户端发送字符串"Hello，you are connected!"。最后关闭该socket。 <br />　　代码实例中的fork()函数生成一个子进程来处理数据传输部分，fork()语句对于子进程返回的值为0。所以包含fork函数的if语句是子进程代码部分，它与if语句后面的父进程代码部分是并发执行的。 <br /><br />客户端程序代码如下： <br />#include&lt;stdio.h&gt; <br />#include &lt;stdlib.h&gt; <br />#include &lt;errno.h&gt; <br />#include &lt;string.h&gt; <br />#include &lt;netdb.h&gt; <br />#include &lt;sys/types.h&gt; <br />#include &lt;netinet/in.h&gt; <br />#include &lt;sys/socket.h&gt; <br />#define SERVPORT 3333 <br />#define MAXDATASIZE 100 /*每次最大数据传输量 */ <br />main(int argc, char *argv[]){ <br />　int sockfd, recvbytes; <br />　char buf[MAXDATASIZE]; <br />　struct hostent *host; <br />　struct sockaddr_in serv_addr; <br />　if (argc &lt; 2) { <br />fprintf(stderr,"Please enter the server's hostname!\n"); <br />exit(1); <br />} <br />　if((host=gethostbyname(argv[1]))==NULL) { <br />herror("gethostbyname出错！"); <br />exit(1); <br />} <br />　if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1){ <br />perror("socket创建出错！"); <br />exit(1); <br />} <br />　serv_addr.sin_family=AF_INET; <br />　serv_addr.sin_port=htons(SERVPORT); <br />　serv_addr.sin_addr = *((struct in_addr *)host-&gt;h_addr); <br />　bzero(&amp;(serv_addr.sin_zero),8); <br />　if (connect(sockfd, (struct sockaddr *)&amp;serv_addr, \ <br />　　 sizeof(struct sockaddr)) == -1) { <br />perror("connect出错！"); <br />exit(1); <br />} <br />　if ((recvbytes=recv(sockfd, buf, MAXDATASIZE, 0)) ==-1) { <br />perror("recv出错！"); <br />exit(1); <br />} <br />　buf[recvbytes] = '\0'; <br />　printf("Received: %s",buf); <br />　close(sockfd); <br />} <br />　　客户端程序首先通过服务器域名获得服务器的IP地址，然后创建一个socket，调用connect函数与服务器建立连接，连接成功之后接收从服务器发送过来的数据，最后关闭socket。 <br />　　函数gethostbyname()是完成域名转换的。由于IP地址难以记忆和读写，所以为了方便，人们常常用域名来表示主机，这就需要进行域名和IP地址的转换。函数原型为： <br />　　struct hostent *gethostbyname(const char *name); <br />　　函数返回为hosten的结构类型，它的定义如下： <br />　　struct hostent { <br />　 char *h_name; /* 主机的官方域名 */ <br />　　 char **h_aliases; /* 一个以NULL结尾的主机别名数组 */ <br />　　 int h_addrtype; /* 返回的地址类型，在Internet环境下为AF-INET */ <br />　　int h_length; /* 地址的字节长度 */ <br />　　 char **h_addr_list; /* 一个以0结尾的数组，包含该主机的所有地址*/ <br />　　}; <br />　　#define h_addr h_addr_list[0] /*在h-addr-list中的第一个地址*/ <br />　　当 gethostname()调用成功时，返回指向struct hosten的指针，当调用失败时返回-1。当调用gethostbyname时，你不能使用perror()函数来输出错误信息，而应该使用herror()函数来输出。 <br /><br />　　无连接的客户/服务器程序的在原理上和连接的客户/服务器是一样的，两者的区别在于无连接的客户/服务器中的客户一般不需要建立连接，而且在发送接收数据时，需要指定远端机的地址。 <br /><br />阻塞和非阻塞 <br />　
　阻塞函数在完成其指定的任务以前不允许程序调用另一个函数。例如，程序执行一个读数据的函数调用时，在此函数完成读操作以前将不会执行下一程序语句。当
服务器运行到accept语句时，而没有客户连接服务请求到来，服务器就会停止在accept语句上等待连接服务请求的到来。这种情况称为阻塞
（blocking）。而非阻塞操作则可以立即完成。比如，如果你希望服务器仅仅注意检查是否有客户在等待连接，有就接受连接，否则就继续做其他事情，则
可以通过将Socket设置为非阻塞方式来实现。非阻塞socket在没有客户在等待时就使accept调用立即返回。 <br />　　#include &lt;unistd.h&gt; <br />　　#include &lt;fcntl.h&gt; <br />　　…… <br />sockfd = socket(AF_INET,SOCK_STREAM,0); <br />fcntl(sockfd,F_SETFL,O_NONBLOCK)； <br />…… <br />　
　通过设置socket为非阻塞方式，可以实现"轮询"若干Socket。当企图从一个没有数据等待处理的非阻塞Socket读入数据时，函数将立即返
回，返回值为-1，并置errno值为EWOULDBLOCK。但是这种"轮询"会使CPU处于忙等待方式，从而降低性能，浪费系统资源。而调用
select()会有效地解决这个问题，它允许你把进程本身挂起来，而同时使系统内核监听所要求的一组文件描述符的任何活动，只要确认在任何被监控的文件
描述符上出现活动，select()调用将返回指示该文件描述符已准备好的信息，从而实现了为进程选出随机的变化，而不必由进程本身对输入进行测试而浪费
CPU开销。Select函数原型为: <br />int select(int numfds,fd_set *readfds,fd_set *writefds， <br />fd_set *exceptfds,struct timeval *timeout); <br />　
　其中readfds、writefds、exceptfds分别是被select()监视的读、写和异常处理的文件描述符集合。如果你希望确定是否可以
从标准输入和某个socket描述符读取数据，你只需要将标准输入的文件描述符0和相应的sockdtfd加入到readfds集合中；numfds的值
是需要检查的号码最高的文件描述符加1，这个例子中numfds的值应为sockfd+1；当select返回时，readfds将被修改，指示某个文件
描述符已经准备被读取，你可以通过FD_ISSSET()来测试。为了实现fd_set中对应的文件描述符的设置、复位和测试，它提供了一组宏： <br />　　FD_ZERO(fd_set *set)----清除一个文件描述符集； <br />　　FD_SET(int fd,fd_set *set)----将一个文件描述符加入文件描述符集中； <br />　　FD_CLR(int fd,fd_set *set)----将一个文件描述符从文件描述符集中清除； <br />　　FD_ISSET(int fd,fd_set *set)----试判断是否文件描述符被置位。 <br />　　Timeout参数是一个指向struct timeval类型的指针，它可以使select()在等待timeout长时间后没有文件描述符准备好即返回。struct timeval数据结构为： <br />　　struct timeval { <br />　　 int tv_sec; /* seconds */ <br />　　 int tv_usec; /* microseconds */ <br />}; <br /><br />POP3客户端实例 <br />　　下面的代码实例基于POP3的客户协议，与邮件服务器连接并取回指定用户帐号的邮件。与邮件服务器交互的命令存储在字符串数组POPMessage中，程序通过一个do-while循环依次发送这些命令。 <br />#include&lt;stdio.h&gt; <br />#include &lt;stdlib.h&gt; <br />#include &lt;errno.h&gt; <br />#include &lt;string.h&gt; <br />#include &lt;netdb.h&gt; <br />#include &lt;sys/types.h&gt; <br />#include &lt;netinet/in.h&gt; <br />#include &lt;sys/socket.h&gt; <br />#define POP3SERVPORT 110 <br />#define MAXDATASIZE 4096 <br /><br />main(int argc, char *argv[]){ <br />int sockfd; <br />struct hostent *host; <br />struct sockaddr_in serv_addr; <br />char *POPMessage[]={ <br />"USER userid\r\n", <br />"PASS password\r\n", <br />"STAT\r\n", <br />"LIST\r\n", <br />"RETR 1\r\n", <br />"DELE 1\r\n", <br />"QUIT\r\n", <br />NULL <br />}; <br />int iLength; <br />int iMsg=0; <br />int iEnd=0; <br />char buf[MAXDATASIZE]; <br /><br />if((host=gethostbyname("your.server"))==NULL) { <br />perror("gethostbyname error"); <br />exit(1); <br />} <br />if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1){ <br />perror("socket error"); <br />exit(1); <br />} <br />serv_addr.sin_family=AF_INET; <br />serv_addr.sin_port=htons(POP3SERVPORT); <br />serv_addr.sin_addr = *((struct in_addr *)host-&gt;h_addr); <br />bzero(&amp;(serv_addr.sin_zero),8); <br />if (connect(sockfd, (struct sockaddr *)&amp;serv_addr,sizeof(struct sockaddr))==-1){ <br />perror("connect error"); <br />exit(1); <br />} <br /><br />do { <br />send(sockfd,POPMessage[iMsg],strlen(POPMessage[iMsg]),0); <br />printf("have sent: %s",POPMessage[iMsg]); <br /><br />iLength=recv(sockfd,buf+iEnd,sizeof(buf)-iEnd,0); <br />iEnd+=iLength; <br />buf[iEnd]='\0'; <br />printf("received: %s,%d\n",buf,iMsg); <br /><br />iMsg++; <br />} while (POPMessage[iMsg]); <br /><br />close(sockfd); <br />}</span>
								</td>
						</tr>
						<!---->
						<tr>
								<td>
										<hr />
								</td>
						</tr>
				</tbody>
		</table>
<img src ="http://www.cppblog.com/lmlf001/aggbug/6367.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/lmlf001/" target="_blank">芥之舟</a> 2006-04-27 11:46 <a href="http://www.cppblog.com/lmlf001/archive/2006/04/27/6367.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>