从iOS应用程序连接到ActionCable

Pan*_*Pan 17 ruby-on-rails websocket ruby-on-rails-5 actioncable

我整天都被困在这一天.我有David Heinemeier Hansson工作正常的非常简单的ActionCable示例应用程序(聊天应用程序)(https://www.youtube.com/watch?v=n0WUjGkDFS0).

我试图用iPhone应用程序点击websocket连接.我连接时能够接收ping ws://localhost:3000/cable,但我不太确定如何从javascript上下文之外订阅频道.

Zha*_*ang 11

噢,看完这个问题后我也遇到了这个问题.

过了一会儿,我终于找到了这个神奇的Github问题页面:

https://github.com/rails/rails/issues/22675

我明白这个补丁会破坏一些测试.这对我来说并不奇怪.但我认为最初的问题仍然是相关的,不应该被关闭.

发送到服务器的以下JSON应该成功:

{"command":"subscribe","identifier":{"channel":"ChangesChannel"}}

它不是!相反,你必须发送:

{"command":"subscribe","identifier":"{\"channel \":\"ChangesChannel \"}"}

在Github用户建议Rails问题后,我终于得到了iOS应用程序订阅房间频道.

我的设置如下:

  • 目标C.
  • 使用PocketSocket框架进行Web套接字连接
  • Rails 5 RC1
  • Ruby 2.2.4p230

我假设您知道如何使用Cocoapods来安装PocketSocket.

相关代码如下:

ViewController.h

#import <PocketSocket/PSWebSocket.h>

@interface ViewController : UIViewController <PSWebSocketDelegate, UITableViewDelegate, UITableViewDataSource, UITextFieldDelegate>

@property (nonatomic, strong) PSWebSocket *socket;
Run Code Online (Sandbox Code Playgroud)

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");
}
Run Code Online (Sandbox Code Playgroud)

重要的提示:

我还在订阅类源代码中挖掘了一下:

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
Run Code Online (Sandbox Code Playgroud)

注意这一行:

connection.server.channel_classes[id_options[:channel]]
Run Code Online (Sandbox Code Playgroud)

我们需要使用该类的名称作为频道.

DHH youtube视频使用"room_channel"作为房间名称,但该频道的类文件名为"RoomChannel".

我们需要使用类名而不是通道的实例名.

发送消息

为了防止其他人想知道如何发送消息,这里是我的iOS代码向服务器发送消息:

-(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];
}
Run Code Online (Sandbox Code Playgroud)

这假设您已连接UITextField来处理UI上某处的返回键或某些"发送"按钮.

整个演示应用程序是一个快速的黑客,显然,如果我要在一个真正的应用程序中,我会使我的代码更清洁,更可重用,并将它抽象成一个类.

从真正的iPhone设备连接到Rails服务器:

为了让iPhone应用程序在真实设备上与Rails服务器通信,而不是iPhone模拟器.

请执行下列操作:

  1. 检查计算机的TCP/IP地址.例如,在我的iMac上,某些天可能是10.1.1.10(如果使用DHCP,将来可能会自动更改).
  2. 编辑您的Rail的config > environment > development.rb文件,并在end关键字之前的某处放入以下行:

    Rails.application.config.action_cable.allowed_request_origins = ['http://10.1.1.10:3000']

  3. 使用以下命令启动Rails服务器:

    rails server -b 0.0.0.0

  4. 在iPhone设备上构建并运行您的iPhone应用程序.您现在应该能够连接并发送消息:D

我从以下链接获得了这些解决方案:

不允许请求源:http:// localhost:3001使用Rails5和ActionCable时

Rails 4.2服务器; 私人和公共IP无法正常工作

希望将来能帮助别人.


fai*_*tti 4

// 首先打开套接字连接

var ws = new WebSocket("ws://localhost:3000/cable");  
Run Code Online (Sandbox Code Playgroud)

// 订阅频道
// 'i' 应该是 json

var i = { 'command': 'subscribe', 'identifier': {'channel':'ProfileChannel', 'Param_1': 'Value_1',...}};

 ws.send(i);
Run Code Online (Sandbox Code Playgroud)

// 之后,您将在“onmessage”函数内接收数据。

干杯!