IOS Socket 04-利用框架CocoaAsyncSocket實現客戶端/服務器端


 

  這篇文章,我們介紹CocoaAsyncSocket框架的使用,主要介紹實現客戶端/服務器端代碼,相信在網上已經很多這樣的文章了,這里做一下自己的總結。這里介紹使用GCD方式

 

一.客戶端

1.下載地址

讀者可以在github下載框架源碼 https://github.com/robbiehanson/CocoaAsyncSocket

下載后,可以看到在Examples下面可以看到很多例子,如果讀者自學能力高,可以略過下面的文章。

 

2.開始使用

1)在 \Source\GCD 目錄下,我們可以看到GCDAsyncSocket的h與m文件,我們復制這兩個文件到項目中。

2)我們在代碼里面導入頭文件 #import "GCDAsyncSocket.h" 與 協議 GCDAsyncSocketDelegate。

 

3.代碼

1)定義全局變量 GCDAsyncSocket *_socket;

2)開始連接服務器

- (void) connect2Server() {
    //1.主機與端口號
    NSString *host = @"127.0.0.1";
    int port = 12345;
    
    //初始化socket,這里有兩種方式。分別為是主/子線程中運行socket。根據項目不同而定
    _socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];//這種是在主線程中運行
    //_socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)]; 這種是在子線程中運行
    
    //開始連接
    NSError *error = nil;
    [_socket connectToHost:host onPort port error:&error];
    if(error) {
        NSLog("error:%@", error);
    }
    
}

3)登錄

-(void)login() {
    //登錄String
    NSString *loginStr = "iam:I am login!";
    NSData *loginData = [loginStr dataUsingEncoding: NSUTF8StringEncoding];
    //發送登錄指令。-1表示不超時。tag200表示這個指令的標識,很大用處
    [_socket writeData: loginData withTimeout:-1 tag:200];
}

4)開始聊天

//發送聊天數據
-(void) sendMsg: (NSString*)msg{
    NSString *sendMsg = [@"msg:I send message to u!"];
    NSData *sendData = [sendMsg dataUsingEncoding: NSUTF8StringEncoding];
    [_socket writeData; sendData withTimeout:-1 tag:201];
}

5)實現協議delegate

#pragma mark -socket的代理

#pragma mark 連接成功

-(void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port{   
    //連接成功
    NSLog(@"%s",__func__);
}

#pragma mark 斷開連接
-(void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err{
    if (err) { 
        NSLog(@"連接失敗");
    }else{
        NSLog(@"正常斷開");  
    }
}

#pragma mark 數據發送成功
-(void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag{  
    NSLog(@"%s",__func__); 
    //發送完數據手動讀取 
    [sock readDataWithTimeout:-1 tag:tag];
}

#pragma mark 讀取數據
-(void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag{    
    //代理是在主/子線程調用
    NSLog(@"%@",[NSThread currentThread]);
    NSString *receiverStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    
    if (tag == 200) {
    //登錄指令
     }else if(tag == 201){
    //聊天數據
    }    
    NSLog(@"%s %@",__func__,receiverStr);
}

 

 

二.服務器端

服務器端不可能在iphone/ipad上運行,所以我們新建一個單純命令行的項目。

1.新建一個類MyChatServer,並且開放公共方法startServer,頭文件如下

#import <Foundation/Foundation.h>

@interface MyChatServer : NSObject
/**
 *  開啟聊天服務器
 */
-(void)startServer;
@end

2.在main文件中,開啟聊天服務器,並且開啟主運行循環,讓服務器接收到客戶端請求

//實現聊天
MyChatServer *chatServer = [[MyChatServer alloc] init];
[chatServer startServer];
//開啟主運行循環
[[NSRunLoop currentRunLoop] run];

3.在MychatServer.m文件中,我們開始實現GCDAsyncSocket的方法(下面代碼都在MychatServer.m中)

1)首先建一個成員變量array對象,把所有的socket(客戶端)對象保存在里面

@property(strong,nonatomic)NSMutableArray *clientSocket;

私有變量serverSocket與golbal_queue

GCDAsyncSocket *_serverSocket;
dispatch_queue_t _golbalQueue;

2)在類初始化的時候,我們初始化私有變量與成員變量

-(instancetype)init{
    if (self = [super init]) {
      _clientSocket = [NSMutableArray array];    
      //創建全局queue
      _golbalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
      //創建服務端的socket,注意這里的是初始化的同時已經指定了delegate
      _serverSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:_golbalQueue];
    } 
    return self;
}

3)開啟服務器聊天startServer

-(void)startChatServer{   
    //打開監聽端口
    NSError *err;
    [_serverSocket acceptOnPort:12345 error:&err];
     if (!err) {
        NSLog(@"服務開啟成功");
    }else{
        NSLog(@"服務開啟失敗");
    }
}

4)實現delegate

#pragma mark 有客戶端建立連接的時候調用
-(void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket{
    //sock為服務端的socket,服務端的socket只負責客戶端的連接,不負責數據的讀取。 newSocket為客戶端的socket
    NSLog(@"服務端的socket %p 客戶端的socket %p",sock,newSocket);
    //保存客戶端的socket,如果不保存,服務器會自動斷開與客戶端的連接(客戶端那邊會報斷開連接的log)
    NSLog(@"%s",__func__);
    [self.clientSocket addObject:newSocket];
    
    //newSocket為客戶端的Socket。這里讀取數據
    [newSocket readDataWithTimeout:-1 tag:100];
}

#pragma mark 服務器寫數據給客戶端
-(void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag{
    NSLog(@"%s",__func__);
    [sock readDataWithTimeout:-1 tag:100];
}

#pragma mark 接收客戶端傳遞過來的數據
-(void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag{
    //sock為客戶端的socket 
    NSLog(@"客戶端的socket %p",sock);
    //接收到數據
    NSString *receiverStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    NSLog(@"length:%ld",receiverStr.length);
    // 把回車和換行字符去掉,接收到的字符串有時候包括這2個,導致判斷quit指令的時候判斷不相等
    receiverStr = [receiverStr stringByReplacingOccurrencesOfString:@"\r" withString:@""];
    receiverStr = [receiverStr stringByReplacingOccurrencesOfString:@"\n" withString:@""];
    
    //判斷是登錄指令還是發送聊天數據的指令。這些指令都是自定義的
    //登錄指令
    if([receiverStr hasPrefix:@"iam:"]){
// 獲取用戶名
        NSString *user = [receiverStr componentsSeparatedByString:@":"][1];
        // 響應給客戶端的數據
        NSString *respStr = [user stringByAppendingString:@"has joined"];
      [sock writeData:[respStr dataUsingEncoding:NSUTF8StringEncoding] withTimeout:-1 tag:0];
    }
    //聊天指令
    if ([receiverStr hasPrefix:@"msg:"]) {
        //截取聊天消息
        NSString *msg = [receiverStr componentsSeparatedByString:@":"][1];
      [sock writeData:[msg dataUsingEncoding:NSUTF8StringEncoding] withTimeout:-1 tag:0];
    }
    //quit指令
    if ([receiverStr isEqualToString:@"quit"]) {
        //斷開連接
        [sock disconnect];
        //移除socket
       [self.clientSocket removeObject:sock]; 
    }
    NSLog(@"%s",__func__);
}

 

查看本文章之前,可以看看

IOS Socket 03-建立連接與登錄

 

可以關注本人的公眾號,多年經驗的原創文章共享給大家。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM