使用CocoaAsyncSocket先来模拟创建一个服务端(创建一个Mac工程):
具体步骤:
1.创建一个用于监听的Socket
2.绑定ip&监听端口&接受新连接
3.监听新的连接
4.接收数据/发送数据
示例代码:
#import "ViewController.h"
#import "GCDAsyncSocket.h"
@interface ViewController () <GCDAsyncSocketDelegate>
// 1. 用于监听的socket
@property (nonatomic,strong) GCDAsyncSocket *listenSocket;
// 用于存放数据交互的socket
@property (nonatomic,strong) NSMutableArray *connectedSockets;
@end
@implementation ViewController
// 点击开始服务器按钮
- (IBAction)clickStartServerButton:(id)sender {
// 2. 绑定ip&监听端口&接受新连接封装在一个方法中
/*
参数1: 地址
参数2: 端口
参数3: 错误
*/
BOOL success = [self.listenSocket acceptOnInterface:@"127.0.0.1" port:1234 error:nil];
if (success) {
NSLog(@"服务器开启成功");
} else {
NSLog(@"服务器开启失败");
}
}
- (void)viewDidLoad {
[super viewDidLoad];
}
- (void)setRepresentedObject:(id)representedObject {
[super setRepresentedObject:representedObject];
}
#pragma mark -- GCDAsyncSocketDelegate
/**
* 3. 已经接受到新的连接后调用
*
* @param sock 服务端用于监听的socket
* @param newSocket 服务端用于数据交互的socket (Socket是一个调用接口,不能被传递的)
*/
- (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket{
/**
* Called when a socket accepts a connection.
* Another socket is automatically spawned to handle it.
*
* You must retain the newSocket if you wish to handle the connection.
* Otherwise the newSocket instance will be released and the spawned connection will be closed.
---> 如果不强引用用于数据交互的socket,当连接后服务器就会把连接关闭了
e.g. 终端中通过telnet指令连接服务器: telnet 127.0.0.1 1234
终端提示: Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Connection closed by foreign host.
一旦有新的连接就会调用这个方法,所以每一个连接都需要强引用负责数据交互的socket
*
* By default the new socket will have the same delegate and delegateQueue.
* You may, of course, change this at any time.
**/
[self.connectedSockets addObject:newSocket]; // 添加到可变数据,进行强引用
// newSocket.connectedHost 连接的端口号,可以打印出连接这台服务器的主机ip
NSLog(@"接收到来自%@的连接",newSocket.connectedHost);
// 数据交互操作一定要使用newSocket,使用服务端用于监听的socket将会接收发送失败
// 4.1 发送数据
NSString *string = [NSString stringWithFormat:@"欢迎连接我的服务器~"];
// withTimeout 超时时间,设置-1代表永远不超时
// tag 类似于button的tag值
[newSocket writeData:[string dataUsingEncoding:NSUTF8StringEncoding] withTimeout:-1 tag:0];
// 接收数据 (只能接收一次)
[newSocket readDataWithTimeout:-1 tag:0];
// 4.2 接收数据 定时器 轮循,一直来接收数据
// [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(readData:) userInfo:newSocket repeats:YES];
// 子线程的消息循环默认不会开启,需要手动开启
// [[NSRunLoop currentRunLoop] run];
//定时器的方式接收数据缺点:1s执行一次,并不实时,受设置的计时约束,另外如果没有新数据也在调用
}
// 发送数据
- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag{
NSLog(@"已经发送数据后调用");
}
/**
* 已经接收到数据后调用
*
* @param sock 数据交互的socket
* @param data 接收到的数据
* @param tag 标记
*/
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag{
NSLog(@"%@",[[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding]);
// 4.2 接收数据 (按需获取)
[sock readDataWithTimeout:-1 tag:0];
}
// 定时器调用的方法
- (void)readData:(NSTimer *)timer{
// 接收数据
[timer.userInfo readDataWithTimeout:-1 tag:0];
}
#pragma mark -- 懒加载
// 用于监听的socket
- (GCDAsyncSocket *)listenSocket{
if (_listenSocket == nil) {
/*
delegateQueue:时效性选择主线程,性能更好选择异步线程
socketQueue: 执行连接,接受再去队列中执行,设置NULL会自动设置队列,使用自己的队列容易出现线程问题
*/
_listenSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_global_queue(0, 0) socketQueue:NULL];
}
return _listenSocket;
}
// 用于存放数据交互的socket
- (NSMutableArray *)connectedSockets{
if (_connectedSockets == nil) {
_connectedSockets = [NSMutableArray array];
}
return _connectedSockets;
}
@end
实时接收数据:
2016-07-26 13:39:01.487 01-服务端Socket[8522:196543] 服务器开启成功
2016-07-26 13:39:13.944 01-服务端Socket[8522:196605] 接收到来自127.0.0.1的连接
2016-07-26 13:39:13.945 01-服务端Socket[8522:196598] 已经发送数据后调用
2016-07-26 13:39:16.328 01-服务端Socket[8522:196768] test
2016-07-26 13:39:19.079 01-服务端Socket[8522:196768] test
2016-07-26 13:39:20.967 01-服务端Socket[8522:196598] test