調用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);}對BSD Socket的一層輕量級封裝,主要優勢在于被集成到系統級的設置與主運行循環中。如必要時開啟無效以及通過系統范圍的VPN進行路由等,并且沒有什么嚴重的缺陷。
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; }}新聞熱點
疑難解答