ios iOS

iOS TCP Server 编程要点

2016-03-15  本文已影响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

上一篇下一篇

猜你喜欢

热点阅读