目的:
了解iPhone的蓝牙开发基础框架
核心:
GAME KIT框架里面的GKPeerPickerController、GKSession,通过这两个类我们可以将两台iPhone使用蓝牙给连接起来并使相互之间可以发送/接收消息.
GKPeerPickerController:提供一套系统界面最终使多台设备之间的蓝牙连接起来,如果机器未开启蓝牙功能时,会提示是否要打开蓝牙功能.最终我们将通过一个回调函数
- (void)peerPickerController:(GKPeerPickerController *)picker didConnectPeer:(NSString *)peerID toSession:(GKSession *)session;
来得到与某个蓝牙连接成功事件.
GKSession:提供将多个蓝牙连接在一起之后的会话处理,如发送消息,接收消息,某个蓝牙设备断开等.
连接过程:
1. GKPeerPickerController建立蓝牙连接界面
2. peerPickerController(sessionForConnectionType) 回调获取GKSession的实体
3. GKSession alloc建立一个自定义的GKSession(可以设置sessionID与type:s/c/peer)
4. peerPickerController(didConnectPeer)一个设备连接成功的通知
5. session(didChangeState=GKPeerStateConnected)一个设备加入到session的通知
发送与接收:
· GKSession sendData通过这个函数来发送数据给指定的peer(可以是多个)
· GKSessionDelegate receiveData通过这个回调函数来接收其它peer发送过来的数据
例子代码(缩减GKTank):
#import
#import "BluetoothTestViewController.h" typedef enum { kStateStartGame, kStatePicker, kStateMultiplayer, kStateMultiplayerCointoss, kStateMultiplayerReconnect } gameStates; typedef enum { NETWORK_ACK, // no packet NETWORK_COINTOSS, // decide who is going to be the server NETWORK_MOVE_EVENT, // send position NETWORK_FIRE_EVENT, // send fire NETWORK_HEARTBEAT // send of entire state at regular intervals } packetCodes; #define kGameSessionID @"gkBluetoothTest" typedef struct tagPacketHeader { int packetID; } stPacketHeader; @interface BluetoothTestViewController() -(void)startPicker; -(void)sendNetworkPacket:(GKSession *)session packetID:(int)packetID withData:(void *)data ofLength:(int)length reliable:(BOOL)howtosend; -(void)recvMsg:(int)packetID data:(char*)data length:(int)length; -(void)setTankPosition:(BOOL)host position:(CGPoint)position; @end @implementation BluetoothTestViewController @synthesize tank1, tank2, gameSession, gamePeerId, connectionAlert; // Implement viewDidLoad to do additional setup after loading the view, typically from a nib. - (void)viewDidLoad { [super viewDidLoad]; // 我们的代码 NSString* uid = [[UIDevice currentDevice] uniqueIdentifier]; _uidHash = [uid hash]; _state = kStateStartGame; _isHost = TRUE; } - (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. } - (void)viewDidUnload { // Release any retained subviews of the main view. // e.g. self.myOutlet = nil; } - (void)dealloc { if(self.connectionAlert.visible) { [self.connectionAlert dismissWithClickedButtonIndex:-1 animated:NO]; } self.connectionAlert = nil; [super dealloc]; } /////////////////////////////////////////////////////////////////////////// -(void)startPicker { GKPeerPickerController* picker; _state = kStatePicker; picker = [[GKPeerPickerController alloc] init]; // note: picker is released in various picker delegate methods when picker use is done. picker.delegate = self; [picker show]; // show the Peer Picker } - (void)invalidateSession:(GKSession *)session { if(session != nil) { [session disconnectFromAllPeers]; session.available = NO; [session setDataReceiveHandler: nil withContext: NULL]; session.delegate = nil; } } - (void)peerPickerControllerDidCancel:(GKPeerPickerController *)picker { // Peer Picker automatically dismisses on user cancel. No need to programmatically dismiss. // autorelease the picker. picker.delegate = nil; [picker autorelease]; // invalidate and release game session if one is around. if(self.gameSession != nil) { [self invalidateSession:self.gameSession]; self.gameSession = nil; } // go back to start mode _state = kStateStartGame; } - (GKSession *)peerPickerController:(GKPeerPickerController *)picker sessionForConnectionType:(GKPeerPickerConnectionType)type { GKSession *session = [[GKSession alloc] initWithSessionID:kGameSessionID displayName:nil sessionMode:GKSessionModePeer]; return [session autorelease]; // peer picker retains a reference, so autorelease ours so we don't leak. } - (void)peerPickerController:(GKPeerPickerController *)picker didConnectPeer:(NSString *)peerID toSession:(GKSession *)session { // Remember the current peer. self.gamePeerId = peerID; // copy // Make sure we have a reference to the game session and it is set up self.gameSession = session; // retain self.gameSession.delegate = self; [self.gameSession setDataReceiveHandler:self withContext:NULL]; // Done with the Peer Picker so dismiss it. [picker dismiss]; picker.delegate = nil; [picker autorelease]; // Start Multiplayer game by entering a cointoss state to determine who is server/client. _state = kStateMultiplayerCointoss; [self sendNetworkPacket:self.gameSession packetID:NETWORK_COINTOSS withData:&_uidHash ofLength:sizeof(_uidHash) reliable:YES]; _state = kStateMultiplayer; // we only want to be in the cointoss state for one loop } - (void)session:(GKSession *)session peer:(NSString *)peerID didChangeState:(GKPeerConnectionState)state { NSLog(@"session:%@, %d", peerID, state); if(_state == kStatePicker) { return; // only do stuff if we're in multiplayer, otherwise it is probably for Picker } if(state == GKPeerStateDisconnected) { // We've been disconnected from the other peer. // Update user alert or throw alert if it isn't already up NSString *message = [NSString stringWithFormat:@"Could not reconnect with %@.", [session displayNameForPeer:peerID]]; if(self.connectionAlert && self.connectionAlert.visible) { self.connectionAlert.message = message; } else { UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Lost Connection" message:message delegate:self cancelButtonTitle:@"End Game" otherButtonTitles:nil]; self.connectionAlert = alert; [alert show]; [alert release]; } // go back to start mode _state = kStateStartGame; } } - (void)sendNetworkPacket:(GKSession *)session packetID:(int)packetID withData:(void *)data ofLength:(int)length reliable:(BOOL)howtosend { // the packet we'll send is resued static unsigned char networkPacket[1024]; const unsigned int packetHeaderSize = sizeof(stPacketHeader); // we have two "ints" for our header if(length < (1024 - packetHeaderSize)) { // our networkPacket buffer size minus the size of the header info stPacketHeader* pPacketHeader = (stPacketHeader*)networkPacket; // header info pPacketHeader->packetID = packetID; // copy data in after the header memcpy( &networkPacket[packetHeaderSize], data, length ); NSData *packet = [NSData dataWithBytes: networkPacket length: (length+packetHeaderSize)]; if(howtosend == YES) { [session sendData:packet toPeers:[NSArray arrayWithObject:gamePeerId] withDataMode:GKSendDataReliable error:nil]; } else { [session sendData:packet toPeers:[NSArray arrayWithObject:gamePeerId] withDataMode:GKSendDataUnreliable error:nil]; } } } - (void)receiveData:(NSData *)data fromPeer:(NSString *)peer inSession:(GKSession *)session context:(void *)context { unsigned char *incomingPacket = (unsigned char *)[data bytes]; stPacketHeader* pPacketHeader = (stPacketHeader*)&incomingPacket[0]; [self recvMsg:pPacketHeader->packetID data:(char*)(incomingPacket + sizeof(stPacketHeader)) length:(int)data.length - sizeof(stPacketHeader)]; } /////////////////////////////////////////////////////////////////////////// -(void) recvMsg:(int)packetID data:(char*)data length:(int)length { switch( packetID ) { case NETWORK_COINTOSS: { int coinToss = *((int*)data); _isHost = (coinToss > _uidHash); } break; case NETWORK_MOVE_EVENT: { [self setTankPosition:!_isHost position:*((CGPoint*)data)]; } break; default: // error break; } } -(void)setTankPosition:(BOOL)host position:(CGPoint)position { if (host) { tank1.center = position; } else { tank2.center = position; } } -(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex { if(buttonIndex == 0) { _state = kStateStartGame; } } ///////////////////////////////////////////////////////////////////////////// - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { if (_state == kStateMultiplayer) { [self touchesMoved:touches withEvent:event]; } } - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { if (_state == kStateMultiplayer) { UITouch *thumb = [[event allTouches] anyObject]; CGPoint thumbPoint = [thumb locationInView:thumb.view]; [self sendNetworkPacket:self.gameSession packetID:NETWORK_MOVE_EVENT withData:&thumbPoint ofLength:sizeof(thumbPoint) reliable:YES]; [self setTankPosition:_isHost position:thumbPoint]; } } - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { if (_state == kStateStartGame) { [self startPicker]; } }