浅学socket及iOS中的AsyncSocket框架

来源:互联网 发布:网络硬盘录像机价格4路 编辑:程序博客网 时间:2024/05/17 23:28

浅学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

0 0