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;
};
}
Related
I am working on socket programming and I am new to socket. I got stuck. I am able to send the message on server easily but not able to get the message from the server. This could mean that my socket.on() method not working.
My socket connection code is
-(void)socketSession
{
NSString *strURl = [NSString stringWithFormat:URl];
NSURL* url = [[NSURL alloc] initWithString:strURl];
socket = [[SocketIOClient alloc] initWithSocketURL:url options:#{#"log": #YES, #"forcePolling": #YES}];
[socket connect];
[self recieveServerMessage];
[socket on:#"connect" callback:^(NSArray* data, SocketAckEmitter* ack) {
NSLog(#"socket connected");
}];
}
I call this method on viewDidLoad in viewController class and recieveServermessage method is
-(void)recieveServerMessage{
NSLog(#"receiving...message...");
[socket on:#"chat" callback:^(NSArray* data, SocketAckEmitter* ack) {
NSLog(#"recieveMessage");
}];
}
My control don't come when message is sent by server.
and below format is given by web server client
socket.on(":chat", function(data) {
// fired when any chat message is raised
// data.to_id = my _id. Please verify this before processing the packet
// data.sender_id = sender's _id. Map against counsellor list API or /api/getUserName?_id=(user's _id)
// data.msg = text message sent by the sender
})
Now please guide me. I have been stuck on this the last two days.
Thanks
Full code of socket io javascript class will be helpful.
You need to write emit function to send message from server to client. Like:
sockets.emit('chat', message);
And from client to server:
[socket emit:#"chat" withItems:#[#"message", #"other data"]];
I found my solution.Main problem was in socket handshake. Now I am using
NSURL* url = [[NSURL alloc] initWithString:#"https://unbottle.me"];
socket = [[SocketIOClient alloc] initWithSocketURL:url
options:#{#"connectParams" : #{#"_id": userID,#"isCounsellor": #NO,#"counsellor_id": counsellorID}}];
Earlier I was not using #"connectParams" key value in dictionary so I was facing this issue.
Thanks
I am working on iOS application where I am using Twilio SDK to manage client calling through device. To implement this i am using hello monkey demo application which i have successfully imported in Xcode.
After initial setup i am able to establish connection successfully but receiving delegate is no longer working. I have gone through complete twilio documentation but no success. Please suggest any alternative or solution ASAP.
Here is my code of Hello Monkey sample project
- (void)viewDidLoad
{
NSLog(#"CLINT ID----------------------- %#",name);
//check out https://github.com/twilio/mobile-quickstart to get a server up quickly
NSString *urlString = [NSString stringWithFormat:#"https://testdemo786.herokuapp.com/token?client=%#", name];
NSURL *url = [NSURL URLWithString:urlString];
NSError *error = nil;
NSString *token = [NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:&error];
if (token == nil) {
NSLog(#"Error retrieving token: %#", [error localizedDescription]);
} else {
_phone = [[TCDevice alloc] initWithCapabilityToken:token delegate:self];
}
}
- (IBAction)dialButtonPressed:(id)sender
{
NSString *to;
if ([name isEqualToString:#"jenny"]) {
to=#"client:tommy";
}
else
{
to=#"client:jenny";
}
to=#"4nmf5j";
NSLog(#"TO---------------------%#",to);
NSDictionary *params = #{#"To": to};
_connection = [_phone connect:params delegate:nil];
}
- (IBAction)hangupButtonPressed:(id)sender
{
[_connection disconnect];
}
- (void)device:(TCDevice *)device didReceiveIncomingConnection:(TCConnection *)connection
{
NSLog(#"Incoming connection from: %#", [connection parameters][#"From"]);
if (device.state == TCDeviceStateBusy) {
[connection reject];
} else {
[connection accept];
_connection = connection;
}
}
-(void)connection:(TCConnection*)connection didFailWithError: (NSError*)error{
NSLog(#"Connection failed with error : %#", error);
}
-(void)connectionDidStartConnecting:(TCConnection*)connection{
NSLog(#"connection started");
}
-(void)connectionDidDisconnect:(TCConnection*)connection{
NSLog(#"connection disconnected");
}
-(void)connectionDidConnect:(TCConnection*)connection{
NSLog(#"connected");
}
- (void)deviceDidStartListeningForIncomingConnections: (TCDevice*)device
{
NSLog(#"Device: %# deviceDidStartListeningForIncomingConnections", device);
}
- (void)device:(TCDevice *)device didStopListeningForIncomingConnections:(NSError *)error
{
NSLog(#"Device: %# didStopListeningForIncomingConnections: %#", device, error);
}
Twilio evangelist here.
Its a bit hard to tell from the code you included, which does look correct to me.
Here are a couple of things to check:
Did you add the TCDeviceDelegate as a protocol on your interface:
#interface FooViewController() <TCDeviceDelegate>
Are you sure you passing the correct client name to the connect method, and that the TwiML being returned from your TwiML Apps Voice Request URL is including that name properly?
You could check this by looking at the Twilio Monitor to see if Twilio logged any errors when retrieving or parsing your TwiML. You can also check your Twilio call logs to see what Twilio says the outcome of the inbound and outbound call legs were.
Hope that helps.
did you set the delegate to self?
_device = [[TCDevice alloc] initWithCapabilityToken:capabilityToken delegate:self];
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
With google cast iOS SDK, The GCKMediaControlChannel's sendTextMessage method is straightforward and it's hard to mis-use so I am guessing this may be a bug in the SDK ... hopefully someone will prove me wrong so I can get back to work!
Here's the code:
NSDictionary *messageDict = #{
#"message": #"blah",
#"num":[NSNumber numberWithInt:2]
};
NSError *error;
NSData *msgData = [NSJSONSerialization dataWithJSONObject:messageDict
options:0
error:&error];
NSString *message = #"" ;
if (!msgData) {
DDLogError(#"ERROR serializing message: %#", error);
return NO ;
} else {
message = [[NSString alloc] initWithData:msgData encoding:NSUTF8StringEncoding];
[self sendTextMessage:message] ;
}
...the receiver produces this error when the message is received [cast.receiver.mediaManager] Ignoring request, requestId is not an integer: undefined
At first view it seems like GCKMediaControlChannel inherits directly its sendTextMessage method from the GCKCastChannel, failing to implement some of the messaging aspects specific to the media channel (in particular failing to wrap the message in a media-style envelope with the requestId and mediaSessionID attributes)
Has anybody else encountered this? Am I missing something? Is there a workaround?
I followed the recommendation on the ticket I created, messaging to the receiver media app using a custom namespace using GCKCastChannel instead of he dedicated GCKMediaControlChannel to work around the issue. The ticket response confirms "don't use sendTextMessage directly with the GCKMediaControlChannel"
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.