目录
一、正向代理服务器
1.1 什么是正向代理服务器
客户端想要访问一个服务器,但是它可能访问这台服务器,这时候这可找一台可以访问目标服务器的另外一台服务器,而这台服务器就被当做是代理人的角色 ,称之为代理服务器,于是客户端把请求发给代理服务器,由代理服务器获得目标服务器的数据并返回给客户端。客户端是清楚目标服务器的地址的,而目标服务器是不清楚来自客户端,它只知道来自哪个代理服务器,所以正向代理可以屏蔽或隐藏客户端的信息。
正向代理服务器模型:
1.2 作用
- 突破自身IP访问界限(访问国外网站、教育网。。。)
- 提高访问速度(通常代理服务器都会设置一个较大的硬盘缓冲区,会将部分请求的响应保存到缓冲区中,若其他用户访问相同信息时,直接从缓冲区中取出信息)
- 隐藏用户的真是IP(上网者可通过这种方法隐藏自己的真实IP)
二、问题介绍
程序运行后,程序调用epoll_wait()等待事件,假设此时有事件发生,如果发生事件的socket为通信socket,调用recv()接收函数能够接收到数据,但是send()函数发送数据会失败。
三、程序代码
3.1 代码功能:
假设程序A需要和程序C通信,但是不想让程序C知道是谁与它通信,此时就用代码实现一个·代理服务程序B来作为中介使得二者通信;
该代理程序实现了:
客户端和服务端之间socket通信
mysql客户端登录mysql服务端
orecle客户端登录orecle服务端
ssh登录服务端
自定义数据服务总线
3.2 代码展示
/*
* 程序名:inetd.cpp (网络代理服务程序 源客户端ip隐藏) (中介)
* writer:cx
*
*/
#include"_public.h"
CLogFile logfile; //日志类
CPActive PActice; //心跳类
//
void EXIT(int sig); //指定信号处理
int initserver(int port); //初始化服务端端口号
int connectdst(const char*ip,int port); //绑定连接对端的ip和port号
bool LoadVroute(const char* inifilename); //加载参数文件中的内容到容器vroute中
/
int epollfd;
struct st_route
{
int listenport; //代理程序监听端口
char dstip[31]; //目标服务程序ip地址
int dstport; //目标服务程序端口号
int listensock; //代理服务器上的监听socket
}stroute;
vector<struct st_route>vroute; //存放从参数文件中读取的数据
#define MAXSOCK 1024
int clientsock[MAXSOCK]; //存放对端socket的值,下标为自身socket,值为对端socket
int clientatime[MAXSOCK]; //每个socket的最近心跳时间,用于超时检验
int main(int argc,char *argv[])
{
//帮助文档
if(argc!=3)
{
printf("Using:./inetd logfile inifile\n\n");
printf("Example:/project/tools1/bin/procctl 5 /project/tools1/bin/inetd /tmp/inetd.log /etc/inetd.conf\n\n\n");
printf("本程序演示\n\n");
return -1;
}
//关闭信号和IO
CloseIOAndSignal(true);signal(2,EXIT);signal(15,EXIT);
if(logfile.Open(argv[1],"a+")==false)
{ printf("logfile.Open(%s) failed.\n",argv[1]); return-1; }
//加载参数文件中的内容到容器vroute中
if(LoadVroute(argv[2])==false)return -1;
//初始化监听端口
for(int ii=0;ii<vroute.size();ii++)
{
vroute[ii].listensock=initserver(vroute[ii].listenport);
if(vroute[ii].listensock<0){logfile.Write("initserver() failed.\n"); EXIT(-1);}
fcntl(vroute[ii].listensock,F_SETFL,fcntl(vroute[ii].listensock,F_GETFD,0)|O_NONBLOCK);
}
epollfd=epoll_create(1); //创建一个epoll文件描述符
struct epoll_event eve;
for(int ii=0;ii<vroute.size();ii++)
{
eve.events=EPOLLIN; //此处是否需要用ET模式?
eve.data.fd=vroute[ii].listensock;
epoll_ctl(epollfd,EPOLL_CTL_ADD,eve.data.fd,&eve); //该函数的作用就是 对指定的fd文件描述符 执行EPOLL_CTL_ADD的操作
}
struct epoll_event eves[30];
//用epoll模型实现监听多个socket的动作
while(1)
{
int iret=epoll_wait(epollfd,eves,30,-1); //epoll_wait返回为I/O准备的文件描述符的数量
//epoll模型的超时机制设置
if(iret<0){perror("epoll() error.");break;}
//iret>0表示有事件发生,查看发生事件的socket
for(int ii=0;ii<iret;ii++)
{
logfile.Write("iret=%d,events=%d,data.fd=%d\n",iret,eves[ii].events,eves[ii].data.fd);
//监听端口有事件发生,处理客户端的连接 自身并且连接服务端
/
int jj;
for(jj=0;jj<vroute.size();jj++)
{
if( eves[ii].data.fd == vroute[jj].listensock )
{
struct sockaddr_in sock;
socklen_t socklen=sizeof(sock);
int srcsock=accept(eves[ii].data.fd,(struct sockaddr*)&sock,&socklen); //为和客户端通信分配socket
if(srcsock<0)break; //跳出循环是因为那些已经连接的sock>还需要通信
if(srcsock>MAXSOCK){ close(srcsock); break; } //跳出循环继续epoll监听即可
//代理服务器连接服务端
int dstsock=connectdst(vroute[jj].dstip,vroute[jj].dstport);
if(dstsock<0)break;
if(dstsock>MAXSOCK){ close(srcsock); break; }
logfile.Write("accept port on (%d) client(%d,%d) suscess\n",vroute[jj].dstport,srcsock,dstsock);
//将源端socket和对端socket加入可读事件
struct epoll_event eve1;
eve1.events=EPOLLIN;
eve1.data.fd=srcsock;
epoll_ctl(epollfd,EPOLL_CTL_ADD,srcsock,&eve1);
eve1.events=EPOLLIN;
eve1.data.fd=dstsock;
epoll_ctl(epollfd,EPOLL_CTL_ADD,dstsock,&eve1);
//更新socket数组中的值和最新时间
clientsock[srcsock]=dstsock;
clientatime[srcsock]=time(0);
clientsock[dstsock]=srcsock;
clientatime[dstsock]=time(0);
break;
}
}
if(jj<vroute.size())continue;
//通信端口有事件发生,接收客户端的报文并打包发送给服务端
/
char buffer[5001]; memset(buffer,0,sizeof(buffer));
int buflen=0;
if( (buflen=recv(eves[ii].data.fd,buffer,sizeof(buffer),0))<=0)
{
logfile.Write("now(%ld) client=(%d,%d) disconnect.\n",time(0),clientsock[clientsock[eves[ii].data.fd]],clientsock[eves[ii].data.fd]);
//关闭客户端和服务端的socket,并将数组的值置为0
close(eves[ii].data.fd);
close(clientsock[eves[ii].data.fd]);
clientsock[clientsock[eves[ii].data.fd]]=0;
clientsock[eves[ii].data.fd]=0;
continue;
}
else
{
logfile.Write("recv client(%d)'buferlen is %d\n",eves[ii].data.fd,buflen);
//将接收到的客户端报文发送给服务端 注意服务端的socket为 client[eves[ii].data.fd]
if(send(clientsock[eves[ii].data.fd],buffer,buflen,0)<=0)
{
logfile.Write("发送:%s to client(%d) failed.\n",buffer,clientsock[eves[ii].data.fd]);
close(eves[ii].data.fd);
close(clientsock[eves[ii].data.fd]);
clientsock[clientsock[eves[ii].data.fd]]=0;
clientsock[eves[ii].data.fd]=0;
continue;
}
}
}
}
return 0;
}
/etc/inetd.conf文件内容如下(内容是自己定义的):
demo01客户端示例程序代码:
#include <poll.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include<errno.h>
#include <unistd.h>
#include <fcntl.h>
int main(int argc,char *argv[])
{
if (argc!=3)
{
printf("Using:./demo01 ip port\nExample:./demo01 127.0.0.1 5005\n\n"); return -1;
}
// 第1步:创建客户端的socket。
int sockfd;
if ( (sockfd = socket(AF_INET,SOCK_STREAM,0))==-1) { perror("socket"); return -1; }
// 第2步:向服务器发起连接请求。
struct hostent* h;
if ( (h = gethostbyname(argv[1])) == 0 ) // 指定服务端的ip地址。
{ printf("gethostbyname failed.\n"); close(sockfd); return -1; }
struct sockaddr_in servaddr;
memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(atoi(argv[2])); // 指定服务端的通讯端口。
memcpy(&servaddr.sin_addr,h->h_addr,h->h_length);
//fcntl(sockfd,F_SETFL,O_NONBLOCK);
if (connect(sockfd, (struct sockaddr *)&servaddr,sizeof(servaddr)) != 0) // 向服务端发起连接清求。
{
perror("connect"); close(sockfd); return -1;
}
int iret;
char buffer[102400];
// 第3步:与服务端通讯,发送一个报文后等待回复,然后再发下一个报文。
for (int ii=0;ii<10;ii++)
{
memset(buffer,0,sizeof(buffer));
sprintf(buffer,"这是第%d个超级女生,编号%03d。",ii+1,ii+1);
if ( (iret=send(sockfd,buffer,strlen(buffer),0))<=0) // 向服务端发送请求报文。
{
if(errno==EAGAIN){printf("errno=%d,",errno);perror("send");printf("\n"); }
else {perror("send"); break;}
}
printf("发送:%s\n",buffer);
memset(buffer,0,sizeof(buffer));
/* struct pollfd pfd; //非阻塞时搭配使用
pfd.fd=sockfd;
pfd.events=POLLIN;
pfd.revents=0;
if( poll(&pfd,1,-1)<0 )break;
*/
if(iret=recv(sockfd,buffer,sizeof(buffer),0)<=0)
{
//if(errno==EAGAIN){printf("errno=%d\n",errno); perror("recv"); }
//else
{ perror("recv"); break; }
}
printf("接收:%s\n",buffer);
/* //非阻塞模式下阻塞接收大于缓冲区大小的内容
memset(buffer,0,sizeof(buffer));
while(1)
{
if ( (iret=recv(sockfd,buffer,10,0))<=0) // 接收服务端的回应报文。
{
if(errno==EAGAIN)break;
printf("iret=%d\n",iret); break;
}
printf("接收:%s\n",buffer);
}
*/
sleep(1); // 每隔一秒后再次发送报文。
}
// 第4步:关闭socket,释放资源。
close(sockfd);
}
demo02服务端示例程序代码:
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <fcntl.h>
int main(int argc,char *argv[])
{
if (argc!=2)
{
printf("Using:./demo02 port\nExample:./demo02 5005\n\n"); return -1;
}
// 第1步:创建服务端的socket。
int listenfd;
if ( (listenfd = socket(AF_INET,SOCK_STREAM,0))==-1) { perror("socket"); return -1; }
//fcntl(listenfd,F_SETFD,fcntl(listenfd,F_GETFD,0)|O_NONBLOCK);
// 第2步:把服务端用于通讯的地址和端口绑定到socket上。
struct sockaddr_in servaddr; // 服务端地址信息的数据结构。
memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_family = AF_INET; // 协议族,在socket编程中只能是AF_INET。
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 任意ip地址。
servaddr.sin_port = htons(atoi(argv[1])); // 指定通讯端口。
if (bind(listenfd,(struct sockaddr *)&servaddr,sizeof(servaddr)) != 0 )
{ perror("bind"); close(listenfd); return -1; }
int opt = 1; unsigned int len = sizeof(opt);
setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&opt,len);
// 第3步:把socket设置为监听模式。
if (listen(listenfd,5) != 0 ) { perror("listen"); close(listenfd); return -1; }
// 第4步:接受客户端的连接。
int clientfd; // 客户端的socket。
int socklen=sizeof(struct sockaddr_in); // struct sockaddr_in的大小
struct sockaddr_in clientaddr; // 客户端的地址信息。
clientfd=accept(listenfd,(struct sockaddr *)&clientaddr,(socklen_t*)&socklen);
if(clientfd<0){printf("errno=%d\n");return -1;}
printf("clientd=%d\n",clientfd);
printf("客户端(%s)已连接。\n",inet_ntoa(clientaddr.sin_addr));
int iret;
char buffer[1024];
// fcntl(clientfd,F_SETFD,fcntl(clientfd,F_GETFD,0)|O_NONBLOCK);
// 第5步:与客户端通讯,接收客户端发过来的报文后,回复ok。
while (1)
{
memset(buffer,0,sizeof(buffer));
if ( (iret=recv(clientfd,buffer,sizeof(buffer),0))<=0) // 接收客户端的请求报文。
{
printf("iret=%d,errno=%d,EAGAIN=%d",iret,errno,EAGAIN); break;
}
printf("接收:%s\n",buffer);
strcpy(buffer,"ok");
if ( (iret=send(clientfd,buffer,strlen(buffer),0))<=0) // 向客户端发送响应结果。
{ perror("send"); break; }
printf("发送:%s\n",buffer);
}
// 第6步:关闭socket,释放资源。
close(listenfd); close(clientfd);
}
客户端运行结果:只发送了第一条报文,没有收到回应报文
服务端运行结果:只显示代理程序连接成功,没有任何数据到达
代理服务程序运行结果(查看日志):
从下图中可以看到代理程序成功连接上了客户端和服务端,并且成功recv了客户端发送的"这是第1个超级女生,编号001。"
调用send()函数返回失败,errno=32; (发送报文到服务端失败);
errno=32的可能原因:
1.socket失败,与服务器端的链接没有成功,从而管道破裂。
2.服务端将客户端的socket断开,一样造成这样的问题。
我们errno=32的原因为第一种:1.socket失败,与服务器端的链接没有成功,从而管道破裂。
我们的代理程序send() "这是第1个超级女生,编号001”这段报文给服务端的时候,二者之间的连接还未建立,所以send()会失败,在这之后二者才建立起连接,以至于服务端根本就无法收到那条发送失败的报文,这就是 服务端 recv 一直阻塞等待报文但是没有任何数据的原因。
导致以上问题出现的原因:
代理服务程序中的 connect 服务端的socket为非阻塞;以至于服务端和客户端连接还没来的及建立,但是代理程序已经将报文发给服务端,当然,连接都未成功建立的情况下,send()失败是必然的
解决方法:
第一种就是connect服务端的socket默认阻塞;
第二种就是设置为非阻塞后sleep()一段时间;