en

hi, it seems you are using microsoft internet explorer. it doesn't match web standard and causes problems browsing this site. please please please use mozilla firefox or google chrome instead. thank you!

zh

哦哦!您正在使用Internet Explorer 瀏覽器,它與我們的網頁標準並不相容,可能會導致畫面顯示不正常。
請改用 Mozilla Firefox 或者 Google Chrome 才能正常瀏覽本網站,謝謝!

11.21.2012

使用 Stream Socket (TCP/IP) 連線至遠端主機

 

在網路傳輸上,TCP/IP 協定的底層運作必須處理封包、標頭和交握等等瑣碎細節,為了簡化這些細節 Berkeley UNIX 提出 Stream Socket 的概念,將網路連線簡化成為簡單的資料串流 Data Stream 概念,透過 Server 端與 Client 端的 Port,讓傳輸的訊息流經其中,就像是主機與使用者之間直接接了一條專線一樣,而程式設計人員若使用 Socket 協定的技術來撰寫網路連線程式,只要針對 Server 與 Client 的連接埠作處理,而不需要故顧慮到底層的運作。

由於本範例主要是針對在 iOS 上使用 Socket 技術連線至遠端主機,所以指使演示傳送與接收 (Send and Receive) 兩部份,並不包含主機端與使用者端的互動方式,因為這必須要考慮考主機端在撰寫設計時所能支援的功能而定。

在開始前先來說明一下實驗環境,Socket 這項技術,幾乎在任何平台上都可以使用,為求方便我們就拿 Windows 7 上已有的主機來作示範。


連線設定
在這部份中我們要在類別的 @interface 區段中加上 <NSStreamDelegate> 協定,並且宣告 NSInputStream 與 NSOutputStream 型態的全域變數,這樣方便之後我們在程式中控制 Data Stream 出入的接口。
@interface StreamDemoViewController : UIViewController <NSStreamDelegate> {
    NSInputStream *iStream;
    NSOutputStream *oStream;
}
@end

接下來自行定義一個連線到的方法函式,其中,我們要在 iOS 上設定要連線的主機位置與他的連結埠,還有要把輸入與輸出的 Stream 打開並使用 Loop 的方式讓它們保持「傾聽」的狀態,這樣才可以隨時接收主機所傳來的訊息,如果你不想接收主機所傳的來的資訊,那麼在 iStream 的部份,也可以選擇不要開啟。
CFReadStreamRef readStream = NULL;
CFWriteStreamRef writeStream = NULL;

NSString *ip = @"192.168.1.124";
NSInteger port = 5555;

CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, (__bridge CFStringRef)ip, port, &readStream, &writeStream);

if (readStream && writeStream) {
    CFReadStreamSetProperty(readStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);
    CFWriteStreamSetProperty(writeStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);

    iStream = (__bridge NSInputStream *)readStream;
    [iStream setDelegate:self];
    [iStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    [iStream open];

    oStream = (__bridge NSOutputStream *)writeStream;
    [oStream setDelegate:self];
    [oStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    [oStream open];
}

ps:__bridge 為強制轉型。


從主機端接收訊息
在實作對主機端發送訊息之前,我們先來講講從主機端接收訊息的部份,當我們完成對主機連線的設定之後,多少都會接收到由主機端所傳來的訊息,你可以使用下列由 <NSStreamDelegate> 協定所產生的方法函式來取得這些接收的訊息。
- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode {

    //當訊息從主機由iStream端進入時
    if (eventCode == NSStreamEventHasBytesAvailable) {
        NSMutableData *data = [[NSMutableData alloc] init];

        //定義接收串流的大小
        uint8_t buf[1024];
        unsigned int len = 0;
        len = [(NSInputStream *)stream read:buf maxLength:1024];
        [data appendBytes:(const void *)buf length:len];

        NSString *str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];

        //將得到得的結果輸出到TextView上
        textView.text = str;
    }
}

NSStreamEvent 的事件除了 NSStreamEventHasBytesAvailable 之外,還有以下幾種類型,你可以參考官方網站的 NSStream Class Reference 了解更多資訊。
NSStreamEventNone
NSStreamEventOpenCompleted
NSStreamEventHasBytesAvailable
NSStreamEventHasSpaceAvailable
NSStreamEventErrorOccurred
NSStreamEventEndEncountered

在上述的設定中,我們定義一個接收訊息的緩衝區 buf,緩衝區的大小可以自行設定,除非你的裝置擁有足夠的記憶體,否則不建議設定過大的緩衝區,在設定好緩衝區之後,接下來就是從緩衝區中取得資料,這裡有另一個資料長度的設定 len,它的長度大小必須在緩衝區之內,你也可以剛好等同於你資料量的大小。

如果是在連續傳輸資料下,你可以想像資料在透過 Stream Socket 做傳輸時,就像一條源源不絕的水管一樣,資料無法分辨彼此,因為他們全部都頭尾相接,除非你每次由主機端所傳來的資料長度都一致,那就另當別論了。

因此,在做接收端的程式設計時,必須先由對方傳一個固定長度的資料(Header),裡頭會存放接下來要送的資料長度大小,透過此大小接收端才可以得知它所要接收的資料長度 len,才能在 buf 中將其切開。

傳送端 :|Header|-->|Data(A)|-->|Header|-->|Data(B)|
接收端 :取得固定長度大小的| Header|--> 利用 Header 得到的長度資訊從 buf 中取得對應資料長度 len --> 不斷重複此步驟。


對主機端傳送資料
在送出訊息之前,你可以透過 [oStream streamStatus]  狀態來判斷是否取主機成功連線,或是當連線成功時,主動由主機端傳送相關訊息,在確定成功連線之後,你可以使用下列方法對主機端傳送資料。

傳送 NSString(你可以將 NSString 格式化成固定長度當做 Header
NSString *str =@"iOS Login Success ...";

const uint8_t *nuit8Text;
nuit8Text = (uint8_t *) [str cStringUsingEncoding:NSASCIIStringEncoding];

[oStream write:nuit8Text maxLength:strlen((char*)nuit8Text)];

傳送NSData
[oStream write:(const uint8_t *)[myData bytes] maxLength:[myData length]];

ps: streamStatus 的相關資訊同樣記載於官方網站的 NSStream Class Reference 中。





120 則留言:

  1. 大大
    不好意思請問若在ARC的狀態下
    iStream = [NSMakeCollectable(readStream) autorelease];
    oStream = [NSMakeCollectable(writeStream) autorelease];

    要怎麼做修正??
    感謝

    回覆刪除
    回覆
    1. 鈞 您好:

      你直接把 autorelease 拿掉就可以了,試試看這樣,強制它轉型..

      謝謝你的提醒,到時候我會替這一篇加上 ARC 的設定

      iStream = (__bridge NSInputStream *)readStream;
      ------------------------------------------------
      這是比較詳細的設定

      CFReadStreamSetProperty(readStream,
      kCFStreamPropertyShouldCloseNativeSocket,
      kCFBooleanTrue);
      iStream = (__bridge NSInputStream *)readStream;
      [iStream setDelegate:self];
      [iStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
      forMode:NSDefaultRunLoopMode];
      [iStream open];

      oStream 設定相同

      刪除
  2. 感謝你^^
    兩行改成
    iStream = (__bridge NSInputStream *)readStream;
    oStream = (__bridge NSOutputStream *)writeStream;
    就可以使用了!!!謝謝~~~~

    回覆刪除
  3. 不好意思另外再問一下iStream在這個程式的公用為何!!!
    我以為是接收!!
    但是接收那邊並未使用@@
    可否指點謝謝

    回覆刪除
    回覆
    1. 鈞 您好:

      他是接受沒錯阿,資料近來的入口,就是字面上的意思。

      如果您只是朝server丟資料出去並不管server的echo。那麼 iStream 就真的對您沒什麼用。

      ios -> oStream -> Server -> Server echo -> iStream -> ios

      換句話說,如果你把上述程式碼的 [iStream open]; 註解,那你將不會收到任何來自server的訊息!!

      刪除
  4. 不好意思!!!最近在整理程式!!!遇到了一個問題~~我把SOCKET的相關函式都丟到NSObject的.m檔裡時
    if (eventCode == NSStreamEventHasBytesAvailable)就都無法進入都會變成NSStreamEventHasSpaceAvailable
    為什麼會這樣??SERVER確認有收到東西也送出資訊
    把同樣的函式放在ViewController就可以跑無誤~“~

    回覆刪除
  5. 鈞 您好:

    setDelegate 有設定嗎? 要指向 NSObject 才可以喔。

    回覆刪除
  6. -(id)init_Create_Sockt :(NSMutableString*) SendMessage_
    {
    CFReadStreamRef readStream = NULL;
    CFWriteStreamRef writeStream = NULL;

    CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, (__bridge CFStringRef)ip, port, &readStream, &writeStream);

    if (readStream && writeStream)
    {
    CFReadStreamSetProperty(readStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);
    CFWriteStreamSetProperty(writeStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);

    iStream = (__bridge NSInputStream *)readStream;
    [iStream setDelegate:self];
    [iStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    [iStream open];

    oStream = (__bridge NSOutputStream *)writeStream;
    [oStream setDelegate:self];
    [oStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    [oStream open];

    NSThread *Thread_Socket = [[NSThread alloc]initWithTarget:self selector:@selector(Upload_Information:) object:SendMessage_];
    [Thread_Socket start];
    }
    return self;
    }

    這樣應該是吧!!!!

    回覆刪除
    回覆
    1. 鈞 您好:

      恩沒錯,你的 stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode 要寫在與 setDelegate 指向的同一個CLASS裡面才可以。

      另外, NSStreamEventHasBytesAvailable 是有資料從主機端接收時才會被觸發,與主機端有沒有收到資料沒關係!

      刪除
    2. 恩恩我是放在同一個CLASS裡!!!也確定主機有送資料出來~~~但他還是無法使用@@

      刪除
    3. 鈞 您好:

      還有一個可能,就是當你主機送出資料時,NSObject 已經被釋放掉了....

      所以無法對一個不存在的物件做設定。

      刪除
    4. ^^~~~牛奶您太強了!!!把它設成全域變數後就好了@@~~謝謝!!麻煩您那麼多次真不好意思~“~

      刪除
    5. 不用客氣,我也只是互相學習,再設定成全域變數之前,您也可考慮全域的物件(singleton)。

      http://furnacedigital.blogspot.tw/2011/09/singleton.html

      刪除
  7. 您好,

    感謝您的分享,我參考您的方式完成了TCP傳送與接收.

    1. 我有個疑問想要請教,關於handleEvent的部分,是否也是在主要執行緒所觸發的呢?

    2. 目前我建立了一個Thread程式一開啟即會執行 , 每500ms 傳送8 bytes資料到server端.
    而server端則會回傳 9 bytes. 收到的資料在handleEvent裡面做處理.

    另外我有幾個按鈕,會觸發不同的command傳給server端.
    目前遇到的問題在於,在按鈕觸發傳送command的時候,Thread也還是在執行,
    請教要如何讓我按按鈕傳送的時候不會跟thread要傳送的,互相fighting呢?

    我原本的想法是設定兩個flag,分別是threadCanRunFlag 以及 threadFinishFlag

    當兩個flag都為true的時候,才向server傳資料[oStream write],

    然後在Thread發送command之後就把threadFinishFlag=false;

    一直到收到server的回傳,就在handleEvent裡面將threadFinishFlag=true;


    我在Button觸發的事件裡面,先將threadCanRunFlag=false;

    然後等threadFinishFlag=true; 表示thread的command收到回傳了, 之後再繼續我按鈕所要傳送的command

    但我發現我在按鈕事件當中while(!threadFinishFlag); 會卡在這邊. 因此我才有第一個疑問.

    想請教是否能從handleEvent當中去控制發送事件?

    我想要的就是一發一收,thread的發送也要有收到, Button觸發才能發送以及接受

    也就是Thread一直在跑,但是按鈕事件按下去的時候,Thread要暫停,等Button的跑完

    但有可能Button按下去的時候thread的只跑到一半,所以我希望Thread跑完完整的一次收發

    才執行Button的事件.

    想請教是否有建議的方式達到我所想要的目的呢?

    謝謝您


    回覆刪除
    回覆
    1. tuzr 您好:

      首先,handleEvent 他必須在與你存在你設定的delegate class中,不一定是要在主執行續下,但是當他的instance存在時,handleEvent才可能會被觸發,你可以想成是被動的function,當有事件發生他才會被呼叫,至於如何呼叫他,只要設定delegate就可以了。

      另外,在handleEvent中可以使用以下這些事件來判斷目前狀況,你可以參考官網
      NSStreamEventNone
      NSStreamEventOpenCompleted
      NSStreamEventHasBytesAvailable
      NSStreamEventHasSpaceAvailable
      NSStreamEventErrorOccurred
      NSStreamEventEndEncountered

      我不太知道你實作想要做成什麼樣子,但是在傳輸上 Stream Socket 並沒有您所想的那麼聰明,你可以以把用戶與主機端想成一條水管相連,當然他只會是單向的,在雙方確認資料之後才會有接下來的動作,如果你是要每隔多少秒傳輸,那用戶端勢必要與主機端先溝通好,你設定flag的觀念並沒有錯,但是網路傳出並非這麼簡單,每秒的傳輸量也要依據當時情況而定,有的時候很快,有的時候很慢,所以才會需要使用handleEvent來幫觸我們判斷目前水管的狀態以防止碰撞。

      據個簡單的例子我要傳送一張圖片給server,首先我會將圖片轉換成byte,並且取得長度,接下來我會傳送一個符號給server告訴他我要開始傳送資料,在傳送時我們會先傳送長度的資訊(除非長度都是固定才不用傳),在主機知道要接收資料的長度後才去開一個buff去收他,這中間的過程你可以自己寫屬於你自己的協定,例如:
      |檔頭20byte|+|實際資料???byte| ,主機端在接收到資料時會先擷取前20byte的資訊來得知後面要傳送來的檔案大小。

      ps:還有一個問題是我自己在實作的時候發現的,類似你這樣每隔幾秒就傳送東西出去,也不管接收端是否有收到才去送下一筆資料,由於封包在傳送的過程會掉,所以導致會後接收端無法接受與預期大小相同的資料,這時候你就需要將連線段開在重新連線,不然就會像你的情況一樣。

      最後,你可以同時開很多個thread來傳送資料,但是port只有一個,所以開在多也是傳不進不去的喔,這樣資料反而會亂掉。

      刪除
    2. 牛奶您好,

      所以如果是參考本頁面範例的做法: delegate設定為self,

      假設在我的按鈕事件裡面有一個迴圈一直在monitor RedeivedFlag, 直到ReceivedFlag=true;才會跳離迴圈

      我預期是handleEvent裡面的NSStreamEventHasBytesAvailable去把ReceivedFlag設定為true.

      如此我才知道可以繼續發送. 但我實作上發現,在按鈕事件執行到 while(!ReceivedFlag);會在這邊無窮回圈了.

      不會觸發到handleEvent.

      想請教這樣是因為delegate設定在主執行緒(self ?)的關係嗎?


      實際應用上是這樣的

      我有一個Thread負責送出 "更新狀態" 這個指令, 然後接收Server回傳的狀態,並顯示在UI上面 (Button的背景圖案變化)

      如您所說,我在這個Thread裡面發送之前,有利用Flag確定前一次送出去的指令已經有收到回應,我才會再傳送.

      上述這是Thread的部分.


      另外關於UI的部分,按鈕事件會傳送給server告訴server我的狀態改變了(UI按鈕背景圖更換),並且接收server回傳的Ack.

      如您所述,我的目的是要避免UI按鈕按下去的時候跟Thread在執行的部分發生碰撞.

      所以希望當按鈕按下去的時候 , 我會先讓Thread停止傳送資料, 然後在監看Thread裡面的指令是否已經完成(完成一發一回), 避免運做到一半 被UI按鈕要傳送的資料卡掉


      另外,我想請教一些問題,
      關於NSStreamEventHasSpaceAvailable,
      這部分是如何觸發呢? 只要 oStream是空的就會嗎?
      是否我要發送前,確認iStrema 以及 oStream 都是NSStreamEventHasSpaceAvailable即可呢?


      感謝您抽空回答,獲益良多




      刪除
    3. 您好:

      NSStreamEventHasSpaceAvailable 是可以被寫入(送出)狀態,不見得是oStream空的,依照您的情況我覺得你可以試試看使用 streamStatus 來判斷狀況會比較適合,看看以下的官方連結:
      http://developer.apple.com/library/ios/documentation/cocoa/Reference/Foundation/Classes/NSStream_Class/Reference/Reference.html#//apple_ref/occ/instm/NSStream/streamStatus

      Stream Status Constants
      These constants indicate the current status of a stream. They are returned by streamStatus.

      typedef enum {
      NSStreamStatusNotOpen = 0,
      NSStreamStatusOpening = 1,
      NSStreamStatusOpen = 2,
      NSStreamStatusReading = 3,
      NSStreamStatusWriting = 4,
      NSStreamStatusAtEnd = 5,
      NSStreamStatusClosed = 6,
      NSStreamStatusError = 7
      };

      也許這可以幫助你解決部份的問題,NSStreamEventHasBytesAvailable這邊不太需要弄個FLAG,這篇會被觸發較表示有東西要進來,你可以用streamStatus 來得知資料是否仍在接收中,我不太確定你設計得初衷,不過Stream本來就會另外再開執行續跑,並不會影響目前的主執行續,還是建議先試試看簡單的SEND 與 RECIVE 都沒問題之後再考慮使用TIMER。

      PS:抱歉我上次沒注意你的設計回的東西有些籠統!

      刪除
    4. 您好,

      謝謝您的分享,

      我會嘗試用Status去做處理.


      我比較納悶的是如果說Stream的Event是另外的執行緒,

      那我再按鈕事件裡面去monitor接收的flag卻會發生沒有偵測到. (當然不排除Server端真的沒有傳送)

      後來為避免這樣的問題我新增了timeout機制,並且在while回圈裡面讓主thread停止50ms.

      這樣做了之後, 流程就正確了,我以為是timeout起了作用,

      但是發現根本都沒有發生timeout, 接收的flag確實有被設定...

      說也奇怪,加了timeout機制之後,反而就好了...百思不得其解



      刪除
    5. 您好:
      有沒有一種可能是,在不知道一地方產生了無限的loop,導致整支程式的focus無法離開那邊,加上了timeout讓程式產生類似中斷點的作用,可以暫時換到其他執行續,也只是我的猜想。

      刪除
  8. 作者你好,最近在試著開發 telnet 的 app,關於Socket的作用我還不是很瞭解,不知道您是否能解答?

    1) 因此,在做接收端的程式設計時,必須先由對方傳一個固定長度的資料(Header),裡頭會存放接下來要送的資料長度大小,透過此大小接收端才可以得知它所要接收的資料長度 len,才能在 buf 中將其切開。

    問:請問要怎樣才能知道主機傳回來的Header是多長?每個主機傳回的Header都是固定的嗎?
    我試過用CocoaPacketAnalyzer(貌似是TCP的封包截取軟體),但還是找不到

    2) 假若說主機要求我登入(例如PTT.CC),我傳送的Data裡面也要包含Header+帳號,主機端才會知道我在傳送什麼嗎?
    如果是的話,這個Header又要怎麼知道呢?

    感謝!

    回覆刪除
    回覆
    1. 強尼您好:

      1)Header 的大小必須由您自行來決定,這是默認的,當然在主機端與使用這端都必須知道它的長度是多長,這是潛規則,也就是你自己的協定。
      上述這個方式也只是雙方溝通的其中一個方法,當然你也可以不寫檔頭把你的資料用使用一些符號做區隔,接著再用字串判斷的方式來取得這也是方法,但是無論使用何種方式都要記得我們無法時時刻刻掌握網路的狀況(eg.封包遺失等等),這時候就必須要啟用同步的機制確保雙方都知道資料遺失需要重新傳送,我所使用的方法是一個固定長度檔頭,只要在這個檔頭內的資料無法正確轉換成integer型態,我就判斷有資料遺失,這時候要確認同步的方法我這邊是採用連續傳送!@!@!@!@!@!@!@!@等符號,接收端只要接到資料任取兩個字串出來判斷為!@或是@!就必須拋棄目前處理的buffer等待新的資料近來。


      2)這完全要看主機端的協定,如果只是傳送帳號密碼,很有可能就是傳一串字串給主機,讓主機端字串的拆解,而這邊提供的方法比較偏向傳送文件圖檔這類的東西,如果只是一般字串倒是不用這麼麻煩。


      希望這樣有幫到您!

      刪除
  9. 匿名8/07/2013

    你好:
    想請問你,我只有要傳送訊息出去然後沒有要接收訊息回來,但我發現當我接收端沒開啟或是沒正常接收指令時,我發送端就會卡住
    想請問一下是否可以判斷我有沒有成功連結接收端呢?

    回覆刪除
    回覆
    1. 您好:

      你可以在 iStream and oStream 中使用 streamStatus 來判斷目前的狀態,如果不想接收資訊使要忽略stream: handleEvent: 函式就可以了。
      在建立連結的部份,如果 CFStreamCreatePairWithSocketToHost 都能夠過的話就表示連結成功了(readStream && writeStream 都不為0)

      刪除
    2. 匿名8/08/2013

      你好:
      我想再請問怎麼知道我CFStreamCreatePairWithSocketToHost這個是有通過,我在Debug的時候不管有沒有開起接收端if (readStream && writeStream)都是對的,但此時的readStream & writeStream 都為0x00000000,
      後我在使用steamStatus來判斷目前狀態如果進到if (readStream && writeStream)這裡後 因為我已經打開oStream 所以自然就會判斷打開了,這樣我應該怎麼做

      刪除
    3. 匿名8/08/2013

      不好意思,我更正一些部分,不管我接收端有沒有開啟 readStream & writeStream 都會有個值出現

      刪除
    4. 您好:

      我隱約記得,因為時間點有點久遠加上我手邊沒有設備可以幫您測試...
      在成功連結主機之後系統會縣送一個回復給你,這是自動的,所以當你連結成功之後都會先觸發一次 NSStreamEventHasBytesAvailable 你可以由這邊判斷是否正確連結,因為你能收到這則訊息表示input與output都沒有問題,至於要如何分別檢視這兩個,我就真的沒有研究過了!

      另外 readStream && writeStream 這部份,我本以為您是要做自我檢測,這兩個部分是判斷應用程式自我本身是否能成功開啟input與output,不管其餘部分是否運作正常,它只要可以丟東西出去,或是收東西近來,確保這兩個環節順利運行,至於與接收端合作的部份仍然需要使用 NSStreamEventHasBytesAvailable 做判斷。

      另外也可以使用 NSStreamEventErrorOccurred 來判斷運行的當下是否出現連線問題。

      刪除
  10. 匿名1/07/2014

    請問牛奶大大:
    我利用
    if (readStream && writeStream)
    {
    CFReadStreamSetProperty(readStream,
    kCFStreamPropertyShouldCloseNativeSocket,
    kCFBooleanTrue);
    CFWriteStreamSetProperty(writeStream,
    kCFStreamPropertyShouldCloseNativeSocket,
    kCFBooleanTrue);

    //開啟 read & write stream
    inputStream = (__bridge NSInputStream *)readStream;
    [inputStream setDelegate:self];
    [inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    [inputStream open];

    outputStream = (__bridge NSOutputStream *)writeStream;
    [outputStream setDelegate:self];
    [outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    [outputStream open];
    }

    也在case NSStreamEventErrorOccurred: 設定一個alert通知,
    當連線正常時可以接收跟傳送,
    但是如果斷線,要75秒alert才會跳出,請問該怎麼樣才能夠5秒就知道斷線了,並跳出alert呢?

    回覆刪除
    回覆
    1. 您好:
      我沒遇過這類問題耶,要等75秒,每次都是這個時間嗎?有可能是連線逾時重新連線,不然就是東西還沒傳完。
      如果你是很多資料分開傳送,像是聊天、傳送連續圖片等等,建議不一定要把SOCKET SESSION 一直掛著,考慮每次都重新建立連線,也可以解決你目前的這個問題。

      刪除
    2. 另外每隔一段時間主動去PING SERVER也是一個辦法

      刪除
    3. 匿名1/07/2014

      測試時是一開始sever 就沒開,送出command後要60-75秒alert才會跳出,如何縮短判斷時間,讓alert早點跳出來

      刪除
    4. 匿名1/07/2014

      牛奶大大您好:
      連線到指定的ip非localhost(若連到localhost,則alert 1~2秒就會通知)
      CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)ip, port, &readStream, &writeStream);
      測試後,發現是在
      Leaving - [aViewController viewDidLoad]跟 Entering -[aViewController stream:handleEvent:] 之間要等待75秒,
      請問這個時間可以縮短嗎?
      不好意思 ,我還是新手,一直麻煩您
      謝謝

      刪除
    5. 您好:
      沒關係大家都是由新手開始, 你可以在[outputStream open];之後加上[oStream streamStatus] 判斷是否連到server:

      iif (![oStream streamStatus] == NSStreamStatusOpen) {
      [self stream:nil handleEvent:NSStreamEventErrorOccurred]; //你可以自己呼叫handleEvent
      }

      刪除
    6. 匿名1/08/2014

      謝謝牛奶大大:
      原來還可以這樣寫啊,又學到了
      不過情形還是一樣><
      在一開始server沒開狀況下,
      CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)ip, port, &readStream, &writeStream); //要75秒
      若是CFStreamCreatePairWithSocketToHost(NULL, (CFStringRef)@"localhost", 80, &readStream, &writeStream); //要1秒,
      我在[outputStream open];之後加上
      if (![oStream streamStatus] == NSStreamStatusOpen) {
      [self stream:nil handleEvent:NSStreamEventErrorOccurred];
      }
      發現沒有進入if中 ,沒有執行[self stream:nil handleEvent:NSStreamEventErrorOccurred];
      我做錯了嗎?

      刪除
    7. 匿名1/08/2014

      哈哈,原來是我少了括號,謝謝牛奶大大的幫忙

      刪除
    8. 匿名1/08/2014

      再請問一下,發現加了
      if (!([outputStream streamStatus] == NSStreamStatusOpen)) {
      [self stream:nil handleEvent:NSStreamEventErrorOccurred];}
      雖然可以解決一開始server沒開狀況,跳出alert,但是如果是server打開狀況,一樣會有alert,也有連線,
      該怎麼處理?

      刪除
    9. 您好:
      應該不太可能耶,除非你 outputStream 沒開,不然不會跳到判斷中,你在 [outputStream open] 後送個訊息給server試試看,確認你的 outputStream 是不是正確開啟!!

      刪除
    10. 匿名1/08/2014

      您好:
      延續前面的問題,在開啟狀態,在 [outputStream open] 後送個訊息給server,可以收到
      請問一下,我在[outputStream open];之後在了 NSLog(@"status = %d",[outputStream streamStatus]);
      看到不論sever是否開啟,streamStatus 都是1 這是正常的嗎?
      我是用http://www.raywenderlich.com/3932/networking-tutorial-for-ios-how-to-create-a-socket-based-iphone-app-and-server 這個例子測的

      刪除
    11. 您好:
      不正常喔,我這邊測試 使用 if ([oStream streamStatus] == NSStreamStatusOpen) 在server未開啟時「不為真」也就是不等於 NSStreamStatusOpen。
      你可以看到他的定義
      typedef NS_ENUM(NSUInteger, NSStreamStatus) {
      NSStreamStatusNotOpen = 0,
      NSStreamStatusOpening = 1,
      NSStreamStatusOpen = 2,
      NSStreamStatusReading = 3,
      NSStreamStatusWriting = 4,
      NSStreamStatusAtEnd = 5,
      NSStreamStatusClosed = 6,
      NSStreamStatusError = 7
      };

      1表示正在開啟
      看定義的方式可以按下command+滑鼠左鍵點擊 NSStreamStatusOpen 字樣。

      刪除
    12. 匿名1/08/2014

      謝謝,請問牛奶大大:
      那麼如果您那邊一開始沒開sever,就執行程式,Leaving - [aViewController viewDidLoad]跟 Entering -[aViewController stream:handleEvent:] 之間 ,會是多久時間?

      刪除
    13. 您好:
      我跟您一樣大約是一分鐘多,這應該是socket 在建立secion一直到timeout的時間,這部份我之前也沒注意,都是假設host永遠在存活的狀態下,並不會第一時間主動去檢測host的狀態,網路上的文當都是直接在 stream:handleEvent 中去做判斷,有就是等待timeout之後才做處理。

      另外除了此方法外,你也可以考慮使用 AsyncSockets,這也蠻多人在使用的,抱歉沒能解決您的問題!

      刪除
    14. 匿名1/09/2014

      我才是應該要謝謝牛奶大大,不厭其煩的一直回答我的問題,替我解惑,謝謝您

      刪除
  11. 匿名3/28/2014

    請問牛奶大大:
    感謝前面的分享,若是函式在ViewController照著做可以成功,
    但因為多個viewcontroller 都需要連線,參考前面的留言得知可以把SOCKET的相關函式都丟到NSObject的.m檔裡,
    但是因為還是新手,不太懂該如何著手,請問是否有範例可以參考?
    謝謝

    回覆刪除
    回覆
    1. 您好:
      我沒有多個view的範例,上面的code就是我在ios裡實作的程式碼了。

      刪除
  12. 好多問題4/02/2014

    牛奶大大您好:
    我在實作時遇到以下問題,想請問該如何解決?
    使用tab bar畫面,有View1 & view 2
    view 1有一個label & button ,view2 有一個label
    將連線部分寫在nsobject中,
    另設一個singleton用來存放接收到的資料,
    希望的效果是,按下按鈕後,會傳送到主機,並從主機端接收資料,並在view1&view 2的label都會出現文字,但是現在我只能做到一半
    例如:按下view1的按鈕後,希望view1& view2的label都會出現文字”OK”
    但是只有view2成功,view1的label是空白的



    connect.m
    - (void) initNetworkCommunication {

    CFReadStreamRef readStream;
    CFWriteStreamRef writeStream;
    CFStreamCreatePairWithSocketToHost(NULL, (CFStringRef)@"localhost", 80, &readStream, &writeStream);

    inputStream = (__bridge NSInputStream *)readStream;
    outputStream = (__bridge NSOutputStream *)writeStream;
    [inputStream setDelegate:self];
    [outputStream setDelegate:self];
    [inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    [outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    [inputStream open];
    [outputStream open];

    }
    - (void)stream:(NSStream *)theStream handleEvent:(NSStreamEvent)streamEvent {
    MySingleton* singleton = [MySingleton getInstance];

    if (eventCode == NSStreamEventHasBytesAvailable) {
    uint8_t buffer[1024];
    int len;

    while ([inputStream hasBytesAvailable]) {
    len = [inputStream read:buffer maxLength:sizeof(buffer)];
    if (len > 0) {

    NSString *output = [[NSString alloc] initWithBytes:buffer length:len encoding:NSASCIIStringEncoding];

    if (nil != output) {

    [singleton doSomethingWithString:output];
    }
    }
    }
    }


    }


    MySingleton.m

    + (MySingleton*)getInstance{
    if (singletonInstance == nil) {
    singletonInstance = [[super alloc] init];
    }
    return singletonInstance;
    }

    -(void)doSomethingWithString:(NSString*)parameter {
    info = parameter;
    }

    -(NSString*)getInfo {
    return info;
    }

    回覆刪除
    回覆
    1. 你資料有重server端成功抓回來嗎?
      你說「但是只有view2成功,view1的label是空白的」,那這不是view之間傳值的問題?怎麼會是socket??

      你要先確認你的問題在哪阿!

      刪除
    2. 好多問題4/03/2014

      牛奶大大您好:
      資料有從sever端成功抓回來,切換到view2時也有把值過去,所以view2成功
      以下是view2的寫法
      view2
      - (void)viewDidLoad
      {
      [super viewDidLoad];
      MySingleton* singleton = [MySingleton getInstance];
      label2.text=[singleton getInfo];
      }
      不好意思我的程度不夠導致表達不夠明確,
      或者是該問,如果- (void)stream:(NSStream *)theStream handleEvent:(NSStreamEvent)streamEvent { }沒有寫在view1.m中
      當資料有從sever端成功抓回來後,該如何更新view 1的label?

      刪除
    3. 您好:
      你應該也不是新手吧,你都知道製作singlton了,不過你的兩個view是同時顯示嗎?如果不是的話你資料在singleton應該是不會lost掉才對。

      你view2可以去撈資料,同樣在view1顯示時也可以去撈,如果你要及時顯示的話,你可以考慮看看NSNotificationCenter,他可以寫在view2中,讓view1去註冊等待通知,類似observer的作法。

      或是使用協定Protocol也可以,給您參考一下:
      在兩個不同的 Class 之間傳遞參數的方法
      http://furnacedigital.blogspot.tw/2012/07/class.html

      刪除
    4. 好多問題4/03/2014

      牛奶大大您好:
      會知道製作singlton是參考此篇文章的回覆中,前面您提供給其他的人的建議,再去找資料及範例照著試作,
      謝謝您的回答,我會再根據您的回答去查資料再去試作看看的
      謝謝

      刪除
  13. 牛奶大大你好:
    看完您這篇文章受益良多,我目前在實作IOS串流傳輸檔案功能,需要與我之前的java socket作連線,但是在IOS這邊,
    我一直遇到一個問題,我將檔案切成4096Bytes的區段去傳送或接收,NSOutputStream和NSInputStream都會出現好像反
    應不及的問題,NSOutputStream還可以解決,呼叫write的時候會回傳這次傳出去多少,把index記錄下來,下次繼續傳就好,
    但是NSInputStream在read的時候,會出現回傳的長度會小於我傳送的,然後再下一次就return -1了,我去檢查streamStatus,
    狀態就變成NSStreamStatusError了,但是如果我再 read / write 之前加上delay( 大概5ms ),所有的傳輸就變正常了,我的感
    覺是似乎 NSInputStream / NSOutputStream 裏面是有一個buffer,但是前一次的 read / write 還沒結束,就被下一次的data覆蓋,
    想請問牛奶大大有何解法?

    謝謝

    回覆刪除
    回覆
    1. 另外再補充一下,Java在inputStream和outputStream都可以強制作flush,不知道ios有沒有? 謝謝

      刪除
    2. 您好:

      我不確定ios有沒有flush,我在實作的時候並沒有將要傳送的資料切斷送出,你可以試著把檔案家上檔頭,當你取得傳送資料整個大小之後,先傳送檔頭,之後再接收的部份一次將完整大小收下。

      如果要使用切斷的方式你必須確認送出的buff是空的才能丟資料,不然就會有覆蓋的問題,NSStreamEventHasSpaceAvailable,應該是這個判斷式。

      刪除
  14. 牛奶大大您好:
    以您的程式碼 我使用去傳送一個字串到某個ipAddress port
    但我在接收對方傳回來的訊息時 總是沒有結果 因此我在接收(stream: handleEvent:)時先偵測我的iStream 發現竟然是NSStreamStatusError
    雞婆多一行eventCode == NSStreamEventErrorOccurred 也確實成立
    我用中斷點放在stream: handleEvent: 發生兩次接收訊息 兩次都是這樣的
    該怎麼辦呢...?

    回覆刪除
    回覆
    1. 張家豪您好:
      你有防火牆擋住之類的情況嘛?
      還是你的port和其他有衝突,port建議開在1萬以上喔!

      刪除
    2. 我在檢查看看 !!
      順帶一提 牛奶大大有玩過modbus 這東西嗎?
      謝謝牛奶大大回覆 :)

      刪除
  15. 家豪 您好:
    我有聽過modbus,不過我本身沒有涉獵太多協定的技術,抱歉唷︿︿;

    回覆刪除
    回覆
    1. ok!謝謝! 那請問一下哦 關於socket 如果我傳給遠方ip
      再傳送過程中會不會有遺失或出錯的問題呢?

      刪除
    2. 張家豪 您好

      會喔,在傳送個過程中很難想像會有什麼問題發生,通常都會出現「封包遺失」,依序csma/cd的原則這類問題發生時系統都會自動進行排解,除非情況一再發生你才會收到echo,不過對方或是你也只會收到一個error並不會有在哪裡遺失封包這類的完整說明(需要相關工具輔助才有辦法得知)。

      ps:系統會依據你的協定tcp或是udp來決定,遺失的封包是否需要重新傳送。

      刪除
  16. 牛奶大大 您好:中秋節快樂 :)
    我遇到一個問題 我的i pod 下載一個socket server端
    我在模擬器上連接i pod的server端 成功連線 但是
    在我的模擬器上卻馬上接收到server端的資料 就在以下這程式碼時崩潰了(有***的那行使程式崩潰):
    if (eventCode == NSStreamEventHasSpaceAvailable) {
    NSLog(@"receiving...");
    NSMutableData *data = [[NSMutableData alloc] init];

    //定義接收串流的大小
    uint8_t buf[1024];
    unsigned int len = 0;
    len = [(NSInputStream *)stream read:buf maxLength:1024]; *****************
    [data appendBytes:(const void *)buf length:len];

    請問是什麼問題呢??

    回覆刪除
    回覆
    1. 您好:

      不清楚耶,也許在buf中沒有東西可以讓他讀取,是在職行完該行當掉,還是他的下一行?剛進中斷的的時候是還沒執行狀態喔。
      看看你的console,應該還有其他線索....

      刪除
    2. 執行完後當掉 -[__NSCFOutputStream read:maxLength:]: unrecognized selector sent to instance 0x8da0430
      若沒東西讀取 也會當掉嗎?

      刪除
    3. 應該是不會,
      你是你自己呼叫「- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode 」的嗎?

      你把 maxLength 改小一點看看。
      我在網路上看到這類的問題大部分都是instance沒設定好,找不到要refer的那個instance.

      刪除
    4. 這是當掉後我複製其中一行的
      我把maxLength 改成256還是當
      順便一提
      要傳送Data的程式碼 我包裝成一個方法 請問傳送的時機是? 在main裏面設定完output&input 接著判斷oStream == open狀態下傳送嗎?

      刪除
    5. 是的!oStream == open才有辦法送東西。你把它封裝起來之後,記得在使用時要宣告成唯一singleton,這樣在不同class間使用才不會有問題

      刪除
  17. 您好:
    我想請問我在使用陣列傳送資料給多台server,而server會在接收到資料後會回傳訊息,但是程式會在執行完陣列後才開始接收訊息,
    這樣之前server所回傳的會被lose掉,請問要如何在陣列中發送資料給一個server後先接收回傳的訊息,再對下一個server發送資料?
    另外,再請問牛奶大大有沒方法可以攢寫一次與多台server連線及傳送資料的程式呢? 謝謝~

    回覆刪除
    回覆
    1. 您好:

      與多台server連線需要建立多個io stream,他們彼此之間也是獨立的,就如同多開幾個程式是一樣的道理,只是彼此間的port不能相同。

      另外在接收訊息部分應該沒有,在執行才會去接收訊息的問題,接收訊息式自動的他屬於另外一個thread,你的問題應該是發生在同步上,你應該先把接收的資料存在一個地方,等你陣列處理完之後再回頭慢慢處理這些資料!

      刪除
  18. 牛奶大大您好:
    更新xcode6之後 用macBook跟i touch做socket之間的封包傳送 好像都連接不上!
    請問牛奶大大有更新嗎? 有的話試試看socket連線有沒有什麼問題 ?
    之前可以互相丟資料的

    回覆刪除
    回覆
    1. 您好:
      我還沒更新xcode6,不過socket協定和xcode沒有什麼關係,你不管用什麼程式語言,socket他就是socket寫法也都大同小異,你看看port是不是已經被其他程式佔用,或是兩台裝置根本在不同網段,先判斷oStream 和 istream是不是都運作正常在傳送資料看看。

      刪除
    2. 兩檯裝置不同網段是什麼意思呢?
      我的i touch是192.168 電腦是169.168
      這樣就不能連接嗎?

      刪除
    3. 是可以接呀~不同網段要過路由器,你要上網查一下 tcp/ip 過 routers的方法!
      而且你192.168是虛擬ip,169.168這個網段貌似是實體ip,但是...好像不在台灣。

      刪除
  19. 牛奶大大妳好
    我做了一個簡單的範例,但是目前遇到瓶頸的問題,使用[oStream write]方法傳送簡單的資料都ok,但是遇到大檔案,如500kb的東西,似乎會掉資料,傳一傳就停住了,然後再開始傳送的時候中間的資料就消失了,是發生什麼問題呢,目前我使用library的方式呼叫我的socket。謝謝您

    回覆刪除
    回覆
    1. 您好:
      你這樣問我也不知道是什麼問題,這跟怎麼呼叫socket沒有太大的關係,你可以試著判斷檔案大小來去接收你的資料,看看是不是符合總長度。
      網路在傳輸時掉資料時會遺失封包是很正常的,但是怕的就是在接收資料時勿把不同筆的資料當成同一筆來處理。

      至於傳一傳就停住,你在傳送時應該是一次就傳送500kb,但是在接收卻是要看你buf設定的大小和你當時的網路情況而定,有時候會斷斷續續才收到,這都屬於正常狀況,如果完全停住你可以檢查 oStream 和 istream 看看是哪裡卡住在去做對應的處理。

      刪除
  20. 請問牛奶大大:
    如果同時連接多個server 要如何辨別接收的回傳訊息是從哪個server送出的
    麻煩你了 謝謝!

    回覆刪除
    回覆
    1. 不同server就是不同的socket,port也不同你一旦建立連線,能連一個server....所以不會有這個問題

      刪除
    2. 請問在"從主機端接收訊息"的部分一樣是利用 協定函式來接收嗎? 那麼接受到不同server所傳出的訊息不是都呼叫同一個函式嗎? 還是可從函式中做判斷?
      麻煩你了 謝謝!

      刪除
    3. 您好 :
      你不可能把兩個server都寫在同一個calss裡吧..這樣會亂掉!
      如果硬要這樣做的話,會是同一個function接收,因為你delegate同樣都是指向自己。

      硬判斷的話可能只能自行設定header了。

      你應該自己寫一個新的class把socket包在裡面,當然裡面也包含了"從主機端接收訊息"的這部份函式,你要連幾台主機就craete幾個你自定的class,這樣他們的函式才會分別獨立出來。

      刪除
  21. 牛奶大大你好~
    請問一下
    如果我要用您這個方法只做接收端的clinet要怎麼改呢?
    我現在是要連結到一個wifi的sever設備上
    手機端這邊想打入ip跟port的就可以接收到資料

    回覆刪除
    回覆
    1. 你好
      如果是同網段不跨Router,或是雙方都實體ip的話,按照上述寫法就可以了!

      刪除
  22. 牛奶大大你好,
    我的write方法是寫在這個類的另外一個方法里,并且用多线程,不知道這樣可不可以, [self.operationQueue addOperationWithBlock:^{

    NSInteger writeBytes = [self.outputStream write:[data bytes] maxLength:[data length]];
    NSLog(@"outputStream.streamStatus %u",self.outputStream.streamStatus);

    [self resetTimeInterval];
    // suspended
    [self.operationQueue setSuspended:YES];
    NSLog(@"write %ld data.length:%lu", (long)writeBytes, (unsigned long)data.length);
    }];
    現在有時我會數據發不出去,導致整個軟件毫無反應,請問write:maxLength:方法一定要寫在 case NSStreamEventHasSpaceAvailable:的這裡嗎

    回覆刪除
    回覆
    1. write:maxLength:方法會阻塞住,數據發不出去可能是什麼原因呢;我是一个新手,这个问题困扰了好久了,请牛奶大大抽空帮我看下,谢谢

      刪除
    2. 沒有規定write:maxLength:一定要寫在 case NSStreamEventHasSpaceAvailable: 裡,這只是看你應用,case NSStreamEventHasSpaceAvailable:也只是一個判斷是而已。

      另外你先確定你多執行續下,write:maxLength 要素出的資料是否為nil(空的),另外在多執行續中是否而已正確取得oStream的instance!

      還有你上方的code,並沒有送出的指令,只有取得長度。

      刪除
    3. 謝謝牛奶大大的回覆,不過我還有一些疑惑:
      1.您說 case NSStreamEventHasSpaceAvailable:也只是一個判斷,那是通常什麼情形下會用到這個事件呢,我看網路上的資料都講得不是很清楚;
      2.我看了執行日誌,write:maxLength 要素出的資料比較少出現為空的情況;您說我需要看 在多執行續中是否而已正確取得oStream的instance,這個我要怎麼判斷呢;
      3.最近的bug時 有時可以發送成功,有時便發送失敗,阻塞住了,所以您是要改成 [self.outputStream write:[data bytes] maxLength:[data length]] 這樣子才有算是發送的指令嗎,那又成功的情況又怎麼解釋呢?
      勞煩牛奶大大了,謝謝您!

      刪除
    4. 謝謝牛奶大大的回覆,不過我還有一些疑惑:
      1.您說 case NSStreamEventHasSpaceAvailable:也只是一個判斷,那是通常什麼情形下會用到這個事件呢,我看網路上的資料都講得不是很清楚;
      2.我看了執行日誌,write:maxLength 要素出的資料比較少出現為空的情況;您說我需要看 在多執行續中是否而已正確取得oStream的instance,這個我要怎麼判斷呢;
      3.最近的bug時 有時可以發送成功,有時便發送失敗,阻塞住了,所以您是要改成 [self.outputStream write:[data bytes] maxLength:[data length]] 這樣子才有算是發送的指令嗎,那又成功的情況又怎麼解釋呢?
      勞煩牛奶大大了,謝謝您!

      刪除
    5. 謝謝牛奶大大的回覆,不過我還有一些疑惑:
      1.您說 case NSStreamEventHasSpaceAvailable:也只是一個判斷,那是通常什麼情形下會用到這個事件呢,我看網路上的資料都講得不是很清楚;
      2.我看了執行日誌,write:maxLength 要素出的資料比較少出現為空的情況;您說我需要看 在多執行續中是否而已正確取得oStream的instance,這個我要怎麼判斷呢;
      3.最近的bug時 有時可以發送成功,有時便發送失敗,阻塞住了,所以您是要改成 [self.outputStream write:[data bytes] maxLength:[data length]] 這樣子才有算是發送的指令嗎,那又成功的情況又怎麼解釋呢?
      我的網路狀況不是很好,一直留言都不成功。勞煩牛奶大大了,謝謝您!

      刪除
    6. NSStreamEventHasSpaceAvailable是判斷目前使否有空間可以寫入,但是並不確保寫入的資料對方一定可以完全收到,因為這要視當時網路壅塞的情況而定
      oStream的instance,只要你可以在目前函是式中可以使用oStream的方法,就表示有取得他的實例,或是觀看其值是否為NULL。
      因為就你付上的CODE,並沒有送出的資訊的程式碼,索我也不知道你資訊是在哪的,在使用多執行緒的時候比需要考慮到同步的關係,另外SOCKET本身就會自行開一個Thread,宣告兩個SOCKET也就開了兩個Thread,我也不確定你在SOCKET裡面又自行開了多個Thread的作用,也許是這樣比的Thread間有衝突,才會導致東西一下送的出去一下子又送不出去。

      刪除
    7. 謝謝大大的回覆,不知道是我的網路原因還是,我張貼的留言都沒有顯示在這邊,不過幸好您可以看得到。
      業務上我這個是在登錄軟件的時候,我試想是我上一次登錄發送成功后,退出軟件時沒有將隊列清空、多線程沒有關閉,再一次登錄便以阻塞原因失敗了,請問大大,我用NSOperationQueue [self.operationQueue addOperationWithBlock:^{這個創建多執行緒,可以關閉多執行緒麼,怎麼關閉呢?
      我需要翻牆與您聯繫,希望你可以看得到,麻煩您了,謝謝牛奶大大!

      刪除
    8. 抱歉,關於多執行緒的問題我並沒有涉獵太多
      不過你可以參考下列的方法關閉多執行緒
      http://stackoverflow.com/questions/7820960/how-to-stop-current-nsoperation

      刪除
    9. 好的,謝謝牛奶大大,多謝提供的幫助

      刪除
  23. 牛奶大大你好,這邊有問題想請教

    我是把這個檔案和介面端的方開寫,介面端會去include這整個寫好的檔案

    檔案裡面會多一個read的function,用來return伺服端的資料

    我在介面端中,會有個button來傳送我的訊息,通常訊息傳過去之後,應該會有從伺服端傳回來的資料show在textview上,但目前遇到的問題是,我的button要按兩下之後才會有資料show在textview上

    這個問題困擾我滿久了,麻煩牛奶大大,謝謝!!

    回覆刪除
  24. 牛奶大大你好!
    我的介面端的程式是include我所寫好的這個檔案
    在我介面端的button中,送訊息給server,照理來說我的textview應該會顯示sever傳回來的訊息,但卻是空的
    變成是我要按兩下button,我的textview才會有東西跑出來
    請問碰到這種情況應該怎樣解決,已經困擾我好久
    謝謝!

    回覆刪除
    回覆
    1. 您好:
      這個問題我想您設定中斷點後應該就可以自行發現問題的所在,我猜按第一下的時候並沒有成功接收到訊息,或是訊息為空,電腦不論做甚麼都需要緩衝時間,如果你是按下按鈕才開始接收立刻就顯示,自然不會有任何東西。在SOCKET的觀念上通常是開一個另外的執行緒,因此這類的同步讓的問題也層出不窮。

      刪除
    2. 好的,我在試試看,謝謝牛奶大大~

      刪除
  25. 牛奶大大,您好:
    最近我也在寫有關socket通訊,有兩個問題想請教您
    1.看到文章起頭您server端是用delphi開發,可以請教是用那顆元件當server收發?
    2.當我用Delphi 的 TSocketServer 當主機收發,訊息接收跟發送是沒有問題的
    改用 TIdTCPServer 時,主機發送訊息 iOS可以收的到,反過來 iOS要發送訊息,Server端完全沒有反應
    不知那邊設置錯誤,或者是Blocking的特性,那該如何解決?

    在麻煩大大指教,謝謝。

    回覆刪除
    回覆
    1. Thomas 您好

      這真的有點久遠的問題,我記得我當時是使用 Delphi 7 的 Indy Servers 分類下的 TIdUDPServer 至於 Indy 版本我不確定是多少,不過應該影響不大。

      1. 試試看先用 TIdUDPServer 並確認是在相同網段下,或是接為實體ip,避開NAT等問題,建立單純的網路環境。
      2. 當時沒有測試到 TIdTCPServer 元件,畢竟該元件在協定上比較複雜,當然也有您說的 Blocking 與 non-Blocking,這部份我就沒做研究
      不過這都已經屬於 Delphi 或是設定上的問題.....。

      抱歉這邊我實在是幫不上您的忙! - See more at: http://furnacedigital.blogspot.tw/2011/03/socket.html?showComment=1423384342577#c6758927001934044879

      刪除
    2. 牛奶大大,您好:
      第二個問題我找到答案了,就是傳遞字串時最後要加上CRLF,Indy才會認為是一個結束。當然當也可以自訂結束字元。

      感謝您抽空解答,謝謝。

      刪除
  26. 你好 我想請問一個問題
    目前我的情況是 我寫了一個python server放在樹莓派上面
    然後另外寫一個obj-c client去連上我的樹莓派
    我會讓我的server 和client都連到同一個wifi下

    其中我python server的bind寫成s.bind(("", 10))
    我想請問我可不可以試試看client端這邊另外寫一個UI開啟後先輸入連上的wifi IP再去連上我的python server呢?

    回覆刪除
    回覆
    1. 你好:

      可以,不過這好像和 Stream Socket 沒有任何關係耶,這邊是假定已經拿到可以正常使用的ip address之後的事情了!
      另外server端用什麼程式語言都沒關係,但是你必須follow Socket 的協定才可以。

      刪除
    2. 感謝 牛奶大大
      我想請問
      CFReadStreamRef readStream = NULL;
      CFWriteStreamRef writeStream = NULL;

      NSString *ip = @"192.168.1.124";
      NSInteger port = 5555;

      CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, (__bridge CFStringRef)ip, port, &readStream, &writeStream);

      if (readStream && writeStream) {
      CFReadStreamSetProperty(readStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);
      CFWriteStreamSetProperty(writeStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);

      iStream = (__bridge NSInputStream *)readStream;
      [iStream setDelegate:self];
      [iStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
      [iStream open];

      oStream = (__bridge NSOutputStream *)writeStream;
      [oStream setDelegate:self];
      [oStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
      [oStream open];
      }
      你上面這段中如果就client端來看的話是不是已經包含了 建立clientsocket Connect to server 然後開始listen從server端傳來的資料或送資料給server呢?

      刪除
    3. 另外 iStream和oStream什麼時候會結束呢?

      刪除
    4. 如果我的client端只想傳可能一個integer過去然後server端要傳自串過來的話這邊適用嗎??
      不好意思我還是個新手還請多多包涵><

      刪除
    5. 您好:

      是的,上面這段code已經包含與主機端建立連線,iStream和oStream你不釋放掉的話他會永遠處於開啟(傾聽狀態),
      只要是傳送資料出去,沒有什麼適不適用耶,因為你一定要用才傳的出去阿,另外在傳的時候無關型態,請先正規劃成NSData格式在傳送,這是比較保險的作法!!

      刪除
  27. 不好意思,請問這個是能對資料庫進行存取動作嗎(電腦上)
    還是只能用AMPPS那類的方法。

    回覆刪除
    回覆
    1. 您好:

      都不是唷!這是網路傳輸協定的一種,和資料庫沒有任何關係。

      刪除
    2. 那沒辦法對server端的東西進行操作嗎
      修改查詢要求回傳資料等等。

      刪除
    3. 這你要查sql語法,而不是socket協定!

      sql語法應該類似這種:(SQLite3 的二三事)
      http://furnacedigital.blogspot.tw/2013/06/sqlite3.html

      不過這並沒有包含資料庫在異地的情況,你可以試試查詢 IOS MYSQL看看!!

      刪除
  28. 你好,目前有在實作用SOCKET傳送圖片,但目前遇到幾個問題....
    1.我現在傳送的機制是先傳送一個Commend(有不同Commend代表不同事件,圖片為IMAGE)給Server端讓他知道要開始傳圖片,
    然後等待伺服器回傳要求圖片檔名我客戶端在傳送檔名給伺服器,然後伺服器在要求知道檔案大小在傳送大小、再來圖片檔案這樣,
    但是如此一來一往感覺沒有效率,當然也有可能中間一遺失我就必須整個動作重頭來一遍....不知道是否可以給些不同的建議?如果是牛奶大大你的話會怎麼做這兩邊個溝通呢?

    2.我看TCP協定的資料,有關於Acknowledge and Retransmission的機制,不知道用在傳送圖片是否合適?對於每筆資料都加上編號並且等待回傳接收到的資訊感覺又是相當的費時...我做的實作相當的要求速度這點,不知道有沒有什麼做法可以做到當資料有遺失可以不重新整個傳送過程,速度又能相當穩定的呢?

    回覆刪除
  29. 陳思維 您好:

    不知道為什麼這邊沒有顯示留言,不過我信箱倒是有收到。
    結構應該是這樣沒錯:

    Client Server
    |-------(command-Type)---------->|
    | |
    |<------(Return-Ready)-------------|
    | |
    |-------(Image-Property)---------->|
    | |
    |<------(Return-Ready)-------------|
    | . |
    | . |
    | . |
    |------------(Image-File)------------>|

    在中間的部份,不太需要這麼麻煩,既然你都已經知道利用一個「command」來替server解釋他需要做什麼事情,那麼我門把流程簡單化,在command-Type時就一次把東西送齊全,例如送出 : "@20字元的command@20字元的檔案名稱@20字元得檔案大小@" ,這些東西上server自己去拆字串解析,因為他下一個收到的一定是圖片身,所以在這中間不需要確認這麼多次。另外檔案大小看你自己需求,有的時候可以在傳檔案前和傳玩之後塞一些特殊字元,讓server知道這段特殊字元之間的資料是可以被轉換成影像的,這也是一種作法。

    另外遺失的話,這你沒辦法控制,這有關MTU等等,你能做的就是重傳!!

    最後,您的第二個問題已經牽扯到TCP的協定部分,你可以查查TCP ACK ,至於是不是合用在你的實作上,這就要回到你設計面了,如果你一直很平凡的「確認」和「準備」所要傳送的資料,並且其中一步如果出錯就「必須」整個「重頭」,那這個機制對於你來說當然是必要的。

    回覆刪除
    回覆
    1. 感謝牛奶大這麼快就回覆!!

      關於一來一往的機制當初構想是想要去區分哪段遺失就重新傳送哪段的資料,但是發現那些區段資料都是非常小的
      比起一來一往好像整個重新傳送的效率會更高吧!

      另外看了很多討論都說傳送"圖片"是可以有容錯空間,就算遺失一點資料也無所謂,如果想要速度就用UDP,我的疑問是圖片資料如果少了一段不就會建立不起整個圖片檔案嗎?每張圖片前面都會有圖片得資訊,如果後面得資料少一段資訊不同,這樣可以重新繪製出一張圖片?繪製出的圖片就只是會少那一段遺失資料的像素嗎?

      刪除
    2. 您好:

      使用UDP的話就無法檢查對方是否收到,即時遺失也不會重新傳送。
      任何檔案在傳送時遺失部分資料,都會造成無法正常使用的情形,除非你有特別設計過你的資料格式。
      圖片遺失部分資料可能會導致色偏、無法顯示完整、位移或是無法正常開啟等等,所以我不確定你說的傳送圖片有容錯空間,是指哪部分。

      以上這些事情我覺得都不是你所需要關心的,畢竟整個網路的情況不是你可以控制的,你只要專心對你目前的buffer負責就好了,送多少就取多少,最多在圖片送完時補個指令,command1+影像+command2,如果取固定長度的buffer轉換成command2不是你預設的東西,那就要重新送圖片!!

      刪除
    3. 好的了解了!!非常感謝牛奶大的指導

      P.S.不知道為什麼我留言完明明就有看到自己的留言,但過一下重新整理就又消失了...

      刪除
    4. 可能有什麼奇怪的字元吧,之前也有人和你一樣,幸好都有Mail通知。

      刪除
  30. 作者已經移除這則留言。

    回覆刪除
    回覆
    1. 你好:

      textView只是顯示存放在str中的字串,不懂你拉完之後沒反應的問題!?
      如果是沒有顯示字串,你可以能要先看看str裡面有沒有字串,有可能是你根本沒接收到資訊。

      刪除
  31. 匿名7/05/2015

    請教一下 因為現在網路上資料 在socket傳送資料方面,往往都是傳送字串,幾乎都是用char來宣告
    那現在我需要傳送的不是字串,而是一個帶有資料的變數,例如: guint8 *data 宣告的名稱比較少見,我需要傳送變數data
    請問再傳送以及接收的寫法,跟傳字串的方式是一樣的嗎?

    回覆刪除
    回覆
    1. 您好:

      傳送的時候,不管你的內容為何方法都是一樣的,把資料打回「原形」傳送,接收端再想辦法捏回原本的資料。

      刪除
  32. 您好:
    我想請問一下我把socket寫成一個NSObject,然後我寫了一個oStream write的func
    那我在oStream write之後,要怎麼知道我在- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode
    收完資料
    我在viewController呼叫oStream write,卻不知道如何在viewController取到server送來的資料

    回覆刪除
    回覆
    1. 您好

      你需要把你寫的物件設定成 singleton 這樣就可以在任何地方找到他囉!

      刪除
  33. 您好:

    想請問一下我在製作上傳圖片到server時,傳送了一個圖片大小(假設是10000bytes)的資料給
    server,但在server 接收端只接收了兩次2000+2000之後server 就一直在等client 傳剩下的bytes , 請問是否知道為甚麼client 沒有把剩下的bytes傳過去呢?

    我有試過傳小檔案的圖片是可以成功的, 所以很困惑,不知道原因在哪

    回覆刪除
    回覆
    1. 剩下的沒有傳過去?應該都有傳吧!!

      你在送出的時候應該是取得檔案的長度(大小)一次送出,
      之後在接收端取收取相同大小的檔案就可以了,

      你可以試試看先把buffer寫成檔案的大小,應該就可以正常接收,
      這邊會錯誤是因為他不知道什麼時候開始,或是什麼時候結束.....

      你應該自己建立一個擋頭,用檔頭去判斷之後的檔案大小才可以正確接收唷
      你已經使用socket來做傳輸,就要從底層開始寫,檔案大小格式都必須自己判斷才是!

      刪除
    2. 匿名6/22/2016

      Hi 感謝您的回覆,


      我找到原因了, 原因出在 ostream 不會一次將所有的buffer 都送給server,所以必須弄一個while 迴圈判斷已經傳送多少,一直把剩下的buffer 傳送完為止,下面是參考code 希望可以幫到有一樣問題的人

      while ( nsdata.length > bytesWritten )
      {
      writeResult = [oStream write:(const uint8_t*)([nsdata bytes])+bytesWritten maxLength:[nsdata length]-bytesWritten];
      //sending NSData over to server

      NSLog(@"WRITE RESULT : %ld",(long)writeResult);
      if ( writeResult == -1 ){
      NSLog(@"error code here");
      break;
      }
      else
      bytesWritten += writeResult;

      if (bytesWritten == writeResult) {

      }
      }


      刪除