在Socket通信中使用定时刷新技术跨越防火墙

来源:互联网 发布:域名301重定向 编辑:程序博客网 时间:2024/06/05 23:08

你写下了一段Socket 客户端代码,socket() , connect() , send()...,和本地服务器通信正常,以为基本OK了。但是,在访问远程服务器的时候,如果跨越防火墙,发现了一个奇怪的现象:本地和远程服务器的通信数据丢失了,客户端说:我明明发了数据,你为什么没有收到?服务器说:我确实没有收到你最近发送的数据。在双方的机器上用netstat 察看,都很正常,奇怪了?

原来,这里有一个BUG在偷偷咬你!

硬件防火墙在通信的两端中间会起到中转的作用,例如一个Internet真实地址位202.166.78.111的服务器,其可能对应的内网地址是192.168.10.22,当一个客户端去连接202.166.78.111的时候,其实是和防火墙在连接,防火墙再与服务器192.168.10.22连接。

防火墙对于长期不活动(没有数据流动)的连接,会偷偷地将其切断,而且并不通知通信的双方!而客户端/服务器端不会得到及时的通知,并作相应的处理,会一直以为这段socket 仍然有效,呵呵!

因此,有效的做法是要么去掉防火墙的这个功能,要么在每个连接上定时刷新。如何刷新?每隔一段时间,发送最简单的数据(可以是ping数据或其他类型),保持当前连接在防火墙上的新鲜有效。

这种情况在应用部署的情况中经常见到。比如很多系统会开发自己的数据库连接池,或借助于中间件(或应用服务器)提供的连接池,来提高系统的性能。但实际的情况往往是在应用服务器和数据库之间会有一道防火墙。在业务数据不是很多的情况下,连接池没有得到充分利用,会有很多一些连接处于闲置状态,导致这些连接长期没有及时刷新,会被防火墙悄悄切断,并且在应用服务器上捕捉不到异常,在用户体验上显示为阻塞状态。这时候,刷新连接的任务就落到开发应用本身了。(当然,有些应用服务器提供了定时刷新连接的选项)

在TCP Socket长连接情况下,可以设置Socket的keep_alive选项,让操作系统来保持连接的有效性。

以下例子,是 C++ 用 ACE Proactor实现的例子,实现的是针对每一个连接,在服务器端进行刷新。在实际情况中,建议由客户端来完成此操作。

USAGE:编译完后,启动服务器端,并启动多个TELNET 模拟客户端,例如在CMD提示符下多次输入:Telnet 127.0.0.1 12000 ,在各个客户端就能看到服务器定期发过来的keep alive 信息了。

 

#include "ace/Asynch_IO.h"
#include 
"ace/Proactor.h"
#include 
"ace/Atomic_Op.h"
#include 
"ace/OS_main.h"
#include 
"ace/OS_NS_sys_socket.h"
#include 
"ace/INET_Addr.h"
#include 
"ace/Asynch_Acceptor.h"
#include 
"ace/Map_Manager.h"
#include 
"ace/OS_NS_time.h"
/**
*用Proactor实现TCP定时器刷新,
*用法:客户端连接上该服务器以后,每隔一秒,从该服务器上接受当前时间的通知(Push Notification)
*/


//定义一个(连接,定时器id)对应的Map,以ACE_Thread_Mutex控制线程序列化读写操作
typedef ACE_Map_Manager<ACE_HANDLE,long,ACE_Thread_Mutex> SOCK_TIMER_MAP;
SOCK_TIMER_MAP g_st_map;

const char * get_date (char *s)
{  
  
static const char* months[] = {"Jan","Feb","Mar","Apr","May","Jun",
                                 
"Jul","Aug","Sep","Oct","Nov","Dec"}
;
  
static const char* days[] = {"Sun","Mon","Tue","Wed","Thu","Fri","Sat"};

  time_t tloc;
  
struct tm tms;
  
char * date_string = s;

  
if (ACE_OS::time (&tloc) != (time_t) -1
      
&& ACE_OS::gmtime_r (&tloc, &tms) != 0)
  
{
    ACE_OS::sprintf (date_string,
                     
"%s, %2.2d %s %4.4d %2.2d:%2.2d:%2.2d GMT",
                     days[tms.tm_wday], tms.tm_mday, months[tms.tm_mon],
                     tms.tm_year 
+ 1900, tms.tm_hour, tms.tm_min, tms.tm_sec);
  }

  
else
    date_string 
= 0;

  
return date_string;
}


class BroadcastService : public ACE_Service_Handler
{
public:

 
//异步接受连接
 virtual void handle_accept (const ACE_Asynch_Accept::Result &result)
 
{
  ACE_DEBUG((LM_DEBUG,
"ACCEPT "));
 }


 
//连接完成
 virtual void handle_connect (const ACE_Asynch_Connect::Result &result)
 
{
  ACE_DEBUG((LM_DEBUG,
"Connect "));
 }


//注册异步连接的完成事件
 virtual void open (ACE_HANDLE new_handle,  ACE_Message_Block &message_block)
 
{
  
this->handle (new_handle);  
  sockaddr_in remote_addr;
  
int remote_size = sizeof remote_addr;
  
if (ACE_OS::getpeername (new_handle,reinterpret_cast<sockaddr *> (&remote_addr), &remote_size) < 0)
   ACE_ERROR ((LM_ERROR, ACE_TEXT(
"%p "), ACE_TEXT("getpeername failed")));
  
else
   ACE_DEBUG(( LM_INFO,
"Connect from %s:%d ",inet_ntoa(remote_addr.sin_addr),ntohs(remote_addr.sin_port)));

  
if (this->reader_.open (*this!= 0 || this->writer_.open (*this!= 0 )
  
{
   ACE_ERROR ((LM_ERROR, ACE_TEXT (
"%p "),ACE_TEXT ("Service open")));
   delete 
this;
   
return;
  }


  ACE_Message_Block 
*mb;
  ACE_NEW_NORETURN (mb, ACE_Message_Block (
1024));
  
if (this->reader_.read (*mb, mb->space ()) != 0)
  
{
   ACE_ERROR ((LM_ERROR, ACE_TEXT (
"%p "),ACE_TEXT ("Service init read")));
   mb
->release ();
   delete 
this;
   
return;
  }

  
//生成定时器,并将id插入栈内存中的map
  long timerid = ScheduleTimer();
  g_st_map.bind(new_handle,timerid);  
 }


 
//异步读取客户端的发送内容
 virtual void handle_read_stream (const ACE_Asynch_Read_Stream::Result &result)
 
{
  ACE_Message_Block 
&mb = result.message_block ();
  
if (!result.success () || result.bytes_transferred () == 0)
  
{
   mb.release ();
   delete 
this;
  }

  
else
  
{
      
//简单Echo
   if (this->writer_.write (mb, mb.length ()) == -1)
   
{
    ACE_ERROR ((LM_ERROR,ACE_TEXT (
"%p "),ACE_TEXT ("starting write")));
    mb.release ();
   }

   
else
   
{
    ACE_Message_Block 
*new_mb;
    ACE_NEW_NORETURN (new_mb, ACE_Message_Block (
1024));
    
this->reader_.read (*new_mb, new_mb->space ());
   }

  }

  
  
return;


 }


 
//异步发送完毕,释放内存块
 virtual void handle_write_stream (const ACE_Asynch_Write_Stream::Result &result)
 
{
  ACE_Message_Block 
&mb = result.message_block ();
  mb.release ();
  
return;
 }


 
//处理定时器超时事件

 
virtual void handle_time_out (const ACE_Time_Value &tv,const void *act = 0)
 
{
  ACE_Message_Block 
*new_mb;
  ACE_NEW_NORETURN (new_mb, ACE_Message_Block (
128));
  
//取出当前事件戳,并插入发送字节流
  ACE_Time_Value tm = ACE_OS::gettimeofday();
  
char buf[128];
  new_mb
->copy(get_date(buf));
  new_mb
->copy(" I am from heaven ");
  
if(this->writer_.write(*new_mb,new_mb->length()) == -1)
  
{
   new_mb
->release();
  }

 }

// 取消定时器,
// 注意:在单个连接断开以后,必须取消定时器,否则会造成资源泄露
 void CancelTimer(ACE_HANDLE handle)
 
{
  
long timerid;
  g_st_map.find(
this->handle(),timerid);
  ACE_Proactor::instance()
->cancel_timer(timerid);
  g_st_map.unbind(
this->handle());
 }


 
long ScheduleTimer()
 
{
  ACE_Time_Value tv (
1);  // 定义定时器超时为 1 秒
  long timer_id= ACE_Proactor::instance()->schedule_timer(*this,
   
"Broadcast",
   ACE_Time_Value::zero,
   tv);
  
return timer_id;
 }

//当前服务被撤销,关闭socket
 ~BroadcastService()
 
{  
  
ifthis->handle()!=ACE_INVALID_HANDLE )
  
{
   CancelTimer(
this->handle());
   sockaddr_in remote_addr;
   
int remote_size = sizeof remote_addr;
   
if (ACE_OS::getpeername (this->handle(),reinterpret_cast<sockaddr *> (&remote_addr), &remote_size) < 0)
    ACE_ERROR ((LM_ERROR, ACE_TEXT(
"%p "), ACE_TEXT("getpeername failed")));
   
else
    ACE_DEBUG(( LM_INFO,
"Disconnect from %s:%d ",inet_ntoa(remote_addr.sin_addr),ntohs(remote_addr.sin_port)));

   ACE_OS::closesocket (
this->handle());
   
this->handle(ACE_INVALID_HANDLE);
  }

 }


private:
 ACE_Asynch_Read_Stream reader_;
 ACE_Asynch_Write_Stream writer_;
 
}
;

 

typedef ACE_Asynch_Acceptor  
< BroadcastService > MyAcceptor;

int ACE_TMAIN(int argc, ACE_TCHAR* argv[])
{
 
//定义监听地址
 const char* str = "0.0.0.0:12000";
 ACE_INET_Addr addr(str); 

 MyAcceptor server; 

 
if(server.open(addr) == -1)
 
{
  ACE_DEBUG ((LM_DEBUG,ACE_TEXT (
"(%P|%t) %p "),ACE_TEXT ("bind failed")));return 1;
 }

 
//循环处理Proactor的异步事件
 while(1){
  ACE_Proactor::instance ()
->proactor_run_event_loop ();
 }

 
return 0;
}


原创粉丝点击