1. 程式人生 > >iOS開發之網路程式設計:Socket網路程式設計

iOS開發之網路程式設計:Socket網路程式設計

一、Socket原理

  • 套接字(socket)概念

套接字(socket)是通訊的基石,是支援TCP/IP協議的網路通訊的基本操作單元。它是網路通訊過程中端點的抽象表示,包含進行網路通訊必須的五種資訊:連線使用的協議,本地主機的IP地址,本地程序的協議埠,遠地主機的IP地址,遠地程序的協議埠。

應用層通過傳輸層進行資料通訊時,TCP會遇到同時為多個應用程式程序提供併發服務的問題。多個TCP連線或多個應用程式程序可能需要通過同一個 TCP協議埠傳輸資料。為了區別不同的應用程式程序和連線,許多計算機作業系統為應用程式與TCP/IP協議互動提供了套接字(Socket)介面。應用層可以和傳輸層通過Socket介面,區分來自不同應用程式程序或網路連線的通訊,實現資料傳輸的併發服務。

  • 建立socket連線

建立Socket連線至少需要一對套接字,其中一個運行於客戶端,稱為ClientSocket,另一個運行於伺服器端,稱為ServerSocket。

套接字之間的連線過程分為三個步驟:伺服器監聽,客戶端請求,連線確認。

伺服器監聽:伺服器端套接字並不定位具體的客戶端套接字,而是處於等待連線的狀態,實時監控網路狀態,等待客戶端的連線請求。

客戶端請求:指客戶端的套接字提出連線請求,要連線的目標是伺服器端的套接字。為此,客戶端的套接字必須首先描述它要連線的伺服器的套接字,指出伺服器端套接字的地址和埠號,然後就向伺服器端套接字提出連線請求。

連線確認:當伺服器端套接字監聽到或者說接收到客戶端套接字的連線請求時,就響應客戶端套接字的請求,建立一個新的執行緒,把伺服器端套接字的描述發給客戶端,一旦客戶端確認了此描述,雙方就正式建立連線。而伺服器端套接字繼續處於監聽狀態,繼續接收其他客戶端套接字的連線請求。

  • socket連線與TCP連線

建立Socket連線時,可以指定使用的傳輸層協議,Socket可以支援不同的傳輸層協議(TCP或UDP),當使用TCP協議進行連線時,該Socket連線就是一個TCP連線。

  • Socket連線與HTTP連線

由於通常情況下Socket連線就是TCP連線,因此Socket連線一旦建立,通訊雙方即可開始相互發送資料內容,直到雙方連線斷開。但在實際網路應用中,客戶端到伺服器之間的通訊往往需要穿越多箇中間節點,例如路由器、閘道器、防火牆等,大部分防火牆預設會關閉長時間處於非活躍狀態的連線而導致 Socket 連線斷連,因此需要通過輪詢告訴網路,該連線處於活躍狀態。

而HTTP連線使用的是“請求—響應”的方式,不僅在請求時需要先建立連線,而且需要客戶端向伺服器發出請求後,伺服器端才能回覆資料。

很多情況下,需要伺服器端主動向客戶端推送資料,保持客戶端與伺服器資料的實時與同步。此時若雙方建立的是Socket連線,伺服器就可以直接將資料傳送給客戶端;若雙方建立的是HTTP連線,則伺服器需要等到客戶端傳送一次請求後才能將資料傳回給客戶端,因此,客戶端定時向伺服器端傳送連線請求,不僅可以保持線上,同時也是在“詢問”伺服器是否有新的資料,如果有就將資料傳給客戶端。

二、Socket實現即時通訊

首先我們使用的Socket不是官方原生的,而是用的第三方在原生Socket基礎上封裝的— —AsyncSocket。

下面是運行於客戶端的Socket——ClientSocket:

  1. // 初始化Socket,設定代理
  2. _socket =[[AsyncSocket alloc] initWithDelegate:self];
  1. // 連線到主機
  2. if(![_socket connectToHost:_clientIPAddress.text onPort:[_clientPort.text intValue] error:&error]){
  3. // 連線失敗
  4. _status.text =@"Could't Connect Server";
  5. }else{
  6. // 連線成功
  7. _status.text =@"Connect Server Successfully";
  8. }
  1. // 傳送資料到Socket,傳送完成會呼叫代理方法
  2. [_socket writeData:[_sendMessage.text dataUsingEncoding:NSUTF8StringEncoding] withTimeout:-1 tag:0];
  1. // 從Socket讀取第一個可用資料,如果timeout的值為負數,則讀取操作不會存在超時
  2. [_socket readDataWithTimeout:-1 tag:0];
  1. #pragma mark AsyncSocketDelegate代理方法
  2. // 建立連線
  3. -(void)onSocket:(AsyncSocket*)sock didConnectToHost:(NSString*)host port:(UInt16)port {
  4. [self append:[NSString stringWithFormat:@"ConnectToHost:%@", host]];
  5. [sock readDataWithTimeout:-1 tag:0];
  6. }
  7. // Socket完成寫入請求資料後呼叫
  8. -(void)onSocket:(AsyncSocket*)sock didWriteDataWithTag:(long)tag {
  9. [sock readDataWithTimeout:-1 tag:0];
  10. }
  11. // 讀取資料
  12. -(void)onSocket:(AsyncSocket*)sock didReadData:(NSData*)data withTag:(long)tag {
  13. NSString*newMessage =[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
  14. [self append:[NSString stringWithFormat:@"%@:%@", sock.connectedHost, newMessage]];
  15. [_socket readDataWithTimeout:-1 tag:0];//可以省略
  16. }
  17. // 是否加密
  18. -(void)onSocketDidSecure:(AsyncSocket*)sock {
  19. NSLog(@"onSocket:%p", sock);
  20. }
  21. // 遇到錯誤時斷開連線
  22. -(void)onSocket:(AsyncSocket*)sock willDisconnectWithError:(NSError*)err {
  23. [self append:[NSString stringWithFormat:@"onSocket:%p, error:%@", sock, err.localizedDescription]];
  24. }
  25. // 斷開連線
  26. -(void)onSocketDidDisconnect:(AsyncSocket*)sock {
  27. NSString*msg =@"Connect is disconnect";
  28. _status.text = msg;
  29. _socket =nil;
  30. }

由於我這邊沒有可用的主機地址,所以只能模擬一下。

下面是運行於伺服器端的Socket——ServerSocket:

  1. // 初始化Socket監聽
  2. listenerSocket =[[AsyncSocket alloc] initWithDelegate:self];
  1. // 開始監聽,接受所給埠的連線
  2. if(![listenerSocket acceptOnPort:_SERVER_PORT_ error:&error]){
  3. return;
  4. }
  1. // 斷開所有connectionSocket
  2. for(int i =0; i < connnectionSocketsArray.count; i++){
  3. [[connnectionSocketsArray objectAtIndex:i] disconnect];
  4. }
  1. // 給當前監聽的Socket傳送資料
  2. [listenerSocket writeData:[_sendMessage.text dataUsingEncoding:NSUTF8StringEncoding] withTimeout:-1 tag:1];
  1. #pragma mark socketDeleaget 代理方法
  2. // 連線socket出錯
  3. -(void)onSocket:(AsyncSocket*)sock willDisconnectWithError:(NSError*)err {
  4. NSLog(@"error:%@", err.localizedDescription);
  5. }
  6. // 收到新的socket連線
  7. -(void)onSocket:(AsyncSocket*)sock didAcceptNewSocket:(AsyncSocket*)newSocket {
  8. [connnectionSocketsArray addObject:newSocket];
  9. }
  10. // 讀取資料
  11. -(void)onSocket:(AsyncSocket*)sock didWriteDataWithTag:(long)tag {
  12. // 從Socket獲取第一個可獲取的位元組資料
  13. [sock readDataWithTimeout:-1 tag:0];
  14. }
  15. // 與伺服器建立連線,併發送訊息
  16. -(void)onSocket:(AsyncSocket*)sock didConnectToHost:(NSString*)host port:(UInt16)port {
  17. NSLog(@"host:%@", host);
  18. NSString*msg =@"Welcome To Socket Test Server";
  19. NSData*data =[msg dataUsingEncoding:NSUTF8StringEncoding];
  20. // 給Socket一個連線成功的訊息
  21. [sock writeData:data withTimeout:-1 tag:0];
  22. }
  23. // 讀取客戶端發來的資料
  24. -(void)onSocket:(AsyncSocket*)sock didReadData:(NSData*)data withTag:(long)tag {
  25. NSString*msg =[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
  26. // 假設當前有2個ip(10.128.18.157; 10.128.18.158)相互發送訊息
  27. NSString*ip =@"10.128.18.159";
  28. // 獲取當前的socket
  29. for(int i =0; i < connnectionSocketsArray.count; i++){
  30. AsyncSocket*socket =(AsyncSocket*)[connnectionSocketsArray objectAtIndex:i];
  31. if([socket.connectedHost isEqualToString:ip]){
  32. // 找到所要傳送訊息的目標Socket,傳送過去
  33. [socket writeData:data withTimeout:-1 tag:0];
  34. // 記錄接收的資訊
  35. if(msg){
  36. [self append:msg];
  37. NSLog(@"%@", msg);
  38. }else{
  39. NSLog(@"error!");
  40. }
  41. }
  42. // 客戶端未連線
  43. else{
  44. NSString*returnMsg =@"The Other Is Not Online";
  45. NSData*returnData =[returnMsg dataUsingEncoding:NSUTF8StringEncoding];
  46. [sock writeData:returnData withTimeout:-1 tag:0];
  47. }
  48. }
  49. }
  50. // 斷開
  51. -(void)onSocketDidDisconnect:(AsyncSocket*)sock {
  52. [connnectionSocketsArray removeObject:sock];
  53. }