美文网首页iOS集合
iOS 网络(三)-Socket

iOS 网络(三)-Socket

作者: 搬砖的crystal | 来源:发表于2021-10-15 16:48 被阅读0次

    一、Socket原理

    1.套接字(Socket)概念

    套接字(Socket)是通信的基石,是支持TCP/IP协议的网络通信的基本操作单元。它是网络通信过程中端点的抽象表示,包含进行网络通信必须的五种信息:连接使用的协议,本地主机的IP地址,本地进程的协议端口,远地主机的IP地址,远地进程的协议端口

    应用层通过传输层进行数据通信时,TCP会遇到同时为多个应用程序进程提供并发服务的问题。多个TCP连接或多个应用程序进程可能需要通过同一个TCP协议端口传输数据。为了区别不同的应用程序进程和连接,许多计算机操作系统为应用程序与TCP/IP协议交互提供了套接字(Socket)接口。应用层可以和传输层通过Socket接口,区分来自不同应用程序进程或网络连接的通信,实现数据传输的并发服务。


    2.建立Socket连接

    建立Socket连接至少需要一对套接字,其中一个运行于客户端,称为ClientSocket,另一个运行于服务器端,称为ServerSocket。

    套接字之间的连接过程分为三个步骤:服务器监听,客户端请求,连接确认。



    服务器监听:服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态,等待客户端的连接请求。

    客户端请求:指客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就向服务器端套接字提出连接请求。

    连接确认:当服务器端套接字监听到或者说接收到客户端套接字的连接请求时,就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发给客户端,一旦客户端确认了此描述,双方就正式建立连接。而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。

    Socket可以支持不同的传输层协议(TCP/UDP),当使用TCP协议进行连接是,该socket连接就是一个TCP连接,UDP同理。

    二、使用

    Core Foundation提供操作socket的方法
    CFNetwork、CFScoket、BSD Scoket。

    CFScoket

    OS官方给出的CFSocket,它是基于BSD Socket进行抽象和封装,CFSocket中包含了少数开销,它几乎可以提供BSD sockets 所具有的一切功能,并且把socket集成进一个“运行循环”当中。CFSocket并不仅仅限于流的sockets(比如TCP),它可以处理任何类型的socket。

    //连接服务器
    -(void)connectServer{
        if (!_socketRef) {
            
            // 创建socket关联的上下文信息
            /*
             typedef struct {
             CFIndex    version; 版本号, 必须为0
             void *    info; 一个指向任意程序定义数据的指针,可以在CFSocket对象刚创建的时候与之关联,被传递给所有在上下文中回调
             const void *(*retain)(const void *info); info 指针中的retain回调,可以为NULL
             void    (*release)(const void *info); info指针中的release回调,可以为NULL
             CFStringRef    (*copyDescription)(const void *info); 回调描述,可以n为NULL
             } CFSocketContext;
             */
            
            CFSocketContext sockContext = {0, (__bridge void *)(self), NULL, NULL, NULL};
            //创建一个socket
            _socketRef = CFSocketCreate(kCFAllocatorDefault, PF_INET, SOCK_STREAM, IPPROTO_TCF, kCFSocketConnectCallBack, ServerConnectCallBack, &sockContext);
            
            //创建sockadd_in的结构体,改结构体作为socket的地址,IPV6需要改参数
            
            //sockaddr_in
            // sin_len;  长度
            //sin_family;协议簇, 用AF_INET -> 互联网络, TCP,UDP 等等
            //sin_port; 端口号(使用网络字节顺序)htons:将主机的无符号短整形数转成网络字节顺序
            //in_addr sin_addr; 存储IP地址, inet_addr()的功能是将一个点分十进制的IP转换成一个长整型数(u_long类型),若字符串有效则将字符串转换为32位二进制网络字节序的IPV4地址, 否则为IMADDR_NONE
            //sin_zero[8]; 让sockaddr与sockaddr_in 两个数据结构保持大小相同而保留的空字节,无需处理
            
            struct sockaddr_in Socketaddr;
            //memset: 将addr中所有字节用0替代并返回addr,作用是一段内存块中填充某个给定的值,它是对较大的结构体或数组进行清零操作的一种最快方法
            memset(&Socketaddr, 0, sizeof(Socketaddr));
            Socketaddr.sin_len = sizeof(Socketaddr);
            Socketaddr.sin_family = AF_INET;
            Socketaddr.sin_port = htons(22222);//服务器绑定的端口号
            Socketaddr.sin_addr.s_addr = inet_addr("服务器绑定的IP地址");
            
            //将地址转化为CFDataRef
            CFDataRef dataRef = CFDataCreate(kCFAllocatorDefault, (UInt8 *)&Socketaddr, sizeof(Socketaddr));
            
            //连接
            //CFSocketError    CFSocketConnectToAddress(CFSocketRef s, CFDataRef address, CFTimeInterval timeout);
            //第一个参数  连接的socket
            //第二个参数  连接的socket的包含的地址参数
            //第三个参数 连接超时时间,如果为负,则不尝试连接,而是把连接放在后台进行,如果_socket消息类型为kCFSocketConnectCallBack,将会在连接成功或失败的时候在后台触发回调函数
            CFSocketConnectToAddress(_socketRef, dataRef, -1);
            
            //加入循环中
            //获取当前线程的runLoop
            CFRunLoopRef runloopRef = CFRunLoopGetCurrent();
            //把socket包装成CFRunLoopSource, 最后一个参数是指有多个runloopsource通过一个runloop时候顺序,如果只有一个source 通常为0
            CFRunLoopSourceRef sourceRef = CFSocketCreateRunLoopSource(kCFAllocatorDefault, _socketRef, 0);
            
            //加入运行循环
            //第一个参数:运行循环管
            //第二个参数: 增加的运行循环源, 它会被retain一次
            //第三个参数:用什么模式把source加入到run loop里面,使用kCFRunLoopCommonModes可以监视所有通常模式添加source
            CFRunLoopAddSource(runloopRef, sourceRef, kCFRunLoopCommonModes);
            
            //之前被retain一次,这边要释放掉
            CFRelease(sourceRef);
        }
    }
    
    /**
     回调函数
    
     @param s socket对象
     @param callbackType 这个socket对象的活动类型
     @param address socket对象连接的远程地址,CFData对象对应的是socket对象中的protocol family (struct sockaddr_in 或者 struct sockaddr_in6), 除了type 类型 为kCFsocketAcceptCallBack 和kCFSocketDataCallBack ,否则这个值通常是NULL
     @param data 跟回调类型相关的数据指针
     kCFSocketConnectCallBack : 如果失败了, 它指向的就是SINT32的错误代码
     kCFSocketAcceptCallBack : 它指向的就是CFSocketNativeHandle
     kCFSocketDataCallBack : 它指向的就是将要进来的Data
     其他情况就是NULL
    
     @param info 与socket相关的自定义的任意数据
     
     */
    void ServerConnectCallBack (CFSocketRef s, CFSocketCallBackType callbackType, CFDataRef address, const void *data, void *info){
        //判断是不是NULL
        if (data != NULL){
            printf("----->>>>>>连接失败\n");
        }else{
            printf("----->>>>>>连接成功\n");
        }
    }
    
    //发送消息
    - (void)sendMessage {
        if (!_socketRef) {
            [[[UIAlertView alloc] initWithTitle:@"对不起" message:@"请先连接服务器" delegate:self cancelButtonTitle:@"确定" otherButtonTitles: nil] show];
            return;
        }
        NSString *stringTosend = @"sendMessage";
    
        const char* data       = [stringTosend UTF8String];
        /** 成功则返回实际传送出去的字符数, 失败返回-1. 错误原因存于errno*/
        long sendData          = send(CFSocketGetNative(_socketRef), data, strlen(data) + 1, 0);
        
        if (sendData < 0) {
            perror("send");
        }
    }
    
    //读取数据
    - (void)_readStreamData{
        //定义一个字符型变量
        char buffer[512];
        /*
         int recv(SOCKET s, char FAR *buf, int len, int flags);
         
         不论是客户还是服务器应用程序都用recv函数从TCP连接的另一端接收数据
         1. 第一个参数指定接收端套接字描述符
         2.第二个参数指明一个缓冲区,改缓冲区用来存放recv函数接受到的数据
         3. 第三个参数指明buf的长度
         4.第四个参数一般置0
         
         */
        long readData;
        //若无错误发生,recv() 返回读入的字节数。如果连接已终止,返回0 如果发生错误,返回-1, 应用程序可通过perror() 获取相应错误信息
        while ((readData = recv(CFSocketGetNative(_socketRef), buffer, sizeof(buffer), 0))) {
            NSString *content = [[NSString alloc] initWithBytes:buffer length:readData encoding:NSUTF8StringEncoding];
            NSLog(@"%@",content);
        }
    }
    
    
    //清空socket
    - (void)_releseSocket{
        if (_socketRef) {
            CFRelease(_socketRef);
        }
        _socketRef = NULL;
    }
    

    相关文章

      网友评论

        本文标题:iOS 网络(三)-Socket

        本文链接:https://www.haomeiwen.com/subject/vnpqoltx.html