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,启动一个循环,等待事件,做出响应:
写一个简单的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];
}
最终的效果是这样: