首页 > 代码库 > CFSocket相关(未完待续)

CFSocket相关(未完待续)

CFSocket相关

今天想深入了解下CFNetworking,然后就去看了Apple的API,又结合了网络上前人的经验,发现了这么一段话。


    Sockets 是网络通讯的最基本一层。一个 socket 起的作用类似与一个电话线接口,它可以使你连接到另一个 socket 上(不论是本地的还是网络另一端的),并且向那个 socket 发送数据。

    最常见的 socket 抽象概念就是 BSD sockets,而 CFSocket 则是 BSD sockets 的抽象。CFSocket 中包含了少数开销,它几乎可以提供 BSD sockets 所具有的一切功能,并且把 socket 集成进一个“运行循环”当中。CFSocket 并不仅仅限于基于流的 sockets (比如 TCP),它可以处理任何类型的 socket。

    你可以利用 CFSocketCreate 功能从头开始创建一个 CFSocket 对象,或者利用 CFSocketCreateWithNative 函数从 BSD socket 创建。然后,需要利用函数 CFSocketCreateRunLoopSource 创建一个“运行循环”源,并利用函数CFRunLoopAddSource 把它加入一个“运行循环”。这样不论 CFSocket 对象是否接收到信息, CFSocket 回调函数都可以运行。

    很清晰明了的解释了CFSocket的使用,具体的可以百度下代码。

主要函数:

第一步:创建

 CFSocketRef  CFSocketCreate(

CFAllocatorRef allocator, //内存分配类型一般为默认KCFAllocatorDefault

  SInt32 protocolFamily, //协议族,一般为Ipv4:PF_INET,(Ipv6,PF_INET6)

SInt32 socketType,     //套接字类型TCP:SOCK_STREAM

                    UDP:SOCK_DGRAM

SInt32 protocol,        //套接字协议TCP:IPPROTO_TCP

                      UDP:IPPROTO_UDP;

CFOptionFlags callBackTypes, //回调事件触发类型

                       Enum CFSocketCallBACKType{

                            KCFSocketNoCallBack = 0,

                            KCFSocketReadCallBack =1,

                            KCFSocketAcceptCallBack = 2,(常用)

                            KCFSocketDtatCallBack = 3,

                            KCFSocketConnectCallBack = 4,

                            KCFSocketWriteCallBack = 8

                            }

CFSocketCallBack callout,    //   触发时调用的函数

Const CFSocketContext *context //  用户定义数据指针

)

 

假设_socket = CFSocketCreate(….);

 

第二步:初始化

int yes = 1 ;

setsocketopt

CFSocketGetNative(_socket),//返回系统原生套接字,补齐缺省

SOL_SOCKET,

SO_REUSEADDR,

(void*)&yes,

sizeof(yes)

)              //对socket进行定义设置

 

第三步:地址

uint16_t port = 12345;

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);

                  CFData Ref  address =CFDataCreate(

                  kCFAllocatorDefault,

                  (UInt8 *)& addr4,

                  sizeof (addr4),

)

 

int rst = CFSocketSetAddress(_socket ,&addr4); 

//将设置数据设入socket

If ( rst != KCFSocketSuccess ){…}

 

第四步:执行       

CFRunLoopRef cfrl  = CFRunLoopGetCurrent();

//获取当前的运行循环                 

CFRunLoopSourceRef sourceRef = CFSoceketCreateRunLoopSource(KCFAllocatorDefault, _socket,0);//创建一个运行循环源对象

CFRunLoopSource(cfrl , sourceRef, KCFRunLoopCommonModes);//以该对象运行到当前运行循环中

CFRelease(sourceRef);

 

服务端响应

CFSocketCallBack callout,      // 触发时调用的函数,该函数会在接收到客户端请求连接时触发:

ServerAcceptCallBack(       //名字可以任意取,但参数是固定的

                    CFSoceketRef        socket     ,

                    CFSocketCallBackType callbacktype,

                    CFDataRef           address,

                    const void * data,      //与回调函数有关的特殊数据指针,

对于接受连接请求事件,这个指针指向该socket的句柄,

对于连接事件,则指向Sint32类型的错误代码

                                            

                    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_in*)name,&namelen)) //获取地址

{

 exit(1)

}

Printf(“%s connected\n”,inet_ntoa((struct sockaddr_in *)name)->sin_addr);

//////////////////////

 

CFReadStreamRef  iStream;

CFWriteStreamRef  oStream;

CFStreamCreatePairWithSocket(       // 创建一个可读写的socket连接

kCFAllocatorDefault

nativeSocketHandle,

&iStream,

&oStream);

     If(iStream && oStream){

     CFStreamClinetContext streamCtxt = {0,NULL, NULL, NULL, NULL};

     If(!CFReadStreamSetClient(

iStream,

kCFStreamEventHasBytesAvailable //有可用数据则执行

readStream,                      //设置读取时候的函数

&steamCtxt))

      {exit(1);}

 

      If(!CFWriteStreamSetClient(       //为流指定一个在运行循环中接受回调的客户端

oStream,

kCFStreamEventCanAcceptBytes, //输出流准备完毕,可输出

writeStream,                    //设置写入时候的函数

&steamCtxt))

      {exit(1);}

 

      

     }

}

}

 

读取流操作(触发式,被动技能)

readStream(CFReadStreamRef stream,CFStreamEventType eventType, void *client CallBackInfo)

{

  UInt8 buff[255];

  CFReadStreamRead(stream,buff,255); //将输入流中数据存入buff

  Printf(“received %s”,buff);

}

CFWriteStreamRef outputStream = NULL; //输出流

写入流操作(仍然被动技能,在输出流准备好的时候调用)

writeStream (CFWriteStreamRef stream, CFStreamEventType eventType, void *clientCallBackInfo)

{

 outputStream = stream;   //输出流被指定

}

 

 

//主动输出,在输出流准备好之后才能调用

FucForWrite()

{

    UInt8 buff[] = “Hunter21,this is Overlord”;

    If(outputStream != NULL)

    {

      CFWriteStreamWrite(outputStream,buff,strlen(buff)+1);

    }

}

------------------------------------------------------------------------------------------

ios编程笔记:CFSocket(客户端)

CFSocketRef _socket;

-(void)Connect

{

    //////////////////////创建套接字//////////////

    CFSocketContext CTX = {0,NULL,NULL,NULL,NULL};

    _socket = CFSocketCreate(

                                kCFAllocatorDefault,

                                PF_INET,

                                SOCK_STREAM,

                                IPPROTO_TCP,

                                kCFSocketConnectCallBack,     // 类型,表示连接时调用

                                ServerConnectCallBack,    // 调用的函数

             )

 

      ////////////////////////////设置地址///////////////////

   NSString *serverAddr = @"192.168.0.110";

   struct   sockaddr_in  addr

   memset(&addr , 0,sizeof(addr));

   addr.sin_len = sizeof(addr);

   addr.sin_family = AF_INET;

   addr.sin_port = htons(12345);

   addr.sin_addr.s_addr = inet_addr([serverAddr  UTF8String]);

 

    CFDataRef address = CFDataCreate(

                                        kCFAllocatorDefault,

                                         (UInt8*)&addr,

                                         sizeof(addr));

 

     /////////////////////////////执行连接/////////////////////

 

    CFSocketConnectToAddress(_socket,address,-1);

    CFRunLoopRef cfrl = CFRunLoopGetCurrent();   // 获取当前运行循环

    CFRunLoopSourceRef  source = CFSocketCreateRunLoopSource(kCFAllocatorDefault,_socket,0);//定义循环对象

    CFRunLoopAddSource(cfrl,source,kCFRunLoopCommonModes); //将循环对象加入当前循环中

    CFRelease(source);

}

 

static void ServerConnectCallBack(

            CFSocketRef socket,

            CFSocketCallBackType type,

            CFDataRef address,

               const void *data,

         void * info)

{

    if(data != NULL)

   {

            printf("connect").//服务器那边已经提过,连接事件时该指针用于存放报错

   }

   else

   {

          printf("connect success");

    }

}

///////////////////监听来自服务器的信息///////////////////

-(void)ReadStream

{

       char buffer[255];

       while(recvCFSocektGetNative(_socket),buffer,sizeof(buffer),0))

       {

             printf(buffer);

       } 

 

}

/////////////////////////发送信息给服务器////////////////////////

- (void) sendMessage

{
 NSString *stringToSend = @"Overlord,this is Hunter21";
 const char *data = http://www.mamicode.com/[stringToSend UTF8String];
 send(CFSocketGetNative(_socket), data, strlen(data) + 1, 0);
 }



有关CFSocket可以看下面的内容:

首先看张图:

技术分享
这是MAC OS X系统中CFNetwork和其余软件层的结构图。
CFNetwork位于底层,但高效地处理协议栈的操作。BSD套接字提供了一些标准对象来方便操作,如与FTP和HTTP服务器通信,解析DNS主机地址。而CFNetwork就是以BSD套接字为基础。
类似,一些cocoa类,如NSURL,使用标准网络协议与服务器通信,就是以CFNetwork为基础。
除此之外,Web Kit是一些cocoa类,显示窗口中的网络内容。而NSURL和Web Kit都是高层之上,要自行处理网络协议。因此,结构如上图。

下图是CFNetwork框架与Core Foundation框架的结构图:
技术分享
 CFSocket API和CFStream API是CFNetwork的基础。套接字是网络通讯的基础,套接字可以连接到网络或是本地的另一个套接字,并允许数据传送。最通常的套接字抽象就是BSD Socket。CFSocket又是BSD Socket的抽象。CFSocket几乎包含BSD Socket的所有功能,而且将Socket融入run-loop中。CFSocket可以处理任何socket,甚至stream-based socket。
CFStream API提供了轻松的与设备无关的读写数据的能力。你可以为内存,文件,网络(使用套接字)的数据建立stream,可以使用stream而不必立即把所有数据都写入到内存中。
stream,流,是一个在搭建的通讯通道里连续传送的字节序列。steam是单向的,所有有必要建立input(read) stream和output(write) stream。除了基于文件的stream,否则,stream中的数据一经取出消耗,就无法找到。
CFStream就是对这些stream的抽象,并提供两种CFType类型:CFReadStream 和 CFWriteStream,他们都符合Core Foundation API的规范。
由图可以看出,CFStream是基于CFSocket,而且CFStream是CFFTP和CFHTTP的基础。而CFStream却不是CFNetwork的一部分,而是Core Foundation的一部分。

CFNetwork API简介:
CFNetwork API可以拆成许多独立的API,可以独立使用,可以联合使用。
CFFTP API
CFHTTP API
CFHTTPAuthentication API
CFHost API
CFNetServices API
CFNetDiagnostics API


CFStream API

  对流的读写操作使我们可以以一种设备无关的方式在各种媒体之间交换数据。你可以为内存、文件或者网络(通过sockets)里面的数据创建流。另外在操作流的时候,所有数据可以分次加载。

  数据流本质上是在通信通道中串行传输的一个字节序列,它是单向的,所以如果需要双向传输的话必须操作一个输入流(读操作)和一个输出流(写操作)。除了基于文件的流以外,其他流都是不可搜索的,也就是说:在流数据被提供或者接收之后,就不能再从这个流当中获取数据了。

  CFStream API 用两个新的 CFType 对象提供了对这些流的一个抽象:CFReadStream 和 CFWriteStream。两个类型的流都遵循常见的核心基础 API 惯例。有关核心基础类型的更多信息,请参考设计概念

  CFStream 的构建基于 CFSocket,同时也是 CFHTTP 和 CFFTP 的基础。在图 1-2 中你可以看到,尽管CFStream 并不是 CFNetwork的正式成员,它却是几乎所有 CFNetwork 成员的基础。

    你几乎可以用操作 UNIX 文件描述符的方式对流进行读写操作。首先,实例化流对象的时候需要指定流的类型(内存、文件或者socket)并且设置任何一个可选项。然后,打开流并可以进行任意次的读写操作。当流还存在的时候,你可以通过流的属性获取有关它的信息。流属性包括有关流的任何信息,比如它的数据源或者目标,这些都不属于被读写的实际数据范畴之内。当你不再需要一个流的时候,需要关闭并把它丢弃。

    CFStream 的函数如果不能进行至少一个字节数据的读写操作的话,它们可能会暂停或者阻塞当前的进程。为了避免在阻塞的时候从一个流读数据或者向一个流写数据,可以使用这些函数的异步操作版本,并且把有关这个流的操作放入一个循环当中。当可以从流中读写数据的时候,你的回调函数就会被调用。

    另外,CFStream 还内置了对安全 Sockets 层 (SSL) 协议的支持。你可以建立一个包含流的 SSL 信息的字典,其中的信息包括需要的安全级别或者自签署的认证。然后把这些信息当作 kCFStreamPropertySSLSettings 属性传递给流,这样一个流就被转换成了一个 SSL 流。

    要创建一个客户定制的 CFStream 是不可能的。比如,如果你想要对客户数据库文件当中的对象进行数据流操作,那么仅仅希望通过创建具有自己风格的 CFStream 对象是办不到这一点的,而只有通过定制 NSStream 的子类(利用 Objective-C)才可以做到。由于 NSStream 对象可以很容易的被转换为 CFStream 对象,所以你创建的 NSStream 子类可以被用在任何需要 CFStream 的地方。任何有关 NSStream 所属类的信息,请参考Cocoa 流编程指南

"有关流的操作"一章描述了如何进行读写流操作。


既然上面有提到Socket的使用是放在runloop里面,那么就需要了解下runloop和多线程的知识。

   iPhone中的线程应用并不是无节制的,官方给出的资料显示iPhone OS下的主线程的堆栈大小是1M,第二个线程开始都是512KB。并且该值不能通过编译器开关或线程API函数来更改。只有主线程有直接修改UI的能力。

一.线程概述

有些程序是一条直线,起点到终点;有些程序是一个圆,不断循环,直到将它切断。直线的如简单的Hello World,运行打印完,它的生命周期便结束了,像昙花一现那样;圆如操作系统,一直运行直到你关机。 
一个运行着的程序就是一个进程或者叫做一个任务,一个进程至少包含一个线程,线程就是程序的执行流。Mac和iOS中的程序启动,创建好一个进程的同时, 一个线程便开始运行,这个线程叫主线程。主线程在程序中的地位和其他线程不同,它是其他线程最终的父线程,且所有界面的显示操作即AppKit或 UIKit的操作必须在主线程进行。 
系统中的每一个进程都有自己独立的虚拟内存空间,而同一个进程中的多个线程则共用进程的内存空间。每创建一个新的线程,都需要一些内存(如每个线程有自己的Stack空间)和消耗一定的CPU时间。另外当多个线程对同一个资源出现争夺的时候需要注意线程安全问题。

 

二.创建线程

创建一个新的线程就是给进程增加了一个执行流,执行流总得有要执行的代码吧,所以新建一个线程需要提供一个函数或者方法作为线程的入口。

1.使用NSThread

NSThread提供了创建线程的途径,还可以提供了检测当前线程是否是主线程的方法。 使用NSThread创建一个新的线程有两种方式:

  • 1.创建一个NSThread的对象,调用其start方法。对于这种方式的NSThread对象的创建,可以使用一个目标对象的方法初始化一个NSThread对象,或者创建一个继承NSThread类的子类,实现其main方法,然后在直接创建这个子类的对象。
  • 2.使用 detachNewThreadSelector:toTarget:withObject:这个类方法创建一个线程,这个比较直接了,直接使用目标对象的方法作为线程启动入口。

2.使用NSObject

其实NSObject直接就加入了多线程的支持,允许对象的某个方法在后台运行。如:

  1. [myObj performSelectorInBackground:@selector(doSomething) withObject:nil]; 

3.POSIX Thread

由于Mac和iOS都是基于Darwin系统,Darwin系统的XUN内核,是基于Mach和BSD的,继承了BSD的POSIX接口,所以可以直接使用POSIX线程的相关接口来使用线程。

创建线程的接口为 pthread_create,当然在创建之前可以通过相关函数设置好线程的属性。以下为POSIX线程使用简单的例子。

  1. // //  main.c //  pthread // //  Created by Lu Kejin on 1/27/12. //  Copyright (c) 2012 Taobao.com. Al 

 

三.多线程进阶

NSOperation&NSOperationQueue

很多时候我们使用多线程,需要控制线程的并发数,毕竟线程也是消耗系统资源的,当程序中同时运行的线程过多时,系统必然变慢。 所以很多时候我们会控制同时运行线程的数目。

NSOperation可以封装我们的操作,然后将创建好的NSOperation对象放到NSOperationQueue中,OperationQueue便开始启动新的线程去执行队列中的操作,OperationQueue的并发度是可以通过如下方式进行设置:

  1. - (void)setMaxConcurrentOperationCount:(NSInteger)count 

GCD

GCD是Grand Central Dispatch的缩写,是一系列的BSD层面的接口,在Mac 10.6 和iOS4.0以后才引入的,且现在NSOperation和NSOperationQueue的多线程的实现就是基于GCD的。目前这个特性也被移植到 FreeBSD上了,可以查看libdispatch这个开源项目。

比如一个在UIImageView中显示一个比较大的图片

  1. dispatch_queue_t imageDownloadQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_async(imageDownloa 

当然,GCD除了处理多线程外还有很多非常好的功能,其建立在强大的kqueue之上,效率也能够得到保障。

 

四.线程间通信

线程间通信和进程间通信从本质上讲是相似的。线程间通信就是在进程内的两个执行流之间进行数据的传递,就像两条并行的河流之间挖出了一道单向流动长沟,使得一条河流中的水可以流入另一条河流,物质得到了传递。

1.performSelect On The Thread

框架为我们提供了强制在某个线程中执行方法的途径,如果两个非主线程的线程需要相互间通信,可以先将自己的当前线程对象注册到某个全局的对象中去,这样相 互之间就可以获取对方的线程对象,然后就可以使用下面的方法进行线程间的通信了,由于主线程比较特殊,所以框架直接提供了在出线程执行的方法。

  1. @interface NSObject (NSThreadPerformAdditions) - (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUnti 

       2.Mach Port 
在苹果的Thread Programming Guide的Run Pool一节的Configuring a Port-Based Input Source 这一段中就有使用Mach Port进行线程间通信的例子。 其实质就是父线程创建一个NSMachPort对象,在创建子线程的时候以参数的方式将其传递给子线程,这样子线程中就可以向这个传过来的 NSMachPort对象发送消息,如果想让父线程也可以向子线程发消息的话,那么子线程可以先向父线程发个特殊的消息,传过来的是自己创建的另一个 NSMachPort对象,这样父线程便持有了子线程创建的port对象了,可以向这个子线程的port对象发送消息了。

当然各自的port对象需要设置delegate以及schdule到自己所在线程的RunLoop中,这样来了消息之后,处理port消息的delegate方法会被调用,你就可以自己处理消息了。

 

五.RunLoop

RunLoop从字面上看是运行循环的意思,这一点也不错,它确实就是一个循环的概念,或者准确的说是线程中的循环。 本文一开始就提到有些程序是一个圈,这个圈本质上就是这里的所谓的RunLoop,就是一个循环,只是这个循环里加入很多特性。 
首先循环体的开始需要检测是否有需要处理的事件,如果有则去处理,如果没有则进入睡眠以节省CPU时间。 所以重点便是这个需要处理的事件,在RunLoop中,需要处理的事件分两类,一种是输入源,一种是定时器,定时器好理解就是那些需要定时执行的操作,输 入源分三类:performSelector源,基于端口(Mach port)的源,以及自定义的源。编程的时候可以添加自己的源。RunLoop还有一个观察者Observer的概念,可以往RunLoop中加入自己的 观察者以便监控着RunLoop的运行过程,CFRunLoop.h中定义了所有观察者的类型:

  1. enum CFRunLoopActivity { kCFRunLoopEntry = (1 << 0), kCFRunLoopBeforeTimers = (1 << 1), kCFRunLoopBeforeSources = ( 

如果你使用过select系统调用写过程序你便可以快速的理解runloop事件源的概念,本质上讲事件源的机制和select一样是一种多路复用IO的 实现,在一个线程中我们需要做的事情并不单一,如需要处理定时钟事件,需要处理用户的触控事件,需要接受网络远端发过来的数据,将这些需要做的事情统统注 册到事件源中,每一次循环的开始便去检查这些事件源是否有需要处理的数据,有的话则去处理。 拿具体的应用举个例子,NSURLConnection网络数据请求,默认是异步的方式,其实现原理就是创建之后将其作为事件源加入到当前的 RunLoop,而等待网络响应以及网络数据接受的过程则在一个新创建的独立的线程中完成,当这个线程处理到某个阶段的时候比如得到对方的响应或者接受完 了网络数据之后便通知之前的线程去执行其相关的delegate方法。所以在Cocoa中经常看到scheduleInRunLoop:forMode: 这样的方法,这个便是将其加入到事件源中,当检测到某个事件发生的时候,相关的delegate方法便被调用。对于CoreFoundation这一层而 言,通常的模式是创建输入源,然后将输入源通过CFRunLoopAddSource函数加入到RunLoop中,相关事件发生后,相关的回调函数会被调 用。如CFSocket的使用。 另外RunLoop中还有一个运行模式的概念,每一个运行循环必然运行在某个模式下,而模式的存在是为了过滤事件源和观察者的,只有那些和当前 RunLoop运行模式一致的事件源和观察者才会被激活。

每一个线程都有其对应的RunLoop,但是默认非主线程的RunLoop是没有运行的,需要为RunLoop添加至少一个事件源,然后去run它。一般情况下我们是没有必要去启用线程的RunLoop的,除非你在一个单独的线程中需要长久的检测某个事件。



CFSocket相关(未完待续)