第一部分、概念的了解
1、什麼是Socket?
Socket又稱之為“套接字”,是系統提供的用于網絡通信的方法。它的實質并不是一種協定,沒有規定計算機應當怎麼樣傳遞消息,隻是給程式員提供了一個發送消息的接口,程式員使用這個接口提供的方法,發送與接收消息。
Socket描述了一個IP、端口對。它簡化了程式員的操作,知道對方的IP以及PORT就可以給對方發送消息,再由伺服器端來處理發送的這些消息。是以,Socket一定包含了通信的雙發,即用戶端(Client)與服務端(server)。
2、Socket的通信過程?
每一個應用或者說服務,都有一個端口。比如DNS的53端口,http的80端口。我們能由DNS請求到查詢資訊,是因為DNS伺服器時時刻刻都在監聽53端口,當收到我們的查詢請求以後,就能夠傳回我們想要的IP資訊。是以,從程式設計上來講,應該包含以下步驟:
1)服務端利用Socket監聽端口;
2)用戶端發起連接配接;
3)服務端傳回資訊,建立連接配接,開始通信;
4)用戶端,服務端斷開連接配接。
3、Socket雙方如何建立起連接配接?
以下過程用代碼表示:
Server端:
1 intport = 2000;
2 IPEndPointServerEP = new IPEndPoint(IPAddress.Any,port);
3 Socketserver = new Socket(AddressFamily.InterNetwork, SocketType.Stream,ProtocolType.Tcp);
4 server.Bind(ServerEP);
5 server.Listen(0);
Client端:
1 intport = 2000;
2 IPAddressserverip = IPAddress.Parse("192.168.1.100");
3 IPEndPointEP = new IPEndPoint(server,port);
4 Socketserver = new Socket(AddressFamily.InterNetwork, SocketType.Stream,ProtocolType.Tcp);
5 server.Bind(EP);
當伺服器端接收到來自用戶端的連接配接以後,需要建立一個socket來處理遠端的資訊。
下面一段代碼應該在伺服器端:
1 Socketclient = server.Accept();
以上很簡單的幾行代碼,将在以後的網絡程式設計中經常用到,後面還會有同步通訊、異步通訊、線程、委托與事件等等
第二部分、各協定的差別
TCP/IP SOCKET HTTP
網絡七層由下往上分别為實體層、資料鍊路層、網絡層、傳輸層、會話層、表示層和應用層。
其中實體層、資料鍊路層和網絡層通常被稱作媒體層,是網絡工程師所研究的對象;
傳輸層、會話層、表示層和應用層則被稱作主機層,是使用者所面向和關心的内容。
http協定 對應于應用層
tcp協定 對應于傳輸層
ip協定 對應于網絡層
三者本質上沒有可比性。 何況HTTP協定是基于TCP連接配接的。
TCP/IP是傳輸層協定,主要解決資料如何在網絡中傳輸;而HTTP是應用層協定,主要解決如何包裝資料。
我們在傳輸資料時,可以隻使用傳輸層(TCP/IP),但是那樣的話,由于沒有應用層,便無法識别資料内容,如果想要使傳輸的資料有意義,則必須使用應用層協定,應用層協定很多,有HTTP、FTP、TELNET等等,也可以自己定義應用層協定。WEB使用HTTP作傳輸層協定,以封裝HTTP文本資訊,然後使用TCP/IP做傳輸層協定将它發送到網絡上。
Socket是對TCP/IP協定的封裝,Socket本身并不是協定,而是一個調用接口(API),通過Socket,我們才能使用TCP/IP協定。
Http和Socket連接配接差別
相信不少初學手機聯網開發的朋友都想知道Http與Socket連接配接究竟有什麼差別,希望通過自己的淺顯了解能對初學者有所幫助。
1、TCP連接配接
要想明白Socket連接配接,先要明白TCP連接配接。手機能夠使用聯網功能是因為手機底層實作了TCP/IP協定,可以使手機終端通過無線網絡建立TCP連接配接。TCP協定可以對上層網絡提供接口,使上層網絡資料的傳輸建立在“無差别”的網絡之上。
建立起一個TCP連接配接需要經過“三次握手”:
第一次握手:用戶端發送syn包(syn=j)到伺服器,并進入SYN_SEND狀态,等待伺服器确認;
第二次握手:伺服器收到syn包,必須确認客戶的SYN(ack=j+1),同時自己也發送一個SYN包(syn=k),即SYN+ACK包,此時伺服器進入SYN_RECV狀态;
第三次握手:用戶端收到伺服器的SYN+ACK包,向伺服器發送确認包ACK(ack=k+1),此包發送完畢,用戶端和伺服器進入ESTABLISHED狀态,完成三次握手。
握手過程中傳送的包裡不包含資料,三次握手完畢後,用戶端與伺服器才正式開始傳送資料。理想狀态下,TCP連接配接一旦建立,在通信雙方中的任何一方主動關閉連接配接之前,TCP 連接配接都将被一直保持下去。斷開連接配接時伺服器和用戶端均可以主動發起斷開TCP連接配接的請求,斷開過程需要經過“四次握手”(過程就不細寫了,就是伺服器和用戶端互動,最終确定斷開)
2、HTTP連接配接
HTTP協定即超文本傳送協定(HypertextTransfer Protocol ),是Web聯網的基礎,也是手機聯網常用的協定之一,HTTP協定是建立在TCP協定之上的一種應用。
HTTP連接配接最顯著的特點是用戶端發送的每次請求都需要伺服器回送響應,在請求結束後,會主動釋放連接配接。從建立連接配接到關閉連接配接的過程稱為“一次連接配接”。
1)在HTTP 1.0中,用戶端的每次請求都要求建立一次單獨的連接配接,在處理完本次請求後,就自動釋放連接配接。
2)在HTTP 1.1中則可以在一次連接配接中處理多個請求,并且多個請求可以重疊進行,不需要等待一個請求結束後再發送下一個請求。
由于HTTP在每次請求結束後都會主動釋放連接配接,是以HTTP連接配接是一種“短連接配接”,要保持用戶端程式的線上狀态,需要不斷地向伺服器發起連接配接請求。通常的做法是即時不需要獲得任何資料,用戶端也保持每隔一段固定的時間向伺服器發送一次“保持連接配接”的請求,伺服器在收到該請求後對用戶端進行回複,表明知道用戶端“線上”。若伺服器長時間無法收到用戶端的請求,則認為用戶端“下線”,若用戶端長時間無法收到伺服器的回複,則認為網絡已經斷開。
3、SOCKET原理
3.1套接字(socket)概念
套接字(socket)是通信的基石,是支援TCP/IP協定的網絡通信的基本操作單元。它是網絡通信過程中端點的抽象表示,包含進行網絡通信必須的五種資訊:連接配接使用的協定,本地主機的IP位址,本地程序的協定端口,遠地主機的IP位址,遠地程序的協定端口。
應用層通過傳輸層進行資料通信時,TCP會遇到同時為多個應用程式程序提供并發服務的問題。多個TCP連接配接或多個應用程式程序可能需要通過同一個 TCP協定端口傳輸資料。為了差別不同的應用程式程序和連接配接,許多計算機作業系統為應用程式與TCP/IP協定互動提供了套接字(Socket)接口。應用層可以和傳輸層通過Socket接口,區分來自不同應用程式程序或網絡連接配接的通信,實作資料傳輸的并發服務。
3.2 建立socket連接配接
建立Socket連接配接至少需要一對套接字,其中一個運作于用戶端,稱為ClientSocket,另一個運作于伺服器端,稱為ServerSocket。
套接字之間的連接配接過程分為三個步驟:伺服器監聽,用戶端請求,連接配接确認。
伺服器監聽:伺服器端套接字并不定位具體的用戶端套接字,而是處于等待連接配接的狀态,實時監控網絡狀态,等待用戶端的連接配接請求。
用戶端請求:指用戶端的套接字提出連接配接請求,要連接配接的目标是伺服器端的套接字。為此,用戶端的套接字必須首先描述它要連接配接的伺服器的套接字,指出伺服器端套接字的位址和端口号,然後就向伺服器端套接字提出連接配接請求。
連接配接确認:當伺服器端套接字監聽到或者說接收到用戶端套接字的連接配接請求時,就響應用戶端套接字的請求,建立一個新的線程,把伺服器端套接字的描述發給用戶端,一旦用戶端确認了此描述,雙方就正式建立連接配接。而伺服器端套接字繼續處于監聽狀态,繼續接收其他用戶端套接字的連接配接請求。
4、SOCKET連接配接與TCP連接配接
建立Socket連接配接時,可以指定使用的傳輸層協定,Socket可以支援不同的傳輸層協定(TCP或UDP),當使用TCP協定進行連接配接時,該Socket連接配接就是一個TCP連接配接。
5、Socket連接配接與HTTP連接配接
由于通常情況下Socket連接配接就是TCP連接配接,是以Socket連接配接一旦建立,通信雙方即可開始互相發送資料内容,直到雙方連接配接斷開。但在實際網絡應用中,用戶端到伺服器之間的通信往往需要穿越多個中間節點,例如路由器、網關、防火牆等,大部分防火牆預設會關閉長時間處于非活躍狀态的連接配接而導緻 Socket 連接配接斷連,是以需要通過輪詢告訴網絡,該連接配接處于活躍狀态。
而HTTP連接配接使用的是“請求—響應”的方式,不僅在請求時需要先建立連接配接,而且需要用戶端向伺服器送出請求後,伺服器端才能回複資料。
很多情況下,需要伺服器端主動向用戶端推送資料,保持用戶端與伺服器資料的實時與同步。此時若雙方建立的是Socket連接配接,伺服器就可以直接将資料傳送給用戶端;若雙方建立的是HTTP連接配接,則伺服器需要等到用戶端發送一次請求後才能将資料傳回給用戶端,是以,用戶端定時向伺服器端發送連接配接請求,不僅可以保持線上,同時也是在“詢問”伺服器是否有新的資料,如果有就将資料傳給用戶端。
HTTP連接配接是什麼意思
HTTP是一個屬于應用層的面向對象的協定,由于其簡捷、快速的方式,适用于分布式超媒體資訊系統。它于1990年提出,經過幾年的使用與發展,得到不斷地完善和擴充。目前在WWW中使用的是HTTP/1.0的第六版,HTTP/1.1的規範化工作正在進行之中,而且HTTP-NG(Next Generation of HTTP)的建議已經提出.(協定,算是全球定位!)
WWW的核心——HTTP協定
衆所周知,Internet的基本協定是TCP/IP協定,目前廣泛采用的FTP、Archie Gopher等是建立在TCP/IP協定之上的應用層協定,不同的協定對應着不同的應用。WWW伺服器使用的主要協定是HTTP協定,即超文體傳輸協定。由于HTTP協定支援的服務不限于WWW,還可以是其它服務,因而HTTP協定允許使用者在統一的界面下,采用不同的協定通路不同的服務,如FTP、Archie、SMTP、NNTP等。另外,HTTP協定還可用于名字伺服器和分布式對象管理。
2.1 HTTP協定簡介
HTTP是一個屬于應用層的面向對象的協定,由于其簡捷、快速的方式,适用于分布式超媒體資訊系統。它于1990年提出,經過幾年的使用與發展,得到不斷地完善和擴充。目前在WWW中使用的是HTTP/1.0的第六版,HTTP/1.1的規範化工作正在進行之中,而且HTTP-NG(Next Generation of HTTP)的建議已經提出。
HTTP協定的主要特點可概括如下:
1.支援客戶/伺服器模式。
2.簡單快速:客戶向伺服器請求服務時,隻需傳送請求方法和路徑。請求方法常用的有GET、HEAD, POST。每種方法規定了客戶與伺服器聯系的類型不同。 由于HTTP協定簡單,使得HTTP伺服器的程式規模小,因而通信速度很快。
3.靈活:HTTP允許傳輸任意類型的資料對象。正在傳輸的類型由Content-Type加以标記。
4.無連接配接:無連接配接的含義是限制每次連接配接隻處理一個請求。伺服器處理完客戶的請求,并收到客戶的應答後,即斷開連接配接。采用這種方式可以節省傳輸時間。
5.無狀态:HTTP協定是無狀态協定。無狀态是指協定對于事務處理沒有記憶能力。缺少狀态意味着如果後續處理需要前面的資訊,則它必須重傳,這樣可能導緻每次連接配接傳送的資料量增大。另一方面,在伺服器不需要先前資訊時它的應答就較快。
2.2 HTTP協定的幾個重要概念
1.連接配接(Connection):一個傳輸層的實際環流,它是建立在兩個互相通訊的應用程式之間。
2.消息(Message):HTTP通訊的基本機關,包括一個結構化的八元組序列并通過連接配接傳輸。
3.請求(Request):一個從用戶端到伺服器的請求資訊包括應用于資源的方法、資源的辨別符和協定的版本号
4.響應(Response):一個從伺服器傳回的資訊包括HTTP協定的版本号、請求的狀态(例如“成功”或“沒找到”)和文檔的MIME類型。
5.資源(Resource):由URI辨別的網絡資料對象或服務。
6.實體(Entity):資料資源或來自服務資源的回映的一種特殊表示方法,它可能被包圍在一個請求或響應資訊中。一個實體包括實體頭資訊和實體的本身内容。
7.客戶機(Client):一個為發送請求目的而建立連接配接的應用程式。
8.使用者代理(User agent):初始化一個請求的客戶機。它們是浏覽器、編輯器或其它使用者工具。
9.伺服器(Server):一個接受連接配接并對請求傳回資訊的應用程式。
10.源伺服器(Origin server):是一個給定資源可以在其上駐留或被建立的伺服器。
11.代理(Proxy):一個中間程式,它可以充當一個伺服器,也可以充當一個客戶機,為其它客戶機建立請求。請求是通過可能的翻譯在内部或經過傳遞到其它的伺服器中。一個代理在發送請求資訊之前,必須解釋并且如果可能重寫它。
代理經常作為通過防火牆的客戶機端的門戶,代理還可以作為一個幫助應用來通過協定處理沒有被使用者代理完成的請求。
12.網關(Gateway):一個作為其它伺服器中間媒介的伺服器。與代理不同的是,網關接受請求就好象對被請求的資源來說它就是源伺服器;送出請求的客戶機并沒有意識到它在同網關打交道。
網關經常作為通過防火牆的伺服器端的門戶,網關還可以作為一個協定翻譯器以便存取那些存儲在非HTTP系統中的資源。
13.通道(Tunnel):是作為兩個連接配接中繼的中介程式。一旦激活,通道便被認為不屬于HTTP通訊,盡管通道可能是被一個HTTP請求初始化的。當被中繼的連接配接兩端關閉時,通道便消失。當一個門戶(Portal)必須存在或中介(Intermediary)不能解釋中繼的通訊時通道被經常使用。
14.緩存(Cache):反應資訊的局域存儲。
2.3 HTTP協定的運作方式
HTTP協定是基于請求/響應範式的。一個客戶機與伺服器建立連接配接後,發送一個請求給伺服器,請求方式的格式為,統一資源辨別符、協定版本号,後邊是MIME資訊包括請求修飾符、客戶機資訊和可能的内容。伺服器接到請求後,給予相應的響應資訊,其格式為一個狀态行包括資訊的協定版本号、一個成功或錯誤的代碼,後邊是MIME資訊包括伺服器資訊、實體資訊和可能的内容。
許多HTTP通訊是由一個使用者代理初始化的并且包括一個申請在源伺服器上資源的請求。最簡單的情況可能是在使用者代理(UA)和源伺服器(O)之間通過一個單獨的連接配接來完成(見圖2-1)。
當一個或多個中介出現在請求/響應鍊中時,情況就變得複雜一些。中介由三種:代理(Proxy)、網關(Gateway)和通道(Tunnel)。
一個代理根據URI的絕對格式來接受請求,重寫全部或部分消息,通過URI的辨別把已格式化過的請求發送到伺服器。
網關是一個接收代理,作為一些其它伺服器的上層,并且如果必須的話,可以把請求翻譯給下層的伺服器協定。
一個通道作為不改變消息的兩個連接配接之間的中繼點。當通訊需要通過一個中介(例如:防火牆等)或者是中介不能識别消息的内容時,通道經常被使用。圖2-2
上面的圖2-2表明了在使用者代理(UA)和源伺服器(O)之間有三個中介(A,B和C)。一個通過整個鍊的請求或響應消息必須經過四個連接配接段。這個差別是重要的,因為一些HTTP通訊選擇可能應用于最近的連接配接、沒有通道的鄰居,應用于鍊的終點或應用于沿鍊的所有連接配接。盡管圖2-2是線性的,每個參與者都可能從事多重的、并發的通訊。例如,B可能從許多客戶機接收請求而不通過A,并且/或者不通過C把請求送到A,在同時它還可能處理A的請求。
任何針對不作為通道的彙聚可能為處理請求啟用一個内部緩存。緩存的效果是請求/響應鍊被縮短,條件是沿鍊的參與者之一具有一個緩存的響應作用于那個請求。下圖說明結果鍊,其條件是針對一個未被UA或A加緩存的請求,B有一個經過C來自O的一個前期響應的緩存拷貝。
圖2-3
在Internet上,HTTP通訊通常發生在TCP/IP連接配接之上。預設端口是TCP 80,但其它的端口也是可用的。但這并不預示着HTTP協定在Internet或其它網絡的其它協定之上才能完成。HTTP隻預示着一個可靠的傳輸。
以上簡要介紹了HTTP協定的宏觀運作方式,下面介紹一下HTTP協定的内部操作過程。
首先,簡單介紹基于HTTP協定的客戶/伺服器模式的資訊交換過程,如圖2-4所示,它分四個過程,建立連接配接、發送請求資訊、發送響應資訊、關閉連接配接。
圖2-4
在WWW中,“客戶”與“伺服器”是一個相對的概念,隻存在于一個特定的連接配接期間,即在某個連接配接中的客戶在另一個連接配接中可能作為伺服器。WWW伺服器運作時,一直在TCP80端口(WWW的預設端口)監聽,等待連接配接的出現。
下面,讨論HTTP協定下客戶/伺服器模式中資訊交換的實作。
1.建立連接配接連接配接的建立是通過申請套接字(Socket)實作的。客戶打開一個套接字并把它限制在一個端口上,如果成功,就相當于建立了一個虛拟檔案。以後就可以在該虛拟檔案上寫資料并通過網絡向外傳送。
2.發送請求
打開一個連接配接後,客戶機把請求消息送到伺服器的停留端口上,完成提出請求動作。
HTTP/1.0 請求消息的格式為:
請求消息=請求行(通用資訊|請求頭|實體頭) CRLF[實體内容]
請求 行=方法請求URL HTTP版本号 CRLF
方法=GET|HEAD|POST|擴充方法
U R L=協定名稱+宿主名+目錄與檔案名
請求行中的方法描述指定資源中應該執行的動作,常用的方法有GET、HEAD和POST。
不同的請求對象對應GET的結果是不同的,對應關系如下:
對象 GET的結果
檔案檔案的内容
程式該程式的執行結果
資料庫查詢 查詢結果
HEAD——要求伺服器查找某對象的元資訊,而不是對象本身。
POST——從客戶機向伺服器傳送資料,在要求伺服器和CGI做進一步處理時會用到POST方法。POST主要用于發送HTML文本中FORM的内容,讓CGI程式處理。
一個請求的例子為:
GEThttp://networking.zju.edu.cn/zju/index.htm HTTP/1.0
頭資訊又稱為元資訊,即資訊的資訊,利用元資訊可以實作有條件的請求或應答 。
請求頭——告訴伺服器怎樣解釋本次請求,主要包括使用者可以接受的資料類型、壓縮方法和語言等。
實體頭——實體資訊類型、長度、壓縮方法、最後一次修改時間、資料有效期等。
實體——請求或應答對象本身。
3.發送響應
伺服器在處理完客戶的請求之後,要向客戶機發送響應消息。
HTTP/1.0的響應消息格式如下:
響應消息=狀态行(通用資訊頭|響應頭|實體頭) CRLF 〔實體内容〕
狀态 行=HTTP版本号 狀态碼原因叙述
狀态碼表示響應類型
1×× 保留
2×× 表示請求成功地接收
3×× 為完成請求客戶需進一步細化請求
4×× 客戶錯誤
5×× 伺服器錯誤
響應頭的資訊包括:服務程式名,通知客戶請求的URL需要認證,請求的資源何時能使用。
4.關閉連接配接
客戶和伺服器雙方都可以通過關閉套接字來結束TCP/IP對話
第三部分、在IOS裡面的使用
在CFSocket中,TCP連接配接的建立為
csocket = CFSocketCreate(
kCFAllocatorDefault,
PF_INET,
SOCK_STREAM,
IPPROTO_TCP,
kCFSocketReadCallBack,
TCPServerConnectCallBack,
&ctx);
。。。。
sError = CFSocketConnectToAddress(csocket,address, -1);
這裡在連接配接成功時回調用TCPServerConnectCallBack方法,
那麼如果需要UDP傳輸資料的話,
csocket = CFSocketCreate(
kCFAllocatorDefault,
PF_INET,
SOCK_DGRAM,
IPPROTO_UDP,
kCFSocketConnectCallBack,
TCPServerConnectCallBack,
&ctx);
1.TCP是有連接配接的,可靠的、可控制的、無邊界的socket通信。
2.UDP是無連接配接的、不可靠的 資料報通信。但是效率高。
是以在TCP中要用回調來确定是否連接配接成功,而原生的socket連接配接成功是通過serveice發回資訊來确定。UDP無連接配接,是以不需要回調,而不管是否發送成功。
第三部分、完整的使用方法
用戶端:
導入頭檔案:
#import <sys/socket.h>
#import <netinet/in.h>
#import <arpa/inet.h>
#import <unistd.h>
1. 建立連接配接
CFSocketContext sockContext = {0, // 結構體的版本,必須為0
self,
// 一個任意指針的資料,可以用在建立時CFSocket對象相關聯。這個指針被傳遞給所有的上下文中定義的回調。
NULL, // 一個定義在上面指針中的retain的回調, 可以為NULL
NULL, NULL};
CFSocketRef _socket = (kCFAllocatorDefault,// 為新對象配置設定記憶體,可以為nil
PF_INET, // 協定族,如果為0或者負數,則預設為PF_INET
SOCK_STREAM, // 套接字類型,如果協定族為PF_INET,則它會預設為SOCK_STREAM
IPPROTO_TCP, // 套接字協定,如果協定族是PF_INET且協定是0或者負數,它會預設為IPPROTO_TCP
kCFSocketConnectCallBack, // 觸發回調函數的socket消息類型,具體見CallbackTypes
TCPServerConnectCallBack, // 上面情況下觸發的回調函數
&sockContext // 一個持有CFSocket結構資訊的對象,可以為nil
);
if (_socket != nil) {
struct sockaddr_in addr4; //IPV4
memset(&addr4, 0, sizeof(addr4));
addr4.sin_len = sizeof(addr4);
addr4.sin_family = AF_INET;
addr4.sin_port = htons(8888);
addr4.sin_addr.s_addr = inet_addr([strAddress UTF8String]); // 把字元串的位址轉換為機器可識别的網絡位址
// 把sockaddr_in結構體中的位址轉換為Data
CFDataRef address = CFDataCreate(kCFAllocatorDefault, (UInt8*)&addr4, sizeof(addr4));
CFSocketConnectToAddress(_socket, // 連接配接的socket
address, // CFDataRef類型的包含上面socket的遠端位址的對象
-1 // 連接配接逾時時間,如果為負,則不嘗試連接配接,而是把連接配接放在背景進行,如果_socket消息類型為kCFSocketConnectCallBack,将會在連接配接成功或失敗的時候在背景觸發回調函數
);
CFRunLoopRef cRunRef = CFRunLoopGetCurrent(); // 擷取目前線程的循環
// 建立一個循環,但并沒有真正加如到循環中,需要調用CFRunLoopAddSource
CFRunLoopSourceRef sourceRef = CFSocketCreateRunLoopSource(kCFAllocatorDefault,_socket, 0);
CFRunLoopAddSource(cRunRef, // 運作循環
sourceRef, // 增加的運作循環源, 它會被retain一次
kCFRunLoopCommonModes // 增加的運作循環源的模式
);
CFRelease(courceRef);
}
2. 設定回調函數
// socket回調函數的格式:
static void TCPServerConnectCallBack(CFSocketRefsocket, CFSocketCallBackType type, CFDataRef address, const void *data, void*info) {
if (data != NULL) {
// 當socket為kCFSocketConnectCallBack時,失敗時回調失敗會傳回一個錯誤代碼指針,其他情況傳回NULL
NSLog(@"連接配接失敗");
return;
}
TCPClient *client = (TCPClient *)info;
// 讀取接收的資料
[info performSlectorInBackground:@selector(readStream) withObject:nil];
}
3. 接收發送資料
// 讀取接收的資料
- (void)readStream {
char buffer[1024];
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
while (recv(CFSocketGetNative(_socket), //與本機關聯的Socket 如果已經失效傳回-1:INVALID_SOCKET
buffer, sizeof(buffer), 0)) {
NSLog(@"%@", [NSString stringWithUTF8String:buffer]);
}
}
// 發送資料
- (void)sendMessage {
NSString*stringTosend = @"你好";
char *data = [stringTosend UTF8String];
send(SFSocketGetNative(_socket), data, strlen(data) + 1, 0);
}
伺服器端:
CFSockteRef _socket;
CFWriteStreamRef outputStream = NULL;
int setupSocket() {
_socket = CFSocketCreate(kCFAllocatorDefault, PF_INET, SOCK_STREAM,IPPROTO_TCP, kCFSocketAcceptCallBack, TCPServerAcceptCallBack, NULL);
if (NULL == _socket) {
NSLog(@"Cannot create socket!");
return 0;
}
int optval = 1;
setsockopt(CFSocketGetNative(_socket), SOL_SOCKET, SO_REUSEADDR, // 允許重用本地位址和端口
(void *)&optval, sizeof(optval));
struct sockaddr_in addr4;
memset(&addr4, 0, sizeof(addr4));
addr4.sin_len = sizeof(addr4);
addr4.sin_family = AF_INET;
addr4.sin_port = htons(port);
addr4.sin_addr.s_addr = htonl(INADDR_ANY);
CFDataRef address = CFDataCreate(kCFAllocatorDefault, (UInt8*)&addr4, sizeof(addr4));
if (kCFSocketSuccess != CFSocketSetAddress(_socket, address)) {
NSLog(@"Bind to address failed!");
if (_socket)
CFRelease(_socket);
_socket = NULL;
return 0;
}
CFRunLoopRef cfRunLoop = CFRunLoopGetCurrent();
CFRunLoopSourceRef source = CFSocketCreateRunLoopSource(kCFAllocatorDefault,_socket, 0);
CFRunLoopAddSource(cfRunLoop, source, kCFRunLoopCommonModes);
CFRelease(source);
return 1;
}
// socket回調函數,同用戶端
void TCPServerAcceptCallBack(CFSocketRefsocket, CFSocketCallBackType type, CFDataRef address, const void *data, void*info) {
if (kCFSocketAcceptCallBack == type) {
// 本地套接字句柄
CFSocketNativeHandle nativeSocketHandle = *(CFSocketNativeHandle *)data;
uint8_t name[SOCK_MAXADDRLEN];
socklen_t nameLen = sizeof(name);
if (0 != getpeername(nativeSocketHandle, (struct sockaddr *)name,&nameLen)) {
NSLog(@"error");
exit(1);
}
NSLog(@"%@ connected.", inet_ntoa( ((struct sockaddr_in*)name)->sin_addr )):
CFReadStreamRef iStream;
CFWriteStreamRef oStream;
// 建立一個可讀寫的socket連接配接
CFStreamCreatePairWithSocket(kCFAllocatorDefault,nativeSocketHandle, &iStream, &oStream);
if (iStream && oStream) {
CFStreamClientContext streamContext = {0, NULL, NULL, NULL};
if (!CFReadStreamSetClient(iStream, kCFStreamEventHasBytesAvaiable,
readStream, // 回調函數,當有可讀的資料時調用
&streamContext)){
exit(1);
}
if (!CFReadStreamSetClient(iStream, kCFStreamEventCanAcceptBytes,writeStream, &streamContext)){
exit(1);
}
CFReadStreamScheduleWithRunLoop(iStream, CFRunLoopGetCurrent(),kCFRunLoopCommomModes);
CFWriteStreamScheduleWithRunLoop(wStream, CFRunLoopGetCurrent(),kCFRunLoopCommomModes);
CFReadStreamOpen(iStream);
CFWriteStreamOpen(wStream);
} else {
close(nativeSocketHandle);
}
}
}
// 讀取資料
void readStream(CFReadStreamRef stream,CFStreamEventType eventType, void *clientCallBackInfo) {
UInt8 buff[255];
CFReadStreamRead(stream, buff, 255);
printf("received: %s", buff);
}
void writeStream (CFWriteStreamRef stream, CFStreamEventTypeeventType, void *clientCallBackInfo) {
outputStream = stream;
}
main {
char *str = "nihao";
if (outputStream != NULL) {
CFWriteStreamWrite(outputStream, str, strlen(line) + 1);
} else {
NSLog(@"Cannot send data!");
}
}
// 開辟一個線程線程函數中
void runLoopInThread() {
int res = setupSocket();
if (!res) {
exit(1);
}
CFRunLoopRun(); // 運作目前線程的CFRunLoop對象