网络 - Socket通讯相关回顾

Socket


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


    网络 - Socket通讯相关回顾_第1张图片
  • 网络中进程之间如何通信
    • 网络中进程之间如何通信?首要解决的问题是如何唯一标识一个进程,否则通信无从谈起!在本地可以通过进程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网络编程层次模型


网络 - Socket通讯相关回顾_第2张图片
  • 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架构程序设计基本框架
    网络 - Socket通讯相关回顾_第3张图片
  • UDP C/S架构程序设计基本框架
    网络 - Socket通讯相关回顾_第4张图片

SocketDemo


主要使用:NSStream、CFStream、CFSocket

效果图

网络 - Socket通讯相关回顾_第5张图片
客户端 + 服务端.png

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

#include 
#include 
#include 
static ViewController *selfClass =nil;

@interface ViewController ()
{
    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 
#include 
#include 

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

写在最后


  • 参考
    Linux Socket编程(不限Linux)
  • 第三方库
    CocoaAsyncSocket

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

你可能感兴趣的:(网络 - Socket通讯相关回顾)