📄 proxy源代码分析 谈linux网络编程技术(1)_梦里香巴拉的空间.htm
字号:
#ft A:visited {
LETTER-SPACING: normal
}
</STYLE>
<DIV id=usrbar><NOBR><A id=hi_index href="http://hi.baidu.com/"
target=_blank>百度空间</A> | <A href="http://www.baidu.com/"
target=_blank>百度首页</A>
<SCRIPT type=text/javascript> document.write('| <a href="https://passport.baidu.com/?login&tpl=sp&tpl_reg=sp&u=http://hi.baidu.com' + encodeURIComponent('/%C3%CE%C0%EF%CF%E3%B0%CD%C0%AD/blog/item/6441782c84d571ed8a1399fb%2Ehtml') + '">登录</a>'); </SCRIPT>
</NOBR></DIV>
<DIV id=newUserTip
style="Z-INDEX: 30000; RIGHT: 0px; FLOAT: right; WIDTH: 225px; POSITION: absolute; TOP: 26px; HEIGHT: 130px; TEXT-ALIGN: right"><A
id=newUserTipShadow
style="DISPLAY: block; FONT-SIZE: 14px; Z-INDEX: 30001; RIGHT: 14px; BACKGROUND: none transparent scroll repeat 0% 0%; OVERFLOW: hidden; WIDTH: 55px; COLOR: #4242f9! important; POSITION: absolute; TOP: 108px; HEIGHT: 14px; TEXT-DECORATION: none! important"
onclick=newUserRegLog() href="http://hi.baidu.com/st/reg.html"
target=_blank> </A>
<EMBED id=newUserTipSwf style="WIDTH: 225px; HEIGHT: 130px"
src=http://hi.baidu.com/ui/flash/userReg/guide.swf
type=application/x-shockwave-flash wmode="transparent"
allowScriptAcess="always"> </DIV>
<SCRIPT language=javascript> <!-- function newUserTipShow(f) { if(f=="0"){//close G("newUserTipSwf").style.width="40px"; G("newUserTip").style.width="40px"; G("newUserTipShadow").style.display="none"; }else{//show G("newUserTip").style.width="225px"; G("newUserTipSwf").style.width="225px"; G("newUserTipShadow").style.display="block"; } } function newUserRegLog(){ var now=new Date(); now.setTime(now.getTime()+5*60*1000); document.cookie="BDSP_REGFLAG=1;expires="+now.toGMTString()+";path=/"; new Image().src="http://hi.baidu.com/sys/statlog/1.gif?m=blog_newer_pro2_click&v=/%C3%CE%C0%EF%CF%E3%B0%CD%C0%AD&t="+Math.random(); } //--> </SCRIPT>
<SCRIPT type=text/javascript>function set_cookie_4_bdtip(index/* start from one */, value){ var bdtip = document.cookie.match(/(^| )BDTIP=([^;]*)(;|$)/); if(!bdtip){ bdtip=new Array(index); for(var i=0,n=bdtip.length;i<n;i++) { if(bdtip[i]=="" || bdtip[i]==null) bdtip[i]=0; if(i == index - 1){ bdtip[i] = value; } } }else{ bdtip = bdtip[2].split('-'); if(index > bdtip.length) bdtip.length= index; for(var i = 0, j = bdtip.length; i < j; i ++){ if(bdtip[i]=="" || bdtip[i]==null) bdtip[i]=0; if(i == index - 1){ bdtip[i] = value; } } } bdtip = bdtip.join('-'); document.cookie = "BDTIP=" + bdtip+ ";expires=Wed, 28-Nov-37 01:45:46 GMT;path=/;";}</SCRIPT>
<DIV id=main align=left><!--[if IE]>
<SCRIPT>
var objmain = document.getElementById("main");
function updatesize(){ var bodyw = window.document.body.offsetWidth; if(bodyw <= 790) objmain.style.width="772px"; else if(bodyw >= 1016) objmain.style.width="996px"; else objmain.style.width="100%"; }
updatesize(); window.onresize = updatesize;
</SCRIPT>
<![endif]-->
<DIV id=header>
<DIV class=lc>
<DIV class=rc></DIV></DIV>
<DIV class=tit><A class=titlink title="梦里香巴拉的空间 http://hi.baidu.com/梦里香巴拉"
href="http://hi.baidu.com/ÃÎÀïÏã°ÍÀ­">梦里香巴拉的空间</A></DIV>
<DIV class=desc></DIV>
<DIV id=tabline></DIV>
<DIV id=tab><A href="http://hi.baidu.com/ÃÎÀïÏã°ÍÀ­">主页</A><A class=on
href="http://hi.baidu.com/ÃÎÀïÏã°ÍÀ­/blog">博客</A><A
href="http://hi.baidu.com/ÃÎÀïÏã°ÍÀ­/album">相册</A><SPAN>|</SPAN><A
href="http://hi.baidu.com/ÃÎÀïÏã°ÍÀ­/profile">个人档案</A> <SPAN>|</SPAN><A
href="http://hi.baidu.com/ÃÎÀïÏã°ÍÀ­/friends">好友</A> </DIV></DIV>
<DIV class=stage>
<DIV class=stagepad>
<DIV style="WIDTH: 100%">
<TABLE class=modth cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD class=modtl width=7> </TD>
<TD class=modtc noWrap>
<DIV class=modhead><SPAN class=modtit>查看文章</SPAN></DIV></TD>
<TD class=modtc noWrap align=right></TD>
<TD class=modtr width=7> </TD></TR></TBODY></TABLE>
<DIV class=modbox id=m_blog>
<DIV class=tit>[转]Proxy源代码分析 谈Linux网络编程技术(1)</DIV>
<DIV class=date>2007-06-03 13:07</DIV>
<TABLE style="TABLE-LAYOUT: fixed">
<TBODY>
<TR>
<TD>
<DIV class=cnt id=blog_text>
<P>首先声明,这段源代码不是我编写的,让我们感谢这位名叫Carl
Harris的大虾,是他编写了这段代码并将其散播到网上供大家学习讨论。这段代码虽然只是描述了最简单的proxy操作,但它的确是经典,它不仅清晰地描述了客户机/服务器系统的概念,而且几乎包括了Linux网络编程的方方面面,非常适合Linux网络编程的初学者学习。<BR> 这段Proxy程序的用法是这样的,我们可以使用这个<FONT
color=#0000ff>proxy登录其它主机的服务端口。</FONT>假如编译后生成了名为Proxy的可执行文件,那么命令及其参数的描述为:<BR> ./Proxy
<proxy_port> <remote_host>
<service_port><BR> 其中参数proxy_port是指由我们指定的代理服务器端口。参数remote_host是指我们<FONT
color=#0000ff>希望连接的远程主机的主机名,</FONT>IP地址也同样有效。这个主机名在网络上应该是唯一的,如果您不确定的话,可以在远程主机上使用uname
-n命令查看一下。参数<FONT
color=#0000ff>service_port是远程主机可提供的服务名,也可直接键入服务对应的端口号</FONT>。这个命令的相应操作是<FONT
color=#0000ff>将代理服务器的proxy_port端口绑定到remote_host的service_port端口</FONT>。然后我们就可以通过<FONT
color=#0000ff>代理服务器</FONT>的proxy_port端口访问remote_host了。例如一台计算机,网络主机名是legends,IP地址为10.10.8.221,如果在我的计算机上执行:<BR> [root@lee
/root]#./proxy 8000 legends telnet<BR> 那么我们就可以通过下面这条命令访问<FONT
color=#0000ff>legends的telnet端</FONT>口。<BR>-----------------------------------------------------------------<BR>[root@lee
/root]#telnet legends 8000<BR>Trying 10.10.8.221...<BR>Connected to
legends(10.10.8.221).<BR>Escape character is '^]'</P>
<P>Red Hat Linux release 6.2(Zoot)<BR>Kernel 2.2.14-5.0 on an
i686<BR>Login:<BR>-----------------------------------------------------------------<BR> 上面的绑定操作也可以使用下面的命令:<BR> [root@lee
/root]#./proxy 8000 10.10.8.221 23<BR> <FONT
color=#0000ff>23是telnet服务的标准端口号,其它服务的对应端口号我们可以在/etc/services中查看。</FONT></P>
<P> 下面我就从这段代码出发谈谈我对Linux网络编程的一些粗浅的认识,不对的地方还请各位大虾多多批评指正。</P>
<P>◆main()函数<BR>-----------------------------------------------------------------<BR>#include
<stdio.h> <BR>#include <ctype.h> <BR>#include <errno.h>
<BR>#include <signal.h> <BR>#include <sys/types.h>
<BR>#include <sys/socket.h> <BR>#include <sys/file.h>
<BR>#include <sys/ioctl.h> <BR>#include <sys/wait.h>
<BR>#include <sys/types.h> <BR>#include <netdb.h> <BR>#define
TCP_PROTO "tcp" <BR>int proxy_port; /* port to listen for proxy
connections on */ <BR>struct sockaddr_in hostaddr; /* host addr
assembled from gethostbyname() */ <BR>extern int errno; /* defined by
libc.a */ <BR>extern char *sys_myerrlist[]; <BR>void parse_args (int argc,
char **argv); <BR>void daemonize (int servfd); <BR>void do_proxy (int
usersockfd); <BR>void reap_status (void); <BR>void errorout (char
*msg);<BR>/*This is my modification. <BR>I'll tell you why we must do this
later*/<BR>typedef void
Signal(int);<BR>/****************************************************************<BR>function:
main <BR>description: Main level driver. After daemonizing the
process, a socket is opened to listen for </P>
<P>connections on the proxy port, connections are accepted and children
are spawned to handle each new</P>
<P>connection. <BR>arguments: argc,argv you know what those are.
<BR>return value: none. <BR>calls: parse_args, do_proxy.
<BR>globals: reads proxy_port.
<BR>****************************************************************/<BR>main
(argc,argv) <BR>int argc; <BR>char **argv; <BR>{ <BR> int clilen;
<BR> int childpid; <BR> int sockfd, newsockfd; <BR> struct
sockaddr_in servaddr, cliaddr; <BR> parse_args(argc,argv); <BR> /*
prepare an address struct to listen for connections */ <BR> bzero((char
*) &servaddr, sizeof(servaddr)); <BR> servaddr.sin_family = AF_INET;
<BR> servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
<BR> servaddr.sin_port = proxy_port; <BR> /* get a socket... */
<BR> if ((sockfd = socket(AF_INET,SOCK_STREAM,0)) < 0) {
<BR> fputs("failed to create server socket\r\n",stderr);
<BR> exit(1); <BR> } <BR> /* ...and bind our address and port to
it */ <BR> if (bind(sockfd,(struct sockaddr_in *)
&servaddr,sizeof(servaddr)) < 0) { <BR> fputs("faild to bind
server socket to specified port\r\n",stderr); <BR> exit(1); <BR> }
<BR> /* get ready to accept with at most 5 clients waiting to connect */
<BR> listen(sockfd,5); <BR> /* turn ourselves into a daemon */
<BR> daemonize(sockfd); <BR> /* fall into a loop to accept new
connections and spawn children */ <BR> while (1) {<BR> /* accept the
next connection */ <BR> clilen = sizeof(cliaddr); <BR> newsockfd =
accept(sockfd, (struct sockaddr_in *) &cliaddr, &clilen);
<BR> if (newsockfd < 0 && errno == EINTR)
<BR> continue; <BR> /* a signal might interrupt our accept() call
*/ <BR> else if (newsockfd < 0) <BR> /* something quite amiss
-- kill the server */ <BR> errorout("failed to accept
connection");<BR> /* fork a child to handle this connection */
<BR> if ((childpid = fork()) == 0) { <BR> close(sockfd);
<BR> do_proxy(newsockfd); <BR> exit(0); <BR> } <BR> /* if
fork() failed, the connection is silently dropped -- oops! */
<BR> lose(newsockfd); <BR> }
<BR> }<BR>-----------------------------------------------------------------<BR> 上面就是Proxy源代码的主程序部分,也许您在网上也曾经看到过这段代码,不过细心的您会发现在上面这段代码中我修改了两个地方,都是在预编译部分。一个地方是在定义外部字符型指针数组时,我将原代码中的 <FONT
color=#0000ff>extern char *sys_errlist[];<BR></FONT>修改为<BR> extern char
*sys_myerrlist[];原因是在我的Linux环境下头文件"stdio.h"已经对sys_errlist[]进行了如下定义:<BR> extern
__const char *__const sys_errlist[];<BR> 也许Carl
Harris在94年编写这段代码时系统还没有定义sys_errlist[],不过现在我们不修改一下的话,编译时系统就会告诉我们sys_errlist发生了定义冲突。<BR> 另外我添加了一个函数类型定义:<BR> typedef
void Sigfunc(int);<BR> 具体原因我将在后面向大家解释。</P>
<P><FONT color=#0000ff>套接字和套接字地址结构定义</FONT></P>
<P> 这段主程序是一段典型的服务器程序。网络通讯最重要的就是套接字的使用,在程序的一开始就对套接字描述符<FONT
color=#0000ff>sockfd和newsockfd</FONT>进行了定义。接下来定义客户机/服务器的套接字地址结构<FONT
color=#0000ff>cliaddr和servaddr</FONT>,存储客户机/服务器的有关通信信息。然后调用parse_args(argc,argv)函数处理命令参数。关于这个parse_args()函数我们待会儿再做介绍。</P>
<P>创建通信套接字</P>
<P> 下面就是建立一个服务器的详细过程。服务器程序的第一个操作是创建一个套接字。这是通过调用函数socket()来实现的。</P>
<P>socket()函数的具体描述为:<BR>-----------------------------------------------------------------<BR> #include
<sys/types.h><BR> #include <sys/socket.h><BR> int socket(int
domain, int type, int
protocol);<BR>-----------------------------------------------------------------<BR> 参数<FONT
color=#0000ff>domain指定套接字使用的协议族,AF_INET表示使用TCP/IP协议族</FONT>,AF_UNIX表示使用Unix协议族,AF_ISO表示套接字使用ISO协议族。type指定套接字类型,一<FONT
color=#0000ff>般的面向连接通信类型(如TCP)设置为SOCK_STREAM,</FONT>当套接字为数据报类型时,type应设置为SOCK_DGRAM,如果是可以直接访问IP协议的原始套接字则type应设置为SOCK_RAW。参数<FONT
color=#0000ff>protocol一般设置为"0",</FONT>表示使用默认协议。当socket()函数成功执行时,返回一个标志这个套接字的描述符,如果出错则返回"-1",并设置errno为相应的错误类型。</P>
<P>设置服务器套接字地址结构</P>
<P> 在通常情况下,首先要将描述服务器信息的套接字地址结构清零,然后在地址结构中填入相应的内容,准备接受<FONT
color=#0000ff>客户机送来的连接建立请求。这个清零操作可以用多种字节处理函数来实现,例</FONT>如bzero()、bcopy()、memset()、memcpy()等,以字母"b"开始的两个函数是和BSD系统兼容的,而后面两个是ANSI
C提供的函数。这段代码中使用的bzero()其描述为:<BR> void bzero(void *s, int
n);<BR> 函数的具体操作是将参数s指定的内存的前n个字节清零。memset()同样也很常用,其描述为:<BR> void
*memset(void *s, int c, size_t
n);<BR> 具体操作是将参数s指定的内存区域的前n个字节设置为参数c的内容。<BR> 下一步就是在已经清零的服务器套接字地址结构中填入相应的内容。Linux系统的套接字是一个通用的网络编程接口,它应该支持多种网络通信协议,每一种协议都使用专门为自己定义的套接字地址结构(例如<FONT
color=#0000ff>TCP/IP网络的套接字地址结构就是structsockaddr_in</FONT>)。不过为了保持套接字函数调用参数的一致性,Linux系统还定义了一种通用的套接字地址结构:<BR>-----------------------------------------------------------------<BR><linux/socket.h><BR>struct
sockaddr<BR>{<BR> unsigned short sa_family; /* address type */<BR> char
sa_data[14]; /* protocol address
*/<BR>}<BR>-----------------------------------------------------------------<BR> 其中sa_family意指套接字使用的协议族地址类型,对于我们的TCP/IP网络,其值应该是AF_INET,sa_data中存储具体的协议地址,不同的协议族有不同的地址格式。这个通用的套接字地址结构一般不用做定义具体的实例,但是常用做套接字地址结构的强制类型转换,如我们经常可以看到这样的用法:<BR> bind(sockfd,(struct
sockaddr *) &servaddr,sizeof(servaddr))<BR> 用于TCP/IP协议族的套接字地址结构是<FONT
color=#0000ff>sockaddr_in</FONT>,其定义为:<BR>-----------------------------------------------------------------<BR><linux/in.h><BR>struct
in_addr<BR>{<BR> __u32 s_addr;<BR>};<BR> struct
sochaddr_in<BR>{<BR> short int sin_family;<BR> unsigned short int
sin_port;<BR> struct in_addr sin_addr;<BR> /*This part has not been
taken into use yet*/<BR> nsigned char_ _ pad[_ _ SOCK_SIZE__-
sizeof(short int) -sizeof(unsigned short int) - sizeof(struct</P>
<P>in_addr)];<BR>};<BR>#define sin_zero_ -
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -