史上最简单的Sockte通信--1

Socket,是基于网络通信协议封装的一个网络通信接口,可以支持TCP或UDP通信协议。通常是应用于TCP协议,这时的socket连接就是一个TCP连接,也遵循TCP建立连接时的“三次握手”。
Socket想要建立连接是需要一个服务器端的ServerSocket和一个客户端的ClientSocket,即需要一对套接字。套接字之间的连接过程分为三个步骤:服务器监听,客户端请求,连接确认。连接确认后就可以进行socket通信了。(关于网络通信及协议不太了解的同学可以移步这里 --> 相关知识)接下来,我们需要的是建立一对套接字。

通信.png

首先是创建ServerSocket,本次是以Mac端模拟服务器端,创建一个Command Line Tool工程,并在创建完成之后保持连接,等待客户端请求。

主要过程:
一: 创建CFSocket
二:设置服务器本地的地址和端口
三:绑定CFSocket到本地地址
四:将CFSocket包装成source添加到runloop里面
int main(int argc, const char * argv[]) {
    @autoreleasepool {
//       一: 创建CFSocket,监听socket的连接,指定TCPServerAcceptCallBack函数为连接回调函数
        CFSocketRef _socket = CFSocketCreate(kCFAllocatorDefault, PF_INET, SOCK_STREAM, IPPROTO_TCP, kCFSocketAcceptCallBack, TCPServerAcceptCallBack, NULL);
        if (_socket == NULL) {
            NSLog(@"创建socket失败");
            return 0;
        }
        int optval = 1;

//        二:设置允许重用本地地址和端口,并配置端口信息
        setsockopt(CFSocketGetNative(_socket), SOL_SOCKET, SO_REUSEADDR, (void*)&optval, sizeof(optval));
//        定义socket地址
        struct sockaddr_in addr4;
        memset(&addr4, 0, sizeof(addr4));
        addr4.sin_len = sizeof(addr4);
        addr4.sin_family = AF_INET;
//        设置本服务器可以监听本机任意可用 的IP地址
//        addr4.sin_addr.s_addr = htonl(INADDR_ANY);
//        设置服务器的监听地址,为本机的地址
        addr4.sin_addr.s_addr = inet_addr("xx.xx.xx.xx");
//        设置服务器监听端口
        addr4.sin_port = htons(30000);
//        将ipv4转换为cfdataref
        CFDataRef address = CFDataCreate(kCFAllocatorDefault, (uint8 *)&addr4, sizeof(addr4));
//      三:  将CFSocket通过CFSocketSetAddress函数绑定到指定的IP
        if (CFSocketSetAddress(_socket, address) != kCFSocketSuccess) {
            NSLog(@"地址绑定失败");
//            如果_socket不为null。释放_socket
            if (_socket) {
                CFRelease(_socket);
                exit(1);
            }
            _socket = NULL;
        }
        NSLog(@"启动循环监听客户端连接-----");
//       四: 获取当前线程的runloop,并将CFSocket作为source绑定到runloop上,保证持续不断的接受来自客户端的请求(TCP第一次握手)
        CFRunLoopRef cfrunloop = CFRunLoopGetCurrent();
//        将socket包装成cfrunloopsource
        CFRunLoopSourceRef source = CFSocketCreateRunLoopSource(kCFAllocatorDefault, _socket, 0);
//        为runloop添加source
        CFRunLoopAddSource(cfrunloop, source, kCFRunLoopCommonModes);
        CFRelease(source);
        CFRunLoopRun();
    }
    return 0;
}
说明:当本地地址是通过WiFi分配的时候,在重新连接WiFi后地址将会发生变化,所以也可以自动获取本机的地址,相关链接为 -->代码获取本机IP地址
上面函数涉及到的TCPServerAcceptCallBack回调函数如下
void TCPServerAcceptCallBack(CFSocketRef socket,CFSocketCallBackType type,CFDataRef address,const void *data,void *info)
{
//如果有客户端连接进来
    if (kCFSocketAcceptCallBack == type) {
//        本地socket的handle
        CFSocketNativeHandle nativaSocketHandle = *(CFSocketNativeHandle *)data;
        uint8_t name[SOCK_MAXADDRLEN];
        socklen_t nameLen = sizeof(name);
//        获取对方的socket信息,和本程序的socket信息
        if (getpeername(nativaSocketHandle, (struct sockaddr*)name, &nameLen) != 0) {
            NSLog(@"error");
            exit(1);
        }
        
//        获取连接信息
        struct sockaddr_in *add_in = (struct sockaddr_in *)name;
        NSLog(@"%s : %d",inet_ntoa(add_in -> sin_addr),add_in -> sin_port);
        CFReadStreamRef iStream;
        
//        创建一个可读写的CFStream
        CFStreamCreatePairWithSocket(kCFAllocatorDefault, nativaSocketHandle, &iStream, &oStream);
        if (iStream && oStream) {
//            打开输入流和输出流
            CFReadStreamOpen(iStream);
            CFWriteStreamOpen(oStream);
            CFStreamClientContext streamConText = {0,NULL,NULL,NULL};
//            readStream函数为有可读数据时候调用
            if (!CFReadStreamSetClient(iStream, kCFStreamEventHasBytesAvailable, readStream, &streamConText)) {
                exit(1);
            }
            CFReadStreamScheduleWithRunLoop(iStream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
            const char *str = "您好,您已成功连接\n";
//            向客户端输出数据
            CFWriteStreamWrite(oStream, (uint8 *)str, strlen(str) + 1);
        }
    }
}
当有可读信息传到的时候调用readStream函数
void readStream (CFReadStreamRef iStream,CFStreamEventType eventType,void *clientCallBackInfo)
{
    uint8 buff[2048];
    CFIndex hasRead = CFReadStreamRead(iStream, buff, 2048);
    if (hasRead > 0) {
        buff[hasRead] = '\0';
        printf("接收到数据:%s\n",buff);
        const char *str = (char *)buff;
        //            向客户端输出数据
        CFWriteStreamWrite(oStream, (uint8 *)str, strlen(str) + 1);
    }
}

此时运行程序可看到:
控制台输出

其次,创建客户端ClientSocket,对服务器进行连接,发送数据

一: 创建CFSocket

 _socket = CFSocketCreate(kCFAllocatorDefault, PF_INET, SOCK_STREAM, IPPROTO_TCP, kCFSocketNoCallBack, nil, NULL);

二: CFSocket创建成功后,定义socket连接地址

//        定义sockadd_in    作为cfsocket的地址
        struct sockaddr_in addr4;
        memset(&addr4, 0, sizeof(addr4));
        addr4.sin_len = sizeof(addr4);
        addr4.sin_family = AF_INET;
//        设置远程服务器地址
        addr4.sin_addr.s_addr = inet_addr("xx.xx.xx.xx");
        addr4.sin_port = htons(30000);
        CFDataRef address = CFDataCreate(kCFAllocatorDefault, (UInt8 *)&addr4, sizeof(addr4));

三: 连接服务器,成功后新线程内通过readStream相应服务器返回的数据

CFSocketError result = CFSocketConnectToAddress(_socket, address, 5);
        if (result == kCFSocketSuccess) {
            isOnline = YES;
            [NSThread detachNewThreadSelector:@selector(readStream) toTarget:self withObject:nil];
        }
服务端连接成功,收到客户端的请求打印出IP地址和端口数据 (TCP第二次握手)

客户端readStream方法

- (void)readStream
{
    char buffer[2048];
    int hadRead;
//    与本机相连的socket如果已经失效,则返回-1
    while (hadRead = recv(CFSocketGetNative(_socket), buffer, sizeof(buffer), 0)) {
        NSString *contend = [[NSString alloc] initWithBytes:buffer length:hadRead encoding:NSUTF8StringEncoding];
        dispatch_async(dispatch_get_main_queue(), ^{
            self.showView.text = [NSString stringWithFormat:@"%@\n%@",contend,self.showView.text];
            NSLog(@"%@\n",contend);
        });
    }
}
客户端成功连接到服务端,显示服务端返回的数据 (TCP第三次握手)
至此,服务端ServerSocket和客户端ClientSocket连接完成,能够进行信息传输
接着进行一次简单的通话
1. 客户端向服务端发送数据“天王盖地虎”
接收到客户端发送的信息
2. 服务端接收到信息后,同样返回“天王盖地虎”给客户端
收到服务端给的回应

至此,客户端和服务端经过三次握手并进行了一次简单的对话。相关的程序代码Socket通信

由于直接原生的代码撸起来太过耗神,所以有大神封装了CocoaAsyncSocket将CFSocket和CFStream进行了封装方便使用,我的下一篇将介绍 ---> 史上最简单的Socket通信--2(CocoaAsyncSocket)

PS:希望能帮到一些同学,如果那里写的不够准确还请大佬在评论区或者留言指正,多谢!别忘了点个赞哈~~~

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

推荐阅读更多精彩内容

  • 参考:http://www.2cto.com/net/201611/569006.html TCP HTTP UD...
    F麦子阅读 2,945评论 0 14
  • 网络由下往上分为 物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。 通过初步的了解,我知道IP协议对应...
    Jason_Wong阅读 1,418评论 0 3
  • 一: 网络各个协议:TCP/IP、SOCKET、HTTP 网络七层由下往上分别为物理层、数据链路层、网络层、传输层...
    iYeso阅读 1,431评论 0 13
  • 1、OC中创建线程的方法是什么?如果指定在主线程中执行代码?如何延时执行代码。【难度系数★★】 1)创建线程的方法...
    木旁_G阅读 1,948评论 2 16
  • 古井薄冰耀天青 碧山白头揽闲情 雪舞飘摇清冷嗅 湿履倒卧半山亭 应该是一九九五年吧?那年三十大雪。清晨我就听到五楼...
    梧桐树边羽阅读 437评论 4 16