在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/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()
...{
if( this->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;
}
- 在Socket通信中使用定时刷新技术跨越防火墙
- 怎么样在HTML中定时刷新
- NSTimer定时刷新,在使用uiscrollV…
- web 项目中 在spring中使用log4j 定时刷新log4j配置文件
- ionic中定时刷新
- Socket在局域网通信中应用
- 使用Socket在公网上通信
- 在Tomcat环境下使用socket通信
- socket 通信技术介绍
- java通信技术---socket
- Android中socket通信的简单使用
- PHP中如何使用socket进行通信?
- 如何在Windows环境下的VS中安装使用Google Protobuf完成SOCKET通信
- 如何在Windows环境下的VS中安装使用Google Protobuf完成SOCKET通信
- 如何在Windows环境下的VS中安装使用Google Protobuf完成SOCKET通信
- 如何在Windows环境下的VS中安装使用Google Protobuf完成SOCKET通信
- 如何在Windows环境下的VS中安装使用Google Protobuf完成SOCKET通信
- [protobuf] 如何在Windows环境下的VS中安装使用Google Protobuf完成SOCKET通信
- xFire实现Java间自定义类型的WebService调用(二)
- windows server 2003 IIS配置
- C# 中类库(dll)的创建和引用
- 一个新的家
- db2在windows上提供的服务
- 在Socket通信中使用定时刷新技术跨越防火墙
- MySQL中SQL优化和架构设计的一些简单想法
- asp.net分页资源
- 重装Windows,只用53款全免费软件:上 (xbeta译)
- 在ASP.NET 2.0中操作数据::为删除数据添加客户端确认
- Linux环境工作笔记(三)
- 至理名言
- CSDN博客开通了
- 重装Windows,只用53款全免费软件:下 (xbeta译)