iOS在App和Server间创建socket教程

原文地址

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地址

原文地址

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,142评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,298评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,068评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,081评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,099评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,071评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,990评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,832评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,274评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,488评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,649评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,378评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,979评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,625评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,643评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,545评论 2 352

推荐阅读更多精彩内容