• (六十四)iOS的socket实现(C+OC混合实现)


    对于微博、微信朋友圈之类的网络通信,使用JSON完全可以满足需求,但是如果要制作网络游戏,就需要建立一个持久连接,这时候就要考虑使用socket。

    在iOS上实现socket大体有两种方法,一是借助自带的输入输出流和C语言socket相结合,二是利用第三方类库CocoaAsyncSocket,本文将介绍前者,在下一篇文章中介绍基于第三方类库的实现方法。

    要调试socket,首先应该有一个简易的socket服务端,下面是用python写的简单服务端,功能是发送iam:name相当于登录操作,服务器会返回name has joined,发送msg:content,服务器会判断当前登录者,然后返回name:msg。

    下面是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 + '
    ')
    
    
    factory = Factory()
    factory.protocol = IphoneChat
    factory.clients = []
    
    reactor.listenTCP(12345, factory)
    print "Iphone Chat server started"
    reactor.run()
    
    

    使用方法也很简单,只需要打开终端,输入python server.py即可运行服务端,运行成功后会在终端打印Iphone Chat server started.


    下面介绍如何实现iOS客户端:

    从上面的源码可以看到,服务端的端口号是12345,假设是在本地使用,则按如下方法进行连接:

        // 建立连接
        NSString *host = @"127.0.0.1";
        int port = 12345;
        // 注意C字符串和OC字符串桥接转换
        // 定义输入输出流
        CFReadStreamRef read_s;
        CFWriteStreamRef write_s;
        CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)host, port, &read_s, &write_s);
    注意CF开头的是C语言的结构体和函数,为了和OC连接,我们需要定义OC的输入输出流:

    NSInputStream *_input_s;
    NSOutputStream *_output_s;
    然后进行桥接转换:不要忘记设置代理,OC的输入输出流有数据时通过代理来通知。

        // 使用代理来通知连接建立是否成功,把C语言的输入输出流转化成OC对象,使用桥接转换。
        _input_s = (__bridge NSInputStream *)(read_s);
        _output_s = (__bridge NSOutputStream *)(write_s);
        _input_s.delegate = _output_s.delegate = self;
    Tip:流的代理名称为NSStreamDelegate。

    之后不要忘记将流加入到主运行循环,并且打开流:

        // 把输入输出流添加到主运行循环,否则代理可能不工作
        [_input_s scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
        [_output_s scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
        
        // 打开输入输出流
        [_input_s open];
        [_output_s open];
    在连接结束时时,不要忘记把流从主循环上移除,判断连接状态通过NSStream的代理方法:

    - (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode{
        
        switch (eventCode) {
            case NSStreamEventEndEncountered:{ // 连接结束
                NSLog(@"关闭输入输出流");
                [_input_s close];
                [_output_s close];
                [_input_s removeFromRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
                [_output_s removeFromRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
                break;
            }
            case NSStreamEventErrorOccurred: // 连接出错
                break;
            case NSStreamEventHasBytesAvailable: // 有字节可读
                [self readData];
                break;
            case NSStreamEventNone: // 无事件
                break;
            case NSStreamEventOpenCompleted: // 连接打开完成
                NSLog(@"打开完成");
                break;
            case NSStreamEventHasSpaceAvailable: // 可以发放字节
                break;
            default:
                break;
        }
        
    }

    建立连接的完整代码为:

        // 建立连接
        NSString *host = @"127.0.0.1";
        int port = 12345;
        // 注意C字符串和OC字符串桥接转换
        // 定义输入输出流
        CFReadStreamRef read_s;
        CFWriteStreamRef write_s;
        CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)host, port, &read_s, &write_s);
        
        // 使用代理来通知连接建立是否成功,把C语言的输入输出流转化成OC对象,使用桥接转换。
        _input_s = (__bridge NSInputStream *)(read_s);
        _output_s = (__bridge NSOutputStream *)(write_s);
        _input_s.delegate = _output_s.delegate = self;
        
        // 把输入输出流添加到主运行循环,否则代理可能不工作
        [_input_s scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
        [_output_s scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
        
        // 打开输入输出流
        [_input_s open];
        [_output_s open];

    想要发送一条消息,只需要通过OC的输出流即可,先把NSString转化为data,然后调用流的write方法,注意这里需要传两个参数,一个是缓冲区大小,一个是最大长度:data.bytes返回的是一个很大的值,而data.length返回的是实际长度,因此我们用data.bytes作为缓冲区的大小,data.length作为实际长度。

        // 发送 iam:name 表示name登录
        NSString *loginStr = @"iam:soulghost";
        // 把string转成NSData
        NSData *data = [loginStr dataUsingEncoding:NSUTF8StringEncoding];
        [_output_s write:data.bytes maxLength:data.length];

    一旦我们收到数据,就会调用上面提到的代理方法,通过eventCode区分(上面用代理方法处理了连接成功和连接结束),如果eventCode为NSStreamEventHasBytesAvailable,代表有字节可读,这时候我们需要一个方法来读取输入流的内容:

        // 建立一个缓冲区,可放1024字节
        uint8_t buf[1024];
        NSInteger len = [_input_s read:buf maxLength:sizeof(buf)];
        
        // 把字节数组转化为字符串
        NSData *data = [NSData dataWithBytes:buf length:len];
        NSString *msg = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    

    输入流的内容会存到unsigned char数组内,uint8_t即unsigned char,我们定义1024字节的缓冲区,接收从输入流中读到的数据,然后用它来初始化一个data,转为字符串后处理。


  • 相关阅读:
    socket阻塞与非阻塞,同步与异步
    Python列表切成多个/生成多个空列表
    virtualbox 下windows与虚拟机实现文件共享---挂载
    centos安装mysql
    centos安装Python2.7
    在遍历或者迭代过程中对集合进行增删 都会发生异常(迭代器的并发异常)
    List
    LinkedList
    增强for循环
    Collection中的迭代器
  • 原文地址:https://www.cnblogs.com/aiwz/p/6154154.html
Copyright © 2020-2023  润新知