美文网首页iOS开发者日记iOS Developer
网络 - Socket通讯相关回顾

网络 - Socket通讯相关回顾

作者: DeadRabbit | 来源:发表于2017-05-30 23:19 被阅读101次

    Socket


    • Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。


    • 网络中进程之间如何通信
      • 网络中进程之间如何通信?首要解决的问题是如何唯一标识一个进程,否则通信无从谈起!在本地可以通过进程PID来唯一标识一个进程,但是在网络中这是行不通的。其实TCP/IP协议族已经帮我们解决了这个问题,网络层的“ip地址”可以唯一标识网络中的主机,而传输层的“协议+端口”可以唯一标识主机中的应用程序(进程)。这样利用三元组(ip地址,协议,端口)就可以标识网络的进程了,网络中的进程通信就可以利用这个标志与其它进程进行交互。
      • 使用TCP/IP协议的应用程序通常采用应用编程接口:UNIX BSD的套接字(socket)和UNIX System V的TLI(已经被淘汰),来实现网络进程之间的通信。就目前而言,几乎所有的应用程序都是采用socket,而现在又是网络时代,网络中进程通信是无处不在,这就是我为什么说“一切皆socket”。
    • socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,都可以用“打开open –> 读写write/read –> 关闭close”模式来操作。我的理解就是Socket就是该模式的一个实现,socket即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭)
      关于如下问题参考这里
    网络中进程之间如何通信?
    socket的基本操作
        socket()函数
        bind()函数
        listen()、connect()函数
        accept()函数
        read()、write()函数等
        close()函数
    

    iOS网络编程层次模型


    • Cocoa层:NSURL,Bonjour,Game Kit,WebKit
    • Core Foundation层:基于 C 的 CFNetwork 和 CFNetServices
    • OS层:基于 C 的 BSD socket

    Cocoa层:是最上层的基于 Objective-C 的 API,比如 URL访问,NSStream,Bonjour,GameKit等,这是大多数情况下我们常用的 API。Cocoa 层是基于 Core Foundation 实现的。

    Core Foundation层:因为直接使用 socket 需要更多的编程工作,所以苹果对 OS 层的 socket 进行简单的封装以简化编程任务。该层提供了 CFNetwork 和 CFNetServices,其中 CFNetwork 又是基于 CFStream 和 CFSocket。

    OS层:最底层的 BSD socket 提供了对网络编程最大程度的控制,但是编程工作也是最多的。因此,苹果建议我们使用 Core Foundation 及以上层的 API 进行编程。

    C/S架构程序设计基本框架


    常用的Socket类型
    有两种:流式Socket(SOCK_STREAM)和数据报式Socket(SOCK_DGRAM)。流式是一种面向连接的Socket,针对于面向连接的TCP服务应用;数据报式Socket是一种无连接的Socket,对应于无连接的UDP服务应用。

    • TCP C/S架构程序设计基本框架
    • UDP C/S架构程序设计基本框架

    SocketDemo


    主要使用:NSStream、CFStream、CFSocket

    效果图

    客户端 + 服务端.png

    客户端主要代码完整代码下载地址

    #include <CoreFoundation/CoreFoundation.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    static ViewController *selfClass =nil;
    
    @interface ViewController ()<NSStreamDelegate>
    {
        NSInteger flag;
    }
    
    @property (nonatomic, strong) NSInputStream *inputStream;
    @property (nonatomic, strong) NSOutputStream *outputStream;
    @property (nonatomic, assign) NSInteger kPort;
    
    @property (weak, nonatomic) IBOutlet UITextField *port;
    
    @property (weak, nonatomic) IBOutlet UITextField *serviceIP;
    
    @property (weak, nonatomic) IBOutlet UILabel *message;
    
    @property (weak, nonatomic) IBOutlet UITextField *putText;
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        selfClass = self;
        // Do any additional setup after loading the view, typically from a nib.
    }
    
    - (IBAction)connectService:(id)sender {
        flag = 0;
        [self initNetworkCommunication];
    }
    - (IBAction)reviveMsg:(id)sender {
        flag = 1;
        [self initNetworkCommunication];
    }
    - (void)initNetworkCommunication {
        CFReadStreamRef readStream;
        CFWriteStreamRef writeStream;
        CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)self.serviceIP.text, (int)[self.port.text intValue], &readStream, &writeStream);
        _inputStream = (__bridge_transfer NSInputStream *)readStream;
        _outputStream = (__bridge_transfer NSOutputStream *)writeStream;
        
        [_inputStream setDelegate:self];
        [_outputStream setDelegate:self];
        
        [_inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
        [_outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
        
        [_inputStream open];
        [_outputStream open];
        
    }
    
    - (void)close {
        [_outputStream close];
        [_outputStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
        [_outputStream setDelegate:nil];
        [_inputStream close];
        [_inputStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
        [_inputStream setDelegate:nil];
    }
    
    - (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
    {
        NSString *event;
        switch (eventCode) {
            case NSStreamEventNone:
                event = @"NSStreamEventNone";
                NSLog(@"%@",event);
                break;
            case NSStreamEventOpenCompleted:
                event = @"NSStreamEventOpenCompleted";
                NSLog(@"%@",event);
                break;
            case NSStreamEventHasBytesAvailable:
                event = @"NSStreamEventHasBytesAvailable";
                NSLog(@"%@",event);
                if (flag == 1 && aStream == _inputStream) {
                    NSMutableData *input = [[NSMutableData alloc] init];
                    uint8_t buffer[1024];
                    NSInteger len;
                    while ([_inputStream hasBytesAvailable]) {
                        len = [_inputStream read:buffer maxLength:sizeof(buffer)];
                        if (len > 0) {
                            [input appendBytes:buffer length:len];
                        }
                    }
                    NSString *result = [[NSString alloc] initWithData:input encoding:NSUTF8StringEncoding];
                    NSLog(@"%@",result);
                    _message.text = result;
                }
            case NSStreamEventHasSpaceAvailable:
                event = @"NSStreamEventHasSpaceAvailable";
                NSLog(@"%@",event);
                if (flag ==0 && aStream == _outputStream) {
                    //输出
                    NSString * putText = selfClass.putText.text;
                    UInt8 buff[1024];
                    memcpy(buff, [putText UTF8String],[putText lengthOfBytesUsingEncoding:NSUTF8StringEncoding] + 1);
                    [_outputStream write:buff maxLength: strlen((const char*)buff)+1];
                    //必须关闭输出流否则,服务器端一直读取不会停止,
                    [_outputStream close];
                }
                break;
            case NSStreamEventErrorOccurred:
                event = @"NSStreamEventErrorOccurred";
                NSLog(@"%@",event);
                [self close];
                break;
            case NSStreamEventEndEncountered:
                event = @"NSStreamEventEndEncountered";
                NSLog(@"%@",event);
                NSLog(@"Error:%ld:%@",[[aStream streamError] code], [[aStream streamError] localizedDescription]);
                break;
            default:
                [self close];
                event = @"Unknown";
                break;
        }
    }
    

    服务端主要代码完整代码下载地址

    #import "ViewController.h"
    #import <CoreFoundation/CoreFoundation.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    
    static ViewController *selfClass =nil;
    @interface ViewController ()
    @property (weak, nonatomic) IBOutlet UITextField *portText;
    @property (nonatomic, assign) NSInteger kPORT;
    @property (weak, nonatomic) IBOutlet UITextField *putText;
    @property (weak, nonatomic) IBOutlet UILabel *readText;
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        selfClass = self;
        // Do any additional setup after loading the view, typically from a nib.
    }
    
    - (IBAction)startService:(id)sender {
        //设置服务端端口号
        self.kPORT = [self.portText.text integerValue];
        
        CFSocketRef service;
        /*CFSocketContext 参数cgindex version 版本号,必须为0;
         *void *info; 一个指向任意程序定义数据的指针,可以在CFScocket对象刚创建的时候与之关联,被传递给所有在上下文中回调,可为NULL;
         *CFAllocatorRetainCallBack retain; info指针中的retain回调,可以为NULL
         *CFAllocatorReleaseCallBack release; info指针中的release的回调,可以为NULL
         *CFAllocatorCopyDescriptionCallBack copyDescription; info指针中的回调描述,可以为NULL
         */
        CFSocketContext CTX = {0,NULL,NULL,NULL,NULL};
        //CFSockerRef
        //内存分配类型,一般为默认的Allocator->kCFAllocatorDefault,
        //协议族,一般为Ipv4:PF_INET,(Ipv6,PF_INET6),
        //套接字类型,TCP用流式—>SOCK_STREAM,UDP用报文式->SOCK_DGRAM,
        //套接字协议,如果之前用的是流式套接字类型:IPROTO_TCP,如果是报文式:IPPROTO_UDP,
        //回调事件触发类型 *1,
        //触发时候调用的方法 *2,
        //用户定义的数据指针,用于对CFSocket对象的额外定义或者申明,可以为NULL
        service = CFSocketCreate(NULL, PF_INET, SOCK_STREAM, IPPROTO_TCP, kCFSocketAcceptCallBack, (CFSocketCallBack)AcceptCallBack, &CTX);
        if (service == NULL) {
            return;
        }
        //设置是否重新绑定标志
        int yes = 1;
        /* 设置socket属性 SOL_SOCKET是设置tcp SO_REUSEADDR是重新绑定,yes 是否重新绑定*/
        setsockopt(CFSocketGetNative(service), SOL_SOCKET, SO_REUSEADDR, (void *)&yes, sizeof(yes));
        
        //设置端口和地址
        struct sockaddr_in addr;
        memset(&addr, 0, sizeof(addr));       //memset函数对指定的地址进行内存拷贝
        addr.sin_len = sizeof(addr);
        addr.sin_family = AF_INET;            //AF_INET是设置 IPv4
        addr.sin_port = htons(self.kPORT);    //htons函数 无符号短整型数转换成“网络字节序”
        addr.sin_addr.s_addr = htonl(INADDR_ANY);  //INADDR_ANY有内核分配,htonl函数 无符号长整型数转换成“网络字节序”
        
        /* 从指定字节缓冲区复制,一个不可变的CFData对象*/
        CFDataRef address = CFDataCreate(kCFAllocatorDefault, (UInt8 *)&addr, sizeof(addr));
        
        /* 设置Socket*/
        if (CFSocketSetAddress(service, (CFDataRef)address) != kCFSocketSuccess) {
            fprintf(stderr, "Socket绑定失败\n");
            CFRelease(service);
            return ;
        }
        /* 创建一个Run Loop Socket源 */
        CFRunLoopSourceRef sourceRef = CFSocketCreateRunLoopSource(kCFAllocatorDefault, service, 0);
        /* Socket源添加到Run Loop中 */
        CFRunLoopAddSource(CFRunLoopGetCurrent(), sourceRef, kCFRunLoopCommonModes);
        CFRelease(sourceRef);
        
        printf("Socket listening on port %zd\n", self.kPORT);
        /* 运行Loop */
        CFRunLoopRun();
        
        
    }
    
    //接受客户端请求后回调函数
    void AcceptCallBack(
                        CFSocketRef socket,
                        CFSocketCallBackType type,
                        CFDataRef address,
                        const void *data,
                        void *info)
    {
        CFReadStreamRef readStream = NULL;
        CFWriteStreamRef writeStream = NULL;
        
        //data参数的含义是,如果是kCFSocketAcceptCallBack类型,data是CFSocketNativeHandle类型的指针
        CFSocketNativeHandle sock = *(CFSocketNativeHandle *) data;
        
        //创建读写socket流
        CFStreamCreatePairWithSocket(kCFAllocatorDefault, sock, &readStream, &writeStream);
        
        if (!readStream || !writeStream) {
            close(sock);
            fprintf(stderr, "CFStreamCreatePairWithSocket() 失败\n");
            return;
        }
        
        CFStreamClientContext streamCtxt = {0,NULL,NULL,NULL,NULL};
        //注册两种回调函数
        CFReadStreamSetClient(readStream, kCFStreamEventHasBytesAvailable, ReadStreamClientCallBack, &streamCtxt);
        CFWriteStreamSetClient(writeStream, kCFStreamEventCanAcceptBytes, WriteStreamClientCallBack, &streamCtxt);
        
        //加入到循环当中
        CFReadStreamScheduleWithRunLoop(readStream, CFRunLoopGetCurrent(),kCFRunLoopCommonModes);
        CFWriteStreamScheduleWithRunLoop(writeStream, CFRunLoopGetCurrent(),kCFRunLoopCommonModes);
        
        CFReadStreamOpen(readStream);
        CFWriteStreamOpen(writeStream);
    }
    
    //读取操作,读取客户端发送的数据
    static UInt8 buff[255];
    void ReadStreamClientCallBack (CFReadStreamRef stream,CFStreamEventType eventType, void* clientCallBackInfo ) {
        
        CFReadStreamRef inputStream = stream;
        
        if (NULL != inputStream) {
            CFReadStreamRead(inputStream, buff, 255);
            
            printf("接收到的数据 :%s\n", buff);
            selfClass.readText.text = [NSString stringWithCString:(char*)buff encoding:NSUTF8StringEncoding];
            CFReadStreamClose(inputStream);
            //从循环中移除
            CFReadStreamUnscheduleFromRunLoop(inputStream, CFRunLoopGetCurrent(),kCFRunLoopCommonModes);
            inputStream = NULL;
        }
        
    }
    
    /* 写入流操作 客户端在读取数据时候调用 */
    void WriteStreamClientCallBack(CFWriteStreamRef stream, CFStreamEventType eventType, void* clientCallBackInfo)
    {
        CFWriteStreamRef    outputStream = stream;
        //输出
        NSString * putText = selfClass.putText.text;
        UInt8 buff[1024];
        memcpy(buff, [putText UTF8String],[putText lengthOfBytesUsingEncoding:NSUTF8StringEncoding] + 1);
        
        if(NULL != outputStream)
        {
            CFWriteStreamWrite(outputStream, buff, strlen((const char*)buff)+1);
            //关闭输出流
            CFWriteStreamClose(outputStream);
            //从循环中移除
            CFWriteStreamUnscheduleFromRunLoop(outputStream, CFRunLoopGetCurrent(),kCFRunLoopCommonModes);
            outputStream = NULL;
        }
    }
    
    

    写在最后


    客户端完整代码下载地址
    服务端完整代码下载地址

    相关文章

      网友评论

      • 泰好笑勒:你好,用CFSocket完成到服务器,怎么做服务端的消息转发?

      本文标题:网络 - Socket通讯相关回顾

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