iOS 网络(三)-Socket

一、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;
}
?著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,100评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,308评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事?!?“怎么了?”我有些...
    开封第一讲书人阅读 159,718评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,275评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,376评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,454评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,464评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,248评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,686评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,974评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,150评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,817评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,484评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,140评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,374评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,012评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,041评论 2 351

推荐阅读更多精彩内容