浅学socket及iOS中的AsyncSocket框架
淺學(xué)socket及iOS中的AsyncSocket框架
Socket介紹:http://blog.csdn.net/xiaoweige207/article/details/6211577
Socket是TCP/IP協(xié)議應(yīng)用程序的變成接口,網(wǎng)絡(luò)層的“ip地址”可以唯一標(biāo)識(shí)網(wǎng)絡(luò)中的主機(jī),而傳輸層的“協(xié)議+端口”可以唯一標(biāo)識(shí)主機(jī)中的應(yīng)用程序(進(jìn)程)。這樣利用三元組(ip地址,協(xié)議,端口)就可以標(biāo)識(shí)網(wǎng)絡(luò)的進(jìn)程了,網(wǎng)絡(luò)中的進(jìn)程通信就可以利用這個(gè)標(biāo)志與其它進(jìn)程進(jìn)行交互。使用TCP/IP協(xié)議的應(yīng)用程序通常采用應(yīng)用編程接口:UNIX? BSD的套接字(socket)和UNIX System V的TLI(已經(jīng)被淘汰),來實(shí)現(xiàn)網(wǎng)絡(luò)進(jìn)程之間的通信。
我們可以這樣理解,因?yàn)閟ocket起源于Unix,而Unix和Linux基本操作模式是“打開(Open)à讀寫(Write/read)à關(guān)閉(Close)”,所以socket也可以理解為就是這種模式的一種實(shí)現(xiàn),socket中一些函數(shù)就是對(duì)其進(jìn)行(讀/寫IO、打開和關(guān)閉)操作的。
?
Socket基本操作
1、socket( )函數(shù):intsocket(int domain,int type,int protocol);
socket函數(shù)對(duì)應(yīng)于普通文件的打開操作,普通文件的打開操作返回一個(gè)文件的描述字,socket函數(shù)就用于創(chuàng)建一個(gè)socket的描述符。
參數(shù):
int domainà即協(xié)議域,又稱為協(xié)議族(family);
常用協(xié)議:常用的協(xié)議族有,AF_INET、AF_INET6、AF_LOCAL(或稱AF_UNIX,Unix域socket)、AF_ROUTE等等。協(xié)議族決定了socket的地址類型,在通信中必須采用對(duì)應(yīng)的地址,如AF_INET決定了要用ipv4地址(32位的)與端口號(hào)(16位的)的組合、AF_UNIX決定了要用一個(gè)絕對(duì)路徑名作為地址。
int typeà指定socket類型。
常用類型:指定socket類型。常用的socket類型有,SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等等
int protocolà指定協(xié)議。
常用協(xié)議:IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它們分別對(duì)應(yīng)TCP傳輸協(xié)議、UDP傳輸協(xié)議、STCP傳輸協(xié)議、TIPC傳輸協(xié)議。
當(dāng)我們調(diào)用socket創(chuàng)建一個(gè)socket時(shí),返回的socket描述字放在協(xié)議簇family中,在協(xié)議簇中的描述字沒有一個(gè)具體的地址,此時(shí)就要通過blind()函數(shù)給它賦值地址,或者調(diào)用conncet()、listen()讓系統(tǒng)自動(dòng)分配端口;
2、bind( )函數(shù):int bind(intsockfd, const struct sockaddr *addr, socklen_t addrlen),bind()函數(shù)的作用就是把一個(gè)地址族中的特定地址賦給socket;
參數(shù):
int sockfdà即scoket描述字,它通過socket()函數(shù)創(chuàng)建,唯一標(biāo)示一個(gè)socket.
const struct sockaddr *addrà一個(gè)const struct sockaddr *指針指向要綁定給sockfd的協(xié)議地址,這個(gè)地址結(jié)構(gòu)根據(jù)地址創(chuàng)建socket是地址協(xié)議族的不同而不同
ipv4對(duì)應(yīng)的是:?
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對(duì)應(yīng)的是:?
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域?qū)?yīng)的是:?
#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à對(duì)應(yīng)的地址的長(zhǎng)度
關(guān)于網(wǎng)絡(luò)字節(jié)序與主機(jī)字節(jié)序的詳細(xì)分析:
http://blog.csdn.net/ernest201210/article/details/8690686
http://blog.csdn.net/icedmilk/article/details/5336296
http://bbs.csdn.net/topics/60375114
服務(wù)器在調(diào)用scoket()、bind()之后就會(huì)調(diào)用listen()來監(jiān)聽這個(gè)socket,如果這是客戶端調(diào)用connect()發(fā)送出請(qǐng)求連接,服務(wù)器就會(huì)接收到請(qǐng)求
3、listen( ):int listen(int sockfd , int backlog);
參數(shù):
int sockfdà即要監(jiān)聽的socket描述字;
int backlogà相應(yīng)socket可以排隊(duì)的最大鏈接個(gè)數(shù)
4、connect( )函數(shù):intconnect(int sockfd , const struct sockaddr * addr ,socklen_t addrlen);
參數(shù):
int sockfdà客戶端的socket描述字;
const struct sockaddr * addr à服務(wù)器的socket地址
socklen_t addrlenàsocket地址的長(zhǎng)度
當(dāng)服務(wù)器開始監(jiān)聽,客戶端發(fā)送連接請(qǐng)求后,TCP服務(wù)器監(jiān)聽到請(qǐng)求就會(huì)調(diào)用accept()函數(shù)接收請(qǐng)求,網(wǎng)絡(luò)連接成功。
5、accept( )函數(shù):int accept(int sockfd, struct sockaddr *addr, socklen _t *addrlen);
參數(shù):
int sockfdà服務(wù)器的socket描述字;
struct sockaddr * addrà指向struct sockaddr * 的指針,用于返回客戶端的協(xié)議地址
socklen_t * addrlenà協(xié)議地址的長(zhǎng)度
【注】1、accept的第一個(gè)描述字參數(shù)是服務(wù)器開始調(diào)用socket就生成的一個(gè)監(jiān)聽描述字,當(dāng)accept成功建立鏈接后就返回一個(gè)已鏈接的scoket描述字,當(dāng)服務(wù)器完成服務(wù)后相應(yīng)的socket描述字就會(huì)被自動(dòng)關(guān)閉。
2、服務(wù)器通常只創(chuàng)建一個(gè)監(jiān)聽socket描述字,它在服務(wù)器的生命周期中一直存在
?
6、read( )、write( )等函數(shù)
網(wǎng)絡(luò)I/O操作有下面幾組:
read( )/write( )
recv( )/send( )
readv( )/writev( )
recvmsg( )/sendmsg( )(推薦使用)
recvfrom( )/sendto( )
這兩個(gè)函數(shù)都是通用的I/O函數(shù),可以把上面的其他函數(shù)替換成這兩個(gè)函數(shù)
它們的聲明如下:
??????#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( )函數(shù):int close(int fd);
close一個(gè)TCP socket的缺省行為時(shí)把該socket標(biāo)記為關(guān)閉,然后立刻返回到調(diào)用進(jìn)程。該描述字不能再由調(diào)用進(jìn)程使用,也就是說不能再作為read或write的第一個(gè)參數(shù)。
【注】close操作只是使用相應(yīng)scoket描述字的引用計(jì)數(shù)-1,只有當(dāng)引用計(jì)數(shù)為0的時(shí)候,才會(huì)觸發(fā)TCP客戶端向服務(wù)器發(fā)送終止連接請(qǐng)求。
【重點(diǎn)】scoket中的TCP三次握手
TCP建立連接要進(jìn)行“三次握手”,即交換三個(gè)分組,流程:
1、客戶端向服務(wù)器端發(fā)送一個(gè)SYN J(例如:客戶向一個(gè)中間人發(fā)送請(qǐng)求,告訴他去蘋果手機(jī)店找老板拿一部iPhone6,客戶已經(jīng)付款,SYN J就是取貨的憑證,中間人拿著憑證去到蘋果手機(jī)店給老板看),第一次握手;
2、服務(wù)器向客戶端響應(yīng)一個(gè)SYN K,并對(duì)SYN J進(jìn)行確認(rèn)ACK J+1(手機(jī)店老板看到憑證后告訴中間人這個(gè)憑證我收到了,但是由于手機(jī)數(shù)量有限,價(jià)值較高,為了確保安全并且客戶收到的是自己訂購的那部手機(jī),需要進(jìn)行最后確認(rèn),手機(jī)店老板再交給中間人一個(gè)憑證,讓他帶回去給客戶確認(rèn),此時(shí)手機(jī)店老板已經(jīng)做好準(zhǔn)備發(fā)貨給客戶),第二次握手;
3、客戶端在向服務(wù)器發(fā)送一個(gè)確認(rèn)ACK K+1(客戶收到確認(rèn)憑證,經(jīng)過確認(rèn)無誤后就給老板發(fā)送一個(gè)確認(rèn)信息,老板收到信息后立刻把手機(jī)發(fā)給客戶),第三次握手;
?
【重點(diǎn)】socket中TCP的四次揮手釋放連接詳解:
1、某個(gè)應(yīng)用進(jìn)程首先調(diào)用close主動(dòng)關(guān)閉連接,這時(shí)TCP發(fā)送一個(gè)FIN M(延續(xù)上面的例子,客戶收到手機(jī)以后,又找到那個(gè)中間人帶著一張憑證FIN M去手機(jī)店告訴老板手機(jī)已經(jīng)到貨,需要關(guān)閉交易)第一次揮手;
2、另一端接收到FIN M之后,執(zhí)行被動(dòng)關(guān)閉,對(duì)這個(gè)FIN進(jìn)行確認(rèn)。它的接受也作為文件結(jié)束符傳遞給應(yīng)用進(jìn)程,因?yàn)镕IN的接受意味著應(yīng)用進(jìn)程在相應(yīng)的連接上再也接收不到額外數(shù)據(jù)(老板收到FIN M的憑證,知道手機(jī)已經(jīng)到貨,將對(duì)FIN進(jìn)行確認(rèn)看客戶是否真的收到手機(jī),是否可以關(guān)閉交易,ACK M+1),第二次揮手;
3、一段時(shí)間后,接收到文件結(jié)束符的應(yīng)用進(jìn)程調(diào)用close關(guān)閉它的socket。這導(dǎo)致它的TCP也發(fā)送一個(gè)FIN N(老板經(jīng)確認(rèn)客戶確實(shí)已經(jīng)收到手機(jī)后,主動(dòng)向執(zhí)行本次操作的應(yīng)用發(fā)送close消息,告訴中間人交易可以關(guān)閉),第三次揮手;
4、接收到這個(gè)FIN的源發(fā)送端TCP對(duì)它進(jìn)行確認(rèn)(中間人為了確保兩邊的交易順利關(guān)閉,還需要再次到客戶那里讓客戶確定一下,等到客戶確認(rèn)交易確實(shí)已經(jīng)執(zhí)行結(jié)束后,中間人得任務(wù)也就完成了,本次通訊結(jié)束),第四次揮手;
?
通過上面的簡(jiǎn)單學(xué)習(xí)可以初步了解socket的主要函數(shù)、啟動(dòng)、連接、關(guān)閉和通信流程,對(duì)于深入的研究socket網(wǎng)上還有很多博客可以提供學(xué)習(xí),例如: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:
接下來要針對(duì)iOS編程中所使用到的AsyncSocket框架做基礎(chǔ)的探究。
AsyncSocket官方文檔地址:
https://github.com/robbiehanson/CocoaAsyncSocket/wiki/Reference_GCDAsyncSocket#isConnected
對(duì)于網(wǎng)絡(luò)通信,蘋果公司的標(biāo)準(zhǔn)推薦是CFNetworkC庫,但相對(duì)編程會(huì)比較繁瑣,同其他平臺(tái)一樣,蘋果也有一套socket(套接字)的開源類庫cocoa AsyncSocket用來簡(jiǎn)化CFNetwork C的操作。
官方網(wǎng)站:http://code.google.com/p/cocoaasyncsocket/
框架使用方法:
1、在項(xiàng)目中引入AsyncSocket庫,下載框架源代碼,把框架源碼導(dǎo)入到項(xiàng)目中(只需要添加RunLoop目錄中的AsyncSocket.h,AsyncSocket.m, AsyncUdpSocket.h, AsyncUdpSocket.m四個(gè)文件)。
2、在項(xiàng)目中添加CFNetwork框架。
3、在UIViewController頭文件中定義AsyncSocket對(duì)象:#import”AsyncSocket.h”
{
??? UITextField??? * textField;
??? AsyncSocket * asyncSocket;
}
宏定義IP、端口號(hào):
#defineSRV_CONNECTED 0
#defineSRV_CONNECT_SUC 1
#define SRV_CONNECT_FAIL2
#defineHOST_IP @"192.168.110.1"
#defineHOST_PORT 8080
4、在需要連接的地方使用connectToHost連接服務(wù)器:
??asyncSocket = [[AsyncSocket alloc] initWithDelegate:self];
initWithDelegate的參數(shù)必須是self,這個(gè)對(duì)象指針中的各個(gè)socket相應(yīng)都被AsyncSocket相應(yīng)。
判斷服務(wù)器接口IP和端口號(hào):(寫在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和端口號(hào)寫在宏定義里,這里做一個(gè)超時(shí)響應(yīng),連接服務(wù)器有一個(gè)超時(shí)可以設(shè)置,超時(shí)后調(diào)用)
?
5、增加socket相應(yīng)事件,delegate會(huì)把當(dāng)前對(duì)象傳遞過去,只要在當(dāng)前對(duì)象實(shí)現(xiàn)相應(yīng)方法即可;
(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)讀取數(shù)據(jù)?
-(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)遇到錯(cuò)誤時(shí)關(guān)閉連接?
-(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、關(guān)于NSData對(duì)象
socket無論收發(fā)數(shù)據(jù)都使用NSData對(duì)象,NSData帶一個(gè)id類型的data,指向數(shù)據(jù)的空間和長(zhǎng)度(length);
NSString和NSData對(duì)象的相互轉(zhuǎn)換:
NSString à NSData
NSData * xmlData=[@”testdata”dataUsingEncoding:NSUTF8StringEncoding];
NSDataàNSString
NSData *data;
NSString*str=[[NSString alloc]initWithData:dataencoding:NSUTF8StringEncoding];
?
7、發(fā)送數(shù)據(jù)
調(diào)用AsyncSocket的 writeData方法來發(fā)送數(shù)據(jù):??
-(void)writeData:(NSData *)data withTimeout:(NSTimeInterval)timeouttag:(long)tag;
Timeout一般設(shè)置-1,tag值要保證服務(wù)器和客戶端一致;
數(shù)據(jù)發(fā)送出去以后必然有專門的方法來處理發(fā)出的數(shù)據(jù),這個(gè)方法就是:
-(void)onSocket(AsyncSocket*)sock didWriteDataWithTag:(long)tag
socket發(fā)送數(shù)據(jù)是以棧的形式存放,所有數(shù)據(jù)放在一個(gè)棧中,存取時(shí)會(huì)出現(xiàn)粘包的現(xiàn)象,所以很多時(shí)候服務(wù)器在收發(fā)數(shù)據(jù)時(shí)是以先發(fā)送內(nèi)容字節(jié)長(zhǎng)度,再發(fā)送內(nèi)容的形式,得到數(shù)據(jù)時(shí)也是先得到一個(gè)長(zhǎng)度,再根據(jù)這個(gè)長(zhǎng)度在棧中讀取這個(gè)長(zhǎng)度的字節(jié)流,如果是這種情況,發(fā)送數(shù)據(jù)時(shí)只需在發(fā)送內(nèi)容前發(fā)送一個(gè)長(zhǎng)度,發(fā)送方法與發(fā)送內(nèi)容一樣。
?
8、接收socket數(shù)據(jù)
socket數(shù)據(jù)經(jīng)過處理后,如果成功得到數(shù)據(jù),就會(huì)調(diào)用方法接收數(shù)據(jù):
-(void)onSocket:(AsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
【注】收發(fā)數(shù)據(jù)中必須注意是添加 [sock readDataWithTimeout:-1 tag:0];否則接收不到數(shù)據(jù),并且這個(gè)函數(shù)在數(shù)據(jù)返回就必須調(diào)用一次。
?
9、socket的斷開連接與重連
首先我們需要了解的就是連接斷開的集中情況,在平時(shí)使用網(wǎng)絡(luò)的時(shí)候最容易出現(xiàn)的就是服務(wù)器斷開連接、用戶主動(dòng)斷開(即退出應(yīng)用程序)和不同IP同時(shí)登陸同一個(gè)賬號(hào)被迫掉線,在Demo中我們可以聲明一個(gè)枚舉類來標(biāo)注socket的斷開方式:enum{
??? SocketOfflineByServer,// 服務(wù)器掉線,默認(rèn)為0
??? SocketOfflineByUser,? // 用戶主動(dòng)cut
};
聲明一個(gè)斷開連接的方法:-(void)cutOffSocket;
在實(shí)現(xiàn)斷開連接方法的時(shí)候聲明是由用戶主動(dòng)斷開連接的,這樣在socket斷開的時(shí)候就知道是服務(wù)器出了問題還是用戶退出了連接。
類似QQ這類軟件會(huì)存在一個(gè)斷線重連的方法,如果是服務(wù)器端強(qiáng)制退出了連接,就需要立刻重連保證用戶正常在線,如果是用戶在客戶端正常退出,就不進(jìn)行操作。這就需要實(shí)現(xiàn)如下方法:
-(void)onSocketDidDisconnect:(AsyncSocket*)sock
{
??? NSLog(@"sorry the connect is failure%ld",sock.userData);
??? if (sock.userData == SocketOfflineByServer){
??????? // 服務(wù)器掉線,重連
??????? [self socketConnectHost];
??? }
??? else if (sock.userData ==SocketOfflineByUser) {
??????? // 如果由用戶斷開,不進(jìn)行重連
??????? return;
??? }
}
socket深層次的東西還有好多有待研究,我也沒有加入具體的Demo實(shí)現(xiàn),只是在瀏覽了別人的博客的基礎(chǔ)上對(duì)一些上層的東西做一下歸納收錄以備后用,底層的框架還需要在以后的工作中慢慢挖掘,如果大家有更加準(zhǔn)確有深度的socket知識(shí)還望能夠一起共享一下。
?
我的博客地址: 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
總結(jié)
以上是生活随笔為你收集整理的浅学socket及iOS中的AsyncSocket框架的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Python 括号问题
- 下一篇: 视频分享网站首页:主体框架完成