📄 proxy源代码分析--谈谈如何学习linux网络编程.txt
字号:
if ((inaddr = inet_addr(pargs.isolated_host)) != INADDR_NONE)
bcopy(&inaddr,&hostaddr.sin_addr,sizeof(inaddr));
else if ((hostp = gethostbyname(pargs.isolated_host)) != NULL)
bcopy(hostp->h_addr,&hostaddr.sin_addr,hostp->h_length);
else {
printf("%s: unknown host\r\n",pargs.isolated_host);
exit(1);
}
if ((servp = getservbyname(pargs.service_name,TCP_PROTO)) != NULL)
hostaddr.sin_port = servp->s_port;
else if (atoi(pargs.service_name) > 0)
hostaddr.sin_port = htons(atoi(pargs.service_name));
else {
printf("%s: invalid/unknown service name or port number\r\n", pargs.service_name);
exit(1);
}
}
-----------------------------------------------------------------
这个函数的作用是传递命令行参数。参数的传递是通过两个全局变量来实现的,这两个变量是int proxy_port和struct sockaddr_in hostaddr。分别用于传递等待连接请求的proxy端口和被绑定的主机网络信息。
检验命令行参数
在进行了局部变量定义以后,函数首先要检测命令行参数是否符合程序的要求,即在命令后紧跟代理服务器端口、远程主机名和服务端口号,如果不满足上述要求,则代理服务器程序结束。如果满足上述要求,则将命令行的这三个参数存储进我们自定义的pargs结构之中。注意pargs结构的三个成员都是以字符形式存放命令行参数信息的,后面我们需要调用函数将这些参数信息都转换成为数字形式的。
传递参数
接下来就要将命令行的三个参数变换成合适的形式赋值给全局变量proxy_port和hostaddr,以供其它函数调用。首先传送代理服务器端口pargs.proxy_port,在这里程序调用了一个系统函数isdigit()检验用户输入的端口号是否有效。isdigit()的具体描述为:
-----------------------------------------------------------------
#include <ctype.h>
int isdigit(int c)
-----------------------------------------------------------------
isdigit()函数用来检测参数"c"是否是数字1~9中间的一个,如果答案是肯定的,则返回非"0"值,反之,返回"0"。程序中采用了这样的方法来对用户的输入进行逐位检验:
if (!isdigit(*(pargs.proxy_port + i)))
break;
在将有效端口号传递给全局变量proxy_port之前,还要将其转换成为网络字节顺序。这是因为网络中存在着多个公司的不同设备,这些设备表示数据的字节顺序是不同的。例如在内存地址0x1000处存储一个16位的整数FF11,不同公司的机器在内存中的存储方式也不相同,有的将FF置于内存指针的起始位置0x1000,11置于0x1001,这称为big-endian顺序;有的却恰恰相反,即little-endian顺序。这种基于主机的数据存储顺序就称为主机字节顺序(host byte order)。为了在不同类型的主机之间进行通信,网络协议就规定了一种统一的网络字节顺序,这种顺序被规定为little-endian顺序。所以数据的网络字节顺序和主机字节顺序有可能是不同的,因此在编写通信程序时一定要注意不同顺序之间的转换。所以,程序中一定要有例程中这样的语句:
proxy_port = htons(atoi(pargs.proxy_port));
函数htons()的作用就是将主机字节顺序转换为网络字节顺序。它的具体描述为:
-----------------------------------------------------------------
#include <netinet/in.h>
unsigned short int htons(unsigned short int data)
-----------------------------------------------------------------
与htons()相似的函数还有三个,它们分别是htonl()、ntohs()和ntohl(),都用于网络与主机字节顺序之间的转换。如果这几个名字比较容易混淆的话,我们可以这样记忆:函数名中的h代表host,n代表network,s代表unsigned short,l代表unsigned long。所以"hton"即为"host-to-network":变换主机字节为网络字节。接收数据的就要用到"ntoh"("network-to-host")函数了。
在我们的例程中,由于端口号一般情况下最多不会超过4位数字,所以选用unsigned short型的htons()即可。
注意在例程中htons()的参数是另一个函数atoi()的返回结果。atoi()函数的具体描述为:
-----------------------------------------------------------------
#include <stdlib.h>
int atoi(const char *nptr)
-----------------------------------------------------------------
它的作用是将字符指针nptr指向的字符串转换成相应的整数并将其作为结果返回。这个操作与函数调用strtol(nptr,(char **)NULL,10)的效果几乎完全相同,唯一的区别是atoi()没有出错返回信息。之所以要调用这个函数是因为,系统在读取命令行的时候将所有的参数都作为字符串处理,所以我们必须将其转换为整数形式。
接下来,例程先将全局变量hostaddr的所有成员清零,然后将成员hostaddr.sin_family设置为TCP/IP协议族标志AF_INET。下面就可将命令行的另外两个参数<remote_host>和<service_port>传递给全局变量hostaddr的两个成员hostaddr.sin_port和hostaddr.sin_addr了。这里我们用到了两个局部变量struct hostent *hostp和struct servent *servp来传递参数信息。struct hostent的详细描述为:
-----------------------------------------------------------------
struct hostent {
char *h_name;
char **h_aliases;
int h_addrtype;
int h_length;
char **h_addr_list;
};
#define h_addr h_addrlist[0]
-----------------------------------------------------------------
hostent成员的含义是h_name代表主机在网络上的的正式名称,h_aliases是所有主机别名的列表,h_addrtype是指主机的地址类型,一般设置为TCP/IP协议族AF_INET,h_length是主机的地址长度,一般设置为4个字节。h_addr_list是主机的IP地址列表。
我们要用它来传递我们期望绑定的远程主机名或是IP地址。因为命令行中的主机名参数已经被存储进pargs.isolated_host,所以我们就调用inet_addr()函数对主机名或主机的IP地址进行二进制和字节顺序转换。inet_addr()函数的描述为:
-----------------------------------------------------------------
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
unsigned long int inet_addr(const char *cp)
-----------------------------------------------------------------
inet_addr()的作用就是将参数cp指向的Internet主机地址从数字/点的形式转换成二进制形式并同时转换为网络字节顺序,并将转换结果直接返回。如果cp指向的IP地址不可用,则函数返回INADDR_NONE或"-1"。
虽然Carl Harris在编写这段程序时使用了这个inet_addr()函数,但是我还是建议大家在编写自己的程序时使用另外一个函数inet_aton()来完成这些功能。原因是inet_addr()在IP地址不可用时返回"-1",但我们想想,IP地址255.255.255.255绝对是一个有效地址,那么其二进制返回值也将是"-1",因此inet_addr()无法对这个IP地址进行处理。而函数inet_aton()则采用了一种更好的方法来返回出错信息,它的具体描述为:
-----------------------------------------------------------------
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int inet_aton(const char *cp, struct in_addr *inp)
-----------------------------------------------------------------
函数执行成功时返回非零,转换结果存入指针inp指向的in_addr结构。这个结构定义我们在前面的文章里已经介绍过了。如果参数cp指向的IP地址不可用,则返回"0"。这就避免发生inet_addr()那样的问题。
如果说用户在命令行中键入的是远程主机的IP地址,那么只用inet_addr()就算完成任务了,但如果用户键入的是主机域名那该怎么办呢?所以我们在例程中可以看到这样的语句:
-----------------------------------------------------------------
if ((inaddr = inet_addr(pargs.isolated_host)) != INADDR_NONE)
bcopy(&inaddr,&hostaddr.sin_addr,sizeof(inaddr));
else if ((hostp = gethostbyname(pargs.isolated_host)) != NULL)
bcopy(hostp->h_addr,&hostaddr.sin_addr,hostp->h_length);
else {
printf("%s: unknown host\r\n",pargs.isolated_host);
exit(1);
}
-----------------------------------------------------------------
其中gethostbyname()函数就是用来转换主机域名的。它的具体描述为:
-----------------------------------------------------------------
#include <netdb.h>
struct hostent *gethostbyname(const char *hostname);
-----------------------------------------------------------------
参数hostname指向我们需要转换的域名地址,函数直接返回转换结果,如果函数执行成功,则结果直接返回到一个指向hostent结构的指针中,否则返回空指针NULL。
例程就是这样调用inet_addr()和gethostbyname()将命令行参数中的主机域名或是主机IP地址传递给全局变量hostaddr的成员sin_addr以便代理执行函数do_proxy()调用。
下面是传递服务名或是服务端口号。这里要用到结构servent做传递中介,struct servent的详细描述为:
-----------------------------------------------------------------
struct servent {
char *s_name;
char **s_aliases;
int s_port;
char *s_proto;
};
-----------------------------------------------------------------
其各成员的含义是s_name为服务的正式名称,如ftp、http等,s_aliases是服务的别名列表,s_port是服务的端口号,例如在一般情况下ftp的端口号为21,http服务的端口号为80,注意此端口号应该存储为网络字节顺序,s_proto是应用协议的类型。
例程中使用getservbyname()函数转换命令行参数中的服务名,此函数的详细描述为:
-----------------------------------------------------------------
#include <netdb.h>
struct servent * getservbyname(const char *servname, const char *protoname);
-----------------------------------------------------------------
它的作用就是转换指针servname指向的服务名为相应的整数表示的端口号,参数protoname表示服务使用的协议,例程中protoname 参数的值为TCP_PROTO,这表示使用TCP协议。函数成功时就返回一个struct servent型的指针,其中的s_port成员就是我们关心的服务端口号。如果用户在命令中键入的是端口号而不是服务名,那么和处理代理端口信息一样,使用下面的语句进行处理:
hostaddr.sin_port = htons(atoi(pargs.service_name));
到这里,命令行的参数已经全部被转换成为网络通信所要求的字节顺序和数字类型,并且存储在三个全局变量中,就等着do_proxy()函数来调用了。
◆daemonize()函数创建守护进程
在对main()函数进行介绍的时候我就提到过,一般服务器程序在接收客户机连接请求之前,都要创建一个守护进程。守护进程是linux/Unix编程中一个非常重要的概念,因为在创建一个守护进程的时候,我们要接触到子进程、进程组、会晤期、信号机制以及文件、目录、控制终端等多个概念,因此详细地讨论一下守护进程,对初学者学习进程间关系是非常有帮助的。下面就是例程中的daemonize()函数:
-----------------------------------------------------------------
/****************************************************************
function: daemonize
description: detach the server process from the current context, creating a pristine, predictable environment in which it will execute.
arguments: servfd file descriptor in use by server.
return value: none.
calls: none.
globals: none.
****************************************************************/
void daemonize (servfd)
int servfd;
{
int childpid, fd, fdtablesize;
/* ignore terminal I/O, stop signals */
signal(SIGTTOU,SIG_IGN);
signal(SIGTTIN,SIG_IGN);
signal(SIGTSTP,SIG_IGN);
/* fork to put us in the background (whether or not the user
specified '&' on the command line */
if ((childpid = fork()) < 0) {
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -