美文网首页ios iOS
iOS TCP Server 编程要点

iOS TCP Server 编程要点

作者: 蓝点工坊 | 来源:发表于2016-03-15 17:48 被阅读1139次

    一.编程结构

    一般使用GCDAsyncSocket 库,这个是对CFNetworks库的直接封装,如果对于POSIX Socket编程很熟的话,这个流程相当熟悉的.

    1.1 数据结构

    #import "GCDAsyncSocket.h"
    
    dispatch_queue_t socketQueue; //执行socket处理的GCD队列,这样socket处理不占用主线程时间
    GCDAsyncSocket *listenSocket;  //服务器socket 
    NSMutableArray *connectedSockets; //客户端socket列表
    
    

    1.1 初始化socket

    
    
        isRunning = NO;
        
        socketQueue = dispatch_queue_create("socketQueue", NULL);
        
        listenSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:socketQueue];
        [listenSocket setAutoDisconnectOnClosedReadStream:NO];
        
        // Setup an array to store all accepted client connections
        connectedSockets = [[NSMutableArray alloc] initWithCapacity:1];
    
    

    1.3 开始侦听

    NSInteger port = 12345; //侦听端口
    NSError *error = nil;
            if(![listenSocket acceptOnPort:port error:&error])
            {
               // [self logError:FORMAT(@"Error starting server: %@", error )];
                NSLog(@"Error starting server: %@", error );
                return;
            }
    

    1.4 处理客户端联接请求

    - (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket
    {
        // This method is executed on the socketQueue (not the main thread)
        
        @synchronized(connectedSockets)
        {
            [connectedSockets addObject:newSocket];
        }
        
        NSString *host = [newSocket connectedHost];
        UInt16 port = [newSocket connectedPort];
        
        dispatch_async(dispatch_get_main_queue(), ^{
            @autoreleasepool {
                
                NSLog(@"Accepted client %@:%hu", host, port);
                
            }
        });
        
        NSString *welcomeMsg = @"Welcome to the AsyncSocket Echo Server\r\n";
        NSData *welcomeData = [welcomeMsg dataUsingEncoding:NSUTF8StringEncoding];
      //向客户端发送欢迎字符串  
        [newSocket writeData:welcomeData withTimeout:-1 tag:WELCOME_MSG];
        
    //一直等待客户端数据,一直收到\r\n才返回.
        [newSocket readDataToData:[GCDAsyncSocket CRLFData] withTimeout:READ_TIMEOUT tag:0];
    }
    

    1.4 处理客户端发送数据

    - (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
    {
        // This method is executed on the socketQueue (not the main thread)
        
        dispatch_async(dispatch_get_main_queue(), ^{
            @autoreleasepool {
                
                NSData *strData = [data subdataWithRange:NSMakeRange(0, [data length] - 2)];
                NSString *msg = [[NSString alloc] initWithData:strData encoding:NSUTF8StringEncoding];
                if (msg)
                {
                    [self logMessage:msg];
                }
                else
                {
                    [self logError:@"Error converting received data into UTF-8 String"];
                }
                
            }
        });
        
        // 将数据原样发回客户端
        [sock writeData:data withTimeout:-1 tag:ECHO_MSG];
    }
    

    1.5关闭客户端联接

    [newSocket disconnect];
    

    二.常见问题

    2.1 客户端socket突然关闭.

    GCDAsyncSocket 可以设置

    [listenSocket setAutoDisconnectOnClosedReadStream:NO];

    防止在客户端主动关闭socket时断开服务端的相应socket.

    但是在实际编程中仍然发现断开情况,还有两种情况比较隐藏,一种上述connectedSockets对象未初始化,当客户端触发didAcceptNewSocket时也能联接上,但是很快断开,调用读数据方法,在几十次联接会有一次读取成功,因此这个错误比较隐蔽,以为是编程哪里碰到不对,实际还是操作空对象问题.

    第二种情况GCDAsycncSocket还有一个selector是

    - (NSTimeInterval)socket:(GCDAsyncSocket *)sock shouldTimeoutReadWithTag:(long)tag
                     elapsed:(NSTimeInterval)elapsed
                   bytesDone:(NSUInteger)length
    

    这个返回值返回0.0表示一直等待数据接收,而返回值大于零表示多少秒后没有读到数据就会主动断开联接!

    2.2 收不到数据

    现象是触发不了- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag ;这样就无法接收数据.

    一种原因在在联接服务器,或服务端accept时,没有触发接收读.
    触发的方式有多种,比如要用下列之一

    [newSocket readDataWithTimeout:-1 tag:0]; //有数据接收就触发
    [newSocket readDataToLength:20 withTimeout:60 tag:0]; //读到指定长度才触发
     [newSocket readDataToData:[GCDAsyncSocket CRLFData] withTimeout:-1 tag:1]; //读到回车换行符才触发
    

    还有一种可能丢数据,我看到有人建议是文件一定加换行符

    三.Mac 测试工具

    Windows下socket测试相当多,但是Mac下很少,我个人还写一个命令行的工具,但是效果不好,现在我用的开源Qt开发的 sockit. http://code.google.com/p/sockit
    当然还有一些缺点,比如对十六进制处理不好,因此我升级一下.

    sockit mac版

    以下是一个TCP echo server的iOS版,
    http://download.csdn.net/detail/work4blue/9462382

    相关文章

      网友评论

      • d2587c509cae:如何使用GCDAsyncSocket 库来搜索需要连接的host呢?

      本文标题:iOS TCP Server 编程要点

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