⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 proxy源代码分析 谈linux网络编程技术(1)_梦里香巴拉的空间.htm

📁 一个经典的网络代理程序和讲解
💻 HTM
📖 第 1 页 / 共 5 页
字号:
      pad<BR>-----------------------------------------------------------------<BR>  其中sin_zero成员并未使用,它是为了和通用套接字地址struct 
      sockaddr兼容而特意引入的。在编程时,一般都通过bzero()或是memset()将其置零。其他成员的设置一般是这样的:<BR>   servaddr.sin_family 
      = AF_INET;<BR>  表示套接字使用TCP/IP协议族。<BR>   <FONT 
      color=#0000ff>servaddr.sin_addr.s_addr = 
      htonl(INADDR_ANY);<BR></FONT>  设置服务器套接字的IP地址为特殊值INADDR_ANY,这表示服务器愿意接收来自任何网络设备接口的客户机连接。<FONT 
      color=#0000ff>htonl()函数的意思是将主机顺序的字节转换成网络顺序的字节。<BR></FONT>   servaddr.sin_port 
      = htons(PORT);<BR>  设置通信端口号,PORT应该是我们已经定义好的。在本例<FONT 
      color=#0000ff>中servaddr.sin_port = proxy_port;</FONT>这是表示端口号是函数的返回值</P>
      <P>proxy_port。<BR>  另外需要说明的一点是,在本例中,我们并没有看到在预编译部分中包含有&lt;linux/socket.h&gt;和&lt;linux/in.h&gt;这两个头文件,那是因为这两个头文件已经分别被包含在&lt;sys/types.h&gt;和&lt;sys/types.h&gt;中了,而且后面这两个头文件是与平台无关的,所以在网络通信中一般都使用这两个头文件。</P>
      <P><STRONG><FONT color=#ff0000>服务器公开地址</FONT></STRONG></P>
      <P>  如果服务器要接受客户机的连接请求,那么它必须先要在整个网络上公开自己的地址。在设置了服务器的套接字地址结构之后,</P>
      <P>可以通过调用函数<FONT 
      color=#0000ff>bind()绑定服务器的地址和套接字来完成公开地址的操作。</FONT>函数bind()的详细描述为:<BR>-----------------------------------------------------------------<BR>#include 
      &lt;sys/types.h&gt;<BR>#include &lt;sys/socket.h&gt;<BR>  <STRONG><FONT 
      color=#0000ff>int bind(int sockfd, struct sockaddr *addr, int 
      addrlen);<BR></FONT></STRONG>-----------------------------------------------------------------<BR>  参数sockfd是我们通过调用socket()创建的套接字描述符。参数addr是本机地址,参数addrlen是套接字地址结构的长度。函数执行成功时返回"0",否则返回"-1",并设置errno变量为EADDRINUAER。<BR>  如果是服务器调用bind()函数,如果设置了套接字的IP地址为某个本地IP地址,那么这表示服务器只接受来自于这个IP地址的特定主机发出的连接请求。不过一般情况下都是将IP地址设置为<FONT 
      color=#0000ff>INADDR_ANY,</FONT>以便接受所有网络设备接口送来的连接请求。<BR>  <FONT 
      color=#0000ff>客户机一般是不会调用bind()函数的</FONT>,因为客户机在连接时不用指定自己的套接字地址端口号,系统会自动为客户机选择一个未用端口号,并且用本地IP地址自动填充客户机套接字地址结构中的相应项。但是在某些特定的情况下客户机需要使用特定的端口号,例如Linux中的<FONT 
      color=#0000ff>rlogin命令</FONT>就要求使用保留端口号,而系统是不能为客户机自动分配保留端口号的,这就需要调用bind()来绑定一个保留端口号了。不过在一些特殊的环境下,这样绑定特定端口号也会带来一些负面影响,如在HTTP服务器进入TIME_WAIT状态后,客户机如果要求再次与服务器建立连接,则服务器会拒绝这一连接请求。如果客户机最后进入TIME_WAIT状态,则马上再次执行bind()函数时会返回出错信息"-1",原因是系统会认为同时有两次连接绑定同一个端口。</P>
      <P><STRONG><FONT color=#0000ff>转换Listening套接字</FONT></STRONG></P>
      <P>  接下来,服务器需要将我们刚才与<FONT 
      color=#0000ff>IP地址和端口号完成绑定的套接字转换成倾听listening套接</FONT>字。只有服务器程序才需要执行</P>
      <P>这一步操作。我们通过调用函数listen()实现这一操作。listen()的详细描述为:<BR>-----------------------------------------------------------------<BR>#include 
      &lt;sys/socket.h&gt;<BR>int listen(int sockfd, int 
      backlog);<BR>-----------------------------------------------------------------<BR>  参数sockfd指定我们要求转换的套接字描述符,参数backlog设置请求队列的最大长度。函数listen()主要完成以下操作。<BR>  首先是将套接字转换成倾听套接字。因为<FONT 
      color=#0000ff>函数socket()创建的套接字都是主动套接字,</FONT>所以客户机可以通过调用函<FONT 
      color=#0000ff>数connect()来</FONT>使用这样的套接字主动和服务器建立连接。而服务器的情况恰恰相反,服务器需要通过套接字接收客户机的连接请求,这就需要一个"被动"套接字。<FONT 
      color=#0000ff>listen()就可将一个尚未连接的主动套接字转换成为这样的"被动"套接字,也就是倾听套接字。</FONT>在执行了listen()函数之后,服务器的TCP就由CLOSED变成LISTEN状态了。<BR>另外listen()可以设置连接请求队列的最大长度。虽然参数backlog的用法非常简单,只是一个简单的整数。但搞清楚请求队列的含义对理解TCP协议的通信过程建立非常重要。TCP协议为每个倾听套接字实际上维护两个队列,一个是未完成连接队列,这个队列中的成员都是未完成3次握手的连接;另一个是完成连接队列,这个队列中的成员都是虽然已经完成了3次握手,但是还未被服务器调用accept()接收的连接。参数backlog实际上指定的是这个倾听套接字完成连接队列的最大长度。在本例中我们是这样用的:listen(sockfd,5);表示完成连接队列的最大长度为5。</P>
      <P><STRONG><FONT color=#0000ff>接收连接</FONT></STRONG></P>
      <P>  接下来我们在主程序中看到通过名为<FONT 
      color=#0000ff>daemonize()</FONT>的自定义函数创建一个守护进程,关于这个daemonize()以及守护进程的相关概念,我们等一会儿再做详细介绍。然后服务器程序进入一个无条件循环,用于监听接收客户机的连接请求。在此过程中如果有<FONT 
      color=#0000ff>客户机调用connect()请</FONT>求连接,那么函数accept()可以从倾听套接字的完成连接队列中接受一个连接请求。如果完成连接队列为空,这个</P>
      <P><FONT 
      color=#0000ff>进程就睡眠。</FONT>accept()的详细描述为:<BR>-----------------------------------------------------------------<BR>#include 
      &lt;sys/socket.h&gt;<BR>  int accept(int sockfd, struct sockaddr *addr, 
      int 
      *addrlen);<BR>-----------------------------------------------------------------<BR>  参数sockfd是我们转换成功的倾听套接字描述符;参数addr是一个指向套接字地址结构的指针,参数addrlen为一个整型指针。</P>
      <P>当函数成功执行时,返回3个结果,函数返回一个新的套接字描述符,服务器可以通过这个新的套接字描述符和客户机进行通信。参数addr所指向的套接字地址结构中将存放客户机的相关信息,addrlen指针将描述前述套接字地址结构的长度。在通常情况下服务器对这些信息不是很感兴趣,因此我们经常可以看到一些源代码中将accept()函数的后两个参数都设置为NULL。不过在这段<FONT 
      color=#0000ff>proxy源代</FONT></P>
      <P><FONT color=#0000ff>码中需要用到有关的客户机信息</FONT>,因此我们看到通过执行<BR>   <FONT 
      color=#0000ff><STRONG>newsockfd = accept(sockfd, (struct sockaddr_in *) 
      &amp;cliaddr, 
      &amp;clilen);<BR></STRONG></FONT>  将客户机的详细信息存放在地址结构cliaddr中。而proxy就通过套接字newsockfd与客户机进行通信。值得注意的是这个返回的套接字描述符与我们转换的倾听套接字是不同的。在一段服务器程序中,可以始终只用一个倾听套接字来接收多个客户机的连接请求;而如果我们要和客户机建立一个实际的连接的话,对每一个请求我们都需要调用accept()返回一个新的套接字。当服务器处理完毕客户机的请求后,一定要将相应的套接字关闭;如果整个服务器程序将要结束,那么一定要将倾听套接字关闭。<BR>  如果accept()函数执行失败,则返回"-1",如果accept()函数阻塞等待客户机调用connect()建立连接,进程在此时恰好捕捉到信号,那么函数在返回"-1"的同时将变量errno的值设置为EINTR。这和accept()函数执行失败是有区别的。因此我们在代码中可以看到这样的语句:<BR>-----------------------------------------------------------------<BR>if 
      (newsockfd &lt; 0 &amp;&amp; errno == EINTR) <BR>continue; <BR>/* a signal 
      might interrupt our accept() call */ <BR>else if (newsockfd &lt; 0) <BR>/* 
      something quite amiss -- kill the server */ <BR>errorout("failed to accept 
      connection");<BR>-----------------------------------------------------------------<BR>  可以看出程序在处理这两种情况时操作是完全不同的,同样是accept()返回"-1",如果有errno 
      == EINTR,那么系统将再次调用accept()接受连接请求,否则服务器进程将直接结束。</P>
      <P><STRONG><FONT color=#0000ff>处理客户机请求</FONT></STRONG></P>
      <P>  当服务器与客户机建立连接以后,就可以处理客户机的请求了。一般情况下服务器程序都要创建一个子进程用于处理客户机请求;而父进程则继续监听,时刻准备接受其它客户机的连接请求。我们这段proxy程序也不例外。它通过调用fork()创建处理客户机请求的子进程。我想在linux/Unix编程中,fork()的重要性不用我再多说什么了,在大型的服务器程序中,一般都要在子进程里,根据<FONT 
      color=#0000ff>客户机请求的不同而通过exec()系列函数调用不同的处理程序</FONT>,这也是在学习linux/Unix编程中一个非常重要的地方。不过我们这个proxy程序旨在讲述一些linux网络编程的基本概念,因此在子程序部分就直接调用了一个完成<FONT 
      color=#0000ff>proxy功能的函数</FONT><FONT 
      color=#ff0000><STRONG>do_proxy(),</STRONG></FONT>其实际参数newsockfd就是accept()返回的套接字描述符。另外值得注意的一点就是,因为子进程继承了所有父进程中可用的文件描述符,</P>
      <P>所以我们必须在子进程中关闭倾听套接字(代码中子进程部分的close(sockfd);),同时在父进程中关闭accept()返回的套接字描述符(例如代码中父进程部分的close(newsockfd);)。</P>
      <P>◆函数parse_args()</P>
      <P>此函数的定义是:void parse_args (int argc, char 
      **argv);<BR>-----------------------------------------------------------------<BR>/**************************************************************** 
      <BR>function:    parse_args <BR>description:  parse the command line args. 
      <BR>arguments:    argc,argv you know what these are. <BR>return value: 
       none. <BR>calls:      none. <BR>globals:     writes proxy_port, writes 
      hostaddr. 
      <BR>****************************************************************/ 
      <BR>void parse_args (argc,argv) <BR>int argc; <BR>char **argv; <BR>{ 
      <BR>  int i; <BR>  struct hostent *hostp; <BR>  struct servent *servp; 
      <BR>  unsigned long inaddr; <BR>  struct { <BR>    char proxy_port [16]; 
      <BR>    char isolated_host [64]; <BR>    char service_name [32]; <BR>  } 
      pargs; <BR>  if (argc &lt; 4) { <BR>     printf("usage: %s 
      &lt;proxy-port&gt; &lt;host&gt; &lt;service-name|port-number&gt;\r\n", 
      argv[0]); <BR>     exit(1); <BR>  } 
      <BR>  strcpy(pargs.proxy_port,argv[1]); 
      <BR>  strcpy(pargs.isolated_host,argv[2]); 
      <BR>  strcpy(pargs.service_name,argv[3]); <BR>  for (i = 0; i &lt; 
      strlen(pargs.proxy_port); i++) <BR>    if (!isdigit(*(pargs.proxy_port + 
      i))) <BR>      break; <BR>  if (i == strlen(pargs.proxy_port)) 
      <BR>    proxy_port = htons(atoi(pargs.proxy_port)); <BR>  else { 
      <BR>    printf("%s: invalid proxy port\r\n",pargs.proxy_port); 
      <BR>    exit(0); <BR>  } <BR>  bzero(&amp;hostaddr,sizeof(hostaddr)); 
      <BR>  hostaddr.sin_family = AF_INET; <BR>  if ((inaddr = 
      inet_addr(pargs.isolated_host)) != INADDR_NONE) 
      <BR>    bcopy(&amp;inaddr,&amp;hostaddr.sin_addr,sizeof(inaddr)); 
      <BR>  else if ((hostp = gethostbyname(pargs.isolated_host)) != NULL) 
      <BR>    bcopy(hostp-&gt;h_addr,&amp;hostaddr.sin_addr,hostp-&gt;h_length); 
      <BR>  else { <BR>    printf("%s: unknown host\r\n",pargs.isolated_host); 
      <BR>    exit(1); <BR>  } <BR>  if ((servp = 
      getservbyname(pargs.service_name,TCP_PROTO)) != NULL) 
      <BR>    hostaddr.sin_port = servp-&gt;s_port; <BR>  else if 
      (atoi(pargs.service_name) &gt; 0) <BR>    hostaddr.sin_port = 
      htons(atoi(pargs.service_name)); <BR>  else { <BR>    printf("%s: 
      invalid/unknown service name or port number\r\n", pargs.service_name); 
      <BR>    exit(1); <BR>  } 
      <BR>}<BR>-----------------------------------------------------------------<BR>  这个函数的作用是传递命令行参数。参数的传递是通过两个全局变量来实现的,这两个变量是int 
      proxy_port和struct sockaddr_in 
hostaddr。分别用于传递等待连接请求的proxy端口和被绑定的主机网络信息。</P>
      <P>检验命令行参数</P>
      <P>  在进行了局部变量定义以后,函数首先要检测命令行参数是否符合程序的要求,即在命令后紧跟代理服务器端口、远程主机名和服务端口号,如果不满足上述要求,则代理服务器程序结束。如果满足上述要求,则将命令行的这三个参数存储进我们自定义的pargs结构之中。注意pargs结构的三个成员都是以字符形式存放命令行参数信息的,后面我们需要调用函数将这些参数信息都转换成为数字形式的。</P>
      <P>传递参数</P>
      <P>  接下来就要将命令行的三个参数变换成合适的形式赋值给全局变量proxy_port和hostaddr,以供其它函数调用。首先传送代理服务器端口pargs.proxy_port,在这里程序调用了一个系统函数isdigit()检验用户输入的端口号是否有效。isdigit()的具体描述为:<BR>-----------------------------------------------------------------<BR>#include 
      &lt;ctype.h&gt;<BR>  int isdigit(int 
      c)<BR>-----------------------------------------------------------------<BR>  isdigit()函数用来检测参数"c"是否是数字1~9中间的一个,如果答案是肯定的,则返回非"0"值,反之,返回"0"。程序中采用了这样的方法来对用户的输入进行逐位检验:<BR>   if 
      (!isdigit(*(pargs.proxy_port + 
      i)))<BR>   break;<BR>  在将有效端口号传递给全局变量proxy_port之前,还要将其转换成<FONT 
      color=#0000ff>为网络字节顺序</FONT>。这是因为网络中存在着多个公司的不同设备,这些设备表示数据的字节顺序是不同的。例如在内存地址0x1000处存储一个16位的整数FF11,不同公司的机器在内存中的存储方式也不相同,有的将FF置于内存指针的起始位置0x1000,11置于0x1001,这称为big-endian顺序;有的却恰恰相反,即little-endian顺序。这种基于主机的数据存储顺序就称为主机字节顺序(host 
      byte 
      order)。为了在不同类型的主机之间进行通信,网络协议就规定了一种统一的网络字节顺序,这种顺序被规定为little-endian顺序。所以数据的网络字节顺序和主机字节顺序有可能是不同的,因此在编写通信程序时一定要注意不同顺序之间的转换。所以,程序中一定要有例程中这样的语句:<BR>   proxy_port 
      = 
      htons(atoi(pargs.proxy_port));<BR>  函数htons()的作用就是将主机字节顺序转换为网络字节顺序。它的具体描述为:<BR>-----------------------------------------------------------------<BR>#include 
      &lt;netinet/in.h&gt;<BR>unsigned short int htons(unsigned short int 
      data)<BR>-----------------------------------------------------------------<BR>  与htons()相似的函数还有三个,它们分别<STRONG><FONT 
      color=#ff0000>是htonl()、ntohs()和ntohl(),都</FONT></STRONG>用于网络与主机字节顺序之间的转换。如果这几个名字比较容易混淆的话,我们可以这样记忆:函数名中的h代表host,n代表network,s代表unsigned 
      short,l代表unsigned long。</P>
      <P>所以"hton"即为"host-to-network":变换<FONT 
      color=#ff0000>主机字节为网络字节</FONT>。接收数据的就要用到"ntoh"("network-to-host")函数了。<BR>  在我们的例程中,由于端口号一般情况下最多不会超过4位数字,所以选用unsigned 
      short型的htons()即可。<BR>注意在例程中htons()的参数是另一个函数<FONT 
      color=#0000ff>atoi()的返回结果</FONT>。atoi()函数的具体描述为:<BR>-----------------------------------------------------------------<BR>#include 
      &lt;stdlib.h&gt;<BR>int atoi(const char 
      *nptr)<BR>-----------------------------------------------------------------<BR>  它的作用是将字符指针<FONT 
      color=#0000ff>nptr指向的字符串转换成相应的整数</FONT>并将其作为结果返回。这个操作与函数调用strtol(nptr,(char 
      **)NULL,10)的效果几乎完全相同,唯一的区别是atoi()没有出错返回信息。之所以要调用这个函数是因为,系统在读取命令行的时候将所有的参数都作为字符串处理,所以我们必须将其转换为整数形式。接下来,例程先将全局变量hostaddr的所有成员清零,然后将成员hostaddr.sin_family设置为TCP/IP协议族标志AF_INET。下面就可将命令行的另外两个参数&lt;remote_host&gt;和&lt;service_port&gt;传递给全局变量hostaddr的两个成员hostaddr.sin_port和hostaddr.sin_addr了。这里我们用到了两个局部变量struct 
      hostent *hostp和struct servent *servp来传递参数信息。struct</P>
      <P>hostent的详细描述为:<BR>-----------------------------------------------------------------<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_addrlist[0]<BR>-----------------------------------------------------------------<BR>  hostent成员的含义是h_name代表主机在网络上的的正式名称,h_aliases是所有主机别名的列表,h_addrtype是指主机的地址类型,一般设置为TCP/IP协议族AF_INET,h_length是主机的地址长度,一般设置为4个字节。h_addr_list是主机的IP地址列表。<BR>  我们要用它来传递我们期望绑定的远程主机名或是IP地址。因为命令行中的主机名参数已经被存储进pargs.isolated_host,所以我们就调用inet_addr()函数对主机名或主机的IP地址进行二进制和字节顺序转换。inet_addr()函数的描述为:<BR>-----------------------------------------------------------------<BR>#include 
      &lt;sys/socket.h&gt;<BR>#include &lt;netinet/in.h&gt;<BR>#include 
      &lt;arpa/inet.h&gt;<BR>unsigned long int inet_addr(const char 
      *cp)<BR>-----------------------------------------------------------------<BR>  inet_addr()的作用就是将参数cp指向的Internet主机地址从数字/点的形式转换成二进制形式并同时转换为网络字节顺序,并将转换结果直接返回。如果cp指向的IP地址不可用,则函数返回INADDR_NONE或"-1"。<BR>  虽然Carl 
      Harris在编写这段程序时使用了这个inet_addr()函数,但是我还是建议大家在编写自己的程序时使用另外一个函数inet_aton()来完成这些功能。原因是inet_addr()在IP地址不可用时返回"-1",但我们想想,IP地址255.255.255.255绝对是一个有效地址,那么其二进制返回值也将是"-1",因此inet_addr()无法对这个IP地址进行处理。而函数inet_aton()则采用了一种更好的方法来返回出错信息,它的具体描述为:<BR>-----------------------------------------------------------------<BR>#include 
      &lt;sys/socket.h&gt;<BR>#include &lt;netinet/in.h&gt;<BR>#include 
      &lt;arpa/inet.h&gt;<BR>int inet_aton(const char *cp, struct in_addr 
      *inp)<BR>-----------------------------------------------------------------<BR>  函数执行成功时返回非零,转换结果存入指针inp指向的in_addr结构。这个结构定义我们在前面的文章里已经介绍过了。如果参数cp指向的IP地址不可用,则返回"0"。这就避免发生inet_addr()那样的问题。<BR>  如果说用户在命令行中键入的是远程主机的IP地址,那么只用inet_addr()就算完成任务了,但如果用户键入的是主机域名那该怎么办呢?所以我们在例程中可以看到这样的语句:<BR>-----------------------------------------------------------------<BR>if 
      ((inaddr = inet_addr(pargs.isolated_host)) != INADDR_NONE) 
      <BR>  bcopy(&amp;inaddr,&amp;hostaddr.sin_addr,sizeof(inaddr)); <BR>  else 
      if ((hostp = gethostbyname(pargs.isolated_host)) != NULL) 
      <BR>  bcopy(hostp-&gt;h_addr,&amp;hostaddr.sin_addr,hostp-&gt;h_length); 
      <BR>  else { <BR>  printf("%s: unknown host\r\n",pargs.isolated_host); 
      <BR>  exit(1); <BR>} 
      <BR>-----------------------------------------------------------------<BR>  其中gethostbyname()函数就是用来转换主机域名的。它的具体描述为:<BR>-----------------------------------------------------------------<BR>#include 
      &lt;netdb.h&gt;<BR>struct hostent *gethostbyname(const char 
      *hostname);<BR>-----------------------------------------------------------------<BR>  参数hostname指向我们需要转换的域名地址,函数直接返回转换结果,如果函数执行成功,则结果直接返回到一个指向hostent</P>
      <P>结构的指针中,否则返回空指针NULL。<BR>  例程就是这样调用inet_addr()和gethostbyname()将命令行参数中的主机域名或是主机IP地址传递给全局变量hostaddr的成员</P>
      <P>sin_addr以便代理执行函数do_proxy()调用。<BR>  下面是传递服务名或是服务端口号。这里要用到结构servent做传递中介,struct 
      servent的详细描述为:<BR>-----------------------------------------------------------------<BR>struct 
      servent {<BR>  char *s_name;<BR>  char **s_aliases;<BR>  int 
      s_port;<BR>  char 
      *s_proto;<BR>};<BR>-----------------------------------------------------------------<BR>  其各成员的含义是s_name为服务的正式名称,如ftp、http等,s_aliases是服务的别名列表,s_port是服务的端口号,例如在一</P>
      <P>般情况下ftp的端口号为21,http服务的端口号为80,注意此端口号应该存储为网络字节顺序,s_proto是应用协议的类型。<BR>  例程中使用getservbyname()函数转换命令行参数中的服务名,此函数的详细描述为:<BR>-----------------------------------------------------------------<BR>#include 
      &lt;netdb.h&gt;<BR>struct servent * getservbyname(const char *servname, 
      const char 
      *protoname);<BR>-----------------------------------------------------------------<BR>  它的作用就是转换指针servname指向的服务名为相应的整数表示的端口号,参数protoname表示服务使用的协议,例程中</P>
      <P>protoname 参数的值为TCP_PROTO,这表示使用TCP协议。函数成功时就返回一个struct 
      servent型的指针,其中的s_port成员就是我</P>
      <P>们关心的服务端口号。如果用户在命令中键入的是端口号而不是服务名,那么和处理代理端口信息一样,使用下面的语句进行处理:<BR>   hostaddr.sin_port 
      = 
      htons(atoi(pargs.service_name));<BR>  到这里,命令行的参数已经全部被转换成为网络通信所要求的字节顺序和数字类型,并且存储在三个全局变量中,就等着do_proxy()函数来调用了。</P></DIV></TD></TR></TBODY></TABLE><BR>
<DIV class=opt><A title=查看该分类中所有文章 
href="http://hi.baidu.com/&Atilde;&Icirc;&Agrave;&iuml;&Iuml;&atilde;°&Iacute;&Agrave;&shy;/blog/category/Linux">类别:Linux</A> | <A 
title=将此文章添加到百度搜藏 onclick="return addToFavor();" 
href="http://cang.baidu.com/do/add" target=_blank>添加到搜藏</A> | 浏览(<SPAN 
id=result></SPAN>) | <A 
href="http://hi.baidu.com/&Atilde;&Icirc;&Agrave;&iuml;&Iuml;&atilde;°&Iacute;&Agrave;&shy;/blog/item/6441782c84d571ed8a1399fb.html#send">评论</A>&nbsp;(0)
<SCRIPT language=javascript>
/*<![CDATA[*/
var pre = [true,'[转]Perl下的BSD SOCKET库', '[转]Perl下的BSD SOCKET库','/%C3%CE%C0%EF%CF%E3%B0%CD%C0%AD/blog/item/986634d5e8b00bc550da4b16.html'];
var post = [true,'[转]Proxy源代码分析 (2)','[转]Proxy源代码分析 (2)', '/%C3%CE%C0%EF%CF%E3%B0%CD%C0%AD/blog/item/60915c8ddd79e113b31bbac7.html'];
if(pre[0] || post[0]){
	document.write('<div style="height:5px;line-height:5px;">&nbsp;</div><div id="in_nav">');
	if(pre[0]){
		document.write('上一篇:<a href="' + pre[3] + '" title="' + pre[1] + '">' +  pre[2] + '</a>&nbsp;&nbsp;&nbsp;&nbsp;');
	}
	if(post[0]){
		document.write('下一篇:<a href="' + post[3] + '" title="' + post[1] + '">' +  post[2] + '</a>');
	}
	document.write('</div>');
}
/*]]>*/
</SCRIPT>
 </DIV>
<DIV class=line></DIV>
<STYLE type=text/css>#in_related_doc A {
	TEXT-DECORATION: none
}
</STYLE>

<DIV id=in_related_tmp></DIV>
<SCRIPT language=javascript type=text/javascript>
/*<![CDATA[*/
function HI_MOD_IN_RELATED_DOC_CALLBACK(arg){
    if(arg.length <= 1) return false;
    var hasMore = arg[0];
    var D=function(A,B){A[A.length]=B;}
    if(arg.length % 2 == 0) D(arg, ["","","",""]);

    var html = ['<div id="in_related_doc"><div class="tit">相关文章:</div>'];
    D(html, '<table cellpadding="0" cellspacing="3" border="0">');
    for(var i = 1, j = arg.length; i < j; i += 2){
        D(html, '<tr>');
        D(html, '<td width="15px"><a style="font-size:25px" >&#8226;</a></td><td><a href="http://hi.baidu.com/' + arg[i][3] + '/blog/item/' + arg[i][2] + '.html" target="_blank" title="' + arg[i][0] + '">' + arg[i][1] + '</a>');
        D(html, new Array(10).join('\u3000'));
        D(html, '</td>');
        if(arg[i + 1][0] != "")
            D(html, '<td width="15px"><a style="font-size:25px" >&#8226;</a></td><td><a href="http://hi.baidu.com/' + arg[i + 1][3] + '/blog/item/' + arg[i + 1][2] + '.html" target="_blank" title="' + arg[i + 1][0] + '">' + arg[i + 1][1] + '</a></td>');
        else
            D(html, '<td>&nbsp;</td><td>&nbsp;</td>');
        D(html, '</tr>');
    }
    if(hasMore) D(html, '<tr><td colspan="4"><a target="_blank" href="/sys/search?pageno=1&type=7&sort=1&word=%5B%D7%AA%5DProxy%D4%B4%B4%FA%C2%EB%B7%D6%CE%F6%20%CC%B8Linux%CD%F8%C2%E7%B1%E0%B3%CC%BC%BC%CA%F5%281%29&item=6441782c84d571ed8a1399fb">更多&gt;&gt;</a></td></tr>');
    D(html, '</table></div><div class="line">&nbsp;</div>');

    var div = document.getElementById('in_related_tmp');
    if(div){
        div.innerHTML = html.join('');
        while(div.firstChild){
            div.parentNode.insertBefore(div.firstChild, div);
        }
        div.parentNode.removeChild(div);
    }
	window.setTimeout("tracker_init('in_related_doc')",100);
}

if(RelatedDocData == -1){	// not supported xhr

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -