socket通信以及GCDAsyncSocket的用法

1.什么是socket?

-网络上的两个程序通过一个双向的通信连接实现数据的交换,这个链接的一端称为一个socket(又叫套接字)

-应用程序通过套接字向网络发出请求或者应答网络请求,常用的协议TCP/UDP

连接过程,具体可分三步:

(1)服务器监听:服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态

(2)客户端请求:客户端的套接字提出连接请求,连接的目标是服务器端的套接字。客户端怎么确保自己能正确连接服务器端呢,客户端的套接字必须首先描述他要连接的服务器的套接字,包括服务器端的地址和端口号,然后向服务器端套接字提出连接请求。

(3)连接确认:当接收到来自客户端套接字的连接请求后,服务器端会响应这个请求,建立一个新的线程,把服务器端的描述发给客户端,一旦客户端确认了此描述,连接就建立好了。而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。

2.GCDAsyncSocket的用法

-创建Socket对象。

GCDAsyncSocket中socket连接后的事件处理都是在代理里面的

 // 创建Socket对象
    // 设置代理,让代理在全局队列中调用
    GCDAsyncSocket *clientSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_global_queue(0, 0)];
    // 用强指针引用着Socket对象
    self.clientSocket = clientSocket;
    
    NSString *host = nil;//host,主机地址
    uint16_t port = 8288;//port,端口号
    // 连接服务器
    NSError *error = nil;
    
    [clientSocket connectToHost:host onPort:port error:&error];
    if (error) {
        NSLog(@"%@", error.localizedDescription);
    }
-常用的几个代理GCDAsyncSocketDelegate.

注意:在连接成功之后要监听数据的读取,接收到数据后也要监听数据的读取,接收数据的代理不会被回调

// 连接成功后的回调
- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port{
    
    // 监听读取数据
    [sock readDataWithTimeout:-1 tag:0];
    
}

// 与服务器断开连接后的回调
- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err{
    // 可以调用该方法查看断开连接的原因
    NSLog(@"%@", err);
}

// 接收数据的回调
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag{
    // 接收到的消息
    NSString *messageStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    NSLog(@"%@", messageStr);
    
    //  准备读取下次的数据
    [sock readDataWithTimeout:-1 tag:0];
    
-发送消息
/**
 *  发送消息
 *
 *  @param sendMessage 发送的消息内容
 */
- (void)sendMessage:(NSString *)sendMessage{
    
    if (sendMessage == nil || sendMessage.length == 0) {
        return;
    }
    
    // 发送
    [self.clientSocket writeData:[sendMessage dataUsingEncoding:NSUTF8StringEncoding] withTimeout:-1 tag:0];
    
}

// 发送消息成功后的回调,消息发送成功后可以在这个代理里面做一些处理
- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag{
    
    
-GCDAsyncSocket的服务端

服务端和客户端的接收和发送数据的方法是一样的,不同的地方就是服务端是开启一个服务被动的等待客户端的连接。

(1)调用GCDAsyncSocket的接口开启服务

/**
 * Tells the socket to begin listening and accepting connections on the given port.
 * When a connection is accepted, a new instance of GCDAsyncSocket will be spawned to handle it,
 * and the socket:didAcceptNewSocket: delegate method will be invoked.
 * 
 * The socket will listen on all available interfaces (e.g. wifi, ethernet, etc)
**/
- (BOOL)acceptOnPort:(uint16_t)port error:(NSError **)errPtr;
此方法绑定一个端口号port,  errPtr是错误变量,可通过该变量判断服务开启是否成功
 
// 定义一个标志位,用于判断当前服务是否开启
BOOL     isRunning;
/**
 *  @brief   开启服务
 */
- (void)startServer
{
    if (!isRunning)
    {
        [self initServer];
        [_serverSocket readDataWithTimeout:-1 tag:0];
        isRunning = YES;
    }
}
 
/**
 *  @brief   服务端初始化
 */
- (void)initServer
{
    allClientArray = [NSMutableArray array];    
    dispatch_queue_t socketQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);    
    _serverSocket = [[GCDAsyncSocket alloc]initWithDelegate:self delegateQueue:socketQueue];    
    NSError *error = nil;    
    [_serverSocket acceptOnPort:_socketPort error:&error];    
    if (error != nil)
    {
        NSLog(@"error --> %@", error);
    }
    else
    {
        NSLog(@"server start...");
    }
}

(2)关闭服务

停止服务直接调用- (void)disconnect方法即可

/**
 *  @brief   停止服务
 */
- (void)stopServer
{
    if (isRunning)
    {
        [_serverSocket disconnect];      
        isRunning = NO;
        NSLog(@"server stop...");
    }
}

在有客户端接入的服务器时,会调用下面三个委托方法:

/**
 * This method is called immediately prior to socket:didAcceptNewSocket:.
 * It optionally allows a listening socket to specify the socketQueue for a new accepted socket.
 * If this method is not implemented, or returns NULL, the new accepted socket will create its own default queue.
 * 
 * Since you cannot autorelease a dispatch_queue,
 * this method uses the "new" prefix in its name to specify that the returned queue has been retained.
 * 
 * Thus you could do something like this in the implementation:
 * return dispatch_queue_create("MyQueue", NULL);
 * 
 * If you are placing multiple sockets on the same queue,
 * then care should be taken to increment the retain count each time this method is invoked.
 * 
 * For example, your implementation might look something like this:
 * dispatch_retain(myExistingQueue);
 * return myExistingQueue;
**/
- (dispatch_queue_t)newSocketQueueForConnectionFromAddress:(NSData *)address onSocket:(GCDAsyncSocket *)sock
 
/**
 * 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.
 * 
 * By default the new socket will have the same delegate and delegateQueue.
 * You may, of course, change this at any time.
**/
- (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket
 
/**
 * Called when a socket connects and is ready for reading and writing.
 * The host parameter will be an IP address, not a DNS name.
**/
- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port;
总结:上面两段代码都是关于如何使用GCDAsyncSocket的,具体的步骤1.初始化socket, 2.建立连接,3.发送接收数据 ,4.断开连接

补充知识:

1.初始化socket源码的四种方法
- (instancetype)init;

- (instancetype)initWithSocketQueue:(nullable dispatch_queue_t)sq;

- (instancetype)initWithDelegate:(nullable id<GCDAsyncSocketDelegate>)aDelegate delegateQueue:(nullable dispatch_queue_t)dq;

- (instancetype)initWithDelegate:(nullable id<GCDAsyncSocketDelegate>)aDelegate delegateQueue:(nullable dispatch_queue_t)dq socketQueue:(nullable dispatch_queue_t)sq;

  aDelegate是socket的代理

  dq是delegate的线程

  sq是socket的线程,这个是可选的设置,可以写为null,这时GCDAsyncSocket内部会帮你创建一个它自己的socket线程,如果要自己提供一个socket线程,不要提供一个并发线程,频繁的socket通信过程中可能会阻塞掉,一般不用自己创建。

注意:在使用套接字前,必须设置委托和委托调度队列,否则将出现错误

2.建立连接时

  如果建立连接成功,会收到socket成功的回调

  - (void)socketDidDisconnect:(GCDAsyncSocket*)sock withError:(NSError*)err

  如果失败了,则会收到以下回调

  - (void)socketDidDisconnect:(GCDAsyncSocket*)sock withError:(NSError*)err

3.发送数据

  [self.socket writeData:data withTimeout:-1 tag:0];

  发送数据的回调

  - (void)socket:(GCDAsyncSocket*)sock didWriteDataWithTag:(long)tag;

4.读取数据的回调

  - (void)socket:(GCDAsyncSocket*)sock didReadData:(NSData*)data withTag:(long)tag;

5.断开连接,重连

  [self.socket disconnect];//断开连接

  

  - (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err

{

//这里可以做重连操作

}

6.主动读取消息

  在发送消息后,需要主动调取didReadDataWithTimeOut方法读取消息,这样才能接收到你发出请求后从服务器端收到的数据

- (void)socket:(GCDAsyncSocket*)sock didWriteDataWithTag:(long)tag

{

    [self.socket readDataWithTimeout:-1 tag:tag];

}

7.关于tag参数

  是为了在回调方法中匹配发起调用的方法的,不会加在传输数据中。

  调用write方法,收到didWriteData回调,调用writeDataWithTimeOut读取数据。收到消息后,会调用didReadData的delegate方法。这是一次数据发送,在接受服务端回应的过程。

  例如

  - (void)writeData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag;

  - (void)onSocket:(AsyncSocket *)sock didWriteDataWithTag:(long)tag;

  该方法和它的代理回调中的tag参数是对应的。源码中tag的传递是包含在当前的写的数据包 GCDAsyncWritePacket currentWrite 中。

   同理,read的tag和readDataWithTimeout代理回调中的tag是一致的,tag传递包含在GCDAsyncReadPacket* currentRead数据包中

  需要注意,根据tag做消息回执的标识,可能会出现错乱的问题

  

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值