iOS socket網(wǎng)絡(luò)編程實例詳解
代碼下載
相關(guān)概念
socket是一個針對TCP和UDP編程的接口,你可以借助它建立TCP連接等。socket是對TCP/IP協(xié)議的封裝,Socket本身并不是協(xié)議,而是一個調(diào)用接口(API),通過Socket,我們才能使用TCP/IP協(xié)議。Socket的出現(xiàn)只是使得程序員更方便地使用TCP/IP協(xié)議棧而已,是對TCP/IP協(xié)議的抽象,從而形成了我們知道的一些最基本的函數(shù)接口。
socket連接:socket連接就是所謂的長連接,理論上客戶端和服務(wù)器端一旦建立起連接將不會主動斷掉;但是由于各種環(huán)境因素可能會是連接斷開,比如說:服務(wù)器端或客戶端主機down了,網(wǎng)絡(luò)故障,或者兩者之間長時間沒有數(shù)據(jù)傳輸,網(wǎng)絡(luò)防火墻可能會斷開該連接以釋放網(wǎng)絡(luò)資源。所以當(dāng)一個socket連接中沒有數(shù)據(jù)的傳輸,那么為了維持連接需要發(fā)送心跳消息~~具體心跳消息格式是開發(fā)者自己定義的。
TCP連接的三次握手:
第一次握手:客戶端發(fā)送syn包(syn=j)到服務(wù)器,并進(jìn)入SYN_SEND狀態(tài),等待服務(wù)器確認(rèn);
第二次握手:服務(wù)器收到syn包,必須確認(rèn)客戶的SYN(ack=j+1),同時自己也發(fā)送一個SYN包(syn=k),即SYN+ACK包,此時服務(wù)器進(jìn)入SYN_RECV狀態(tài);
第三次握手:客戶端收到服務(wù)器的SYN+ACK包,向服務(wù)器發(fā)送確認(rèn)包ACK(ack=k+1),此包發(fā)送完畢,客戶端和服務(wù)器進(jìn)入ESTABLISHED狀態(tài),完成三次握手。
握手過程中傳送的包里不包含數(shù)據(jù),三次握手完畢后,客戶端與服務(wù)器才正式開始傳送數(shù)據(jù)。理想狀態(tài)下,TCP連接一旦建立,在通信雙方中的任何一方主動關(guān)閉連接之前,TCP 連接都將被一直保持下去。斷開連接時服務(wù)器和客戶端均可以主動發(fā)起斷開TCP連接的請求,斷開過程需要經(jīng)過“四次握手”。
socket建立網(wǎng)絡(luò)連接的步驟
建立Socket連接至少需要一對套接字,其中一個運行于客戶端,稱為ClientSocket ,另一個運行于服務(wù)器端,稱為ServerSocket 。
套接字之間的連接過程分為三個步驟:服務(wù)器監(jiān)聽,客戶端請求,連接確認(rèn)。
1。服務(wù)器監(jiān)聽:服務(wù)器端套接字并不定位具體的客戶端套接字,而是處于等待連接的狀態(tài),實時監(jiān)控網(wǎng)絡(luò)狀態(tài),等待客戶端的連接請求。
2??蛻舳苏埱螅褐缚蛻舳说奶捉幼痔岢鲞B接請求,要連接的目標(biāo)是服務(wù)器端的套接字。為此,客戶端的套接字必須首先描述它要連接的服務(wù)器的套接字,指出服務(wù)器端套接字的地址和端口號,然后就向服務(wù)器端套接字提出連接請求。
3。連接確認(rèn):當(dāng)服務(wù)器端套接字監(jiān)聽到或者說接收到客戶端套接字的連接請求時,就響應(yīng)客戶端套接字的請求,建立一個新的線程,把服務(wù)器端套接字的描述發(fā)給客戶端,一旦客戶端確認(rèn)了此描述,雙方就正式建立連接。而服務(wù)器端套接字繼續(xù)處于監(jiān)聽狀態(tài),繼續(xù)接收其他客戶端套接字的連接請求。
BSD socket
BSD socket:完全由c語言實現(xiàn),并且可以在Objective-C代碼中使用。
優(yōu)點:不同平臺中易于移植
缺點:
無法訪問操作系統(tǒng)內(nèi)建的網(wǎng)絡(luò)特性(比如系統(tǒng)范圍的VPN)。
更糟糕的是初始化socket連接并不會自動打開設(shè)備的Wi-Fi或是蜂窩網(wǎng)絡(luò),無線網(wǎng)絡(luò)會智能的關(guān)閉以節(jié)省電池電量,任何通信連接都會失敗,除非其他網(wǎng)絡(luò)進(jìn)程激活了無線網(wǎng)。
CFNetwork對BSD Socket的分裝可以激活設(shè)備的無線網(wǎng),所以幾乎所有場景都建議使用CFNetwork而不是BSD Socket.
最常用的API:
1.int socket(int, int, int):創(chuàng)建并初始化新的socket,如果成功放回一個文件描述符,如果失敗返回0。
2.int bind(int, const struct sockaddr *, socklen_t):為指定的地址與端口號分配socket。
3.int listen(int, int) __DARWIN_ALIAS(listen):用于服務(wù)端監(jiān)聽客戶端
4.int accept(int, struct sockaddr * __restrict, socklen_t * __restrict):接受連接請求,將客戶端地址存儲到clientAddress中。
5.int connect(int, const struct sockaddr *, socklen_t):連接到指定的服務(wù)器。
6.ssize_t send(int, const void *, size_t, int):在socket上最多發(fā)送XX數(shù)據(jù)。
7.ssize_t recv(int, void *, size_t, int):在socket上最多讀取XX數(shù)據(jù)。
……
服務(wù)端邏輯:如果接收到客戶端發(fā)送過來”1”,那么回復(fù)”你猜!”;客戶端發(fā)送過來”2”,那么回復(fù)”謝謝!”;客戶端發(fā)送過來”3”,那么回復(fù)”對不起!”;客戶端發(fā)送過來”4”,那么回復(fù)”好的!”;客戶端發(fā)送過來其他的內(nèi)容,那么一半概率原樣回復(fù),一半概率回復(fù)”不知道你在說什么!”。
創(chuàng)建服務(wù)端:
1.創(chuàng)建socket
struct sockaddr_in server_addr; server_addr.sin_len = sizeof(struct sockaddr_in); server_addr.sin_family = AF_INET;//AF_INET互聯(lián)網(wǎng)地址簇 server_addr.sin_port = htons(port); server_addr.sin_addr.s_addr = inet_addr(address); bzero(&server_addr.sin_zero, 8); //創(chuàng)建socket int server_socket = socket(AF_INET, SOCK_STREAM, 0);//SOCK_STREAM 有鏈接 if (server_socket == -1) { perror("socket error!"); return 1; }
2.綁定socket:將創(chuàng)建的socket綁定到本地的IP地址和端口,此socket是半相關(guān)的,只是負(fù)責(zé)偵聽客戶端的連接請求,并不能用于和客戶端通信
int bind_result = bind(server_socket, (struct sockaddr*)&server_addr, sizeof(server_addr)); if (bind_result == -1) { perror("bind error!"); return 1; }
3.listen偵聽
if (listen(server_socket, 5)) { perror("listen error!"); return 1; }
4.接受客戶端連接
int client_socket; socklen_t address_len; struct sockaddr_in client_address; for (; ; ) { address_len = sizeof(client_address); client_socket = accept(server_socket, (struct sockaddr*)&client_address, &address_len); //1.使用多線程 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ //這個函數(shù)進(jìn)行數(shù)據(jù)操作 str_echo(client_socket); }); }
5.接收和發(fā)送數(shù)據(jù)
char buf[1024]; while (1) { bzero(buf, 1024); long byte_num = recv(socket, buf, 1024, 0); if (byte_num < 0) { return; } buf[byte_num] = '\0'; printf("client said:%s\n", buf); char *result; NSString *str = [NSString stringWithCString:buf encoding:NSUTF8StringEncoding]; if ([str isEqualToString:@"1"]) { result = "你猜!"; } else if ([str isEqualToString:@"2"]) { result = "謝謝!"; } else if ([str isEqualToString:@"3"]) { result = "對不起!"; } else if ([str isEqualToString:@"4"]) { result = "好的!"; } else { if (arc4random()%2 == 0) { result = "不知道你在說什么!"; } else { result = buf; } } send(socket, result, 1024, 0); }
創(chuàng)建客戶端:
struct sockaddr_in server_addr; server_addr.sin_len = sizeof(struct sockaddr_in); server_addr.sin_family = AF_INET; server_addr.sin_port = htons(11332); server_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); bzero(&(server_addr.sin_zero), 8); int server_socket = socket(AF_INET, SOCK_STREAM, 0); if (server_socket == -1) { perror("socket error"); return 1; } char receive_msg[1024]; char reply_msg[1024]; if (connect(server_socket, (struct sockaddr*)&server_addr, sizeof(struct sockaddr_in)) == 0) { //connect 成功之后,其實系統(tǒng)將你創(chuàng)建的socket綁定到一個系統(tǒng)分配的端口上,且其為全相關(guān),包含服務(wù)器端的信息,可以用來和服務(wù)器端進(jìn)行通信。 while (1) { bzero(reply_msg, 1024); printf("replay:"); scanf("%s", reply_msg); if (send(server_socket, reply_msg, 1024, 0) == -1) { perror("send error!"); return 1; } bzero(receive_msg, 1024); long byte_num = recv(server_socket, receive_msg, 1024, 0); receive_msg[byte_num] = '\0'; printf("server said:%s\n", receive_msg); } } return 0;
NSStream 讀寫文件
Cocoa Streams包含三個相關(guān)的類: NSStream、NSInputStream 和NSOutputStream。
NSStream:是個抽象類,定義了一些基本屬性和方法;
NSInputStream:是NSStream的子類,可通過它從NSData、File和socket中讀取數(shù)據(jù)流;
NSOutputStream:也是NSStream的子類,可通過它將數(shù)據(jù)流寫入NSData、File和socket。
還可以給stream對象設(shè)置Delegate(NSStreamDelegate),如果沒有精確了給stream指定Delegate,那么默認(rèn)將Delegate設(shè)置為其自己。
NSStreamDelegate只有一個方法
(void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
NSStreamEventNone :改默認(rèn)值不代表任何事件
NSStreamEventOpenCompleted :socket被成功打開
NSStreamEventHasBytesAvailable :有可以讀取的字節(jié)
NSStreamEventHasSpaceAvailable :緩存中可以寫入字節(jié)
NSStreamEventErrorOccurred :操作出現(xiàn)錯誤CFReadStreamCopyError()和CFWriteStreamCopyError()會提供更多錯誤細(xì)節(jié)
NSStreamEventEndEncountered :socket達(dá)到字節(jié)流的末尾
通過NSStream對象讀數(shù)據(jù),可以分為如下步驟完成:
a) 從數(shù)據(jù)源創(chuàng)建和初始化一個NSStream對象;
b) 配置run loop,并打開stream對象;
c) 響應(yīng)NSInputStream事件(NSStreamDelegate);
d) 關(guān)閉NSInputStream對象。
- (void)viewDidLoad { [super viewDidLoad]; NSString *documentStr = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject]; NSString *dataPath = [documentStr stringByAppendingPathComponent:@"data.txt"]; NSFileManager *manager = [NSFileManager defaultManager]; if ([manager fileExistsAtPath:dataPath]) { self.read = YES; NSLog(@"string:%@", [NSString stringWithContentsOfFile:dataPath encoding:NSUTF8StringEncoding error:nil]); NSInputStream *inStream = [[NSInputStream alloc] initWithFileAtPath:dataPath]; [inStream setDelegate:self]; [inStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; [inStream open]; } else { if ([manager createFileAtPath:dataPath contents:nil attributes:nil]) { self.read = NO; NSOutputStream *outStream = [[NSOutputStream alloc] initToFileAtPath:dataPath append:YES]; [outStream setDelegate:self]; [outStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; [outStream open]; } else { NSLog(@"文件創(chuàng)建失?。?); } } } #pragma mark - <NSStreamDelegate>代理方法 - (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode { if (self.read) { NSInputStream *inStream = (NSInputStream *)aStream; switch (eventCode) { case NSStreamEventHasBytesAvailable: { uint8_t data[1024]; [inStream read:data maxLength:1024]; printf("%s", data); [inStream close]; [inStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; inStream = nil; } break; case NSStreamEventEndEncountered: { [inStream close]; [inStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; inStream = nil; } break; default: break; } } else{ NSOutputStream *outStream = (NSOutputStream *)aStream; switch (eventCode) { case NSStreamEventHasSpaceAvailable: { uint8_t data[] = "{name:'張三', age:10}"; [outStream write:data maxLength:strlen((char *)data)]; [outStream close]; [outStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; outStream = nil; } break; case NSStreamEventEndEncountered: { [outStream close]; [outStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; outStream = nil; } break; default: break; } } }
NSStream Socket
NSStream類不支持在IOS平臺上創(chuàng)建socket連接,而CFStream支持在IOS平臺的socket行為。所以若知道遠(yuǎn)程主機的DNS或者是IP地址,可以使用CFStreamCreatePairWithSocketToHost函數(shù)來創(chuàng)建socket連接,通過該函數(shù)創(chuàng)建了CFStream類型為全雙工的socket連接,接著可以利用toll-free bridge,將CFStream對象轉(zhuǎn)換為NSStream對象。
通過NSStream對象進(jìn)行Socket通信,與通過NSStream進(jìn)行IO操作的步驟基本一樣:
a) 創(chuàng)建NSStream對象,通過CFStreamCreatePairWithSocketToHost函數(shù)創(chuàng)建CFReadStreamRef 和CFWriteStreamRef 對象;繼而將兩者轉(zhuǎn)換為NSInputStream 和NSOutputStream 對象;
b) 配置run loop,并打開NSInputStream 和NSOutputStream對象;
c) 響應(yīng)事件,在Delegate中響應(yīng)不同的信號;
d) 關(guān)閉NSStream對象。
服務(wù)端:服務(wù)端依然使用上面例子的服務(wù)端
客戶端的實現(xiàn):點擊1~5這五個按鈕,分別從服務(wù)端返回不同的數(shù)據(jù),進(jìn)而展示出來。
由于實現(xiàn)相對簡單,就不加闡述說明了,實現(xiàn)代碼如下:
- (void)viewDidLoad { [super viewDidLoad]; [self startSocket:@"127.0.0.1" andPort:11332]; } - (void)startSocket:(NSString *)address andPort:(int)port { CFReadStreamRef readRef; CFWriteStreamRef writeRef; CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)address, port, &readRef, &writeRef); NSInputStream *inputStream = (__bridge NSInputStream *)readRef; NSOutputStream *outputStream = (__bridge NSOutputStream *)writeRef; inputStream.delegate = self; outputStream.delegate = self; [inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; [outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; [inputStream open]; [outputStream open]; self.outputStream = outputStream; } - (IBAction)sendMsg:(UIButton *)sender { if (sender.currentTitle > 0) { const char *output = sender.currentTitle.UTF8String; [self.outputStream write:(const uint8_t *)output maxLength:strlen(output)]; } } - (void)showMessage:(NSString *)msg { if (!self.msgLabel) { UIFont *font = [UIFont systemFontOfSize:14]; UIColor *color = [UIColor blackColor]; UIColor *backColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0.2]; UILabel *label = [[UILabel alloc] init]; label.textColor = color; label.font = font; label.textAlignment = NSTextAlignmentCenter; label.backgroundColor = backColor; self.msgLabel = label; } CGFloat screenW = [UIScreen mainScreen].bounds.size.width; CGFloat screenH = [UIScreen mainScreen].bounds.size.height; CGFloat W = screenW - 16; CGSize size = [msg boundingRectWithSize:CGSizeMake(W, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:self.msgLabel.font} context:nil].size; W = size.width; CGFloat H = size.height; CGFloat X = (screenW - W)/2; CGFloat Y = (screenH - H)/2; self.msgLabel.frame = CGRectMake(X, Y, W, H); self.msgLabel.text = msg; [self.view addSubview:self.msgLabel]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [self.msgLabel removeFromSuperview]; }); } #pragma mark - <NSStreamDelegate>代理方法 - (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode { switch (eventCode) { case NSStreamEventNone: break; case NSStreamEventOpenCompleted: break; case NSStreamEventHasBytesAvailable: { uint8_t buf[1024]; NSInteger len = 0; NSInputStream *inputStream = (NSInputStream *)aStream; len = [inputStream read:buf maxLength:1024]; if (len) { [self showMessage:[NSString stringWithCString:(const char *)buf encoding:NSUTF8StringEncoding]]; } break; } case NSStreamEventHasSpaceAvailable: break; case NSStreamEventErrorOccurred: { [aStream close]; break; } case NSStreamEventEndEncountered: { [aStream close]; [aStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; aStream = nil; break; } default: break; }
以上所述是小編給大家介紹的iOS socket網(wǎng)絡(luò)編程,希望對大家有所幫助,如果大家有任何疑問歡迎給我留言,小編會及時回復(fù)大的!
- iOS利用AVPlayer播放網(wǎng)絡(luò)音樂的方法教程
- 詳解iOS中多個網(wǎng)絡(luò)請求的同步問題總結(jié)
- Vue.js教程之a(chǎn)xios與網(wǎng)絡(luò)傳輸?shù)膶W(xué)習(xí)實踐
- iOS中設(shè)置網(wǎng)絡(luò)超時時間+模擬的方法詳解
- IOS開發(fā)中異步網(wǎng)絡(luò)請求上實現(xiàn)同步邏輯
- IOS 出現(xiàn)問題POST網(wǎng)絡(luò)請求狀態(tài)code:500的解決方法
- iOS判斷網(wǎng)絡(luò)請求超時的方法
- IOS 網(wǎng)絡(luò)請求中設(shè)置cookie
相關(guān)文章
Kotlin中l(wèi)et、run、with、apply及also的用法和差別
作用域函數(shù)是Kotlin比較重要的一個特性,分為5種let、run、with、apply及also,這五個函數(shù)的工作方式非常相似,但是我們需要了解這5種函數(shù)的差異,以便在不同的場景更好的利用它,這篇文章主要介紹了Kotlin中l(wèi)et、run、with、apply及also的差別,需要的朋友可以參考下2023-11-11java 學(xué)習(xí)筆記(入門篇)_java的安裝與配置
學(xué)習(xí)Java已經(jīng)很長時間了,由于基礎(chǔ)不好遇到問題就無從下手,所以,打算寫Java的隨手筆記來鞏固基礎(chǔ),加強學(xué)習(xí),接下來講解java的安裝,配置等,感興趣的朋友可以參考下2013-01-01