Proxy源代码分析谈谈如何学习linux网络编程络编程.docx

上传人:b****2 文档编号:918396 上传时间:2023-04-30 格式:DOCX 页数:30 大小:37.27KB
下载 相关 举报
Proxy源代码分析谈谈如何学习linux网络编程络编程.docx_第1页
第1页 / 共30页
Proxy源代码分析谈谈如何学习linux网络编程络编程.docx_第2页
第2页 / 共30页
Proxy源代码分析谈谈如何学习linux网络编程络编程.docx_第3页
第3页 / 共30页
Proxy源代码分析谈谈如何学习linux网络编程络编程.docx_第4页
第4页 / 共30页
Proxy源代码分析谈谈如何学习linux网络编程络编程.docx_第5页
第5页 / 共30页
Proxy源代码分析谈谈如何学习linux网络编程络编程.docx_第6页
第6页 / 共30页
Proxy源代码分析谈谈如何学习linux网络编程络编程.docx_第7页
第7页 / 共30页
Proxy源代码分析谈谈如何学习linux网络编程络编程.docx_第8页
第8页 / 共30页
Proxy源代码分析谈谈如何学习linux网络编程络编程.docx_第9页
第9页 / 共30页
Proxy源代码分析谈谈如何学习linux网络编程络编程.docx_第10页
第10页 / 共30页
Proxy源代码分析谈谈如何学习linux网络编程络编程.docx_第11页
第11页 / 共30页
Proxy源代码分析谈谈如何学习linux网络编程络编程.docx_第12页
第12页 / 共30页
Proxy源代码分析谈谈如何学习linux网络编程络编程.docx_第13页
第13页 / 共30页
Proxy源代码分析谈谈如何学习linux网络编程络编程.docx_第14页
第14页 / 共30页
Proxy源代码分析谈谈如何学习linux网络编程络编程.docx_第15页
第15页 / 共30页
Proxy源代码分析谈谈如何学习linux网络编程络编程.docx_第16页
第16页 / 共30页
Proxy源代码分析谈谈如何学习linux网络编程络编程.docx_第17页
第17页 / 共30页
Proxy源代码分析谈谈如何学习linux网络编程络编程.docx_第18页
第18页 / 共30页
Proxy源代码分析谈谈如何学习linux网络编程络编程.docx_第19页
第19页 / 共30页
Proxy源代码分析谈谈如何学习linux网络编程络编程.docx_第20页
第20页 / 共30页
亲,该文档总共30页,到这儿已超出免费预览范围,如果喜欢就下载吧!
下载资源
资源描述

Proxy源代码分析谈谈如何学习linux网络编程络编程.docx

《Proxy源代码分析谈谈如何学习linux网络编程络编程.docx》由会员分享,可在线阅读,更多相关《Proxy源代码分析谈谈如何学习linux网络编程络编程.docx(30页珍藏版)》请在冰点文库上搜索。

Proxy源代码分析谈谈如何学习linux网络编程络编程.docx

Proxy源代码分析谈谈如何学习linux网络编程络编程

Proxy源代码分析--谈谈如何学习linux网络编程

linux是一个可靠性非常高的操作系统,但是所有用过Linux的朋友都会感觉到,Linux和Windows这样的"傻瓜"操作系统(这里丝毫没有贬低Windows的意思,相反这应该是Windows的优点)相比,后者无疑在易操作性上更胜一筹。

但是为什么又有那么多的爱好者钟情于Linux呢,当然自由是最吸引人的一点,另外Linux强大的功能也是一个非常重要的原因,尤其是Linux强大的网络功能更是引人注目。

放眼今天的WAP业务、银行网络业务和曾经红透半边天的电子商务,都越来越倚重基于Linux的解决方案。

因此Linux网络编程是非常重要的,而且当我们一接触到Linux网络编程,我们就会发现这是一件非常有意思的事情,因为以前一些关于网络通信概念似是而非的地方,在这一段段代码面前马上就豁然开朗了。

在刚开始学习编程的时候总是让人感觉有点理不清头绪,不过只要多读几段代码,很快我们就能体会到其中的乐趣了。

下面我就从一段Proxy源代码开始,谈谈如何进行Linux网络编程。

  首先声明,这段源代码不是我编写的,让我们感谢这位名叫CarlHarris的大虾,是他编写了这段代码并将其散播到网上供大家学习讨论。

这段代码虽然只是描述了最简单的proxy操作,但它的确是经典,它不仅清晰地描述了客户机/服务器系统的概念,而且几乎包括了Linux网络编程的方方面面,非常适合Linux网络编程的初学者学习。

  这段Proxy程序的用法是这样的,我们可以使用这个proxy登录其它主机的服务端口。

假如编译后生成了名为Proxy的可执行文件,那么命令及其参数的描述为:

   ./Proxy

  其中参数proxy_port是指由我们指定的代理服务器端口。

参数remote_host是指我们希望连接的远程主机的主机名,IP地址也同样有效。

这个主机名在网络上应该是唯一的,如果您不确定的话,可以在远程主机上使用uname-n命令查看一下。

参数service_port是远程主机可提供的服务名,也可直接键入服务对应的端口号。

这个命令的相应操作是将代理服务器的proxy_port端口绑定到remote_host的service_port端口。

然后我们就可以通过代理服务器的proxy_port端口访问remote_host了。

例如一台计算机,网络主机名是legends,IP地址为10.10.8.221,如果在我的计算机上执行:

   [root@lee/root]#./proxy8000legendstelnet

  那么我们就可以通过下面这条命令访问legends的telnet端口。

-----------------------------------------------------------------

[root@lee/root]#telnetlegends8000

Trying10.10.8.221...

Connectedtolegends(10.10.8.221).

Escapecharacteris'^]'

RedHatLinuxrelease6.2(Zoot)

Kernel2.2.14-5.0onani686

Login:

-----------------------------------------------------------------

  上面的绑定操作也可以使用下面的命令:

   [root@lee/root]#./proxy800010.10.8.22123

   23是telnet服务的标准端口号,其它服务的对应端口号我们可以在/etc/services中查看。

  下面我就从这段代码出发谈谈我对Linux网络编程的一些粗浅的认识,不对的地方还请各位大虾多多批评指正。

◆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);

/****************************************************************

function:

   main

description:

 Mainleveldriver.Afterdaemonizingtheprocess,asocketisopenedtolistenfor        connectionsontheproxyport,connectionsareacceptedandchildrenarespawnedto        handleeachnewconnection.

arguments:

  argc,argvyouknowwhatthoseare.

returnvalue:

 none.

calls:

    parse_args,do_proxy.

globals:

   readsproxy_port.

****************************************************************/

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[];

  也许CarlHarris在94年编写这段代码时系统还没有定义sys_errlist[],不过现在我们不修改一下的话,编译时系统就会告诉我们sys_errlist发生了定义冲突。

  另外我添加了一个函数类型定义:

   typedefvoidSigfunc(int);

  具体原因我将在后面向大家解释。

套接字和套接字地址结构定义

  这段主程序是一段典型的服务器程序。

网络通讯最重要的就是套接字的使用,在程序的一开始就对套接字描述符sockfd和newsockfd进行了定义。

接下来定义客户机/服务器的套接字地址结构cliaddr和servaddr,存储客户机/服务器的有关通信信息。

然后调用parse_args(argc,argv)函数处理命令参数。

关于这个parse_args()函数我们待会儿再做介绍。

创建通信套接字

  下面就是建立一个服务器的详细过程。

服务器程序的第一个操作是创建一个套接字。

这是通过调用函数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()、bcopy()、memset()、memcpy()等,以字母"b"开始的两个函数是和BSD系统兼容的,而后面两个是ANSIC提供的函数。

这段代码中使用的bzero()其描述为:

   voidbzero(void*s,intn);

  函数的具体操作是将参数s指定的内存的前n个字节清零。

memset()同样也很常用,其描述为:

   void*memset(void*s,intc,size_tn);

  具体操作是将参数s指定的内存区域的前n个字节设置为参数c的内容。

  下一步就是在已经清零的服务器套接字地址结构中填入相应的内容。

Linux系统的套接字是一个通用的网络编程接口,它应该支持多种网络通信协议,每一种协议都使用专门为自己定义的套接字地址结构(例如TCP/IP网络的套接字地址结构就是structsockaddr_in)。

不过为了保持套接字函数调用参数的一致性,Linux系统还定义了一种通用的套接字地址结构:

-----------------------------------------------------------------

structsockaddr

{

  unsignedshortsa_family;/*addresstype*/

  charsa_data[14];/*protocoladdress*/

}

-----------------------------------------------------------------

  其中sa_family意指套接字使用的协议族地址类型,对于我们的TCP/IP网络,其值应该是AF_INET,sa_data中存储具体的协议地址,不同的协议族有不同的地址格式。

这个通用的套接字地址结构一般不用做定义具体的实例,但是常用做套接字地址结构的强制类型转换,如我们经常可以看到这样的用法:

   bind(sockfd,(structsockaddr*)&servaddr,sizeof(servaddr))

  用于TCP/IP协议族的套接字地址结构是sockaddr_in,其定义为:

-----------------------------------------------------------------

structin_addr

{

  __u32s_addr;

};

  structsochaddr_in

{

  shortintsin_family;

  unsignedshortintsin_port;

  structin_addrsin_addr;

   /*Thisparthasnotbeentakenintouseyet*/

   nsignedchar__pad[__SOCK_SIZE__-sizeof(shortint)-sizeof(unsignedshortint)-      sizeof(structin_addr)];

};

#definesin_zero_-pad

-----------------------------------------------------------------

  其中sin_zero成员并未使用,它是为了和通用套接字地址structsockaddr兼容而特意引入的。

在编程时,一般都通过bzero()或是memset()将其置零。

其他成员的设置一般是这样的:

   servaddr.sin_family=AF_INET;

  表示套接字使用TCP/IP协议族。

   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次握手的连接

展开阅读全文
相关资源
猜你喜欢
相关搜索
资源标签

当前位置:首页 > 工作范文 > 演讲主持

copyright@ 2008-2023 冰点文库 网站版权所有

经营许可证编号:鄂ICP备19020893号-2