結合上一篇的知識,接下來將介紹基於 TCP 協議的 Socket 編程;由於 Socket 需要有客戶端和服務端,那麼現在實現的是關於服務端的簡單程序。服務端采用的是CFStream 類來實現的。
這個服務端是把Xcode中的 Command Line Tool 來作為服務端的;當然,你也可以把 iPhone 作為服務端,但是要利用其他的框架,比如 AsyncSocket (https://github.com/roustem/AsyncSocket) ,裡面有分為 UDP 和 TCP 實現的 Socket,源程序裡也有許多關於數據流的操作,可以作為深入理解來用,當然也是比較復雜的。
下面來看一下簡單實現的 TCP 服務端程序:(TCPServer.m)
#import此段程序只涉及比較簡單的數據流操作,詳細的數據流操作請參考 AsyncSocket 的源碼;至此,那麼一個服務端就已經實現了。#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; } }
未完待續......