iOS-Socket開發學習筆記-1

weixin_34146805發表於2017-05-04

BSD socket API

/**
 socket 建立並初始化 socket,返回該 socket 的檔案描述符,如果描述符為 -1 表示建立失敗。

 @param addressFamily   是 IPv4(AF_INET) 或 IPv6(AF_INET6)。
 @param type 表示        socket 的型別,通常是流stream(SOCK_STREAM) 或資料包文datagram(SOCK_DGRAM)
 @param protocol        引數通常設定為0,以便讓系統自動為選擇我們合適的協議,對於 stream socket 來說會是 TCP 協議(IPPROTO_TCP),而對於 datagram來說會是   UDP 協議(IPPROTO_UDP)。
 @return                返回該 socket 的檔案描述符
 *//
int socket(int addressFamily, int type, int protocol);

/**
 伺服器端偵聽客戶端的請求

 @param socketFileDescriptor 服務端socket
 @param backlogSize 客戶端連線請求緩衝區佇列的大小
 @return 0 成功或者其他 錯誤代號,(不是非0即真)
 */
int listen(int socketFileDescriptor, int backlogSize) __DARWIN_ALIAS(listen);


/**
 接受客戶端連線請求並將客戶端的網路地址資訊儲存到 clientAddress 中。
 
 當客戶端連線請求被伺服器接受之後,客戶端和伺服器之間的鏈路就建立好了,兩者就可以通訊了

 @param socketFileDescriptor 伺服器的socket描述字
 @param address 指向struct sockaddr *的指標,用於返回客戶端的協議地址
 @param addressStructLength address結構體資料長度
 @return 由核心自動生成的一個全新的描述字,代表與返回客戶的TCP連線。
 */
int accept(int socketFileDescriptor, struct sockaddr * __restrict address, socklen_t * __restrict addressStructLength)
__DARWIN_ALIAS_C(accept);

/**
 客戶端向特定網路地址的伺服器傳送連線請求,連線成功返回0,失敗返回 -1。
 當伺服器建立好之後,客戶端通過呼叫該介面向伺服器發起建立連線請求。對於 UDP 來說,該介面是可選的,如果呼叫了該介面,表明設定了該 UDP socket 預設的網路地址。對 TCP socket來說這就是傳說中三次握手建立連線發生的地。
 注意:該介面呼叫會阻塞當前執行緒,直到伺服器返回。

 @param socketFileDescriptor 客戶端sockets
 @param serverAddress 向資料結構sockaddr的指標,其中包括目的埠和IP地址,伺服器的"結構體"地址;提示:C 語言中沒有物件
 @param serverAddressLength 結構體資料長度
 @return 0 成功或者其他 錯誤代號,(不是非0即真)
 */
int connect(int socketFileDescriptor, const struct sockaddr *serverAddress, socklen_t serverAddressLength) __DARWIN_ALIAS_C(connect);


/**
 從 socket 中讀取資料,讀取成功返回成功讀取的位元組數,否則返回 -1。
 一旦連線建立好之後,就可以通過 send或者receive 介面傳送或接收資料了。注意呼叫 connect 設定了預設網路地址的 UDP socket 也可以呼叫該介面來傳送資料。

 @param socketFileDescriptor 客戶端sockets
 @param buf 接受資料的buffer
 @param bufferLength buffer長度
 @param flags 接收方式,0表示阻塞,必須等待伺服器返回資料
 @return 如果成功,則返回讀入的位元組數,失敗則返回SOCKET_ERROR
 */
ssize_t recv(int socketFileDescriptor, void *buf, size_t bufferLength, int flags) __DARWIN_ALIAS_C(recv);


/**
 通過 socket 傳送資料,傳送成功返回成功傳送的位元組數,否則返回 -1。
 一旦連線建立好之後,就可以通過 send/receive 介面傳送或接收資料了。注意呼叫 connect 設定了預設網路地址的 UDP socket 也可以呼叫該介面來接收資料。

 @param socketFileDescriptor    指定傳送端套接字描述符
 @param buf                     存放應用程式要傳送資料的緩衝區
 @param bufferLength            傳送的資料的位元組數
 @param flags                   傳送方式,0表示阻塞,必須等待伺服器返回資料
 @return                        如果成功,則返回傳送的位元組數,失敗則返回SOCKET_ERROR
 */
ssize_t send(int socketFileDescriptor, const void *buf, size_t bufferLength, int flags) __DARWIN_ALIAS_C(send);


/**
 將 socket 與特定主機地址與埠號繫結,成功繫結返回0,失敗返回 -1。
 
 成功繫結之後,根據協議(TCPUDP)的不同,我們可以對 socket 進行不同的操作:
 UDP:因為 UDP 是無連線的,繫結之後就可以利用 UDP socket 傳送資料了。
 TCP:而 TCP 是需要建立端到端連線的,為了建立 TCP 連線伺服器必須呼叫 listen(int socketFileDescriptor, int backlogSize) 來設定伺服器的緩衝區佇列以接收客戶端的連線請求,backlogSize 表示客戶端連線請求緩衝區佇列的大小。當呼叫 listen 設定之後,伺服器等待客戶端請求,然後呼叫 accept 來接受客戶端的連線請求。

 @param socketFileDescriptor    主機socket
 @param addressToBind           資料結構sockaddr的指標,其中包括目的埠和IP地址,伺服器的"結構體"地址;提示:C 語言中沒有物件
 @param addressStructLength     sockaddr結構體資料長度
 @return                        0 成功/其他 錯誤代號,(不是非0即真)
 */
int bind(int socketFileDescriptor, const struct sockaddr *addressToBind, socklen_t addressStructLength) __DARWIN_ALIAS(bind);


/**
 伺服器端偵聽客戶端的請求

 @param socketFileDescriptor 服務端socket
 @param backlogSize 客戶端連線請求緩衝區佇列的大小
 @return 0 成功/其他 錯誤代號,(不是非0即真)
 */
int listen(int socketFileDescriptor, int backlogSize) __DARWIN_ALIAS(listen);


/**
 接受客戶端連線請求並將客戶端的網路地址資訊儲存到 clientAddress 中。
 
 當客戶端連線請求被伺服器接受之後,客戶端和伺服器之間的鏈路就建立好了,兩者就可以通訊了

 @param socketFileDescriptor 伺服器的socket描述字
 @param address 指向struct sockaddr *的指標,用於返回客戶端的協議地址
 @param addressStructLength address結構體資料長度
 @return 由核心自動生成的一個全新的描述字,代表與返回客戶的TCP連線。
 */
int accept(int socketFileDescriptor, struct sockaddr * __restrict address, socklen_t * __restrict addressStructLength)
__DARWIN_ALIAS_C(accept);

一、客戶端操作

  • 1、繫結地址和埠操作
int server_socket = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in server_addr;
server_addr.sin_len = sizeof(struct sockaddr_in);// 結構體長度
server_addr.sin_family = AF_INET; //sin_family指代協議族,在socket程式設計中只能是AF_INET
server_addr.sin_port = htons(1234);//儲存埠號(使用網路位元組順序),在linux下,埠號的範圍0~65535,同時0~1024範圍的埠號已經被系統使用或保留。
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");//儲存IP地址,使用in_addr這個資料結構
bzero(&(server_addr.sin_zero), 8);//是為了讓sockaddr與sockaddr_in兩個資料結構保持大小相同而保留的空位元組, 初始值應該使用函式 bzero() 來全部置零
  • 2、接受客戶端的連結
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        dispatch_async(queue, ^{
            //建立新的socket
            int aResult = connect(server_socket, (struct sockaddr*)&server_addr, sizeof(struct sockaddr_in));
            if (aResult == -1) {
                NSLog(@"連結失敗");
            }else{
                self.server_socket = server_socket;
                [self acceptFromServer];
            }
        });
  • 3、從服務端接受訊息
- (void)acceptFromServer {
    while (1) {
        //接受伺服器傳來的資料
        char buf[1024];
        long iReturn = recv(self.server_socket, buf, 1024, 0);
        if (iReturn > 0) {
            NSString *str = [NSString stringWithCString:buf encoding:NSUTF8StringEncoding];

            //篩選字首
            if ([str hasPrefix:@"list:"]) {
                NSString *arrayStr = [str substringFromIndex:5];
                NSArray *list = [arrayStr componentsSeparatedByString:@","];
                self.userArray = [NSMutableArray arrayWithArray:list];
                dispatch_async(dispatch_get_main_queue(), ^{
                    [self.onlineTable reloadData];
                });
                NSLog(@"當前線上使用者列表:%@",arrayStr);
            }else{
                //回到主執行緒 介面上顯示內容
                [self showLogsWithString:str];
            }
             
        }else if (iReturn == -1){
            NSLog(@"接受失敗-1");
            break;
        }
    }
}
  • 4、給客戶端傳送資訊
- (void)sendMsg:(NSString*)msg {
    char *buf[1024] = {0};
    const char *p1 = (char*)buf;
    p1 = [msg cStringUsingEncoding:NSUTF8StringEncoding];
    send(self.server_socket, p1, 1024, 0);
}

二、服務端操作

  • 1、繫結地址和埠
    //建立socket
    int server_socket = socket(AF_INET, SOCK_STREAM, 0);
    if (server_socket == -1) {
        NSLog(@"建立失敗");
        [self showLogsWithString:@"socket建立失敗"];

    } else {
        //繫結地址和埠
        struct sockaddr_in server_addr;
        server_addr.sin_len = sizeof(struct sockaddr_in);
        server_addr.sin_family = AF_INET;
        server_addr.sin_port = htons(1234);
        server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
        bzero(&(server_addr.sin_zero), 8);
        
        int bind_result = bind(server_socket, (struct sockaddr*)&server_addr, sizeof(server_addr));
        if (bind_result == -1) {
            NSLog(@"繫結埠失敗");
            [self showLogsWithString:@"繫結埠失敗"];

        } else {
            if (listen(server_socket, kMaxConnectCount)==-1) {
                NSLog(@"監聽失敗");
                [self showLogsWithString:@"監聽失敗"];

            } else {
                for (int i = 0; i < kMaxConnectCount; i++) {
                    //接受客戶端的連結
                    [self acceptClientWithServerSocket:server_socket];
                }
            }
        }
    }
  • 2、建立執行緒接受客戶端
- (void)acceptClientWithServerSocket:(int)server_socket {
    struct sockaddr_in client_address;
    socklen_t address_len;
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(queue, ^{
        //建立新的socket
        while (1) {
            int client_socket = accept(server_socket, (struct sockaddr*)&client_address,&address_len );
            if (client_socket == -1) {
                [self showLogsWithString:@"接受客戶端連結失敗"];
                NSLog(@"接受客戶端連結失敗");
            }else{
                NSString *acceptInfo = [NSString stringWithFormat:@"客戶端 in,socket:%d",client_socket];
                [self showLogsWithString:acceptInfo];
                
                //接受客戶端資料
                [self recvFromClinetWithSocket:client_socket];
            }
        }
    });
}
  • 3、接受客戶端資料
- (void)recvFromClinetWithSocket:(int)client_socket{
    while (1) {
        //接受客戶端傳來的資料
        char buf[1024] = {0};
        long iReturn = recv(client_socket, buf, 1024, 0);
        if (iReturn > 0) {
            NSLog(@"客戶端來訊息了");
            NSString *str = [NSString stringWithCString:buf encoding:NSUTF8StringEncoding];
            [self showLogsWithString:[NSString stringWithFormat:@"客戶端來訊息了:%@",str]];
            [self checkRecvStr:str andClientSocket:client_socket];
        } else if (iReturn == -1) {
            NSLog(@"讀取訊息失敗");
            [self showLogsWithString:@"讀取訊息失敗"];
            break;
        } else if (iReturn == 0) {
            NSLog(@"客戶端走了");
            [self showLogsWithString:[NSString stringWithFormat:@"客戶端 out socket:%d",client_socket]];
            NSMutableArray *array = [NSMutableArray arrayWithArray:self.clientArray];
            for (ClientModel *model in array) {
                if (model.clientSocket == client_socket) {
                    [self.clientNameArray removeObject:model.clientName];
                    [self.clientArray removeObject:model];
                }
            }
            
            close(client_socket);
            
            break;
        }
    }
}

相關文章