Proxy源代码分析.docx
- 文档编号:4356102
- 上传时间:2022-11-30
- 格式:DOCX
- 页数:18
- 大小:32.67KB
Proxy源代码分析.docx
《Proxy源代码分析.docx》由会员分享,可在线阅读,更多相关《Proxy源代码分析.docx(18页珍藏版)》请在冰豆网上搜索。
Proxy源代码分析
Proxy源代码分析
-
这段Proxy程序的用法是这样的,我们可以使用这个proxy登录其它主机的服务端口。
◆main()函数
-----------------------------------------------------------------
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#defineTCP_PROTO "tcp"
intproxy_port; /*porttolistenforproxyconnectionson*/
structsockaddr_inhostaddr; /*hostaddrassembledfromgethostbyname()*/
externinterrno; /*definedbylibc.a*/
externchar*sys_myerrlist[];
voidparse_args(intargc,char**argv);
voiddaemonize(intservfd);
voiddo_proxy(intusersockfd);
voidreap_status(void);
voiderrorout(char*msg);
/*Thisismymodification.
I'lltellyouwhywemustdothislater*/
typedefvoidSignal(int);
main(argc,argv)
intargc;
char**argv;
{
intclilen;
intchildpid;
intsockfd,newsockfd;
structsockaddr_inservaddr,cliaddr;
parse_args(argc,argv);
/*prepareanaddressstructtolistenforconnections*/
bzero((char*)&servaddr,sizeof(servaddr));
servaddr.sin_family=AF_INET;
servaddr.sin_addr.s_addr=htonl(INADDR_ANY);
servaddr.sin_port=proxy_port;
/*getasocket...*/
if((sockfd=socket(AF_INET,SOCK_STREAM,0))<0){
fputs("failedtocreateserversocket\r\n",stderr);
exit
(1);
}
/*...andbindouraddressandporttoit*/
if (bind(sockfd,(structsockaddr_in*)&servaddr,sizeof(servaddr))<0){
fputs("faildtobindserversockettospecifiedport\r\n",stderr);
exit
(1);
}
/*getreadytoacceptwithatmost5clientswaitingtoconnect*/
listen(sockfd,5);
/*turnourselvesintoadaemon*/
daemonize(sockfd);
/*fallintoalooptoacceptnewconnectionsandspawnchildren*/
while
(1){
/*acceptthenextconnection*/
clilen=sizeof(cliaddr);
newsockfd=accept(sockfd,(structsockaddr_in*)&cliaddr,&clilen);
if(newsockfd<0&&errno==EINTR)
continue;
/*asignalmightinterruptouraccept()call*/
elseif(newsockfd<0)
/*somethingquiteamiss--killtheserver*/
errorout("failedtoacceptconnection");
/*forkachildtohandlethisconnection*/
if((childpid=fork())==0){
close(sockfd);
do_proxy(newsockfd);
exit(0);
}
/*iffork()failed,theconnectionissilentlydropped--oops!
*/
lose(newsockfd);
}
}
-----------------------------------------------------------------
上面就是Proxy源代码的主程序部分,这段代码中修改了两个地方,都是在预编译部分。
一个地方是在定义外部字符型指针数组时,将原代码中的
externchar*sys_errlist[];
修改为
externchar*sys_myerrlist[];原因是在Linux环境下头文件"stdio.h"已经对sys_errlist[]进行了如下定义:
extern__constchar*__constsys_errlist[];
如果不修改一下的话,编译时系统就会告诉sys_errlist发生了定义冲突。
另外添加了一个函数类型定义:
typedefvoidSigfunc(int);
套接字和套接字地址结构定义
这段主程序是一段典型的服务器程序。
网络通讯最重要的就是套接字的使用,在程序的一开始就对套接字描述符sockfd和newsockfd进行了定义。
接下来定义客户机/服务器的套接字地址结构cliaddr和servaddr,存储客户机/服务器的有关通信信息。
然后调用parse_args(argc,argv)函数处理命令参数。
创建通信套接字
下面就是建立一个服务器的详细过程。
服务器程序的第一个操作是创建一个套接字。
这是通过调用函数socket()来实现的。
socket()函数的具体描述为:
-----------------------------------------------------------------
#include
#include
intsocket(intdomain,inttype,intprotocol);
-----------------------------------------------------------------
参数domain指定套接字使用的协议族,AF_INET表示使用TCP/IP协议族,AF_UNIX表示使用Unix协议族,AF_ISO表示套接字使用ISO协议族。
type指定套接字类型,一般的面向连接通信类型(如TCP)设置为SOCK_STREAM,当套接字为数据报类型时,type应设置为SOCK_DGRAM,如果是可以直接访问IP协议的原始套接字则type应设置为SOCK_RAW。
参数protocol一般设置为"0",表示使用默认协议。
当socket()函数成功执行时,返回一个标志这个套接字的描述符,如果出错则返回"-1",并设置errno为相应的错误类型。
设置服务器套接字地址结构
在通常情况下,首先要将描述服务器信息的套接字地址结构清零,然后在地址结构中填入相应的内容,准备接受客户机送来的连接建立请求。
这段代码中使用的bzero()其描述为:
voidbzero(void*s,intn);
函数的具体操作是将参数s指定的内存的前n个字节清零。
下一步就是在已经清零的服务器套接字地址结构中填入相应的内容。
为了保持套接字函数调用参数的一致性,Linux系统定义了一种通用的套接字地址结构:
-----------------------------------------------------------------
structsockaddr
{
unsignedshortsa_family;/*addresstype*/
charsa_data[14];/*protocoladdress*/
}
-----------------------------------------------------------------
其中sa_family意指套接字使用的协议族地址类型,对于我们的TCP/IP网络,其值应该是AF_INET,sa_data中存储具体的协议地址,不同的协议族有不同的地址格式。
servaddr.sin_addr.s_addr=htonl(INADDR_ANY);
设置服务器套接字的IP地址为特殊值INADDR_ANY,这表示服务器愿意接收来自任何网络设备接口的客户机连接。
htonl()函数的意思是将主机顺序的字节转换成网络顺序的字节。
servaddr.sin_port=htons(PORT);
设置通信端口号,PORT应该是我们已经定义好的。
在本例中servaddr.sin_port=proxy_port;这是表示端口号是函数的返回值proxy_port。
另外需要说明的一点是,在本例中,我们并没有看到在预编译部分中包含有
服务器公开地址
如果服务器要接受客户机的连接请求,那么它必须先要在整个网络上公开自己的地址。
在设置了服务器的套接字地址结构之后,可以通过调用函数bind()绑定服务器的地址和套接字来完成公开地址的操作。
函数bind()的详细描述为:
-----------------------------------------------------------------
#include
#include
intbind(intsockfd,structsockaddr*addr,intaddrlen);
-----------------------------------------------------------------
参数sockfd是我们通过调用socket()创建的套接字描述符。
参数addr是本机地址,参数addrlen是套接字地址结构的长度。
函数执行成功时返回"0",否则返回"-1",并设置errno变量为EADDRINUAER。
如果是服务器调用bind()函数,如果设置了套接字的IP地址为某个本地IP地址,那么这表示服务器只接受来自于这个IP地址的特定主机发出的连接请求。
不过一般情况下都是将IP地址设置为INADDR_ANY,以便接受所有网络设备接口送来的连接请求。
客户机一般是不会调用bind()函数的,因为客户机在连接时不用指定自己的套接字地址端口号,系统会自动为客户机选择一个未用端口号,并且用本地IP地址自动填充客户机套接字地址结构中的相应项。
但是在某些特定的情况下客户机需要使用特定的端口号,例如Linux中的rlogin命令就要求使用保留端口号,而系统是不能为客户机自动分配保留端口号的,这就需要调用bind()来绑定一个保留端口号了。
不过在一些特殊的环境下,这样绑定特定端口号也会带来一些负面影响,如在HTTP服务器进入TIME_WAIT状态后,客户机如果要求再次与服务器建立连接,则服务器会拒绝这一连接请求。
如果客户机最后进入TIME_WAIT状态,则马上再次执行bind()函数时会返回出错信息"-1",原因是系统会认为同时有两次连接绑定同一个端口。
转换Listening套接字
接下来,服务器需要将我们刚才与IP地址和端口号完成绑定的套接字转换成倾听listening套接字。
只有服务器程序才需要执行这一步操作。
我们通过调用函数listen()实现这一操作。
listen()的详细描述为:
-----------------------------------------------------------------
#include
intlisten(intsockfd,intbacklog);
-----------------------------------------------------------------
参数sockfd指定我们要求转换的套接字描述符,参数backlog设置请求队列的最大长度。
函数listen()主要完成以下操作。
首先是将套接字转换成倾听套接字。
因为函数socket()创建的套接字都是主动套接字,所以客户机可以通过调用函数connect()来使用这样的套接字主动和服务器建立连接。
而服务器的情况恰恰相反,服务器需要通过套接字接收客户机的连接请求,这就需要一个"被动"套接字。
listen()就可将一个尚未连接的主动套接字转换成为这样的"被动"套接字,也就是倾听套接字。
在执行了listen()函数之后,服务器的TCP就由CLOSED变成LISTEN状态了。
另外listen()可以设置连接请求队列的最大长度。
虽然参数backlog的用法非常简单,只是一个简单的整数。
但搞清楚请求队列的含义对理解TCP协议的通信过程建立非常重要。
TCP协议为每个倾听套接字实际上维护两个队列,一个是未完成连接队列,这个队列中的成员都是未完成3次握手的连接;另一个是完成连接队列,这个队列中的成员都是虽然已经完成了3次握手,但是还未被服务器调用accept()接收的连接。
参数backlog实际上指定的是这个倾听套接字完成连接队列的最大长度。
在本例中我们是这样用的:
listen(sockfd,5);表示完成连接队列的最大长度为5。
接收连接
接下来我们在主程序中看到通过名为daemonize()的自定义函数创建一个守护进程,关于这个daemonize()以及守护进程的相关概念,我们等一会儿再做详细介绍。
然后服务器程序进入一个无条件循环,用于监听接收客户机的连接请求。
在此过程中如果有客户机调用connect()请求连接,那么函数accept()可以从倾听套接字的完成连接队列中接受一个连接请求。
如果完成连接队列为空,这个进程就睡眠。
accept()的详细描述为:
-----------------------------------------------------------------
#include
intaccept(intsockfd,structsockaddr*addr,int*addrlen);
-----------------------------------------------------------------
参数sockfd是我们转换成功的倾听套接字描述符;参数addr是一个指向套接字地址结构的指针,参数addrlen为一个整型指针。
当函数成功执行时,返回3个结果,函数返回一个新的套接字描述符,服务器可以通过这个新的套接字描述符和客户机进行通信。
参数addr所指向的套接字地址结构中将存放客户机的相关信息,addrlen指针将描述前述套接字地址结构的长度。
在通常情况下服务器对这些信息不是很感兴趣,因此我们经常可以看到一些源代码中将accept()函数的后两个参数都设置为NULL。
不过在这段proxy源代码中需要用到有关的客户机信息,因此我们看到通过执行
newsockfd=accept(sockfd,(structsockaddr_in*)&cliaddr,&clilen);
将客户机的详细信息存放在地址结构cliaddr中。
而proxy就通过套接字newsockfd与客户机进行通信。
值得注意的是这个返回的套接字描述符与我们转换的倾听套接字是不同的。
在一段服务器程序中,可以始终只用一个倾听套接字来接收多个客户机的连接请求;而如果我们要和客户机建立一个实际的连接的话,对每一个请求我们都需要调用accept()返回一个新的套接字。
当服务器处理完毕客户机的请求后,一定要将相应的套接字关闭;如果整个服务器程序将要结束,那么一定要将倾听套接字关闭。
如果accept()函数执行失败,则返回"-1",如果accept()函数阻塞等待客户机调用connect()建立连接,进程在此时恰好捕捉到信号,那么函数在返回"-1"的同时将变量errno的值设置为EINTR。
这和accept()函数执行失败是有区别的。
因此我们在代码中可以看到这样的语句:
-----------------------------------------------------------------
if(newsockfd<0&&errno==EINTR)
continue;/*asignalmightinterruptouraccept()call*/
elseif(newsockfd<0)/*somethingquiteamiss--killtheserver*/
errorout("failedtoacceptconnection");
-----------------------------------------------------------------
可以看出程序在处理这两种情况时操作是完全不同的,同样是accept()返回"-1",如果有errno==EINTR,那么系统将再次调用accept()接受连接请求,否则服务器进程将直接结束。
处理客户机请求
当服务器与客户机建立连接以后,就可以处理客户机的请求了。
一般情况下服务器程序都要创建一个子进程用于处理客户机请求;而父进程则继续监听,时刻准备接受其它客户机的连接请求。
这段proxy程序通过调用fork()创建处理客户机请求的子进程。
在大型的服务器程序中,一般都要在子进程里,根据客户机请求的不同而通过exec()系列函数调用不同的处理程序。
不过这个proxy程序旨在讲述一些linux网络编程的基本概念,因此在子程序部分就直接调用了一个完成proxy功能的函数do_proxy(),其实际参数newsockfd就是accept()返回的套接字描述符。
另外值得注意的一点就是,因为子进程继承了所有父进程中可用的文件描述符,所以我们必须在子进程中关闭倾听套接字(代码中子进程部分的close(sockfd);),同时在父进程中关闭accept()返回的套接字描述符(例如代码中父进程部分的close(newsockfd);)。
◆函数parse_args()
此函数的定义是:
voidparse_args(intargc,char**argv);
voidparse_args(argc,argv)
intargc;
char**argv;
{
inti;
structhostent*hostp;
structservent*servp;
unsignedlonginaddr;
struct{
charproxy_port[16];
charisolated_host[64];
charservice_name[32];
}pargs;
if(argc<4){
printf("usage:
%s
exit
(1);
}
strcpy(pargs.proxy_port,argv[1]);
strcpy(pargs.isolated_host,argv[2]);
strcpy(pargs.service_name,argv[3]);
for(i=0;i if(! isdigit(*(pargs.proxy_port+i))) break; if(i==strlen(pargs.proxy_port)) proxy_port=htons(atoi(pargs.proxy_port)); else{ printf("%s: invalidproxyport\r\n",pargs.proxy_port); exit(0); } bzero(&hostaddr,sizeof(hostaddr)); hostaddr.sin_family=AF_INET; if((inaddr=inet_addr(pargs.isolated_host))! =INADDR_NONE) bcopy(&inaddr,&hostaddr.sin_addr,sizeof(inaddr)); elseif((hostp=gethostbyname(pargs.isolated_host))! =NULL) bcopy(hostp->h_addr,&hostaddr.sin_addr,hostp->h_length); else{ printf("%s: unknownhost\r\n",pargs.isolated_host); exit (1); } if((servp=getservbyname(pargs.service_name,TCP_PROTO))! =NULL) hostaddr.sin_port=servp->s_port; elseif(atoi(pargs.service_name)>0) hostaddr.sin_port=htons(atoi(pargs.service_name)); else{ printf("%s: invalid/unknownservicenameorportnumber\r\n"
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Proxy 源代码 分析