I'm developing an application which is connecting to another device and in order to connect to it my app has to find the IP of it. I'm doing it through UDP socket. I'm sending a message to the server application in the other device and then the server app sends me another message but after I receive it I only use the IP address. Then I want to get it from this .m file to another .m file which is going to make the TCP connection between the two devices. Here's the code I use:
NSString *SearchCommand = #"AT+S";
static int receivedByteCount = 0;
BOOL connectedThroughUDP;
- (void) activate {
connectedThroughUDP = NO;
AsyncUdpSocket *udpSocket = [[AsyncUdpSocket alloc] initWithDelegate:self];
[udpSocket bindToPort:1234 error:nil ];
[udpSocket enableBroadcast:YES error:nil];
NSData* data=[SearchCommand dataUsingEncoding:NSUTF8StringEncoding];
if ([udpSocket sendData:data toHost:#"255.255.255.255" port:30100 withTimeout:3 tag:0])
{
//2 second timeout. onUdpSocket methods will provide results
[udpSocket receiveWithTimeout:2 tag:0];
NSLog(#"Connected.");
}
}
- (BOOL)onUdpSocket:(AsyncUdpSocket *)sock didReceiveData:(NSData *)data withTag:(long)tag fromHost:(NSString *)host port:(UInt16)port{
NSString *receiveddata = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
addressOfServer = host;
NSLog(#"addressOfServer = %#", addressOfServer);
connectedThroughUDP = YES;
return YES;
}
- (NSString *) getHost {
return addressOfServer;
}
- (BOOL) isItConnected {
return connectedThroughUDP;
}
addressOfServer is global variable.
In the Log when I want NSLog(#"addressOfServer = %#", addressOfServer); I receive the IP address which I want but then when I want to access it from the getHost method I receive (null).
I know it will be something very simple but it caused me a real headache in the past 3 hours so I would be very thankful if you can help me!
Are you calling these 3 lines right after each other like so:
UDPConnection *udpConnection = [[UDPConnection alloc] init];
[udpConnection activate];
host = [udpConnection getHost];
If so your issue is that [udpConnection activate]; is going to take some time to connect and figure out the address. You are calling [udpConnection getHost]; too soon.
You will need to setup a delegate or completion block that will be triggered when didReceiveData is fired
Related
I'm very new to Objective-C and would like to communicate between an iOS app which I'm working on and my Python 3 socket server (which works). The problem is I don't know how to use sockets in Objective-C and don't know where to start when installing libraries in Xcode 8. For now I just want to be able to send data to the server and receive a response back on the iOS device. I have seen sys.socket.h but again, I don't know how to use it, any help would be much appreciated.
Thanks!
Few years ago I used SocketRocket. I am posting some of the code of it from my old project. I don't know if that still works but you might get the idea of using it.
Connecting to server
NSMutableURLRequest *pushServerRequest = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:#"ws://192.168.1.1"]];
[pushServerRequest setValue:#"WebSocket" forHTTPHeaderField:#"Upgrade"];
[pushServerRequest setValue:#"Upgrade" forHTTPHeaderField:#"Connection"];
[pushServerRequest setValue:"somekey" forHTTPHeaderField:#"Sec-WebSocket-Protocol"];
SRWebSocket *wsmain = [[SRWebSocket alloc] initWithURLRequest:pushServerRequest]; //Declare this as a global variable in a header file
wsmain.delegate=self;
[wsmain open];
Delegate Methods
-(void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message
{
NSLog(#"Message %#",message);
}
- (void)webSocketDidOpen:(SRWebSocket *)webSocket
{ NSLog(#"Connected");
}
Sending Commands
[wsmain send:commands];
By sending commands, you will receive a response in didReceiveMessage method.
have you seen https://github.com/robbiehanson/CocoaAsyncSocket?
It's easy to use socket
NSString *host = #"10.70.0.22";
int port = 1212;
_socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)];
NSError *error = nil;
[_socket connectToHost:host onPort:port error:&error];
if (error) {
NSLog(#"%#",error);
}
delegate
-(void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port{
NSLog(#"success");
}
-(void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err{
if (err) {
NSLog(#"error %#",err);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self connectToServer];
});
}else{
NSLog(#"disconnect");
}
}
Another option is socket.io. It has server libraries and a iOS client library written in Swift. It's API is quite extensive.
I have been stuck on this all day. I have the very simple ActionCable example app (the chat app) by David Heinemeier Hansson working correctly (https://www.youtube.com/watch?v=n0WUjGkDFS0).
I am trying to hit the websocket connection with an iPhone app. I am able to receive pings when I connect to ws://localhost:3000/cable, but I'm not quite sure how to subscribe to channels from outside of a javascript context.
Oh man, I went through this problem too after reading this question.
After a while, I finally found this magical Github issue page:
https://github.com/rails/rails/issues/22675
I do understand that this patch would break some tests. That is not
surprising to me. But the original issue I believe is still relevant
and shouldn't be closed.
The following JSON sent to the server should succeed:
{"command": "subscribe","identifier":{"channel":"ChangesChannel"}}
It does not! Instead you must send this:
{"command":
"subscribe","identifier":"{\"channel\":\"ChangesChannel\"}"}
I finally got the iOS app to subscribe to room channel following the Github user suggestion about Rails problem.
My setup is as follow:
Objective C
Using PocketSocket framework for making web socket connection
Rails 5 RC1
Ruby 2.2.4p230
I assume you know how to use Cocoapods to install PocketSocket.
The relevant codes are as follow:
ViewController.h
#import <PocketSocket/PSWebSocket.h>
#interface ViewController : UIViewController <PSWebSocketDelegate, UITableViewDelegate, UITableViewDataSource, UITextFieldDelegate>
#property (nonatomic, strong) PSWebSocket *socket;
ViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self initViews];
[self initConstraints];
[self initSocket];
}
-(void)initSocket
{
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"ws://localhost:3000/cable"]];
self.socket = [PSWebSocket clientSocketWithRequest:request];
self.socket.delegate = self;
[self.socket open];
}
-(void)joinChannel:(NSString *)channelName
{
NSString *strChannel = #"{ \"channel\": \"RoomChannel\" }";
id data = #{
#"command": #"subscribe",
#"identifier": strChannel
};
NSData * jsonData = [NSJSONSerialization dataWithJSONObject:data options:0 error:nil];
NSString * myString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
NSLog(#"myString= %#", myString);
[self.socket send:myString];
}
#pragma mark - PSWebSocketDelegate Methods -
-(void)webSocketDidOpen:(PSWebSocket *)webSocket
{
NSLog(#"The websocket handshake completed and is now open!");
[self joinChannel:#"RoomChannel"];
}
-(void)webSocket:(PSWebSocket *)webSocket didReceiveMessage:(id)message
{
NSData *data = [message dataUsingEncoding:NSUTF8StringEncoding];
id json = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
NSString *messageType = json[#"type"];
if(![messageType isEqualToString:#"ping"] && ![messageType isEqualToString:#"welcome"])
{
NSLog(#"The websocket received a message: %#", json[#"message"]);
[self.messages addObject:json[#"message"]];
[self.tableView reloadData];
}
}
-(void)webSocket:(PSWebSocket *)webSocket didFailWithError:(NSError *)error
{
NSLog(#"The websocket handshake/connection failed with an error: %#", error);
}
-(void)webSocket:(PSWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean
{
NSLog(#"The websocket closed with code: %#, reason: %#, wasClean: %#", #(code), reason, (wasClean) ? #"YES": #"NO");
}
Important Note:
I also digged a bit into the subscription class source code:
def add(data)
id_key = data['identifier']
id_options = ActiveSupport::JSON.decode(id_key).with_indifferent_access
subscription_klass = connection.server.channel_classes[id_options[:channel]]
if subscription_klass
subscriptions[id_key] ||= subscription_klass.new(connection, id_key, id_options)
else
logger.error "Subscription class not found (#{data.inspect})"
end
end
Notice the line:
connection.server.channel_classes[id_options[:channel]]
We need to use the name of the class for the channel.
The DHH youtube video uses "room_channel" for the room name but the class file for that channel is named "RoomChannel".
We need to use the class name not the instance name of the channel.
Sending Messages
Just in case others want to know how to send messages also, here is my iOS code to send a message to the server:
-(void)sendMessage:(NSString *)message
{
NSString *strMessage = [[NSString alloc] initWithFormat:#"{ \"action\": \"speak\", \"message\": \"%#\" }", message];
NSString *strChannel = #"{ \"channel\": \"RoomChannel\" }";
id data = #{
#"command": #"message",
#"identifier": strChannel,
#"data": strMessage
};
NSData * jsonData = [NSJSONSerialization dataWithJSONObject:data options:0 error:nil];
NSString * myString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
NSLog(#"myString= %#", myString);
[self.socket send:myString];
}
This assumes you've hooked up your UITextField to handle pressing the return key or some "send" button somewhere on your UI.
This whole demo app was a quick hack, obviously, if I was to do it in a real app, I would make my code more cleaner, more reusable and abstract it into a class altogether.
Connecting to Rails server from real iPhone device:
In order for iPhone app to talk to Rails server on real device, not iPhone simulator.
Do the following:
Check your computer's TCP/IP address. On my iMac for example, it might be 10.1.1.10 on some days (can change automatically in the future if using DHCP).
Edit your Rail's config > environment > development.rb file and put in the following line somewhere like before the end keyword:
Rails.application.config.action_cable.allowed_request_origins = ['http://10.1.1.10:3000']
Start your Rails server using following command:
rails server -b 0.0.0.0
Build and run your iPhone app onto the iPhone device. You should be able to connect and send messages now :D
I got these solutions from following links:
Request origin not allowed: http://localhost:3001 when using Rails5 and ActionCable
Rails 4.2 server; private and public ip not working
Hope that helps others in the future.
// open socket connection first
var ws = new WebSocket("ws://localhost:3000/cable");
// subscribe to channel
// 'i' should be in json
var i = { 'command': 'subscribe', 'identifier': {'channel':'ProfileChannel', 'Param_1': 'Value_1',...}};
ws.send(i);
// After that you'll receive data inside the 'onmessage' function.
Cheers!
Actually, Here is the code snippet that i'm using to connect to action cable.
function WebSocketTest()
{
var ws = new WebSocket("ws://localhost:3000/cable");
ws.onopen = function(data)
{
var i = JSON.stringify({"command":"subscribe" , "identifier": JSON.stringify({"channel":"CHANNEL_NAME"})});
// send data request
var j = JSON.stringify({"command":"message","identifier": JSON.stringify({"channel":"CHANNEL_NAME"}),"data": {"message":"Hello World","action": "METHOD_NAME_IN_CHANNEL","email": "abc#xyz.com", "token" : "xxxxxxxxxxxxx", "id": {"id_message" : "something", "ddd" : "something"}}})
var response = ws.send(i);
setTimeout(function()
{
var response1 = ws.send(j);
}, 1000);
};
ws.onmessage = function (evt)
{
var received_msg = evt.data;
};
}
Alright, this involves a lot of network coding from this part of a multiplayer tutorial.
Basically, I'm trying to implement a multiplayer game using GameKit as per the tutorial linked above. I put in all of the necessary network coding and more or less understand it, however I've hit a snag somewhere along the line of method calls. Basically, the setup that I have is that one device acts as the host and the rest act as the clients. I have two separate UIViewcontrollers for the host and clients respectively where the connection is established.
Now the thing is, the connection gets established, but it's only the host that recognizes the connection, not the client. The problem is here:
- (void)sendPacketToAllClients:(Packet *)packet
{
[_players enumerateKeysAndObjectsUsingBlock:^(id key, Player *obj, BOOL *stop)
{
obj.receivedResponse = [_session.peerID isEqualToString:obj.peerID];
}];
GKSendDataMode dataMode = GKSendDataReliable;
NSData *data = [packet data];
NSError *error;
if (![_session sendDataToAllPeers:data withDataMode:dataMode error:&error])
{
NSLog(#"Error sending data to clients: %#", error);
}
}
This is implemented in GameMultiplayer, where the actual game will be implemented. What this method is supposed to be doing is sending data packets to each of the clients saying that the host received the connection request and is able to connect with them. After [_session sendDataToAllPeers:data withDataMode:dataMode error:&error] is called (the method in the if statement), this method is supposed to be triggered:
- (void)receiveData:(NSData *)data fromPeer:(NSString *)peerID inSession:(GKSession *)session context:(void *)context
{
#ifdef DEBUG
NSLog(#"Game: receive data from peer: %#, data: %#, length: %d", peerID, data, [data length]);
#endif
Packet *packet = [Packet packetWithData:data];
if (packet == nil)
{
NSLog(#"Invalid packet: %#", data);
return;
}
Player *player = [self playerWithPeerID:peerID];
if (player != nil)
{
player.receivedResponse = YES; // this is the new bit
}
if (self.isServer)
[self serverReceivedPacket:packet fromPlayer:player];
else
[self clientReceivedPacket:packet];
}
This method is in the next part of the tutorial I linked above (which is here) and is supposed to receive the packets that the host sends to all clients and implement the next methods in this networking chain. However, the method never gets called. No debug breakpoints are triggered and I get nothing in the console.
I understand if I need to provide more source material, but there is a lot of network coding already implemented, so I want to keep it down to what people need to see. Also, [_session setDataReceiveHandler:self withContext:nil] and _session.delegate = self are written in another method that is called in GameMultiplayer, so that's not the problem. Does anyone know what I need to fix?
EDIT: As requested, here's where GKSession is initialized:
#property (nonatomic, strong, readonly) GKSession *session; //This is done in the header file
#synthesize session = _session; //This is done in the main file
- (void)startAcceptingConnectionsForSessionID:(NSString *)sessionID
{
if (_serverState == ServerStateIdle)
{
_serverState = ServerStateAcceptingConnections;
_connectedClients = [NSMutableArray arrayWithCapacity:self.maxClients];
_session = [[GKSession alloc] initWithSessionID:sessionID displayName:nil sessionMode:GKSessionModeServer];
_session.delegate = self;
_session.available = YES;
}
}
The session is initialized in MatchmakingServer, which is used in the host view controller. The session is then passed on to the main view controller of the app, which then initializes GameMultiplayer and sends the GKSession to it. Here's where the host view controller sends it to the main view controller:
- (IBAction)startAction:(id)sender
{
if (_matchmakingServer != nil && [_matchmakingServer connectedClientCount] > 0)
{
NSString *name = [self.nameTextField.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
if ([name length] == 0)
name = _matchmakingServer.session.displayName;
[_matchmakingServer stopAcceptingConnections];
[self.delegate hostViewController:self startGameWithSession:_matchmakingServer.session playerName:name clients:_matchmakingServer.connectedClients];
}
}
and then the main view controller handles that method call here:
- (void)hostViewController:(MatchmakerHost *)controller startGameWithSession:(GKSession *)session playerName:(NSString *)name clients:(NSArray *)clients
{
[self dismissViewControllerAnimated:NO completion:^
{
[self startGameWithBlock:^(GameMultiplayer *aGame)
{
[aGame startServerGameWithSession:session playerName:name clients:clients];
}];
}];
}
and finally, this is where that method call is implemented in GameMultiplayer:
- (void)startServerGameWithSession:(GKSession *)session playerName:(NSString *)name clients:(NSArray *)clients
{
_clients = clients;
const char* className = class_getName([[_clients objectAtIndex:0] class]);
NSLog(#"yourObject is a: %s", className);
self.isServer = YES;
_session = session;
_session.available = NO;
_session.delegate = self;
[_session setDataReceiveHandler:self withContext:nil];
_state = GameStateWaitingForSignIn;
[self.delegate gameWaitingForClientsReady:self];
// Create the Player object for the server.
Player *player = [[Player alloc] init];
player.name = name;
player.peerID = _session.peerID;
player.position = PlayerPositionBottom;
[_players setObject:player forKey:player.peerID];
// Add a Player object for each client.
int index = 0;
for (NSString *peerID in clients)
{
Player *player = [[Player alloc] init];
player.peerID = peerID;
[_players setObject:player forKey:player.peerID];
if (index == 0)
player.position = ([clients count] == 1) ? PlayerPositionTop : PlayerPositionLeft;
else if (index == 1)
player.position = PlayerPositionTop;
else
player.position = PlayerPositionRight;
index++;
}
NSLog(#"Players:");
Packet *packet = [Packet packetWithType:PacketTypeSignInRequest];
[self sendPacketToAllClients:packet];
// for (int i = 0; i < [_players count]; i++) {
// NSLog([NSString stringWithFormat:#"%#", [clients objectAtIndex:i]]);
// }
}
I think you are calling send to fast. When server realize about connection it will send confirmation to client to really establish connection - so client knows about it succeed.
If you are sending packets before that happens - it will be lost.
Just do this:
[self performSelector:#selector(sendPacketToAllClients) withObject:nil afterDelay:1.0];
instead of:
[self sendPacketToAllClients];
I had the same problem that connection is established in different moment with small delay on client. The best is to send first packet from client that he is ready to receive packets from server - and than proceed normally from there.
Also try debugging:
- (void)session:(GKSession *)session peer:(NSString *)peerID didChangeState:(GKPeerConnectionState)state
On both devices (server and client).
I have also had my troubles with GKSession. I was interested to learn (on this site) today that GKSession is being deprecated in favor of using the Multipeer Connectivity Framework. With luck, Wenderlich et al. will do a tutorial using the new technology. :)
The system has some similarities to GKSession, so is not too hard to wrap your head around.
Apple's doc link.
i am establishing a UDP connection using GCDAsyncSocket(ios device). Everything working fine and im able to send and receive messages, my problem is that i want to exchange data fast. I can send pretty fast data from my iphone to a pc but i cant get at that speed data from pc, more specific i want to be able to get data every 100ms.
I use this function when i connect successfully:
-(void)startRead {
[NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(startRead) userInfo:nil repeats:YES];
[asyncSocket readDataWithTimeout:-1 tag:0];
}
With this i can read data with 1sec interval but if i try to put 0.1 seconds my program freezes.(Same with values under 1second) Im sure that im doing something wrong here and there will be a way to achieve what i want so if anybody know plz help!!
thanx
I believe the above comment is correct, you've not set the Delegate correctly on init. The socket creation should be something like this
GCDAsyncUdpSocket* udpSocket = [[GCDAsyncUdpSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
NSError *error = nil;
if (![udpSocket bindToPort:0 error:&error])
{
[self logError:[NSString stringWithFormat:#"Error binding: %#", error]];
return;
}
if (![udpSocket beginReceiving:&error])
{
[self logError:[NSString stringWithFormat:#"Error receiving: %#", error]];
return;
}
NSString *_host = nil;
uint16_t _port = 0;
[GCDAsyncUdpSocket getHost:&_host port:&_port fromAddress:udpSocket.localAddress];
[self logInfo:[NSString stringWithFormat:#"Socket setup on host %#:%d", _host, _port]];
[self logInfo:#"Socket created successfully."];
Unless you're using a different version of GCDAsyncUdpSocket than I'm familiar with, the correct callback method is actually the below method. This is called automatically when the delegate is set and a packet is received on the correct port.
- (void)udpSocket:(GCDAsyncUdpSocket *)sock didReceiveData:(NSData *)data fromAddress:(NSData *)address withFilterContext:(id)filterContext
I really need some help with my project...
I need to exchange data with my server written in Java. I tried using GCDAsyncSocket, and I can send message to server, read it on server, but when server sends response to client, I can't (don't know how to) read it on client. Here is part of my code:
- (void) someMethod{
NSError *err = nil;
asyncSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
if(![asyncSocket connectToHost:#"localhost" onPort:7777 error:&err]){
// If there was an error, it's likely something like "already connected" or "no delegate set"
NSLog(#"I goofed: %#", err);
}
NSString *requestStr = #"<?xml version='1.0' encoding='UTF-8' standalone='yes' ?><root><service>1</service><type>1</type><userProperties><username>ivo</username></userProperties></root>";
NSData *requestData = [requestStr dataUsingEncoding:NSUTF8StringEncoding];
[asyncSocket writeData:requestData withTimeout:-1.0 tag:0];
[asyncSocket readDataToData:[GCDAsyncSocket CRLFData] withTimeout:1.0 tag:0];
[asyncSocket disconnectAfterWriting];
}
- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag{
if (tag == 0)
NSLog(#"First request sent");
else if (tag == 2)
NSLog(#"Second request sent");
}
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag {
NSString *str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(#"%#",str);
}
Please help, if there is another way I am willing to try as I am getting desperate...
I see that you're sending XML, with no particular terminator at the end of your request data, yet you're expecting the server to send a response terminated by a \r\n?
What does the protocol specify?
Sending and receiving data over tcp is a common cause of confusion because tcp is stream based. It has no concept of individual reads/writes. It treats all data as conceptually a never ending stream. The protocol dictates message boundaries. For a better explanation, see the "Common Pitfalls" article from GCDAsyncSocket's wiki:
https://github.com/robbiehanson/CocoaAsyncSocket/wiki/CommonPitfalls
I think it will help explain a lot.