浅学socket及iOS中的AsyncSocket框架
淺學socket及iOS中的AsyncSocket框架
Socket介紹:http://blog.csdn.net/xiaoweige207/article/details/6211577
Socket是TCP/IP協議應用程序的變成接口,網絡層的“ip地址”可以唯一標識網絡中的主機,而傳輸層的“協議+端口”可以唯一標識主機中的應用程序(進程)。這樣利用三元組(ip地址,協議,端口)就可以標識網絡的進程了,網絡中的進程通信就可以利用這個標志與其它進程進行交互。使用TCP/IP協議的應用程序通常采用應用編程接口:UNIX? BSD的套接字(socket)和UNIX System V的TLI(已經被淘汰),來實現網絡進程之間的通信。
我們可以這樣理解,因為socket起源于Unix,而Unix和Linux基本操作模式是“打開(Open)à讀寫(Write/read)à關閉(Close)”,所以socket也可以理解為就是這種模式的一種實現,socket中一些函數就是對其進行(讀/寫IO、打開和關閉)操作的。
?
Socket基本操作
1、socket( )函數:intsocket(int domain,int type,int protocol);
socket函數對應于普通文件的打開操作,普通文件的打開操作返回一個文件的描述字,socket函數就用于創建一個socket的描述符。
參數:
int domainà即協議域,又稱為協議族(family);
常用協議:常用的協議族有,AF_INET、AF_INET6、AF_LOCAL(或稱AF_UNIX,Unix域socket)、AF_ROUTE等等。協議族決定了socket的地址類型,在通信中必須采用對應的地址,如AF_INET決定了要用ipv4地址(32位的)與端口號(16位的)的組合、AF_UNIX決定了要用一個絕對路徑名作為地址。
int typeà指定socket類型。
常用類型:指定socket類型。常用的socket類型有,SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等等
int protocolà指定協議。
常用協議:IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它們分別對應TCP傳輸協議、UDP傳輸協議、STCP傳輸協議、TIPC傳輸協議。
當我們調用socket創建一個socket時,返回的socket描述字放在協議簇family中,在協議簇中的描述字沒有一個具體的地址,此時就要通過blind()函數給它賦值地址,或者調用conncet()、listen()讓系統自動分配端口;
2、bind( )函數:int bind(intsockfd, const struct sockaddr *addr, socklen_t addrlen),bind()函數的作用就是把一個地址族中的特定地址賦給socket;
參數:
int sockfdà即scoket描述字,它通過socket()函數創建,唯一標示一個socket.
const struct sockaddr *addrà一個const struct sockaddr *指針指向要綁定給sockfd的協議地址,這個地址結構根據地址創建socket是地址協議族的不同而不同
ipv4對應的是:?
struct sockaddr_in {????
sa_family_t??? sin_family; /* address family: AF_INET*/????
in_port_t????? sin_port;?? /* port in network byte order */????
struct in_addr sin_addr;?? /* internet address */
};
?/*Internet address. */
struct in_addr {????
uint32_t?????? s_addr;???? /* address in network byte order */
};
ipv6對應的是:?
struct sockaddr_in6 {?????
sa_family_t???? sin6_family;?? /* AF_INET6 */?????
in_port_t?????? sin6_port;???? /* port number */?????
uint32_t??????? sin6_flowinfo; /* IPv6 flow information*/?????
struct in6_addr sin6_addr;???? /* IPv6 address */?????
uint32_t??????? sin6_scope_id; /* Scope ID (new in 2.4)*/?
};?
struct in6_addr {?????
unsigned char?? s6_addr[16];?? /* IPv6 address */
?};
Unix域對應的是:?
#define UNIX_PATH_MAX??? 108?struct sockaddr_un {????
?sa_family_t sun_family;?????????????? /* AF_UNIX */????
?char?sun_path[UNIX_PATH_MAX];? /* pathname */? };
socklen_t addrlenà對應的地址的長度
關于網絡字節序與主機字節序的詳細分析:
http://blog.csdn.net/ernest201210/article/details/8690686
http://blog.csdn.net/icedmilk/article/details/5336296
http://bbs.csdn.net/topics/60375114
服務器在調用scoket()、bind()之后就會調用listen()來監聽這個socket,如果這是客戶端調用connect()發送出請求連接,服務器就會接收到請求
3、listen( ):int listen(int sockfd , int backlog);
參數:
int sockfdà即要監聽的socket描述字;
int backlogà相應socket可以排隊的最大鏈接個數
4、connect( )函數:intconnect(int sockfd , const struct sockaddr * addr ,socklen_t addrlen);
參數:
int sockfdà客戶端的socket描述字;
const struct sockaddr * addr à服務器的socket地址
socklen_t addrlenàsocket地址的長度
當服務器開始監聽,客戶端發送連接請求后,TCP服務器監聽到請求就會調用accept()函數接收請求,網絡連接成功。
5、accept( )函數:int accept(int sockfd, struct sockaddr *addr, socklen _t *addrlen);
參數:
int sockfdà服務器的socket描述字;
struct sockaddr * addrà指向struct sockaddr * 的指針,用于返回客戶端的協議地址
socklen_t * addrlenà協議地址的長度
【注】1、accept的第一個描述字參數是服務器開始調用socket就生成的一個監聽描述字,當accept成功建立鏈接后就返回一個已鏈接的scoket描述字,當服務器完成服務后相應的socket描述字就會被自動關閉。
2、服務器通常只創建一個監聽socket描述字,它在服務器的生命周期中一直存在
?
6、read( )、write( )等函數
網絡I/O操作有下面幾組:
read( )/write( )
recv( )/send( )
readv( )/writev( )
recvmsg( )/sendmsg( )(推薦使用)
recvfrom( )/sendto( )
這兩個函數都是通用的I/O函數,可以把上面的其他函數替換成這兩個函數
它們的聲明如下:
??????#include <unistd.h>
??????ssize_t read(int fd, void *buf, size_t count);
??????ssize_t write(int fd, const void *buf, size_t count);
??????#include <sys/types.h>
??????#include <sys/socket.h>
??????ssize_t send(int sockfd, const void *buf, size_t len, int flags);
??????ssize_t recv(int sockfd, void *buf, size_t len, int flags);
??????ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
????????????????????? const struct sockaddr*dest_addr, socklen_t addrlen);
??????ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
??????????????????????? struct sockaddr*src_addr, socklen_t *addrlen);
??????ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
??????ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
7、close( )函數:int close(int fd);
close一個TCP socket的缺省行為時把該socket標記為關閉,然后立刻返回到調用進程。該描述字不能再由調用進程使用,也就是說不能再作為read或write的第一個參數。
【注】close操作只是使用相應scoket描述字的引用計數-1,只有當引用計數為0的時候,才會觸發TCP客戶端向服務器發送終止連接請求。
【重點】scoket中的TCP三次握手
TCP建立連接要進行“三次握手”,即交換三個分組,流程:
1、客戶端向服務器端發送一個SYN J(例如:客戶向一個中間人發送請求,告訴他去蘋果手機店找老板拿一部iPhone6,客戶已經付款,SYN J就是取貨的憑證,中間人拿著憑證去到蘋果手機店給老板看),第一次握手;
2、服務器向客戶端響應一個SYN K,并對SYN J進行確認ACK J+1(手機店老板看到憑證后告訴中間人這個憑證我收到了,但是由于手機數量有限,價值較高,為了確保安全并且客戶收到的是自己訂購的那部手機,需要進行最后確認,手機店老板再交給中間人一個憑證,讓他帶回去給客戶確認,此時手機店老板已經做好準備發貨給客戶),第二次握手;
3、客戶端在向服務器發送一個確認ACK K+1(客戶收到確認憑證,經過確認無誤后就給老板發送一個確認信息,老板收到信息后立刻把手機發給客戶),第三次握手;
?
【重點】socket中TCP的四次揮手釋放連接詳解:
1、某個應用進程首先調用close主動關閉連接,這時TCP發送一個FIN M(延續上面的例子,客戶收到手機以后,又找到那個中間人帶著一張憑證FIN M去手機店告訴老板手機已經到貨,需要關閉交易)第一次揮手;
2、另一端接收到FIN M之后,執行被動關閉,對這個FIN進行確認。它的接受也作為文件結束符傳遞給應用進程,因為FIN的接受意味著應用進程在相應的連接上再也接收不到額外數據(老板收到FIN M的憑證,知道手機已經到貨,將對FIN進行確認看客戶是否真的收到手機,是否可以關閉交易,ACK M+1),第二次揮手;
3、一段時間后,接收到文件結束符的應用進程調用close關閉它的socket。這導致它的TCP也發送一個FIN N(老板經確認客戶確實已經收到手機后,主動向執行本次操作的應用發送close消息,告訴中間人交易可以關閉),第三次揮手;
4、接收到這個FIN的源發送端TCP對它進行確認(中間人為了確保兩邊的交易順利關閉,還需要再次到客戶那里讓客戶確定一下,等到客戶確認交易確實已經執行結束后,中間人得任務也就完成了,本次通訊結束),第四次揮手;
?
通過上面的簡單學習可以初步了解socket的主要函數、啟動、連接、關閉和通信流程,對于深入的研究socket網上還有很多博客可以提供學習,例如:http://tony98889.blog.163.com/blog/static/114618632008919195827/
http://www.cnblogs.com/Jason-Damon/archive/2013/04/18/3029385.html
http://blog.csdn.net/wuqiuming2008/article/details/6776264
?
AsyncSocket:
接下來要針對iOS編程中所使用到的AsyncSocket框架做基礎的探究。
AsyncSocket官方文檔地址:
https://github.com/robbiehanson/CocoaAsyncSocket/wiki/Reference_GCDAsyncSocket#isConnected
對于網絡通信,蘋果公司的標準推薦是CFNetworkC庫,但相對編程會比較繁瑣,同其他平臺一樣,蘋果也有一套socket(套接字)的開源類庫cocoa AsyncSocket用來簡化CFNetwork C的操作。
官方網站:http://code.google.com/p/cocoaasyncsocket/
框架使用方法:
1、在項目中引入AsyncSocket庫,下載框架源代碼,把框架源碼導入到項目中(只需要添加RunLoop目錄中的AsyncSocket.h,AsyncSocket.m, AsyncUdpSocket.h, AsyncUdpSocket.m四個文件)。
2、在項目中添加CFNetwork框架。
3、在UIViewController頭文件中定義AsyncSocket對象:#import”AsyncSocket.h”
{
??? UITextField??? * textField;
??? AsyncSocket * asyncSocket;
}
宏定義IP、端口號:
#defineSRV_CONNECTED 0
#defineSRV_CONNECT_SUC 1
#define SRV_CONNECT_FAIL2
#defineHOST_IP @"192.168.110.1"
#defineHOST_PORT 8080
4、在需要連接的地方使用connectToHost連接服務器:
??asyncSocket = [[AsyncSocket alloc] initWithDelegate:self];
initWithDelegate的參數必須是self,這個對象指針中的各個socket相應都被AsyncSocket相應。
判斷服務器接口IP和端口號:(寫在viewDidLoad里面)
if(![asyncSocketconnectToHost:@"127.0.0.1" onPort:8888 error:&err])?
{?
?//[_asyncSocket connectToHost:hostonPort:nPort error:&error];?
[_asyncSocketconnectToHost:host onPort:nPort withTimeout:2 error:&error];? (建議使用)
???????NSLog(@"Error: %@", err);?
}
(IP和端口號寫在宏定義里,這里做一個超時響應,連接服務器有一個超時可以設置,超時后調用)
?
5、增加socket相應事件,delegate會把當前對象傳遞過去,只要在當前對象實現相應方法即可;
(1)建立連接:
-(void)onSocket:(AsyncSocket*)sock didConnectToHost:(NSString *)host port:(UInt16)port?
{?
??? NSLog(@"onScoket:%p ?did ?connecte ?to ?host:%@ ?on port:%d",sock,host,port);?
??? [sock readDataWithTimeout:1 tag:0];?
}
(2)讀取數據?
-(void)onSocket:(AsyncSocket*)sock didReadData:(NSData *)data withTag:(long)tag?
{?
??? NSString *aStr=[[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];?
??? NSLog(@"aStr==%@",aStr);?
??? [aStr release];?
??? NSData *aData=[@"Hi there"dataUsingEncoding:NSUTF8StringEncoding];?
??? [sock writeData:aData withTimeout:-1tag:1];?
??? [sock readDataWithTimeout:1 tag:0];?
}?
(3)是否加密?
-(void)onSocketDidSecure:(AsyncSocket*)sock?
{?
??? NSLog(@"onSocket:%p did go a secureline:YES",sock);?
}?
(4)遇到錯誤時關閉連接?
-(void)onSocket:(AsyncSocket*)sock willDisconnectWithError:(NSError *)err?
{?
??? NSLog(@"onSocket:%p will disconnectwith error:%@",sock,err);?
}?
(5)斷開連接?
-(void)onSocketDidDisconnect:(AsyncSocket*)sock?
{?
???NSLog(@"onSocketDidDisconnect:%p",sock);?
}?
6、關于NSData對象
socket無論收發數據都使用NSData對象,NSData帶一個id類型的data,指向數據的空間和長度(length);
NSString和NSData對象的相互轉換:
NSString à NSData
NSData * xmlData=[@”testdata”dataUsingEncoding:NSUTF8StringEncoding];
NSDataàNSString
NSData *data;
NSString*str=[[NSString alloc]initWithData:dataencoding:NSUTF8StringEncoding];
?
7、發送數據
調用AsyncSocket的 writeData方法來發送數據:??
-(void)writeData:(NSData *)data withTimeout:(NSTimeInterval)timeouttag:(long)tag;
Timeout一般設置-1,tag值要保證服務器和客戶端一致;
數據發送出去以后必然有專門的方法來處理發出的數據,這個方法就是:
-(void)onSocket(AsyncSocket*)sock didWriteDataWithTag:(long)tag
socket發送數據是以棧的形式存放,所有數據放在一個棧中,存取時會出現粘包的現象,所以很多時候服務器在收發數據時是以先發送內容字節長度,再發送內容的形式,得到數據時也是先得到一個長度,再根據這個長度在棧中讀取這個長度的字節流,如果是這種情況,發送數據時只需在發送內容前發送一個長度,發送方法與發送內容一樣。
?
8、接收socket數據
socket數據經過處理后,如果成功得到數據,就會調用方法接收數據:
-(void)onSocket:(AsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
【注】收發數據中必須注意是添加 [sock readDataWithTimeout:-1 tag:0];否則接收不到數據,并且這個函數在數據返回就必須調用一次。
?
9、socket的斷開連接與重連
首先我們需要了解的就是連接斷開的集中情況,在平時使用網絡的時候最容易出現的就是服務器斷開連接、用戶主動斷開(即退出應用程序)和不同IP同時登陸同一個賬號被迫掉線,在Demo中我們可以聲明一個枚舉類來標注socket的斷開方式:enum{
??? SocketOfflineByServer,// 服務器掉線,默認為0
??? SocketOfflineByUser,? // 用戶主動cut
};
聲明一個斷開連接的方法:-(void)cutOffSocket;
在實現斷開連接方法的時候聲明是由用戶主動斷開連接的,這樣在socket斷開的時候就知道是服務器出了問題還是用戶退出了連接。
類似QQ這類軟件會存在一個斷線重連的方法,如果是服務器端強制退出了連接,就需要立刻重連保證用戶正常在線,如果是用戶在客戶端正常退出,就不進行操作。這就需要實現如下方法:
-(void)onSocketDidDisconnect:(AsyncSocket*)sock
{
??? NSLog(@"sorry the connect is failure%ld",sock.userData);
??? if (sock.userData == SocketOfflineByServer){
??????? // 服務器掉線,重連
??????? [self socketConnectHost];
??? }
??? else if (sock.userData ==SocketOfflineByUser) {
??????? // 如果由用戶斷開,不進行重連
??????? return;
??? }
}
socket深層次的東西還有好多有待研究,我也沒有加入具體的Demo實現,只是在瀏覽了別人的博客的基礎上對一些上層的東西做一下歸納收錄以備后用,底層的框架還需要在以后的工作中慢慢挖掘,如果大家有更加準確有深度的socket知識還望能夠一起共享一下。
?
我的博客地址: http://blog.csdn.net/qadq211314
文檔參考:http://my.oschina.net/amoyai/blog/91694
http://my.oschina.net/u/937568/blog/127082
http://blog.csdn.net/zltianhen/article/details/6560322
http://blog.csdn.net/jeepxiaozi/article/details/9154925
http://my.oschina.net/joanfen/blog/287238
總結
以上是生活随笔為你收集整理的浅学socket及iOS中的AsyncSocket框架的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Python 括号问题
- 下一篇: 视频分享网站首页:主体框架完成