iOS中基於 Socket 的 C/S 結構網絡通信(中) – iPhone手機開發技術文章 iPhone軟體開發教學課程

結合上一篇的知識,接下來將介紹基於 TCP 協議的 Socket 編程;由於 Socket 需要有客戶端和服務端,那麼現在實現的是關於服務端的簡單程序。服務端采用的是CFStream 類來實現的。

這個服務端是把Xcode中的 Command Line Tool 來作為服務端的;當然,你也可以把 iPhone 作為服務端,但是要利用其他的框架,比如 AsyncSocket (https://github.com/roustem/AsyncSocket)
,裡面有分為 UDP 和 TCP 實現的 Socket,源程序裡也有許多關於數據流的操作,可以作為深入理解來用,當然也是比較復雜的。

下面來看一下簡單實現的 TCP 服務端程序:(TCPServer.m)

#import 
#include 
#include 

#define PORT 9000

void AcceptCallBack(CFSocketRef, CFSocketCallBackType, CFDataRef, const void *, void *);

void WriteStreamClientCallBack(CFWriteStreamRef stream, CFStreamEventType eventType, void *);

void ReadStreamClientCallBack (CFReadStreamRef stream, CFStreamEventType eventType,void *);

int main(int argc, const char * argv[])
{
    /* 定義一個Server Socket引用 */
    CFSocketRef sserver;

    /* 創建socket context */
    CFSocketContext CTX = { 0, NULL, NULL, NULL, NULL };
    
    /* 創建server socket  TCP IPv4 設置回調函數 */
    sserver = CFSocketCreate(NULL, PF_INET, SOCK_STREAM, IPPROTO_TCP,
                             kCFSocketAcceptCallBack, (CFSocketCallBack)AcceptCallBack, &CTX);
    if (sserver == NULL)
        return -1;
    
    
    /* 設置是否重新綁定標志 */
    int yes = 1;    
    /* 設置socket屬性 SOL_SOCKET是設置tcp SO_REUSEADDR是重新綁定,yes 是否重新綁定*/
    setsockopt(CFSocketGetNative(sserver), SOL_SOCKET, SO_REUSEADDR,
               (void *)&yes, sizeof(yes));
    
    /* 設置端口和地址 */
    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));             //memset函數對指定的地址進行內存拷貝
    addr.sin_len = sizeof(addr);
    addr.sin_family = AF_INET;                  //AF_INET是設置 IPv4
    addr.sin_port = htons(PORT);                //htons函數 無符號短整型數轉換成“網絡字節序”
    addr.sin_addr.s_addr = htonl(INADDR_ANY);   //INADDR_ANY有內核分配,htonl函數 無符號長整型數轉換成“網絡字節序”
        
    /* 從指定字節緩沖區復制,一個不可變的CFData對象*/
    CFDataRef address = CFDataCreate(kCFAllocatorDefault, (UInt8*)&addr, sizeof(addr));
    
    /* 設置Socket*/
    if (CFSocketSetAddress(sserver, (CFDataRef)address) != kCFSocketSuccess) {
        fprintf(stderr, "Socket綁定失敗\n");
        CFRelease(sserver);
        return -1;
    }
    
    /* 創建一個Run Loop Socket源 */
    CFRunLoopSourceRef sourceRef = CFSocketCreateRunLoopSource(kCFAllocatorDefault, sserver, 0);
    /* Socket源添加到Run Loop中 */
    CFRunLoopAddSource(CFRunLoopGetCurrent(), sourceRef, kCFRunLoopCommonModes);
    CFRelease(sourceRef);
    
    printf("Socket listening on port %d\n", PORT);
    /* 運行Loop */
    CFRunLoopRun();
    
    
}

/* 接收客戶端請求後,回調函數  */
void AcceptCallBack(
                    CFSocketRef socket,
                    CFSocketCallBackType type,
                    CFDataRef address,
                    const void *data,
                    void *info)
{
    CFReadStreamRef readStream = NULL;
    CFWriteStreamRef writeStream = NULL;
    
    /* data 參數涵義是,如果是kCFSocketAcceptCallBack類型,data是CFSocketNativeHandle類型的指針 */
    CFSocketNativeHandle sock = *(CFSocketNativeHandle *) data;
    
    /* 創建讀寫Socket流 */
    CFStreamCreatePairWithSocket(kCFAllocatorDefault, sock,
                                 &readStream, &writeStream);
    
    if (!readStream || !writeStream) {
        close(sock);
        fprintf(stderr, "CFStreamCreatePairWithSocket() 失敗\n");
        return;
    }
    
    CFStreamClientContext streamCtxt = {0, NULL, NULL, NULL, NULL};
    // 註冊兩種回調函數
    CFReadStreamSetClient(readStream, kCFStreamEventHasBytesAvailable, ReadStreamClientCallBack, &streamCtxt);
    CFWriteStreamSetClient(writeStream, kCFStreamEventCanAcceptBytes, WriteStreamClientCallBack, &streamCtxt);
    
    //加入到循環當中
    CFReadStreamScheduleWithRunLoop(readStream, CFRunLoopGetCurrent(),kCFRunLoopCommonModes);
    CFWriteStreamScheduleWithRunLoop(writeStream, CFRunLoopGetCurrent(),kCFRunLoopCommonModes);
    
    CFReadStreamOpen(readStream);
    CFWriteStreamOpen(writeStream);
    
}

/* 讀取流操作 客戶端有數據過來時候調用 */
void ReadStreamClientCallBack(CFReadStreamRef stream, CFStreamEventType eventType, void* clientCallBackInfo){
    
    UInt8 buff[255];
    CFReadStreamRef inputStream = stream;
    
    if(NULL != inputStream)
    {        
        CFReadStreamRead(stream, buff, 255);        
        printf("接受到數據:%s\n",buff);        
        CFReadStreamClose(inputStream);
        CFReadStreamUnscheduleFromRunLoop(inputStream, CFRunLoopGetCurrent(),kCFRunLoopCommonModes);
        inputStream = NULL;
    }
}

/* 寫入流操作 客戶端在讀取數據時候調用 */
void WriteStreamClientCallBack(CFWriteStreamRef stream, CFStreamEventType eventType, void* clientCallBackInfo)
{
    CFWriteStreamRef    outputStream = stream;
    //輸出
    UInt8 buff[] = "Hello Client!";
    if(NULL != outputStream)
    {
        CFWriteStreamWrite(outputStream, buff, strlen((const char*)buff)+1);
        //關閉輸出流
        CFWriteStreamClose(outputStream);
        CFWriteStreamUnscheduleFromRunLoop(outputStream, CFRunLoopGetCurrent(),kCFRunLoopCommonModes);
        outputStream = NULL;
    }
}

此段程序隻涉及比較簡單的數據流操作,詳細的數據流操作請參考 AsyncSocket 的源碼;至此,那麼一個服務端就已經實現瞭。



未完待續……

發佈留言