史上最简单的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:希望能帮到一些同学,如果那里写的不够准确还请大佬在评论区或者留言指正,多谢!别忘了点个赞哈~~~

你可能感兴趣的:(史上最简单的Sockte通信--1)