版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/u014530704/article/details/78804163
本文主要讲代理服务器源码,是一位叫Carl Harris大神写的,非常简单易懂,把代理服务器(Proxy Server)本质完全体现出来。相信读懂了这段代码,以后想搞定http代理等其他类型的代理服务器也能行。在附录中会贴出proxy全部源码,仅供学习使用。

一、代理服务器的定义
代理服务器(Proxy Server)是一种重要的服务器安全功能,它的工作主要在开放系统互联(OSI)模型的会话层,从而起到防火墙的作用。代理(英语:Proxy),也称网络代理,是一种特殊的网络服务,允许一个网络终端(一般为客户端)通过这个服务与另一个网络终端(一般为服务器)进行非直接的连接。一些网关、路由器等网络设备具备网络代理功能。一般认为代理服务有利于保障网络终端的隐私或安全,防止攻击。
以上截取了网上Proxy Server的定义。看起来晦涩难懂。简单来说,代理服务器就是起到一个转发功能。比如,你在本机A想要访问国外的服务器C,你本机没权限访问C,需要通过向服务器B发送数据,B再把你的数据发送给C,C返回数据也是先把数据交给了B,然后B再转交给你。这里B服务器别名为代理服务器(Proxy Server)。等会分析到proxy源码,就更加清楚了。

二、proxy源码分析
以下是proxy的主程序。

int main(int argc, char **argv)
{
int clilen;
int childpid;
int sockfd, newsockfd;
struct sockaddr_in servaddr, cliaddr;
parse_args(argc, argv);//prepare an address struct to listen for connect
bzero((char*)&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = proxy_port;
//get asocket ..
if((sockfd = socket(AF_INET, SOCK_STREAM, )) < )
{
fputs("failed to crate server socket\r\n", stderr);
exit();
}
// and bind our address and port to it
if(bind(sockfd, (struct sockaddr_in*)&servaddr, sizeof(servaddr)) < )
{
fputs("failed to bind server socket to specified/r/n", stderr);
exit();
}
// get ready to accept with at most 5 clients waitting to connect
listen(sockfd, );
// turn ourselves into daemon
daemonize(sockfd);
//fall into a loop to accept new connections and spawn children
while()
{
//accept the next connection
clilen = sizeof(cliaddr);
newsockfd = accept(sockfd, (struct sockaddr*)&cliaddr, &clilen);
if(newsockfd < && errno == EINTR)
continue;
//a signal might interrupt our accept call
else if(newsockfd < )
errorout("failed to accept connection");//sth quiet amiss--kill server
//fork a child to handle this connection
if((childpid = fork()) == )
{
close(sockfd);// inherit
do_proxy(newsockfd);
exit();
}
// if fork falied the connection is silently dropped --oops
close(newsockfd);
}
return ;
}

从上面的程序看出,先是用函数parse_args(argc, argv) 解析获得用户输入的信息,包括想要绑定的代理服务器端口号、远程主机的host name 和 远程主机的服务号。获取之后设置到全局变量。接着用socket函数创建tcp 套接字,用bind函数绑定刚才获取到的端口号,用listen监听有没有客户端连接到代理服务器。然后,程序调用daemonize(sockfd)函数,这个函数的作用是创建一个守护进程。程序往下是在while循环里面调用accept函数阻塞,如果发现有客户端连接,就返回一个套接字,并fork出子进程处理这个套接字发过来的请求。do_proxy函数就是专门来干这事的。
do_proxy函数代码如下:

void do_proxy (int usersockfd)
{
int isosockfd;
fd_set rdfdset;
int connstat;
int iolen;
char buf[];
/* open a socket to connect to the isolated host */
if ((isosockfd = socket(AF_INET,SOCK_STREAM,)) < )
errorout("failed to create socket to host");
/* attempt a connection */
connstat = connect(isosockfd,(struct sockaddr *) &hostaddr, sizeof(hostaddr));
switch (connstat)
{
case :
break;
case ETIMEDOUT:
case ECONNREFUSED:
case ENETUNREACH:
strcpy(buf,sys_myerrlist[errno]);
strcat(buf,"\r\n");
write(usersockfd, buf, strlen(buf));
close(usersockfd);
exit();
/* die peacefully if we can't establish a connection */
break;
default:
errorout("failed to connect to host");
}
/* now we're connected, serve fall into the data echo loop */
while ()
{
/* Select for readability on either of our two sockets */
FD_ZERO(&rdfdset);
FD_SET(usersockfd,&rdfdset);
FD_SET(isosockfd,&rdfdset);
if (select(FD_SETSIZE,&rdfdset,NULL,NULL,NULL) < )
errorout("select failed");
/* is the client sending data? */
if (FD_ISSET(usersockfd,&rdfdset))
{
if ((iolen = read(usersockfd,buf,sizeof(buf))) <= )
break; /* zero length means the client disconnected */
write(isosockfd,buf,iolen);
/* copy to host -- blocking semantics */
}
/* is the host sending data? */
if(FD_ISSET(isosockfd,&rdfdset))
{
if((iolen = read(isosockfd,buf,sizeof(buf))) <= )
break; /* zero length means the host disconnected */
write(usersockfd,buf,iolen);
/* copy to client -- blocking semantics */
}
}
/* we're done with the sockets */
close(isosockfd);
close(usersockfd);
}

do_proxy函数是整个程序的核心,代理服务器的工作原理就是从这里体现出来的。函数有一个参数,是客户端连接好的套接字,也就是accept函数的返回值,可用于和客户端发送或者接收消息。在这个函数中,会再用socket函数创建一个新的套接字,这个套接字是用于和远程服务器连接的。在之前parse_args函数中,我们获取过远程服务器的主机名和服务端口号。接着调用connect函数向远程服务器发起连接请求。从这可以看出,代理服务器既是服务器,同时也是远程服务器的客户端。当两边都建立连接好以后,调用select函数,把两边的套接字描述符都加入监视。select函数可参考TCP socket select用法分析。如果发现是客户端A有发送信息请求访问远程服务器C时,本代理服务器就读取A的套接字描述符,把信息存放到buf中,然后再把buf中的数据写到C。反过来,当C有数据响应要发给A时,同样也是先发送给B,然后B再发送给A。

关于代码,这里再讲一下守护进程,函数如下所示:

void daemonize(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()) < )
{
fputs("failed to fork first child/r/n",stderr);
exit();
}
else if(childpid >)
exit();// terminate parent, continue child
//dissociate from process group
if (setpgrp(,getpid()) < )
{
fputs("failed to become process group leader/r/n",stderr);
exit();
}
/* lose controlling terminal */
if ((fd = open("/dev/tty", O_RDWR)) >= )
{
ioctl(fd,TIOCNOTTY,NULL);
close(fd);
}
/* close any open file descriptors */
for(fd = , fdtablesize = getdtablesize(); fd < fdtablesize; fd++)
if (fd != servfd)
close(fd);
/* set working directory to allow filesystems to be unmounted */
chdir("/");
/* clear the inherited umask */
umask();
/* setup zombie prevention */
signal(SIGCLD,(sigfunc *)reap_status);
}

从上面可以看出,程序调用signal忽略一些信号,然后父进程fork出子进程,父进程退出,子进程调用setpgrp(0,getpid()),与进程组分离。接着使子进程与终端脱离,关闭不相关的文件描述符,调用umask将文件模式创建屏蔽字设置为0,最后处理信号SIGCLD。所有这些过程都是为了创建一个守护进程。让程序能够在后台运行,并且不受终端控制。

三、proxy完整源码

/****************************************************************************
program: proxyd
module: proxyd.c
summary: provides proxy tcp service for a host on an isolated network.
programmer: Carl Harris (ceharris@vt.edu)
date: 22 Feb 94
description:
This code implements a daemon process which listens for tcp connec-
tions on a specified port number. When a connection is established,
a child is forked to handle the new client. The child then estab-
lishes a tcp connection to a port on the isolated host. The child
then falls into a loop in which it writes data to the isolated host
for the client and vice-versa. Once a child has been forked, the
parent resumes listening for additional connections.
The name of the isolated host and the port to serve as proxy for,
as well as the port number the server listen on are specified as
command line arguments.
****************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
//#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/file.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <netdb.h> #define TCP_PROTO "tcp" int proxy_port; //port to listen for proxy connections on
struct sockaddr_in hostaddr; //host addr assembled form gethostbyname()
extern int errno; //define by libc.a
char *sys_myerrlist[]; void parse_args(int argc, char **argv);
void daemonize(int servfd);
void do_proxy(int usersockfd);
void reap_status(void);
void errout(char *msg); typedef void sigfunc(int );//sigfunc可以声明一个函数类型
/*description:   Main level driver. After daemonizing the process,
* a socket is opened to listen for connections on the proxy port,
* connections are accepted and children are spawned to handle each
* new connection.
*/ int main(int argc, char **argv)
{
int clilen;
int childpid;
int sockfd, newsockfd;
struct sockaddr_in servaddr, cliaddr;
parse_args(argc, argv);//prepare an address struct to listen for connect
bzero((char*)&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = proxy_port;
//get asocket ..
if((sockfd = socket(AF_INET, SOCK_STREAM, )) < )
{
fputs("failed to crate server socket\r\n", stderr);
exit();
}
// and bind our address and port to it
if(bind(sockfd, (struct sockaddr_in*)&servaddr, sizeof(servaddr)) < )
{
fputs("failed to bind server socket to specified/r/n", stderr);
exit();
}
// get ready to accept with at most 5 clients waitting to connect
listen(sockfd, );
// turn ourselves into daemon
daemonize(sockfd);
//fall into a loop to accept new connections and spawn children
while()
{
//accept the next connection
clilen = sizeof(cliaddr);
newsockfd = accept(sockfd, (struct sockaddr*)&cliaddr, &clilen);
if(newsockfd < && errno == EINTR)
continue;
//a signal might interrupt our accept call
else if(newsockfd < )
errorout("failed to accept connection");//sth quiet amiss--kill server
//fork a child to handle this connection
if((childpid = fork()) == )
{
close(sockfd);// inherit
do_proxy(newsockfd);
exit();
}
// if fork falied the connection is silently dropped --oops
close(newsockfd);
}
return ;
} //parse the command line args
void parse_args(int argc, char **argv)
{
int i;
struct hostent *hostp;//host entry
struct servent *servp;
unsigned long inaddr;
struct {
char proxy_port[];
char isolate_host[];
char service_name[];
}pargs;
if(argc < )
{
printf("usage:%s<proxy-port> <host> <service-name|port-number> \r\n", argv[]);
exit();
}
strcpy(pargs.proxy_port, argv[]);
strcpy(pargs.isolate_host, argv[]);
strcpy(pargs.service_name, argv[]);
for(i=; i<strlen(pargs.proxy_port); i++)
if(!isdigit(*(pargs.proxy_port+i)))
break;
if(i == strlen(pargs.proxy_port))
proxy_port = htons(atoi(pargs.proxy_port));//port short
else
{
printf("invalid proxy port\r\n", pargs.proxy_port);
exit();
}
bzero(&hostaddr, sizeof(hostaddr));
hostaddr.sin_family = AF_INET;
if((inaddr= inet_addr(pargs.isolate_host))!= INADDR_NONE)
bcopy(&inaddr, &hostaddr.sin_addr, sizeof(inaddr));
else if((hostp == gethostbyname(pargs.isolate_host)) != NULL)
bcopy(hostp->h_addr,&hostaddr.sin_addr,hostp->h_length);
else
{
printf("%s unknow host \r\n", pargs.isolate_host);
exit();
}
if((servp = getservbyname(pargs.service_name, TCP_PROTO)) != NULL)
hostaddr.sin_port = servp->s_port;
else if(atoi(pargs.service_name) >)
hostaddr.sin_port = htons(atoi(pargs.service_name));
else
{
printf("%s invalid unknow service or port no.\r\n", pargs.service_name);
exit();
}
} /*detach the server process from the current context, creating a pristine,
* predictable environment in which it will */ void daemonize(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()) < )
{
fputs("failed to fork first child/r/n",stderr);
exit();
}
else if(childpid >)
exit();// terminate parent, continue child
//dissociate from process group
if (setpgrp(,getpid()) < )
{
fputs("failed to become process group leader/r/n",stderr);
exit();
}
/* lose controlling terminal */
if ((fd = open("/dev/tty", O_RDWR)) >= )
{
ioctl(fd,TIOCNOTTY,NULL);
close(fd);
}
/* close any open file descriptors */
for(fd = , fdtablesize = getdtablesize(); fd < fdtablesize; fd++)
if (fd != servfd)
close(fd);
/* set working directory to allow filesystems to be unmounted */
chdir("/");
/* clear the inherited umask */
umask();
/* setup zombie prevention */
signal(SIGCLD,(sigfunc *)reap_status);
} //handle a SIGCLD signal by reaping the exit status of the perished child,
//and discarding it.
void reap_status()
{
int pid;
union wait status;
while ((pid = wait3(&status,WNOHANG,NULL)) > )
; /* loop while there are more dead children */
} //does the actual work of virtually connecting a client to the telnet
//service on the isolated host. void do_proxy (int usersockfd)
{
int isosockfd;
fd_set rdfdset;
int connstat;
int iolen;
char buf[];
/* open a socket to connect to the isolated host */
if ((isosockfd = socket(AF_INET,SOCK_STREAM,)) < )
errorout("failed to create socket to host");
/* attempt a connection */
connstat = connect(isosockfd,(struct sockaddr *) &hostaddr, sizeof(hostaddr));
switch (connstat)
{
case :
break;
case ETIMEDOUT:
case ECONNREFUSED:
case ENETUNREACH:
strcpy(buf,sys_myerrlist[errno]);
strcat(buf,"\r\n");
write(usersockfd, buf, strlen(buf));
close(usersockfd);
exit();
/* die peacefully if we can't establish a connection */
break;
default:
errorout("failed to connect to host");
}
/* now we're connected, serve fall into the data echo loop */
while ()
{
/* Select for readability on either of our two sockets */
FD_ZERO(&rdfdset);
FD_SET(usersockfd,&rdfdset);
FD_SET(isosockfd,&rdfdset);
if (select(FD_SETSIZE,&rdfdset,NULL,NULL,NULL) < )
errorout("select failed");
/* is the client sending data? */
if (FD_ISSET(usersockfd,&rdfdset))
{
if ((iolen = read(usersockfd,buf,sizeof(buf))) <= )
break; /* zero length means the client disconnected */
write(isosockfd,buf,iolen);
/* copy to host -- blocking semantics */
}
/* is the host sending data? */
if(FD_ISSET(isosockfd,&rdfdset))
{
if((iolen = read(isosockfd,buf,sizeof(buf))) <= )
break; /* zero length means the host disconnected */
write(usersockfd,buf,iolen);
/* copy to client -- blocking semantics */
}
}
/* we're done with the sockets */
close(isosockfd);
close(usersockfd);
} //displays an error message on the console and kills the current process.
void errorout (char *msg)
{
FILE *console;
console = fopen("/dev/console","a");
fprintf(console,"proxyd: %s\r\n",msg);
fclose(console);
exit();
}

Proxy Server源码及分析(TCP Proxy源码 Socket实现端口映射)的更多相关文章

  1. WebBench压力测试工具(详细源码注释+分析)

    本文适合人群:对WebBench实现感兴趣的人 WebBench原理: Linux下使用的服务器压力测试工具,利用fork建立多个子进程,每个子进程在测试时间内不断发送请求报文,建立多个连接,然后由父 ...

  2. 转:Jmeter 用户思考时间(User think time),定时器,和代理服务器(proxy server)

    在负载测试中需要考虑的的一个重要要素是思考时间(think time), 也就是在两次成功的访问请求之间的暂停时间. 有多种情形挥发导致延迟的发生: 用户需要时间阅读文字内容,或者填表,或者查找正确的 ...

  3. DRF源码系列分析

    DRF源码系列分析 DRF源码系列分析--版本 DRF源码系列分析--认证 DRF源码系列分析--权限 DRF源码系列分析--节流

  4. 5.深入Istio源码:Pilot-agent作用及其源码分析

    转载请声明出处哦~,本篇文章发布于luozhiyun的博客:https://www.luozhiyun.com 本文使用的Istio源码是 release 1.5. 介绍 Sidecar在注入的时候会 ...

  5. urllib源码简单分析

    对下面这段代码做分析 import urllib params = urllib.urlencode({'wd': 'python'}) f = urllib.urlopen("http:/ ...

  6. 【Orleans开胃菜系列2】连接Connect源码简易分析

    [Orleans开胃菜系列2]连接Connect源码简易分析 /** * prism.js Github theme based on GitHub's theme. * @author Sam Cl ...

  7. Activity源码简要分析总结

    Activity源码简要分析总结 摘自参考书籍,只列一下结论: 1. Activity的顶层View是DecorView,而我们在onCreate()方法中通过setContentView()设置的V ...

  8. spring事务源码分析结合mybatis源码(三)

    下面将结合mybatis源码来分析下,这种持久化框架是如何对connection使用,来达到spring事务的控制. 想要在把mybatis跟spring整合都需要这样一个jar包:mybatis-s ...

  9. Proxy Server代理服务器(轉載)

    宽带IP城域网开通以来,单位连上了宽带网,10M的带宽让我们感受到了宽带的魅力.电信只提供7个IP地址,对任何一个单位来说都太少了,常用的解决办法是使用代理服务器.微软的MS Proxy Server ...

随机推荐

  1. Linu如何查看磁盘占用情况及处理办法

    free -h: 查看当前剩余的内存大小 df: 查看文件系统磁盘使用率,可能free -h得到的剩余空间还有很多,但是df查询得到的部分文件系统磁盘使用率较高 当发现磁盘使用率较高的时候,可以: 先 ...

  2. 【RAC】 RAC For W2K8R2 安装--grid的安装(四)

    [RAC] RAC For W2K8R2 安装--grid的安装(四) 一.1  BLOG文档结构图 一.2  前言部分 一.2.1  导读 各位技术爱好者,看完本文后,你可以掌握如下的技能,也可以学 ...

  3. MySQL存储过程02

    这次接着说MySQL存储过程: 我们先看它的多分支控制结构case: case的语句很简单: case 变量名 when 条件1 then 输出结果1; when 条件2 then 输出结果2; .. ...

  4. 利用ViewStub实现布局懒惰加载

    这个问题也是头条面试官问的,本身没什么难度,但以前确实没仔细研究过. 1.使用介绍 ViewStub是一种不可见的尺寸为0的View,用来实现布局资源的懒加载.当ViewStub被设置为用户可见或其  ...

  5. Python入门篇-StringIO和BytesIO

    Python入门篇-StringIO和BytesIO 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.StringIO(用于文本处理) 1>.使用案例 #!/usr/bin ...

  6. C++(四十五) — 类型转换(static_cast、dynamic_cast 、const_cast、reinterpreter_cast)

     0.总结 (1)要转换的变量,转换前.转换后.转换后的结果. (2)一般情况下,避免进行类型转换. 1._static_cast(静态类型转换,int 转换为char) 格式:TYPE B = st ...

  7. 次小生成树(lca)

    题目描述 原题来自:BeiJing 2010 组队赛 给定一张 N 个点 M 条边的无向图,求无向图的严格次小生成树. 设最小生成树的边权之和为 sum,严格次小生成树就是指边权之和大于 sum 的生 ...

  8. 深度学习Keras框架笔记之Dense类(标准的一维全连接层)

    深度学习Keras框架笔记之Dense类(标准的一维全连接层) 例: keras.layers.core.Dense(output_dim,init='glorot_uniform', activat ...

  9. 查询响应慢,DB近乎崩溃

    时间:18.11.22 一. 起由: 公司最近因业务,有大量注册,每天大约几万,貌似也不太高? 晚上8点左右,网站后台,前台突然大面积提示502.网站几乎瘫痪.买的阿里云的负载均衡和读写分离.分别是5 ...

  10. vscode——如何对MarkDown文件进行预览

    前言 一般都是用Typora直接进行编写了,今天恰好在vs中写完代码,就需要编辑文档,这里就记录下如何预览吧 步骤 ctrl+shift+p打开命令面板,然后输入markdowm->选择在侧边打 ...