美文网首页iOS 开发技巧大全
iOS在App和Server间创建socket教程

iOS在App和Server间创建socket教程

作者: ShawnDu | 来源:发表于2017-07-21 21:17 被阅读208次

    原文地址

    iOS在App和Server间创建socket教程

    在大多数场景下,使用HTTP能满足我们的网络请求,但是有一些情况下,需要更低层的通信方式,如基于tcp的socket通信。
    关于socket的基础知识,可以参考这篇文章iOS Socket传输基本理论 网络层协议.
    提到socket,一定会有app和server,下面先看在server中搭建一个环境。

    socket server篇

    需要先安装python,安装python后,再安装twisted:

    sudo yum install twisted
    

    Twisted是用python写的,需要会一点python,它是一个基于事件的引擎,很容易信用编写出基于TCP, UDP, SSH, IRC, 或者FTP的web应用。
    Twisted的设计模式为ractor patten,很像iOS中的runloop,启动一个循环,等待事件,做出响应:

    reactor-loop-socket

    写一个简单的server

    from twisted.internet.protocol import Factory
    from twisted.internet import reactor
     
    factory = Factory()
    reactor.listenTCP(6222, factory)
    reactor.run()
    

    保存成server.py, 然后运行:

    python server.py
    

    监听端口写成6222,是随便开启的一个,最好不要80吧,因为80在你的server上可能已经被占用了。
    传输协议定义成如下:

    • 加入聊天:iam: 昵称;
    • 发送消息:msg:消息;
      再加入跟踪client的功能和处理client消息的功能,完整的server.py如下:
    from twisted.internet.protocol import Protocol, Factory
    from twisted.internet import reactor
    
    class IphoneChat(Protocol):
        def connectionMade(self):
            #self.transport.write("""connected""")
            self.factory.clients.append(self)
            print "clients are ", self.factory.clients
    
        def connectionLost(self, reason):
            self.factory.clients.remove(self)
    
        def dataReceived(self, data):
            #print "data is ", data
            a = data.split(':')
            if len(a) > 1:
                command = a[0]
                content = a[1]
    
                msg = ""
                if command == "iam":
                    self.name = content
                    msg = self.name + " has joined"
    
                elif command == "msg":
                    msg = self.name + ": " + content
    
                print msg
    
                for c in self.factory.clients:
                    c.message(msg)
    
        def message(self, message):
            self.transport.write(message + '\n')
    
    
    factory = Factory()
    factory.protocol = IphoneChat
    factory.clients = []
    
    reactor.listenTCP(6222, factory)
    print "Iphone Chat server started"
    reactor.run()
    

    iOS Client篇

    客户端主要需要做三件事:

    • 加入聊天;
    • 发送消息;
    • 接收消息。

    使用Stream

    在iOS中,我们用stream来建立socket连接。
    stream是发送接收数据的抽象,数据可以是文件中的,C buffer中或者是网络连接中的。steam中有代理方法,可以处理一些事件,如连接打开时,数据接收了时,连接断开时。
    NSStream是父类,NSInputStream和NSOutputStream是NSStream的子类,前者是用来读取输入流,后者是输出。上述的类都是基于CFStream的。
    在ChatClientViewController.m中加入以下方法:

    - (void)initNetworkCommunication {
        
        CFReadStreamRef readStream;
        CFWriteStreamRef writeStream;
        CFStreamCreatePairWithSocketToHost(NULL, (CFStringRef)@"your host", 6222, &readStream, &writeStream);
        
        self.inputStream = (__bridge NSInputStream *)readStream;
        self.outputStream = (__bridge NSOutputStream *)writeStream;
    }
    

    这个强大的函数CFStreamCreatePairWithSocketToHost把两个streams绑定到主机和端口上,调用完后,就很容易从CFStreams到NSStreams。
    再设置上代理,以便于在代理方法里面做一些处理:

     [self.inputStream setDelegate:self];
     [self.outputStream setDelegate:self];
    

    同时需要遵从协议:

    @interface ChatClientViewController ()<NSStreamDelegate>
    

    我们不希望输入流和输出流断掉,并且想在有数据流时做出处理,没有时,能去响应别的事件,而不想被阻塞。所以需要把把这两个流加入到runloop中:

    [self.inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    [self.outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    

    最后再打开:

    [self.inputStream open];
    [self.outputStream open];
    

    需要在viewDidLoad中加入:

    [self initNetworkCommunication];
    

    加入聊天

    发送协议中规定的加入聊天的格式:

    - (IBAction)joinChat:(id)sender {
        [self.view bringSubviewToFront:self.chatView];
        NSString *response  = [NSString stringWithFormat:@"iam:%@", self.inputNameField.text];
        NSData *data = [[NSData alloc] initWithData:[response dataUsingEncoding:NSUTF8StringEncoding]];
        [self.outputStream write:[data bytes] maxLength:[data length]];
    }
    

    发送消息

    发送协议中规定的发送消息的格式:

    - (IBAction)sendMessage:(id)sender {
        NSString *response  = [NSString stringWithFormat:@"msg:%@", self.inputMessageField.text];
        NSData *data = [[NSData alloc] initWithData:[response dataUsingEncoding:NSUTF8StringEncoding]];
        [self.outputStream write:[data bytes] maxLength:[data length]];
        self.inputMessageField.text = @"";
    }
    

    接收消息

    在代理方法在做处理:

    - (void)stream:(NSStream *)theStream handleEvent:(NSStreamEvent)streamEvent
    

    NSStreamEvent有以下几种情况:

    typedef NS_OPTIONS(NSUInteger, NSStreamEvent) {
        NSStreamEventNone = 0,
        NSStreamEventOpenCompleted = 1UL << 0,
        NSStreamEventHasBytesAvailable = 1UL << 1,
        NSStreamEventHasSpaceAvailable = 1UL << 2,
        NSStreamEventErrorOccurred = 1UL << 3,
        NSStreamEventEndEncountered = 1UL << 4
    };
    

    NSStreamEventHasBytesAvailable中对接收的数据做处理:

    case NSStreamEventHasBytesAvailable:
                
                if (theStream == self.inputStream) {
                    
                    uint8_t buffer[1024];
                    NSUInteger len;
                    
                    while ([self.inputStream hasBytesAvailable]) {
                        len = [self.inputStream read:buffer maxLength:sizeof(buffer)];
                        if (len > 0) {
                            
                            NSString *output = [[NSString alloc] initWithBytes:buffer length:len encoding:NSUTF8StringEncoding];
                            
                            if (nil != output) {
                                
                                NSLog(@"server said: %@", output);
                                [self messageReceived:output];
                                
                            }
                        }
                    }
                }
    

    messageReceived函数在把接收到的string显示出来:

    - (void)messageReceived:(NSString *)message {
        [self.messages addObject:message];
        [self.displayMessageTableView reloadData];
        NSIndexPath *topIndexPath = [NSIndexPath indexPathForRow:self.messages.count-1
                                                       inSection:0];
        [self.displayMessageTableView scrollToRowAtIndexPath:topIndexPath
                          atScrollPosition:UITableViewScrollPositionMiddle
                                  animated:YES];
    }
    

    最终的效果是这样:


    iOS在App和Server间创建socket教程-登录界面iOS在App和Server间创建socket教程-登录界面
    iOS在App和Server间创建socket教程-消息界面iOS在App和Server间创建socket教程-消息界面
    iOS在App和Server间创建socket教程-server界面iOS在App和Server间创建socket教程-server界面

    github地址

    原文地址

    相关文章

      网友评论

      • __weak:博主你好,我是个菜鸟,想请教一下你。我们公司是刚成立的一个小公司,做的是共享单车方面的(不明白老板为啥在这环境下还坚持做这个),我现在想要做的用户在骑行单车过程和结束后把骑行数据一直不间断的上传到服务器(类似于摩拜的行程详情吧),之前考虑过用(CocoaAsyncSocket)这个第三方实现tcp的长连接,但这样会对服务器造成很大压力,毕竟刚成立的小公司服务器可能支撑不住。后来后台和安卓那些人建议说做个socket server,就是让后台发请求给前端,然后IP及时更新给后台。我百度了下就找到博主的写的算是比较详细的,我想问的是这个socket server到底有什么不同,这样真的会相对来说减轻服务器压力吗?另外博主的那个安装python的也是我们前端弄的吗?没做过socket,对这方面真的不懂啊,还请楼主不吝赐教。非常感谢
        __weak:@ShawnDu 感谢兄弟,另外你博客很多干货,不错不错
        ShawnDu:如果在打开app的时候,只需要实时上传东西,不需要实时从server拿数据,没必要做成socket长连接,http就可以;如果没有这么高的实时性,可以考虑在行程过程中,本地记录,结束的时候一次上传;如果要用socket连接,需要server启服务,监听client来的连接;文章开头说的安装twists是在linux server上安装的,你可以在mac上 跑Vapor,启服务 https://github.com/dulingkang/Vapor-demo

      本文标题:iOS在App和Server间创建socket教程

      本文链接:https://www.haomeiwen.com/subject/fnfykxtx.html