实现epoll机制对IO进行管理实现正向代理服务程序

目录

一、正向代理服务器

         1.1 什么是正向代理服务器 

         1.2 作用

二、问题介绍

三、程序代码

        3.1 代码功能

         3.2 代码展示


一、正向代理服务器

  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()一段时间;

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值