The purpose of this tutorial is to set up a basic chat client on the iPhone that is able to connect to a chat server running on your computer. Since this tutorial is for the iPhone, I’m going to assume that you’re using a Mac and therefore the example chat server is intended to be run on OS X. We’ll get to that in a minute though.
I’m going to assume you’ve completed the last RakNet tutorial and have compiled RakNet into an iPhone library. We’ll be using that library again here in this tutorial.
The first step is to create a fresh iPhone project. I chose a View-based Application since it’s basic and doesn’t include anything extra:
You can name the project whatever you want, but I’ll be using “RaknetTextClient.”
Once the project is created, the first thing you’ll need to do is add the library to the project as described in the last tutorial. I’ll paraphrase it again here though:
- To add the library, right-click the Frameworks folder and go to Add>Existing items. Chose your RakNet library.
- To add the headers, right-click the project and go to Get Info. Scroll down to the Search Paths and add the folder containing your RakNet source to the Header Search Paths variable.
With these items done you’re ready to start adding RakNet code. First, open up AppDelegate.h. We’re going to use this file to control the network updates for RakNet. To do this, we need to add a new NSTimer to the interface and a networkUpdate method. After these changes, I have this:
If you want to copy the code, here are the changes:
NSTimer * mTimer;
-(void) networkUpdate: (NSTimer *) theTimer ;
Since our project is using RakNet, it will need to be able to compile C++ code. To do this, we need to rename our AppDelegate.m file to AppDelegate.mm. This will tell Xcode that it can expect some C++ code in the project.
In the newly renamed file, we also need to make some changes to work with the changes to AppDelegate.h. The first item of business is to add the following line to the method -(BOOL)application: … didFinishLaunchingWithOptions: …
mTimer = [NSTimer scheduledTimerWithTimeInterval: 0.1 target:self selector:@selector(networkUpdate:) userInfo:nil repeats: YES];
This line can go anywhere, but I placed it right before the return YES; line. The next step is to add the networkUpdate method. This can go anywhere in the file, but I put it directly after the method mentioned above. The method looks like:
-(void) networkUpdate: (NSTimer *) theTimer
{
[self.viewController tickClient];
}
That’s all for AppDelegate changes, onto the ViewController modifications. These are a bit more extensive since we’re going to be adding some actual RakNet code. The first set of changes needed are in ViewController.h. Add the following #includes below the existing #includes in the file:
#include "MessageIdentifiers.h"
#include "RakPeerInterface.h"
#include "RakNetStatistics.h"
#include "RakNetTypes.h"
#include "BitStream.h"
#include "PacketLogger.h"
#include <assert.h>
#include <cstdio>
#include <cstring>
#include <stdlib.h>
#include "RakNetTypes.h"
#include "Kbhit.h"
#include <unistd.h>
#include "Gets.h"
After all of the #includes you’ll also need to add  using namespace RakNet; before the @interface declaration. Inside the declaration, add the following variables:
RakPeerInterface *mRakPeer;
SystemAddress mClientID;
Packet* mPacket;
unsigned char mPacketID;
UITextField *mSendText;
UITextView *mTextBox;
The first four are for RakNet, and the last two are for the simple UI we’re going to set up in a bit. We also need to add some methods and properties; these are added after the interface declaration. Add the following:
-(void)appendMessage:(NSString*)message;
-(IBAction)sendMessage;
-(void)tickClient;
@property (nonatomic, retain) IBOutlet UITextField *mSendText;
@property (nonatomic, retain) IBOutlet UITextView *mTextBox;
That’s all for the ViewController header file.
Like the AppDelegate.mm file, we need to rename the ViewController.m file so it has the .mm extension. Inside the file, we first need to add a @synthesize line for our two properties. This goes directly after the @implementation line.
@synthesize mTextBox, mSendText;
Directly after this, I added the following method that was taken straight from RakNet’s chat sample:
unsigned char GetPacketIdentifier(RakNet::Packet *p)
{
if (p==0)
return 255;
if ((unsigned char)p->data[0] == ID_TIMESTAMP)
{
RakAssert(p->length > sizeof(RakNet::MessageID) + sizeof(RakNet::Time));
return (unsigned char) p->data[sizeof(RakNet::MessageID) + sizeof(RakNet::Time)];
}
else
return (unsigned char) p->data[0];
}
This method will be used by the network code to determine what sort of message is being sent. The next code change is in the viewDidLoad method. By default this method is commented out, so uncomment it and then add the following code after [super viewDidLoad];Â
mRakPeer = RakPeerInterface::GetInstance();
mClientID = UNASSIGNED_SYSTEM_ADDRESS;
SocketDescriptor socketDescriptor(54001,0);
socketDescriptor.socketFamily=AF_INET;
mRakPeer->Startup(8,&socketDescriptor, 1);
mRakPeer->SetOccasionalPing(true);
ConnectionAttemptResult car = mRakPeer->Connect("192.168.2.17", 54000, "Rumpelstiltskin", (int) strlen("Rumpelstiltskin"));
RakAssert(car==RakNet::CONNECTION_ATTEMPT_STARTED);
You’ll want to change a few things here based on your network configuration. In the Connect() method, the first number is the IP address of the machine that will be running the server and the second number is the port for the server. If you’re only going to run this application on the iPhone simulator, you can use “127.0.0.1” as the IP. If you plan on testing on the device itself you’ll need to use the internal IP of the computer that you plan to use as the server. There are a variety of ways to find this; I got mine by checking the config page for my router. If you’re using AirPort and a WiFi connection it’ll be list on the first page of the AirPort settings, something like “AirPort is connected toand has the IP address 192.168.2.17.” You should be able to keep the port number the same. The “Rumpelstiltskin” is the default password in RakNet and should be left as-is to work with the server application.
Anyways, there are a few more changes that need to be made. Add the following set of methods (it’s a long one):
//Sends a message to the server and appends it to the text view
- (IBAction)sendMessage
{
NSString* message = [NSString stringWithFormat:@"iPhone: %@",mSendText.text];
const char* cMessage = [message cStringUsingEncoding:NSASCIIStringEncoding];
mRakPeer->Send(cMessage, (int) strlen(cMessage)+1, HIGH_PRIORITY, RELIABLE_ORDERED, 0, RakNet::UNASSIGNED_SYSTEM_ADDRESS, true);
[mSendText setText:@""];
[self appendMessage:message];
}
//Appends a string to the text view
-(void)appendMessage:(NSString*)message
{
mTextBox.text = [NSString stringWithFormat:@"%@\n%@", mTextBox.text,message];
}
//Update the network and checks for incoming packets
-(void)tickClient
{
for (mPacket = mRakPeer->Receive(); mPacket; mRakPeer->DeallocatePacket(mPacket), mPacket=mRakPeer->Receive())
{
// We got a packet, get the identifier with our handy function
mPacketID = GetPacketIdentifier(mPacket);
// Check if this is a network message packet
switch (mPacketID)
{
case ID_DISCONNECTION_NOTIFICATION:
// Connection lost normally
[self appendMessage:@"Disconnected from server."];
break;
case ID_ALREADY_CONNECTED:
[self appendMessage:@"Already connected to server."];
break;
case ID_INCOMPATIBLE_PROTOCOL_VERSION:
[self appendMessage:@"Protocol version error"];
break;
case ID_REMOTE_DISCONNECTION_NOTIFICATION:
[self appendMessage:@"A client disconnected from the server."];
break;
case ID_REMOTE_CONNECTION_LOST:
[self appendMessage:@"A client lost connection with the server."];
break;
case ID_REMOTE_NEW_INCOMING_CONNECTION:
[self appendMessage:@"Another client is connecting!"];
break;
case ID_CONNECTION_BANNED:
[self appendMessage:@"You are banned from this server."];
break;
case ID_CONNECTION_ATTEMPT_FAILED:
[self appendMessage:@"Connection failed. "];
break;
case ID_NO_FREE_INCOMING_CONNECTIONS:
[self appendMessage:@"Server is full."];
break;
case ID_INVALID_PASSWORD:
[self appendMessage:@"Invalid password."];
break;
case ID_CONNECTION_LOST:
[self appendMessage:@"Connection lost."];
break;
case ID_CONNECTION_REQUEST_ACCEPTED:
[self appendMessage:@"Connection accepted."];
break;
default:
[self appendMessage:[NSString stringWithCString:(const char*)mPacket->data encoding:NSASCIIStringEncoding]];
break;
}
}
}
There’s only one more change needed to this file and then we’ll build the UI. In the viewDidUnload method, add the following code to cleanup the network:
// Be nice and let the server know we quit.
mRakPeer->Shutdown(300);
// We're done with the network
RakPeerInterface::DestroyInstance(mRakPeer);
That’s all the source code changes we’ll need! Next, open up theViewController.xib file. We need to place 3 items: a UITextView, a UITextField and a UIButton. The UITextView should be connected to the “mTextBox” variable, and then UITextField should connect to the “mSendText” variable. The “sendMessage” method should be connect to the UIButton’s Touch Up event. Here’s the full configuration I used:
Save the .xib, and then compile. At this point you’re ready to test the app, so you’ll need to run some sort of server. I’ve compiled the RakNet sample server as a Mac app, which will work with this iPhone app. You can download the server file here.
Download and launch the server app, and enter “54000” as the port to listen on. You can then run your iPhone app on the simulator or device, and it should connect to the server. A connection message will be displayed in the server window when a client successfully connects. Typing messages in the server window will send the messages to all connected clients, as well typing/sending messages on the iPhone app.
Let me know if there are any issues and I’ll try to help resolve them. I’m going to do put up an example of using RakNet’s ReplicaManager on the iPhone as well, possibly tonight but more likely tomorrow.
T