IOS底層網絡之Socket

BSD Socket

創建Socket

調用socket(int addressFamily, int type, int protocol),返回值類型int

參數:
- addressFamily:Socket的網絡域,IPV4(AF_INET )或者 IPV6(AF_INET6);
- type:Socket類型,流式Socket(SOCK_STREAM)、數據包Socket(SOCK_DGRAM)
- protocol:協議枚舉值,根據Socket類型自動選擇,流式選擇IPPROTO_TCP,數據包則選擇IPPROTO_UDP。

返回值:如果創建成功,返回值爲新文件說明符的號碼;如果創建失敗,返回-1。

注意:創建完成後,通信尚未開始,Socket也沒有被指定爲輸入或輸出Socket(直到首次使用Socket時纔會指定)。

建立連接

  • 配置Socket服務器
    (1)先調用bind(int socketFileDescriptor, sockaddr *addressToBind, int addressStructLength),與具有唯一地址的Socket關聯。接收一個Socket並將其分配或綁定到某個特定的地址與端口。成功則返回0,否則返回-1.
    (2)如果在socket(int, int, int)中的連接類型如果爲UDP:可以開始向外界傳輸數據了,因爲UDP是個無連接的協議,不需要再另一端監聽;
    (3)若爲TCP:要調用listen(int socketFileDescriptor, int backlogSize)來建立好緩衝區隊列的數據結構。socketFileDescriptor會成爲只讀socket,不能用於發送消息;backlogSize表示有多少個掛起的連接在排隊的同時等待服務器代碼的使用。在監聽時,服務器會等待進來的連接請求並調用accept(int socketFileDescriptor, sockaddr *clientAddress, int clientAddressStructLength)來接收請求。這會將掛起的請求從緩衝區中移除,並使用客戶端的地址信息(主要是IP和port)來裝配clientAddress結構體。接受了掛起的請求後,服務器就可以從客戶端接收消息了。

  • Socket客戶端連接
    (1)TCP Socket:客戶端首先通過connect(int socketFileDescriptor, sockaddr *serverAddress, int serverAddressStructLength)協商一個到服務器的連接。在TCP握手時該調用會阻塞,成功返回0,否則-1.
    (2)UDP Socket:connect方法是可選的。如果調用它則會爲所有的UDP傳輸Socket設定默認地址,這樣會方便UDP數據包的發送和接收。如果設備通過主機名而不是IP地址進行連接,它可能不清楚如何繼續,因爲socketaddr結構體只包含一個IP地址。可以通過DNS(Domain Name System)將主機名轉爲IP地址。

發送/接收消息

  • TCP

    發送消息:int send(int socketFileDescriptor, char *buffer, int bufferLength, int flags), socketFileDescriptor:其描述的Socket會將緩存中介於0與bufferLength之間的字節發送出去。成功則返回成功發送出去的字節數量,失敗返回-1。

    接收消息:int receive(int socketFileDescriptor, char *buffer, int bufferLength, int flags),緩存會通過從Socket讀取的第一個bufferLength長度的字節副本來裝配。成功則返回成功讀取的字節數量,失敗返回-1。

  • UDP:
    (1)如果調用connect()來設定默認地址的UDP,則可以同上TCP一樣調用send和receive;
    (2)沒有調用connect():

    發送消息:int sendto(int socketFileDescriptor, char *buffer, int bufferLength, int flags, sockaddr *destinationAddress, int destinationAddressLength)使用相同的Socket連接發送給多個地址,和send類似,只不過它爲目標地址提供額外的參數;

    接收消息:int recvfrom(int socketFileDescriptor, char *buffer, int bufferLength, int flags, sockaddr *fromAddress, int *fromAddressLength),最後一個參數是指向整數的指針,值是fromAddress結構體的最終長度。

    代碼舉例:創建socket來接收信息

- (void)loadCurrentStatus:(NSURL *)url {
    // 創建流式Socket
    int socketFileDescriptor = socket(AF_INET, SOCK_STREAM, 0);
    if (socketFileDescriptor == -1) {   // 創建失敗
        return;
    }

    // 將主機名轉爲IP
    struct hostent *remoteHostEnt = gethostbyname([[url host] UTF8String]);
    if (remoteHostEnt == NULL) {
        // 轉換失敗
        return;
    }

    struct in_addr *remoteAddr = (struct in_addr *)remoteHostEnt->h_addr_list[0];

    // 設置socket參數來打開IP地址
    struct sockaddr_in socketParameters;
    socketParameters.sin_family = AF_INET;
    socketParameters.sin_addr = *remoteAddr;
    socketParameters.sin_port = htons([[url port] intValue]); // 整數轉爲網絡字節序

    // 連接socket

    // 當sin_family爲AF_INET時,sockaddr_in和sockaddr這兩個結構體佈局一樣,所以sockaddr_in可以轉爲sockaddr
    if (connect(socketFileDescriptor, (struct sockaddr * )&socketParameters, sizeof(socketParameters)) == -1) {   // 連接失敗
        return;
    }

    // 連接成功

    NSMutableData *data = [[NSMutableData alloc] init];
    BOOL waitingForData = YES;

    // 接收數據
    while(waitingForData) {
        const char *buffer[1024];
        int length = sizeof(buffer);

        // read a buffer's amount of data from the socket, the number of bytes read is returned.
        int result = recv(socketFileDescriptor, &buffer, length, 0);
        if (result > 0) {
            // 接收成功
            [data appendBytes:buffer length:result];
        } else {
            waitingForData = NO; // 退出接收數據
        }
    }

    // 讀取完成後關閉socket
    close(socketFileDescriptor);

    NSString *resultsString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    NSLog(@"received string: %@", resultsString);
}

CFNetwork

對BSD Socket的一層輕量級封裝,主要優勢在於被集成到系統級的設置與主運行循環中。如必要時開啓無效以及通過系統範圍的VPN進行路由等,並且沒有什麼嚴重的缺陷。

創建socket:

CFStreamCreatePairWithSocketToHost(),可以針對給定的主機名和端口創建一對socket,一個用於讀,一個用於寫。其中框架會負責將主機名轉換爲IP地址,將端口號轉換爲網絡字節序。如果不需要其中一個Socket,只需將NULL作爲讀或寫流參數,就不會創建它了。

注意:使用前,必須通過CFReadStreamOpen()或CFWriteStreamOpen()打開流。這兩個調用都是異步的,在成功打開後會通過kCFStreamEventOpenCompleted調用回調函數。

代碼舉例:創建與打開流

- (void)loadCurrentStatus:(NSURL *)url {

    // keep a reference to self to use for controller callbacks
    CFStreamClientContext ctx = {0, (__bridge void *)self, NULL, NULL, NULL};

    // get callbacks for stream data, stream end, and any errors
    CFOptionFlags registeredEvents = (kCFStreamEventHasBytesAvailable | // socket有可以讀取的字節
                                      kCFStreamEventEndEncountered |    // socket到達字節流的末尾
                                      kCFStreamEventErrorOccurred); // 操作出現錯誤

    // 創建一個只讀socket
    CFReadStreamRef readStream;

    CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, (__bridge CFStringRef)[url host], [[url port] intValue], &readStream, NULL);

    //schedule the stream ton the run loop to enable callbacks

    // 如果設置了kCFStreamEventOpenComplete,打開成功後會調用回調函數

    // 註冊socket回調函數
    if (CFReadStreamSetClient(readStream, registeredEvents, socketCallback, &ctx)) {

        // 根據給定的運行循環來調度流
        CFReadStreamScheduleWithRunLoop(readStream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);

    } else {
        //調用回調失敗
        return;
    }

    // 打開readStream
    if (CFReadStreamOpen(readStream) == NO) {
        // 打開失敗
        return;
    }


    CFErrorRef error = CFReadStreamCopyError(readStream);
    if (error != NULL) {
        if (CFErrorGetCode(error) != 0) {
            // 連接失敗

        }
        CFRelease(error);
        return;
    }

    //連接成功,去開始進程
    CFRunLoopRun();
}

// 一個回調函數,當registeredEvents發生時,該函數就會被調用
void socketCallback(CFReadStreamRef stream, CFStreamEventType event, void *myPtr) {
    switch (event) {
        case kCFStreamEventHasBytesAvailable:
            // 讀取bytes
            while(CFReadStreamHasBytesAvailable(stream)) {
                UInt8 buffer[1024];
                int numBytesRead = CFReadStreamRead(stream, buffer, 1024);
                NSData *data = [NSData dataWithBytes:buffer length:numBytesRead];
                NSLog(@"接收到的數據 = %@", data);
            }

            break;

        case  kCFStreamEventErrorOccurred: {
            CFErrorRef error = CFReadStreamCopyError(stream);
            if (error != NULL) {
                if (CFErrorGetCode(error) != 0) {
                    // 獲取錯誤信息
                }
                CFRelease(error);
            }
        }
            break;

        case kCFStreamEventEndEncountered: {
            // 關閉stream
            CFReadStreamClose(stream);

            // stop processing callback methods
            CFReadStreamUnscheduleFromRunLoop(stream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);

            // 結束當前線程的主運行
            CFRunLoopStop(CFRunLoopGetCurrent());
        }
            break;

        default:
            break;
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章