socket编程(十一)CFNetworking框架/CFSocket////CFStream属于CoreFoundation

来源:互联网 发布:交互作用数据分析 编辑:程序博客网 时间:2024/06/06 04:03

socket编程的几中方法:


C语言底层socket----BSD socket--CFSocket可以代替他的所有工作;----C语言

CFSocket;------c语言;

CFStream输入输出流socket通信;----OC的Corefoundation框架中

GCDSocket;-------第三方的框架


1.CFSocket;------c语言;

//////////////////////////////////////lb

IPV4:

//struct sockaddr_in {

//    __uint8_t sin_len;

//    sa_family_t sin_family;//iPv4地址族

//    in_port_t sin_port;     //端口号

//    struct in_addr sin_addr;//IPV4 address

//    char sin_zero[8];

//};


////////////////////////////////////lb

IPV6:

//  struct sockaddr_in6 {

//  __uint8_t sin6_len;    /* length of this struct(sa_family_t) */

//  sa_family_t sin6_family;/* AF_INET6 (sa_family_t) */

//  in_port_t sin6_port;    /* Transport layer port # (in_port_t) */

// __uint32_t sin6_flowinfo;/* IP6 flow information */

// struct in6_addr sin6_addr;/* IP6 address */

//  __uint32_t sin6_scope_id;/* scope zone index */

//                      };


////////////////////////////////////lb

getAddrinfo的参数:

//  struct addrinfo {

//         int ai_flags;/* AI_PASSIVE, AI_CANONNAME, AI_NUMERICHOST */

//        int ai_family;/* PF_xxx */地址族,

//        int ai_socktype;/* SOCK_xxx */套接字类型

//        int ai_protocol;/* 0 or IPPROTO_xxx for IPv4 and IPv6 */协议类型

//        socklen_t ai_addrlen; /* length of ai_addr */指向缓冲区的字节数

//        char *ai_canonname;/* canonical name for hostname */主机的规范化名称

//        struct sockaddr *ai_addr;/* binary address */指向sockaddr结构的指针。getaddrinfo返回的每个addrinfo结构内的ai_addr都指向一个filled-in套接字地址结构。



//        struct addrinfo *ai_next;/* next structure in linked list */指向链表中下一个addrinfo结构的指针,如果是链表的最后一个addrinfo结构,则ai_nextNULL



//    };



////////////////////////////////lb

/**转换IPV4和IPV6

 int  getaddrinfo(

 

 const char* nodename,

 

 const char* servname,

 

 const struct addrinfo* hints,

 

 struct addrinfo** res

 

 );

 

 *  nodename:节点名可以是主机名,也可以是数字地址。(IPV410进点分,或是IPV616进制)

 

 *  servname:包含十进制数的端口号或服务名如(ftp,http

 

 *  hints:是一个空指针或指向一个addrinfo结构的指针,由调用者填写关于它所想返回的信息类型的线索。

 

 *  res:存放返回addrinfo结构链表的指针,指向由一个或多个addrinfo结构体组成的链表,包含了主机的响应信息

 * 返回值:成功返回0,失败返回非零的 sockets error code

 

 */

////////////////////////////////lb


//建立连接(IPV4和IPV6兼容)

- (void)createConnect

{

    /////////////////***********************************/////////////////lb

    struct addrinfo hints, *res, *res0;

    int error, s;

    const char *cause =NULL;

    constchar *ipv4_or_ipv6_str ="60.173.247.137";//or IPv6 address string is well /ip地址

//       const char *ipv4_or_ipv6_str = "http://www.i-things.cn";

    NSUInteger port =9001;//port of connecting server/端口号

    memset(&hints,0,sizeof(hints));//分配一个hints结构体,把它清零后填写需要的字段,再调用getaddrinfo,然后遍历一个链表逐个尝试每个返回地址。

    

    hints.ai_family = PF_UNSPEC;

    hints.ai_socktype = SOCK_STREAM;

    hints.ai_flags = AI_DEFAULT;


    

    error = getaddrinfo(ipv4_or_ipv6_str, NULL, &hints, &res);//函数的返回值:成功返回0,失败返回非零的 sockets error code

    

    if (error)//非零则失败

    {

        errx(1,"%s",gai_strerror(error));

        /*NOTREACHED*/

    }

    s = -1;

    for (res = res0; res; res = res->ai_next)

    {

        s = socket(res->ai_family,

                   res->ai_socktype,

                   res->ai_protocol);//返回值:非负描述符成功,返回一个新的套接字描述,出错返回-1

        

        close(s);/////////////很关键,释放占用的socket描述//////////

        NSLog(@"ssssssss%d",s);

        //socket上下文

            CFSocketContext sockContext = {0,

                self,

                NULL,

                NULL,

                NULL};

        

            //创建socket

            m_pSocket =CFSocketCreate(kCFAllocatorDefault,

                        res->ai_family,//AF_UNSPEC不限,PF_INET,PF_INET6

                        res->ai_socktype,

                        res->ai_protocol,

                        kCFSocketConnectCallBack,

                        TCPClientConnectCallBack,  //连接后的回调函数

                        &sockContext

                           );



        if (s < 0)

        {

            cause = "socket";

            continue;

        }

        

        switch(res->ai_addr->sa_family)//IPV4还是IPV6

        {

            case AF_INET://IPV4

            {

                struct sockaddr_in *v4sa = (struct sockaddr_in *)res->ai_addr;

                v4sa->sin_port = htons(port);

                CFDataRef address = CFDataCreate(kCFAllocatorDefault, (UInt8 *)&v4sa,sizeof(v4sa));

                

                // 建立连接

                CFSocketConnectToAddress(m_pSocket,

                                         address,

                                         -1     //超时

                                         );

                

                CFRunLoopRef cRunRef = CFRunLoopGetCurrent();

                

                CFRunLoopSourceRef sourceRef =CFSocketCreateRunLoopSource(kCFAllocatorDefault,m_pSocket,0);

                CFRunLoopAddSource(cRunRef,

                                   sourceRef,

                                   kCFRunLoopCommonModes

                                   );

                CFRelease(sourceRef);

                CFRelease(address);

                NSLog(@"连接成功1");

                

            }

                break;

            case AF_INET6://IPV6

            {

                

                struct sockaddr_in6 *v6sa = (struct sockaddr_in6 *)res->ai_addr;

                v6sa->sin6_port = htons(port);

                

                CFDataRef address6 = CFDataCreate(kCFAllocatorDefault, (UInt8 *)&v6sa,sizeof(v6sa));

                       //建立连接IPV6

                CFSocketConnectToAddress(m_pSocket,

                                         address6,

                                         -1

                                         );

                

                CFRunLoopRef cRunRef = CFRunLoopGetCurrent();

                

                CFRunLoopSourceRef sourceRef =CFSocketCreateRunLoopSource(kCFAllocatorDefault,m_pSocket,0);

                CFRunLoopAddSource(cRunRef,

                                   sourceRef,

                                   kCFRunLoopCommonModes

                                   );

                CFRelease(sourceRef);

                CFRelease(address6);

                NSLog(@"连接成功2");

                

                

            }

                break;

        }

        

//        函数说明:connect()用来将参数sockfdsocket连至参数serv_addr指定的网络地址. 结构sockaddr请参考bind().参数addrlensockaddr的结构长度.

//        

//        返回值:成功则返回0,失败返回-1,错误原因存于errno.

//        

//        错误代码:

//        1EBADF参数sockfd非合法socket处理代码

//        2EFAULT参数serv_addr指针指向无法存取的内存空间

//        3ENOTSOCK参数sockfd为一文件描述词,socket.

//        4EISCONN参数sockfdsocket已是连线状态

//        5   ETIMEDOUT企图连线的操作超过限定时间仍未有响应.

//        6ENETUNREACH无法传送数据包至指定的主机.

//        7EAFNOSUPPORT sockaddr结构的sa_family不正确.

//        8EALREADY socket为不可阻断且先前的连线操作还未完成.


        if (connect(s, res->ai_addr, res->ai_addrlen) <0)//连接失败的处理

        {

            cause = "connect";

            close(s);//关闭套接字描述

            s = -1;

            continue;

        }


        

        break/* okay we got one *///连接成功就跳出循环

    }

    if (s <0)//socket描述失败

    {

        err(1,"%s", cause);

        /*NOTREACHED*/

    }

    else//socket描述成功

    {

        printf("描述成功connected");

        

    }


    freeaddrinfo(res);

    

//}

    ////////////**********************************************////////////lb

    

    

    

    //=============================================================lb

   IPV4:连接 

    //  //  创建socket上下文

    //    CFSocketContext sockContext = {0,

    //        self,

    //        NULL,

    //        NULL,

    //        NULL};

    //

    //    //创建socket

    //    m_pSocket = CFSocketCreate(kCFAllocatorDefault,

    //                               AF_UNSPEC,//AF_UNSPEC不限,PF_INET,PF_INET6

    //                               SOCK_STREAM,

    //                               IPPROTO_TCP,

    //                               kCFSocketConnectCallBack,

    //                               TCPClientConnectCallBack,   //连接后的回调函数

    //                               &sockContext

    //                               );

    

    

    //    if (m_pSocket != NULL)

    //    {

    

    

    //                struct sockaddr_in addr4;   // IPV4

    //                memset(&addr4, 0, sizeof(addr4));

    //                addr4.sin_len = sizeof(addr4);

    //                addr4.sin_family = AF_INET;//协议族

    //                addr4.sin_port = htons(9001);//端口号,高低位转换

    //                addr4.sin_addr.s_addr = inet_addr([serverIp UTF8String]);//ip地址

    //

    //                CFDataRef address = CFDataCreate(kCFAllocatorDefault, (UInt8 *)&addr4, sizeof(addr4));

    //

    //            //建立连接

    //                CFSocketConnectToAddress(m_pSocket,

    //                                         address,

    //                                         -1      //超时

    //                                         );

    //

    //                CFRunLoopRef cRunRef = CFRunLoopGetCurrent();

    //

    //                CFRunLoopSourceRef sourceRef = CFSocketCreateRunLoopSource(kCFAllocatorDefault, m_pSocket, 0);

    //                CFRunLoopAddSource(cRunRef,

    //                                   sourceRef,

    //                                   kCFRunLoopCommonModes

    //                                   );

    //                CFRelease(sourceRef);

    //                CFRelease(address);

    //===================================================

    

    

    //    }



=================================

socket网络的层级:



CFSocket属于CFNetworking;
CFNetwork位于底层,但高效地处理协议栈的操作。BSD套接字提供了一些标准对象来方便操作,如与FTP和HTTP服务器通信,解析DNS主机地址。而CFNetwork就是以BSD套接字为基础。
类似,一些cocoa类,如NSURL,使用标准网络协议与服务器通信,就是以CFNetwork为基础。
除此之外,Web Kit是一些cocoa类,显示窗口中的网络内容。而NSURL和Web Kit都是高层之上,要自行处理网络协议。

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可以拆成许多独立的API,可以独立使用,可以联合使用。
CFFTP API
CFHTTP API
CFHTTPAuthentication API
CFHost API
CFNetServices API
CFNetDiagnostics API


====================CFStream====================================
CFStream属于CoreFoundation;
CFStream装换成OC操作;

#import "ViewController.h"


@interface ViewController ()<NSStreamDelegate,UITextFieldDelegate,UITableViewDataSource,UITableViewDelegate>{

    NSInputStream *_inputStream;//对应输入流

    NSOutputStream *_outputStream;//对应输出流

}

@property (weak, nonatomic) IBOutletNSLayoutConstraint *inputViewConstraint;

@property (weak, nonatomic) IBOutletUITableView *tableView;


@property (nonatomic,strong)NSMutableArray *chatMsgs;//聊天消息数组


@end


@implementation ViewController


-(NSMutableArray *)chatMsgs{

    if (!_chatMsgs) {

        _chatMsgs = [NSMutableArrayarray];

    }

    

    return_chatMsgs;

}


- (void)viewDidLoad {

    [superviewDidLoad];

    // Do any additional setup after loading the view, typically from a nib.

  

    

    // 2.收发数据

    // 做一个聊天

    // 1.用户登录

    // 2.收发数据

    

    // 监听键盘

    [[NSNotificationCenterdefaultCenter]addObserver:selfselector:@selector(kbFrmWillChange:)name:UIKeyboardWillChangeFrameNotificationobject:nil];

}



-(void)kbFrmWillChange:(NSNotification *)noti{

    NSLog(@"%@",noti.userInfo);

    

    // 获取窗口的高度

    

    CGFloat windowH = [UIScreenmainScreen].bounds.size.height;

    

   

    

    // 键盘结束的Frm

    CGRect kbEndFrm = [noti.userInfo[UIKeyboardFrameEndUserInfoKey]CGRectValue];

     // 获取键盘结束的y

    CGFloat kbEndY = kbEndFrm.origin.y;

    

    

    self.inputViewConstraint.constant = windowH - kbEndY;

}

//NSStreamDelegate---------返回输入输出流的状态;

-(void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode{

    NSLog(@"%@",[NSThreadcurrentThread]);


//    NSStreamEventOpenCompleted = 1UL << 0,//输入输出流打开完成

//    NSStreamEventHasBytesAvailable = 1UL << 1,//有字节可读

//    NSStreamEventHasSpaceAvailable = 1UL << 2,//可以发放字节

//    NSStreamEventErrorOccurred = 1UL << 3,// 连接出现错误

//    NSStreamEventEndEncountered = 1UL << 4// 连接结束

    switch (eventCode) {

        caseNSStreamEventOpenCompleted:

            NSLog(@"输入输出流打开完成");

            break;

        caseNSStreamEventHasBytesAvailable:

            NSLog(@"有字节可读");

            [self readData];

            break;

        caseNSStreamEventHasSpaceAvailable:

            NSLog(@"可以发送字节");

            break;

        caseNSStreamEventErrorOccurred:

                        NSLog(@"连接出现错误");

            break;

        caseNSStreamEventEndEncountered:

             NSLog(@"连接结束");

            

            // 关闭输入输出流

            [_inputStream close];

            [_outputStream close];

            

            // 从主运行循环移除

            [_inputStream removeFromRunLoop:[NSRunLoopmainRunLoop]forMode:NSDefaultRunLoopMode];

            [_outputStream removeFromRunLoop:[NSRunLoopmainRunLoop]forMode:NSDefaultRunLoopMode];

            break;

        default:

            break;

    }

    

}


- (IBAction)connectToHost:(id)sender {

    // 1.建立连接

    NSString *host = @"127.0.0.1";

    int port = 12345;

    

    // 定义C语言输入输出流

    CFReadStreamRef readStream;

    CFWriteStreamRef writeStream;

    CFStreamCreatePairWithSocketToHost(NULL, (__bridgeCFStringRef)host, port, &readStream, &writeStream);

    

    // C语言的输入输出流转化成OC对象

    _inputStream = (__bridgeNSInputStream *)(readStream);

    _outputStream = (__bridgeNSOutputStream *)(writeStream);

    

    

    // 设置代理

    _inputStream.delegate =self;

    _outputStream.delegate =self;

    

    

    // 把输入输入流添加到主运行循环

    // 不添加主运行循环代理有可能不工作

    [_inputStream scheduleInRunLoop:[NSRunLoopmainRunLoop]forMode:NSDefaultRunLoopMode];

    [_outputStream scheduleInRunLoop:[NSRunLoopmainRunLoop]forMode:NSDefaultRunLoopMode];

    

    // 打开输入输出流

    [_inputStream open];

    [_outputStream open];

}



- (IBAction)loginBtnClick:(id)sender {

    

    // 登录

    // 发送用户名和密码

    // 在这里做的时候,只发用户名,密码就不用发送

    

    // 如果要登录,发送的数据格式为 "iam:zhangsan";

    // 如果要发送聊天消息,数据格式为 "msg:did you have dinner";

    

    //登录的指令

    NSString *loginStr = @"iam:zhangsan";

    

    //Str转成NSData

    NSData *data = [loginStr dataUsingEncoding:NSUTF8StringEncoding];


    //输出缓冲区数据

    [_outputStream write:data.bytes maxLength:data.length];

}


#pragma mark 读了服务器返回的数据

-(void)readData{

    

    //建立一个缓冲区可以放1024个字节

    uint8_t buf[1024];

    

   // 返回实际装的字节数,读取缓冲区数据

    NSInteger len = [_inputStream read:buf maxLength:sizeof(buf)];


    // 把字节数组转化成字符串

    NSData *data = [NSDatadataWithBytes:buflength:len];

    

    // 从服务器接收到的数据

    NSString *recStr =  [[NSStringalloc]initWithData:dataencoding:NSUTF8StringEncoding];

    

    NSLog(@"%@",recStr);

    

    [selfreloadDataWithText:recStr];

    

}


-(BOOL)textFieldShouldReturn:(UITextField *)textField{

    

    NSString *text = textField.text;

    

    NSLog(@"%@",text);

    // 聊天信息

    NSString *msgStr = [NSString stringWithFormat:@"msg:%@",text];

    

    //Str转成NSData

    NSData *data = [msgStr dataUsingEncoding:NSUTF8StringEncoding];

    

    // 刷新表格

    [selfreloadDataWithText:msgStr];

    

    // 发送数据

    [_outputStream write:data.bytes maxLength:data.length];

    

    // 发送完数据,清空textField

    textField.text = nil;

    

    return YES;

}


-(void)reloadDataWithText:(NSString *)text{

    [self.chatMsgsaddObject:text];

    

    [self.tableViewreloadData];

    

    // 数据多,应该往上滚动

    NSIndexPath *lastPath = [NSIndexPath indexPathForRow:self.chatMsgs.count -1 inSection:0];

    [self.tableView scrollToRowAtIndexPath:lastPathat ScrollPosition:UITableViewScrollPositionBottom animated:YES];

}


#pragma mark 表格的数据源


-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{

    return self.chatMsgs.count;

}



- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath

{

    static NSString *ID =@"Cell";

    UITableViewCell *cell = [tableViewdequeueReusableCellWithIdentifier:ID];

   

    cell.textLabel.text =self.chatMsgs[indexPath.row];

    

    return cell;

}


-(void)scrollViewWillBeginDragging:(UIScrollView *)scrollView{

    [self.view endEditing:YES];

}

@end





0 0
原创粉丝点击