当前位置:  编程技术>移动开发
本页文章导读:
    ▪播发音频声音文件        播放音频声音文件 iphone开发中播放声音文件主要使用AVAudioPlayer 类,它的功能非常强大支持播放音频的格式也非常的多,我们可以把它看成一个高级的音乐播放器,它支持的播放格式有 .........
    ▪ TCP兑现P2P通信、TCP穿越NAT的方法、TCP打洞        TCP实现P2P通信、TCP穿越NAT的方法、TCP打洞 这个标题用了两个顿号三个名称,其实说得是同一个东西,只是网上有不同的说法罢了,另外好像还有人叫TCP打孔(我的朋友小妞听说后问“要打.........
    ▪ Java中HashCode()懂得       Java中HashCode()理解 先从一个java基础面试的问题开始引入吧:问题:两个对象值相同(x.equals(y) == true),但却可有不同的hash code,这句话对不对? 答案:不对,有相同的hash code。下面用我收集.........

[1]播发音频声音文件
    来源: 互联网  发布时间: 2014-02-18
播放音频声音文件

iphone开发中播放声音文件主要使用AVAudioPlayer 类,它的功能非常强大支持播放音频的格式也非常的多,我们可以把它看成一个高级的音乐播放器,它支持的播放格式有
■ AAC
■ AMR(AdaptiveMulti-Rate, aformatforspeech)
■ ALAC(AppleLossless)
■ iLBC(internetLowBitrateCodec, anotherformatforspeech)
■ IMA4(IMA/ADPCM)
■ linearPCM(uncompressed)
■ µ-lawanda-law
■ MP3(MPEG-1audiolayer3

今天主要介绍一下播放mp3 . 




       AVAudioPlayer 是 AVFoundation.framework 中定义的一个类,所以使用要先在工程中引入AVFoundation.framework 如图所示点击"+"号将AVFoundation导入。




       

 将音频文件放入资源文件夹中




下面我开始介绍代码中如何调用AVAudioPlayer 播放音频文件

  声明类
#import "playSoundViewController.h"
#import <UIKit/UIKit.h>
#import <AVFoundation/AVFoundation.h>
@interface playSoundViewController : UIViewController {
    
    IBOutlet UIButton * playSound;//播放音乐  
    IBOutlet UIButton * playPause;//播放暂停  
    IBOutlet UIButton * playStop;//播放停止  
    //定义一个声音的播放器
    AVAudioPlayer *player;
}

-(IBAction)playSoundPressed:(id)pressed;
-(IBAction)playPausePressed:(id)pressed;
-(IBAction)playStopPressed:(id)pressed;
@end
 

@implementation playSoundViewController

- (void)dealloc
{
    [super dealloc];
    //程序的严谨性 在显示对象关闭后把相应的对象清空
    //时刻谨记
    [playSound release];
    [player release];
}

- (void)didReceiveMemoryWarning
{
    // Releases the view if it doesn't have a superview.
    [super didReceiveMemoryWarning];
    
    // Release any cached data, images, etc that aren't in use.
}

#pragma mark - View lifecycle


// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad
{
    [super viewDidLoad];
    //在这里实现声音的播放代码
    //找到mp3在资源库中的路径 文件名称为sound 类型为mp3
    NSString *path = [[NSBundle mainBundle] pathForResource:@"sound" ofType:@"mp3"];
    //在这里判断以下是否能找到这个音乐文件
    if (path) {
        //从path路径中 加载播放器
        player = [[AVAudioPlayer alloc]initWithContentsOfURL:[[NSURL alloc]initFileURLWithPath:path]error:nil];
        //初始化播放器
        [player prepareToPlay];
        
        //设置播放循环次数,如果numberOfLoops为负数 音频文件就会一直循环播放下去
        player.numberOfLoops = -1;
        
        //设置音频音量 volume的取值范围在 0.0为最小 0.1为最大 可以根据自己的情况而设置
        player.volume = 0.5f;
        
        NSLog(@"播放加载");
    }
    
}

-(void)playSoundPressed:(id)pressed
{
    //点击按钮后开始播放音乐
    //当player有值的情况下并且没有在播放中 开始播放音乐
    if (player) 
    {
        if (![player isPlaying]) 
        {
            [player play];
            NSLog(@"播放开始");
        }
    }
}

-(void)playPausePressed:(id)pressed
{
    //暂停播放声音
    if (player) {
        if ([player isPlaying]) {
            [player pause];
            NSLog(@"播放暂停");
        }
    }
}

-(void)playStopPressed:(id)pressed
{
    //停止播放声音
    if (player) {
        if ([player isPlaying]) {
            [player stop];
            NSLog(@"播放停止");
        }
    }
}


- (void)viewDidUnload
{
    [super viewDidUnload];
    // Release any retained subviews of the main view.
    // e.g. self.myOutlet = nil;
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    // Return YES for supported orientations
    return (interfaceOrientation == UIInterfaceOrientationPortrait);
}

@end
 

    
[2] TCP兑现P2P通信、TCP穿越NAT的方法、TCP打洞
    来源: 互联网  发布时间: 2014-02-18
TCP实现P2P通信、TCP穿越NAT的方法、TCP打洞
这个标题用了两个顿号三个名称,其实说得是同一个东西,只是网上有不同的说法罢了,另外好像还有人叫TCP打孔(我的朋友小妞听说后问“要打孔啊,要不要我帮你去借个电钻过来啊?”“~!·¥%……·!”)。

闲话少说,我们先看一下技术背景:
Internet的迅速发展以及IPv4 地址数量的限制使得网络地址翻译(NAT,Network Address Trans2lation)设备得到广泛应用。NAT设备允许处于同一NAT后的多台主机共享一个公网(本文将处于同一NAT后的网络称为私网,处于NAT前的网络称为公网) IP 地址。一个私网IP 地址通过NAT设备与公网的其他主机通信。公网和私网IP地址域,如下图所示:


广域网与私网示意图

一般来说都是由私网内主机(例如上图中“电脑A-01”)主动发起连接,数据包经过NAT地址转换后送给公网上的服务器(例如上图中的“Server”),连接建立以后可双向传送数据,NAT设备允许私网内主机主动向公网内主机发送数据,但却禁止反方向的主动传递,但在一些特殊的场合需要不同私网内的主机进行互联(例如P2P软件、网络会议、视频传输等),TCP穿越NAT的问题必须解决。网上关于UDP穿越NAT的文章很多,而且还有配套源代码,但是我个人认为UDP数据虽然速度快,但是没有保障,而且NAT为UDP准备的临时端口号有生命周期的限制,使用起来不够方便,在需要保证传输质量的应用上TCP连接还是首选(例如:文件传输)。
网上也有不少关于TCP穿越NAT(即TCP打洞)的介绍文章,但不幸我还没找到相关的源代码可以参考,我利用空余时间写了一个可以实现TCP穿越NAT,让不同的私网内主机建立直接的TCP通信的源代码。

这里需要介绍一下NAT的类型:
NAT设备的类型对于TCP穿越NAT,有着十分重要的影响,根据端口映射方式,NAT可分为如下4类,前3种NAT类型可统称为cone类型。
(1)全克隆( Full Cone) : NAT把所有来自相同内部IP地址和端口的请求映射到相同的外部IP地址和端口。任何一个外部主机均可通过该映射发送IP包到该内部主机。
(2)限制性克隆(Restricted Cone) : NAT把所有来自相同内部IP地址和端口的请求映射到相同的外部IP地址和端口。但是,只有当内部主机先给IP地址为X的外部主机发送IP包,该外部主机才能向该内部主机发送IP包。
(3)端口限制性克隆( Port Restricted Cone) :端口限制性克隆与限制性克隆类似,只是多了端口号的限制,即只有内部主机先向IP地址为X,端口号为P的外部主机发送1个IP包,该外部主机才能够把源端口号为P的IP包发送给该内部主机。
(4)对称式NAT ( Symmetric NAT) :这种类型的NAT与上述3种类型的不同,在于当同一内部主机使用相同的端口与不同地址的外部主机进行通信时, NAT对该内部主机的映射会有所不同。对称式NAT不保证所有会话中的私有地址和公开IP之间绑定的一致性。相反,它为每个新的会话分配一个新的端口号。

我们先假设一下:有一个服务器S在公网上有一个IP,两个私网分别由NAT-A和NAT-B连接到公网,NAT-A后面有一台客户端A,NAT-B后面有一台客户端B,现在,我们需要借助S将A和B建立直接的TCP连接,即由B向A打一个洞,让A可以沿这个洞直接连接到B主机,就好像NAT-B不存在一样。
实现过程如下(请参照源代码):
1、 S启动两个网络侦听,一个叫【主连接】侦听,一个叫【协助打洞】的侦听。
2、 A和B分别与S的【主连接】保持联系。
3、 当A需要和B建立直接的TCP连接时,首先连接S的【协助打洞】端口,并发送协助连接申请。同时在该端口号上启动侦听。注意由于要在相同的网络终端上绑定到不同的套接字上,所以必须为这些套接字设置 SO_REUSEADDR 属性(即允许重用),否则侦听会失败。
4、 S的【协助打洞】连接收到A的申请后通过【主连接】通知B,并将A经过NAT-A转换后的公网IP地址和端口等信息告诉B。
5、 B收到S的连接通知后首先与S的【协助打洞】端口连接,随便发送一些数据后立即断开,这样做的目的是让S能知道B经过NAT-B转换后的公网IP和端口号。
6、 B尝试与A的经过NAT-A转换后的公网IP地址和端口进行connect,根据不同的路由器会有不同的结果,有些路由器在这个操作就能建立连接(例如我用的TPLink R402),大多数路由器对于不请自到的SYN请求包直接丢弃而导致connect失败,但NAT-A会纪录此次连接的源地址和端口号,为接下来真正的连接做好了准备,这就是所谓的打洞,即B向A打了一个洞,下次A就能直接连接到B刚才使用的端口号了。
7、 客户端B打洞的同时在相同的端口上启动侦听。B在一切准备就绪以后通过与S的【主连接】回复消息“我已经准备好”,S在收到以后将B经过NAT-B转换后的公网IP和端口号告诉给A。
8、 A收到S回复的B的公网IP和端口号等信息以后,开始连接到B公网IP和端口号,由于在步骤6中B曾经尝试连接过A的公网IP地址和端口,NAT-A纪录了此次连接的信息,所以当A主动连接B时,NAT-B会认为是合法的SYN数据,并允许通过,从而直接的TCP连接建立起来了。

整个实现过程靠文字恐怕很难讲清楚,再加上我的语言表达能力很差(高考语文才考75分,总分150分,惭愧),所以只好用代码来说明问题了。


// 服务器地址和端口号定义
#define SRV_TCP_MAIN_PORT 4000 // 服务器主连接的端口号
#define SRV_TCP_HOLE_PORT 8000 // 服务器响应客户端打洞申请的端口号

这两个端口是固定的,服务器S启动时就开始侦听这两个端口了。//
// 将新客户端登录信息发送给所有已登录的客户端,但不发送给自己
//
BOOL SendNewUserLoginNotifyToAll ( LPCTSTR lpszClientIP, UINT nClientPort, DWORD dwID )
{
ASSERT ( lpszClientIP && nClientPort > 0 );
g_CSFor_PtrAry_SockClient.Lock();
for ( int i=0; i<g_PtrAry_SockClient.GetSize(); i++ )
{
CSockClient *pSockClient = (CSockClient*)g_PtrAry_SockClient.GetAt(i);
if ( pSockClient && pSockClient->m_bMainConn && pSockClient->m_dwID > 0 && pSockClient->m_dwID != dwID )
{
if ( !pSockClient->SendNewUserLoginNotify ( lpszClientIP, nClientPort, dwID ) )
{
g_CSFor_PtrAry_SockClient.Unlock();
return FALSE;
}
}
}

g_CSFor_PtrAry_SockClient.Unlock ();
return TRUE;
}

当有新的客户端连接到服务器时,服务器负责将该客户端的信息(IP地址、端口号)发送给其他客户端。//
// 执行者:客户端A
// 有新客户端B登录了,我(客户端A)连接服务器端口 SRV_TCP_HOLE_PORT ,申请与客户端B建立直接的TCP连接
//
BOOL Handle_NewUserLogin ( CSocket &MainSock, t_NewUserLoginPkt *pNewUserLoginPkt )
{
printf ( "New user ( %s:%u:%u ) login server\n", pNewUserLoginPkt->szClientIP,
pNewUserLoginPkt->nClientPort, pNewUserLoginPkt->dwID );

BOOL bRet = FALSE;
DWORD dwThreadID = 0;
t_ReqConnClientPkt ReqConnClientPkt;
CSocket Sock;
CString csSocketAddress;
char szRecvBuffer[NET_BUFFER_SIZE] = {0};
int nRecvBytes = 0;
// 创建打洞Socket,连接服务器协助打洞的端口号 SRV_TCP_HOLE_PORT
try
{
if ( !Sock.Socket () )
{
printf ( "Create socket failed : %s\n", hwFormatMessage(GetLastError()) );
goto finished;
}
UINT nOptValue = 1;
if ( !Sock.SetSockOpt ( SO_REUSEADDR, &nOptValue , sizeof(UINT) ) )
{
printf ( "SetSockOpt socket failed : %s\n", hwFormatMessage(GetLastError()) );
goto finished;
}
if ( !Sock.Bind ( 0 ) )
{
printf ( "Bind socket failed : %s\n", hwFormatMessage(GetLastError()) );
goto finished;
}
if ( !Sock.Connect ( g_pServerAddess, SRV_TCP_HOLE_PORT ) )
{
printf ( "Connect to [%s:%d] failed : %s\n", g_pServerAddess,
SRV_TCP_HOLE_PORT, hwFormatMessage(GetLastError()) );
goto finished;
}
}
catch ( CException e )
{
char szError[255] = {0};
e.GetErrorMessage( szError, sizeof(szError) );
printf ( "Exception occur, %s\n", szError );
goto finished;
}
g_pSock_MakeHole = &Sock;
ASSERT ( g_nHolePort == 0 );
VERIFY ( Sock.GetSockName ( csSocketAddress, g_nHolePort ) );

// 创建一个线程来侦听端口 g_nHolePort 的连接请求
dwThreadID = 0;
g_hThread_Listen = ::CreateThread ( NULL, 0, ::ThreadProc_Listen, LPVOID(NULL), 0, &dwThreadID );
if (!HANDLE_IS_VALID(g_hThread_Listen) ) return FALSE;
Sleep ( 3000 );

// 我(客户端A)向服务器协助打洞的端口号 SRV_TCP_HOLE_PORT 发送申请,希望与新登录的客户端B建立连接
// 服务器会将我的打洞用的外部IP和端口号告诉客户端B
ASSERT ( g_WelcomePkt.dwID > 0 );
ReqConnClientPkt.dwInviterID = g_WelcomePkt.dwID;
ReqConnClientPkt.dwInvitedID = pNewUserLoginPkt->dwID;
if ( Sock.Send ( &ReqConnClientPkt, sizeof(t_ReqConnClientPkt) ) != sizeof(t_ReqConnClientPkt) )
goto finished;

// 等待服务器回应,将客户端B的外部IP地址和端口号告诉我(客户端A)
nRecvBytes = Sock.Receive ( szRecvBuffer, sizeof(szRecvBuffer) );
if ( nRecvBytes > 0 )
{
ASSERT ( nRecvBytes == sizeof(t_SrvReqDirectConnectPkt) );
PACKET_TYPE *pePacketType = (PACKET_TYPE*)szRecvBuffer;
ASSERT ( pePacketType && *pePacketType == PACKET_TYPE_TCP_DIRECT_CONNECT );
Sleep ( 1000 );
Handle_SrvReqDirectConnect ( (t_SrvReqDirectConnectPkt*)szRecvBuffer );
printf ( "Handle_SrvReqDirectConnect end\n" );
}
// 对方断开连接了
else
{
goto finished;
}

bRet = TRUE;
finished:
g_pSock_MakeHole = NULL;
return bRet;

}

这里假设客户端A先启动,当客户端B启动后客户端A将收到服务器S的新客户端登录的通知,并得到客户端B的公网IP和端口,客户端A启动线程连接S的【协助打洞】端口(本地端口号可以用GetSocketName()函数取得,假设为M),请求S协助TCP打洞,然后启动线程侦听该本地端口(前面假设的M)上的连接请求,然后等待服务器的回应。//
// 客户端A请求我(服务器)协助连接客户端B,这个包应该在打洞Socket中收到
//
BOOL CSockClient::Handle_ReqConnClientPkt(t_ReqConnClientPkt *pReqConnClientPkt)
{
ASSERT ( !m_bMainConn );
CSockClient *pSockClient_B = FindSocketClient ( pReqConnClientPkt->dwInvitedID );
if ( !pSockClient_B ) return FALSE;
printf ( "%s:%u:%u invite %s:%u:%u connection\n", m_csPeerAddress, m_nPeerPort, m_dwID,
pSockClient_B->m_csPeerAddress, pSockClient_B->m_nPeerPort, pSockClient_B->m_dwID );

// 客户端A想要和客户端B建立直接的TCP连接,服务器负责将A的外部IP和端口号告诉给B
t_SrvReqMakeHolePkt SrvReqMakeHolePkt;
SrvReqMakeHolePkt.dwInviterID = pReqConnClientPkt->dwInviterID;
SrvReqMakeHolePkt.dwInviterHoleID = m_dwID;
SrvReqMakeHolePkt.dwInvitedID = pReqConnClientPkt->dwInvitedID;
STRNCPY_CS ( SrvReqMakeHolePkt.szClientHoleIP, m_csPeerAddress );
SrvReqMakeHolePkt.nClientHolePort = m_nPeerPort;
if ( pSockClient_B->SendChunk ( &SrvReqMakeHolePkt, sizeof(t_SrvReqMakeHolePkt), 0 ) != sizeof(t_SrvReqMakeHolePkt) )
return FALSE;

// 等待客户端B打洞完成,完成以后通知客户端A直接连接客户端外部IP和端口号
if ( !HANDLE_IS_VALID(m_hEvtWaitClientBHole) )
return FALSE;
if ( WaitForSingleObject ( m_hEvtWaitClientBHole, 6000*1000 ) == WAIT_OBJECT_0 )
{
if ( SendChunk ( &m_SrvReqDirectConnectPkt, sizeof(t_SrvReqDirectConnectPkt), 0 )
== sizeof(t_SrvReqDirectConnectPkt) )
return TRUE;
}

return FALSE;
}

服务器S收到客户端A的协助打洞请求后通知客户端B,要求客户端B向客户端A打洞,即让客户端B尝试与客户端A的公网IP和端口进行connect。//
// 执行者:客户端B
// 处理服务器要我(客户端B)向另外一个客户端(A)打洞,打洞操作在线程中进行。
// 先连接服务器协助打洞的端口号 SRV_TCP_HOLE_PORT ,通过服务器告诉客户端A我(客户端B)的外部IP地址和端口号,然后启动线程进行打洞,
// 客户端A在收到这些信息以后会发起对我(客户端B)的外部IP地址和端口号的连接(这个连接在客户端B打洞完成以后进行,所以
// 客户端B的NAT不会丢弃这个SYN包,从而连接能建立)
//
BOOL Handle_SrvReqMakeHole ( CSocket &MainSock, t_SrvReqMakeHolePkt *pSrvReqMakeHolePkt )
{
ASSERT ( pSrvReqMakeHolePkt );
// 创建Socket,连接服务器协助打洞的端口号 SRV_TCP_HOLE_PORT,连接建立以后发送一个断开连接的请求给服务器,然后连接断开
// 这里连接的目的是让服务器知道我(客户端B)的外部IP地址和端口号,以通知客户端A
CSocket Sock;
try
{
if ( !Sock.Create () )
{
printf ( "Create socket failed : %s\n", hwFormatMessage(GetLastError()) );
return FALSE;
}
if ( !Sock.Connect ( g_pServerAddess, SRV_TCP_HOLE_PORT ) )
{
printf ( "Connect to [%s:%d] failed : %s\n", g_pServerAddess,
SRV_TCP_HOLE_PORT, hwFormatMessage(GetLastError()) );
return FALSE;
}
}
catch ( CException e )
{
char szError[255] = {0};
e.GetErrorMessage( szError, sizeof(szError) );
printf ( "Exception occur, %s\n", szError );
return FALSE;
}

CString csSocketAddress;
ASSERT ( g_nHolePort == 0 );
VERIFY ( Sock.GetSockName ( csSocketAddress, g_nHolePort ) );

// 连接服务器协助打洞的端口号 SRV_TCP_HOLE_PORT,发送一个断开连接的请求,然后将连接断开,服务器在收到这个包的时候也会将
// 连接断开
t_ReqSrvDisconnectPkt ReqSrvDisconnectPkt;
ReqSrvDisconnectPkt.dwInviterID = pSrvReqMakeHolePkt->dwInvitedID;
ReqSrvDisconnectPkt.dwInviterHoleID = pSrvReqMakeHolePkt->dwInviterHoleID;
ReqSrvDisconnectPkt.dwInvitedID = pSrvReqMakeHolePkt->dwInvitedID;
ASSERT ( ReqSrvDisconnectPkt.dwInvitedID == g_WelcomePkt.dwID );
if ( Sock.Send ( &ReqSrvDisconnectPkt, sizeof(t_ReqSrvDisconnectPkt) ) != sizeof(t_ReqSrvDisconnectPkt) )
return FALSE;
Sleep ( 100 );
Sock.Close ();

// 创建一个线程来向客户端A的外部IP地址、端口号打洞
t_SrvReqMakeHolePkt *pSrvReqMakeHolePkt_New = new t_SrvReqMakeHolePkt;
if ( !pSrvReqMakeHolePkt_New ) return FALSE;
memcpy ( pSrvReqMakeHolePkt_New, pSrvReqMakeHolePkt, sizeof(t_SrvReqMakeHolePkt) );
DWORD dwThreadID = 0;
g_hThread_MakeHole = ::CreateThread ( NULL, 0, ::ThreadProc_MakeHole,
LPVOID(pSrvReqMakeHolePkt_New), 0, &dwThreadID );
if (!HANDLE_IS_VALID(g_hThread_MakeHole) ) return FALSE;

// 创建一个线程来侦听端口 g_nHolePort 的连接请求
dwThreadID = 0;
g_hThread_Listen = ::CreateThread ( NULL, 0, ::ThreadProc_Listen, LPVOID(NULL), 0, &dwThreadID );
if (!HANDLE_IS_VALID(g_hThread_Listen) ) return FALSE;

// 等待打洞和侦听完成
HANDLE hEvtAry[] = { g_hEvt_ListenFinished, g_hEvt_MakeHoleFinished };
if ( ::WaitForMultipleObjects ( LENGTH(hEvtAry), hEvtAry, TRUE, 30*1000 ) == WAIT_TIMEOUT )
return FALSE;
t_HoleListenReadyPkt HoleListenReadyPkt;
HoleListenReadyPkt.dwInvitedID = pSrvReqMakeHolePkt->dwInvitedID;
HoleListenReadyPkt.dwInviterHoleID = pSrvReqMakeHolePkt->dwInviterHoleID;
HoleListenReadyPkt.dwInvitedID = pSrvReqMakeHolePkt->dwInvitedID;
if ( MainSock.Send ( &HoleListenReadyPkt, sizeof(t_HoleListenReadyPkt) ) != sizeof(t_HoleListenReadyPkt) )
{
printf ( "Send HoleListenReadyPkt to %s:%u failed : %s\n",
g_WelcomePkt.szClientIP, g_WelcomePkt.nClientPort,
hwFormatMessage(GetLastError()) );
return FALSE;
}

return TRUE;
}

客户端B收到服务器S的打洞通知后,先连接S的【协助打洞】端口号(本地端口号可以用GetSocketName()函数取得,假设为X),启动线程尝试连接客户端A的公网IP和端口号,根据路由器不同,连接情况各异,如果运气好直接连接就成功了,即使连接失败,但打洞便完成了。同时还要启动线程在相同的端口(即与S的【协助打洞】端口号建立连接的本地端口号X)上侦听到来的连接,等待客户端A直接连接该端口号。//
// 执行者:客户端A
// 服务器要求主动端(客户端A)直接连接被动端(客户端B)的外部IP和端口号
//
BOOL Handle_SrvReqDirectConnect ( t_SrvReqDirectConnectPkt *pSrvReqDirectConnectPkt )
{
ASSERT ( pSrvReqDirectConnectPkt );
printf ( "You can connect direct to ( IP:%s  PORT:%d  ID:%u )\n", pSrvReqDirectConnectPkt->szInvitedIP,
pSrvReqDirectConnectPkt->nInvitedPort, pSrvReqDirectConnectPkt->dwInvitedID );

// 直接与客户端B建立TCP连接,如果连接成功说明TCP打洞已经成功了。
CSocket Sock;
try
{
if ( !Sock.Socket () )
{
printf ( "Create socket failed : %s\n", hwFormatMessage(GetLastError()) );
return FALSE;
}
UINT nOptValue = 1;
if ( !Sock.SetSockOpt ( SO_REUSEADDR, &nOptValue , sizeof(UINT) ) )
{
printf ( "SetSockOpt socket failed : %s\n", hwFormatMessage(GetLastError()) );
return FALSE;
}
if ( !Sock.Bind ( g_nHolePort ) )
{
printf ( "Bind socket failed : %s\n", hwFormatMessage(GetLastError()) );
return FALSE;
}
for ( int ii=0; ii<100; ii++ )
{
if ( WaitForSingleObject ( g_hEvt_ConnectOK, 0 ) == WAIT_OBJECT_0 )
break;
DWORD dwArg = 1;
if ( !Sock.IOCtl ( FIONBIO, &dwArg ) )
{
printf ( "IOCtl failed : %s\n", hwFormatMessage(GetLastError()) );
}
if ( !Sock.Connect ( pSrvReqDirectConnectPkt->szInvitedIP, pSrvReqDirectConnectPkt->nInvitedPort ) )
{
printf ( "Connect to [%s:%d] failed : %s\n",
pSrvReqDirectConnectPkt->szInvitedIP,
pSrvReqDirectConnectPkt->nInvitedPort,
hwFormatMessage(GetLastError()) );
Sleep (100);
}
else break;
}
if ( WaitForSingleObject ( g_hEvt_ConnectOK, 0 ) != WAIT_OBJECT_0 )
{
if ( HANDLE_IS_VALID ( g_hEvt_ConnectOK ) ) SetEvent ( g_hEvt_ConnectOK );
printf ( "Connect to [%s:%d] successfully !!!\n",
pSrvReqDirectConnectPkt->szInvitedIP, pSrvReqDirectConnectPkt->nInvitedPort );

// 接收测试数据
printf ( "Receiving data ...\n" );
char szRecvBuffer[NET_BUFFER_SIZE] = {0};
int nRecvBytes = 0;
for ( int i=0; i<1000; i++ )
{
nRecvBytes = Sock.Receive ( szRecvBuffer, sizeof(szRecvBuffer) );
if ( nRecvBytes > 0 )
{
printf ( "-->>> Received Data : %s\n", szRecvBuffer );
memset ( szRecvBuffer, 0, sizeof(szRecvBuffer) );
SLEEP_BREAK ( 1 );
}
else
{
SLEEP_BREAK ( 300 );
}
}
}
}
catch ( CException e )
{
char szError[255] = {0};
e.GetErrorMessage( szError, sizeof(szError) );
printf ( "Exception occur, %s\n", szError );
return FALSE;
}

return TRUE;
}

在客户端B打洞和侦听准备好以后,服务器S回复客户端A,客户端A便直接与客户端B的公网IP和端口进行连接,收发数据可以正常进行,为了测试是否真正地直接TCP连接,在数据收发过程中可以将服务器S强行终止,看是否数据收发还正常进行着。

程序执行步骤和方法:

要准备好环境,如果要真实测试的话需要用2个连到公网上的局域网,1台具有公网地址的电脑(为了协助我测试,小曹、小妞可费了不少心,我还霸占了他们家的电脑,在此表示感谢)。如果不是这样的环境,程序执行可能会不正常,因为我暂时未做相同局域网的处理。
在具有公网地址的电脑上执行“TcpHoleSrv.exe”程序,假设这台电脑的公网IP地址是“129.208.12.38”。
在局域网A中的一台电脑上执行“TcpHoleClt-A.exe 129.208.12.38”
在局域网B中的一台电脑上执行“TcpHoleClt-B.exe 129.208.12.38”
程序执行成功后的界面:客户端出现“Send Data”或者“Received Data”表示穿越NAT的TCP连接已经建立起来,数据收发已经OK。



服务器S

客户端A

客户端B


1 楼 liyanginchina 2011-08-23  
请问一下,
1、 A和B分别与S的【主连接】保持联系。
那么S的【主连接】已经知道A、B的外网IP及Port了,
5、 B收到S的连接通知后首先与S的【协助打洞】端口连接,随便发送一些数据后立即断开,这样做的目的是让S能知道B经过NAT-B转换后的公网IP和端口号。
第5步又让S获得B的公网IP及Port,我想这一步是否是必须的?

    
[3] Java中HashCode()懂得
    来源: 互联网  发布时间: 2014-02-18
Java中HashCode()理解
先从一个java基础面试的问题开始引入吧:
问题:
两个对象值相同(x.equals(y) == true),但却可有不同的hash code,这句话对不对?
答案:
不对,有相同的hash code。

下面用我收集的资料说明来分析分析:

首先,想要明白hashCode的作用,你必须要先知道Java中的集合。
  总的来说,Java中的集合(Collection)有两类,一类是List,再有一类是Set。
      你知道它们的区别吗?前者集合内的元素是有序的,元素可以重复;后者元素无序,但元素不可重复。那么这里就有一个问题了:要想保证元素不重复,可两个元素是否重复应该依据什么来判断呢?这就是Object.equals方法了。但是,如果每增加一个元素就检查一次,那么当元素很多时,后添加到集合中的元素比较的次数就非常多了。也就是说,如果集合中现在已经有100个元素,那么第101个元素加入集合时,它就要调用100次equals方法。这显然会大大降低效率。
      于是,Java采用了哈希表的原理。哈希算法也称为散列算法,是将数据依特定算法直接指定到一个地址上。hashCode方法实际上返回的就是对象存储的物理地址。
      这样一来,当集合要添加新的元素时,先调用这个元素的hashCode方法,就一下子能定位到它应该放置的物理位置上。如果这个位置上没有元素,它就可以直接存储在这个位置上,不用再进行任何比较了;如果这个位置上已经有元素了,就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就散列其它的地址。所以这里存在一个冲突解决的问题。这样一来实际调用equals方法的次数就大大降低了,几乎只需要一两次。
      所以,Java对于eqauls方法和hashCode方法是这样规定的:
1、如果两个对象相同,那么它们的hashCode值一定要相同;
2、如果两个对象的hashCode相同,它们并不一定相同
      上面说的对象相同指的是用eqauls方法比较。
      上面就是我看到找个题目后,找到的更进一步的内容说明,分享在这里给大家学习。针对的是跟我一样java基础不是很牛的人物。哈哈

    
最新技术文章:
▪Android开发之登录验证实例教程
▪Android开发之注册登录方法示例
▪Android获取手机SIM卡运营商信息的方法
▪Android实现将已发送的短信写入短信数据库的...
▪Android发送短信功能代码
▪Android根据电话号码获得联系人头像实例代码
▪Android中GPS定位的用法实例
▪Android实现退出时关闭所有Activity的方法
▪Android实现文件的分割和组装
▪Android录音应用实例教程
▪Android双击返回键退出程序的实现方法
▪Android实现侦听电池状态显示、电量及充电动...
▪Android获取当前已连接的wifi信号强度的方法
▪Android实现动态显示或隐藏密码输入框的内容
▪根据USER-AGENT判断手机类型并跳转到相应的app...
▪Android Touch事件分发过程详解
▪Android中实现为TextView添加多个可点击的文本
▪Android程序设计之AIDL实例详解
▪Android显式启动与隐式启动Activity的区别介绍
▪Android按钮单击事件的四种常用写法总结
▪Android消息处理机制Looper和Handler详解
▪Android实现Back功能代码片段总结
▪Android实用的代码片段 常用代码总结
▪Android实现弹出键盘的方法
▪Android中通过view方式获取当前Activity的屏幕截...
▪Android提高之自定义Menu(TabMenu)实现方法
▪Android提高之多方向抽屉实现方法
▪Android提高之MediaPlayer播放网络音频的实现方法...
▪Android提高之MediaPlayer播放网络视频的实现方法...
▪Android提高之手游转电视游戏的模拟操控
 


站内导航:


特别声明:169IT网站部分信息来自互联网,如果侵犯您的权利,请及时告知,本站将立即删除!

©2012-2021,,E-mail:www_#163.com(请将#改为@)

浙ICP备11055608号-3