Socket

作者: yahibo | 来源:发表于2019-05-23 19:18 被阅读0次

    socket又称为套接字,是应用层和传输层中间的软件抽象层,在网络中两个应用通过双向通信连接实现数据交互,向网络中另一个应用发送请求或应答其他网络请求,区别于WebSocketsocket是一组接口,WebSocket是协议。通信及关闭通信过程需要建立三次握手连接和四次挥手协议。

    三次握手建立连接

    • 第一次握手:客户端向服务端发送一个SYN(同步序列编号)包到服务端,并进入syn_send状态,等待服务器进行确认
    • 第二次握手:服务器收到并确认客户端的SYN包,同时服务端也发送一个SYN包(SYN+ACK确认字符)包,此时服务器进入syn_recv状态
    • 第三次握手:客户端接收到SYN+ACK包之后,向服务器发送确认包SYN+1,此时客户端与服务端进入到确立状态

    完成三次握手之后两端便可以通讯了。

    握手过程:

    build_connect.png

    有聚有散,四次挥手协议

    • 第一次挥手:客户端向服务端发送FIN+ACK包,告诉服务端需要要结束连接
    • 第二次挥手:服务端收到断开连接消息后,返回ACK包表示确认,确认序号为收到的序号加1,表示同意断开
    • 第三次挥手:服务端数据传输完成后关闭客户端的连接,并向客户端发送FIN+ACK包通知客户端关闭连接
    • 第四次挥手:客户端发送FIN+ACK包确认,FIN+1,服务端接收数据后断开连接,客户端等待一段时间,没有消息后即断开连接

    完成之后TCP连接断开,以上为客户端主动发起断开连接的请求,服务器发起过程同上。

    挥手过程:

    bye.png

    socket连接示意图:

    socket.png

    五层协议体系结构

    framework.png

    一、建立socket链接,Mac端终端使用nc命令做端口监听,oc作为客户端建立socket连接。

    nc/netcat(选项)(参数)

    -g<网关>:设置路由器跃程通信网关,最多设置8个;
    -G<指向器数目>:设置来源路由指向器,其数值为4的倍数;
    -h:在线帮助;
    -i<延迟秒数>:设置时间间隔,以便传送信息及扫描通信端口;
    -l:使用监听模式,监控传入的资料;
    -n:直接使用ip地址,而不通过域名服务器;
    -o<输出文件>:指定文件名称,把往来传输的数据以16进制字码倾倒成该文件保存;
    -p<通信端口>:设置本地主机使用的通信端口;
    -r:指定源端口和目的端口都进行随机的选择;
    -s<来源位址>:设置本地主机送出数据包的IP地址;
    -u:使用UDP传输协议;
    -v:显示指令执行过程;
    -w<超时秒数>:设置等待连线的时间;
    -z:使用0输入/输出模式,只在扫描通信端口时使用。

    1、服务端 端口监听
    nc -l 6666
    2、永久监听TCP端口
    nc -lk port
    3、临时监听UDP
    nc -lu port
    4、永久监听UDP
    nc -luk port
    5、连接服务端
    nc -v 127.0.0.1 666
    6、端口扫描
    nc -v -w 1 127.0.0.1 -z 1-1000

    客户端代码:

    #import "ViewController.h"
    #import <sys/socket.h>
    #import <arpa/inet.h>//inet_addr
    
    #define connect_host @"127.0.0.1"
    #define connect_port 6666
    @interface ViewController (){
        int clientSocket;
        dispatch_queue_t queSerial;
    }
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        //button
        UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
        button.frame = CGRectMake((self.view.frame.size.width-200)/2, 100, 200, 30);
        button.backgroundColor = [UIColor grayColor];
        [button setTitle:@"发送普通文本" forState:UIControlStateNormal];
        [button addTarget:self action:@selector(btn:) forControlEvents:UIControlEventTouchUpInside];
        [self.view addSubview:button];
        button = [UIButton buttonWithType:UIButtonTypeCustom];
        button.frame = CGRectMake((self.view.frame.size.width-200)/2, 150, 200, 30);
        button.backgroundColor = [UIColor grayColor];
        [button setTitle:@"发送图片" forState:UIControlStateNormal];
        [button addTarget:self action:@selector(btn:) forControlEvents:UIControlEventTouchUpInside];
        [self.view addSubview:button];
        
        button = [UIButton buttonWithType:UIButtonTypeCustom];
        button.frame = CGRectMake((self.view.frame.size.width-200)/2, 200, 200, 30);
        button.backgroundColor = [UIColor grayColor];
        [button setTitle:@"发送文本加图片" forState:UIControlStateNormal];
        [button addTarget:self action:@selector(btn:) forControlEvents:UIControlEventTouchUpInside];
        [self.view addSubview:button];
        
        [self initSocket];
    }
    -(void)btn:(UIButton *)button{
        if ([button.titleLabel.text isEqualToString:@"发送普通文本"]) {
            [self sendMessage:@"发送普通文本"];
        }else if ([button.titleLabel.text isEqualToString:@"发送图片"]) {
            UIImage *image = [UIImage imageNamed:@"hibo"];
            NSData *data = UIImagePNGRepresentation(image);
            NSLog(@"height:%f",image.size.height);
            NSString *dataString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
            NSLog(@"%@",dataString);
    //        [self sendMessage:dataString];
        }else if ([button.titleLabel.text isEqualToString:@"发送文本加图片"]) {
            
        }
    }
    //发起socket连接
    -(BOOL)initSocket{
        /*
         第一个参数:adress_family,协议簇 AF_INET:IPV4
         第二个参数:数据格式->SOCK_STREAM(TCP)/SOCK_DGRAM(UDP)
         第三个参数:protocal IPPROTO_TCP,如果为0会根据第二个参数选择合适的协议
         返回值:>0成功 -1失败
         */
        clientSocket = socket(AF_INET, SOCK_STREAM, 0);
        NSLog(@"clientsocket:%d",clientSocket);
        if (clientSocket>0) {
            NSLog(@"socket create success");
        }else{
            NSLog(@"socket create error");
        }
        /*
         连接
         第一个参数:客户端socket
         第二个参数:指向数据结构,socketAddr的指针,其中包括目的端口和IP地址
         第三个参数:结构体数据长度
         返回值:0成功 其他错误
         */
        struct sockaddr_in addr4 = {0};
        addr4.sin_family = AF_INET;//ipv4
        addr4.sin_len = sizeof(addr4);
        addr4.sin_addr.s_addr = inet_addr(connect_host.UTF8String);
        addr4.sin_port = htons(connect_port);//是将整型变量从主机字节顺序转变成网络字节顺序, 就是整数在地址空间存储方式变为高位字节存放在内存的低地址处
        int flag = connect(clientSocket, (const struct sockaddr *)&addr4, sizeof(addr4));
        if (!flag) {
            [self receiveMessage];
        }else{
            clientSocket = 0;
            NSLog(@"连接失败");
        }
        return flag;
    }
    //接收消息
    -(void)receiveMessage{
        if (!queSerial) {
            queSerial=dispatch_queue_create("jrQueueSerial", DISPATCH_QUEUE_SERIAL);
        }
        dispatch_async(queSerial, ^{
            uint8_t buffer[1024];
            ssize_t recvLen = recv(self->clientSocket, buffer, sizeof(buffer), 0);
            NSData *data = [NSData dataWithBytes:buffer length:recvLen];
            NSString *str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
            if (recvLen>0) {
                NSLog(@"%@:%@",str,[NSThread currentThread]);
                NSLog(@"%@",str);
                [self receiveMessage];
            }else{
                NSLog(@"连接断开");
                self->clientSocket = 0;
            }
        });
    }
    //发送消息
    -(BOOL)sendMessage:(NSString *)message{
        if (clientSocket==0) {
            BOOL flag = [self initSocket];
            if (!flag)return NO;
        }
        ssize_t sendLen = send(clientSocket, message.UTF8String, strlen(message.UTF8String), 0);
        NSLog(@"发送消息长度:%zd",sendLen);
        return sendLen>=0;
    }
    @end
    

    1、在终端执行命令监听端口6666:

    nc -l 127.0.0.1 6666
    

    进入等待连接状态。

    2、执行以上oc代码进行socket连接:

    client_socket.png

    3、以上连接成功,由服务端发送一条消息,客户端接收打印如下:

    client_receive.png

    注:以上代码中图片发送及文本+图片发送未完成。

    二、oc模拟服务端代替Mac终端命令,一步步实现服务端的三次握手及通信。主要使用方法:

    1、创建一个socket:

    socket(AF_INET, SOCK_STREAM, 0);
    

    第一个参数:address_family,协议簇 AF_INET对应的IPV4
    第二个参数:数据格式可选两种SOCK_STREAM(TCP)、SOCK_DGRAM(UDP)
    第三个参数:protocal IPPROTO_TCP,设置为0会根据第二个参数选择相应的协议;
    返回值:sockaddr -1失败 其他成功为socket标号1、2、3、 4,指示为当前socket

    2、端口绑定:

    bind(sockaddr, (const struct sockaddr *)&addr4, sizeof(addr4));
    

    第一个参数:创建的socket描述号;
    第二个参数:对应socketaddr_in(对应IPV4)的结构体包含了端口号;
    第三个参数:socketaddr_in结构体长度。

    3、监听端口:

    listen(sockaddr, 5);
    

    第一个参数:创建的socket标号;
    第二个参数:可以排队的最大连接个数。

    4、获取连接的socket标号和以上使用的标号不同

    accept(sockaddr, (struct sockaddr *)&aptsockaddr, &addrLen);
    

    第一个参数:创建的socket描述号;
    第二个参数:可以排队的最大连接个数;
    返回值:接收后的socket标号。

    5、接收客户端消息:

    recv(aptsocket, buffer, len, 0);
    

    第一个参数:accept返回的标号理解为当前socket
    第二个参数:接收字符的缓存变量;
    第三个参数:一般设置0
    返回值:接收到的数据长度。

    6、发送消息:

    send(aptsocket, message.UTF8String, strlen(message.UTF8String), 0);
    

    第一个参数:accept返回的标号理解为当前socket
    第二个参数:发送的消息字符char *型数据;
    第三个参数:一般设置0
    返回值:发送的数据长度。

    服务端代码:

    /*
     ipv6
     struct sockaddr_in6 addr6 = {0};
     bzero(&addr6, sizeof(addr6));
     addr6.sin6_len = sizeof(addr6);
     addr6.sin6_family = AF_INET6;
     addr6.sin6_port = htons(connect_port);
     
     htons将主机的无符号短整形数转换成网络字节顺序
     htonl将主机的无符号长整形数转换成网络字节顺序
     */
    
    #import "ViewController.h"
    #import <sys/socket.h>
    #import <arpa/inet.h>//inet_addr
    
    #define connect_host @"127.0.0.1"
    #define connect_port 6666
    
    @interface ViewController ()
    {
        int sockaddr;//创建的socket地址
        int aptsocket;//同意后返回的socket地址
        dispatch_queue_t queSerial;//接收消息的队列
        BOOL socket_flag;//socket标识
    }
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        //button
        UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
        button.frame = CGRectMake((self.view.frame.size.width-200)/2, 100, 200, 30);
        button.backgroundColor = [UIColor grayColor];
        [button setTitle:@"回复文本" forState:UIControlStateNormal];
        [button addTarget:self action:@selector(btn:) forControlEvents:UIControlEventTouchUpInside];
        [self.view addSubview:button];
        button = [UIButton buttonWithType:UIButtonTypeCustom];
        button.frame = CGRectMake((self.view.frame.size.width-200)/2, 150, 200, 30);
        button.backgroundColor = [UIColor grayColor];
        [button setTitle:@"关闭链接" forState:UIControlStateNormal];
        [button addTarget:self action:@selector(btn:) forControlEvents:UIControlEventTouchUpInside];
        [self.view addSubview:button];
        button = [UIButton buttonWithType:UIButtonTypeCustom];
        button.frame = CGRectMake((self.view.frame.size.width-200)/2, 200, 200, 30);
        button.backgroundColor = [UIColor grayColor];
        [button setTitle:@"创建链接" forState:UIControlStateNormal];
        [button addTarget:self action:@selector(btn:) forControlEvents:UIControlEventTouchUpInside];
        [self.view addSubview:button];
        //创建服务端socket
        [self initServerSocket];
    }
    -(void)btn:(UIButton *)button{
        if ([button.titleLabel.text isEqualToString:@"回复文本"]) {
            [self sendMessage:@"回复:你好啊!!"];
        }else if ([button.titleLabel.text isEqualToString:@"关闭链接"]) {
            [self closeSocket];
        }else if ([button.titleLabel.text isEqualToString:@"创建链接"]) {
            [self initServerSocket];
        }
    }
    //初始化socket
    -(BOOL)initServerSocket{
        socket_flag = YES;
        //1、创建一个socket
        sockaddr = socket(AF_INET, SOCK_STREAM, 0);
        NSLog(@"sockaddr:%d",sockaddr);
        if (sockaddr==-1) {
            NSLog(@"创建失败");
            return NO;
        }
        //ipv4
        struct sockaddr_in addr4 = {0};
        //参数说明:s 要置零的数据的起始地址; n 要置零的数据字节个数。
        memset(&addr4, 0, sizeof(addr4));
        addr4.sin_len = sizeof(addr4);
        addr4.sin_family = AF_INET;
        addr4.sin_port = htons(connect_port);//将主机的无符号短整形数转换成网络字节顺序
        addr4.sin_addr.s_addr = INADDR_ANY;//就是指定地址为0.0.0.0的地址,这个地址事实上表示不确定地址,或“所有地址”、“任意地址”
        //2、端口绑定
        int error = bind(sockaddr, (const struct sockaddr *)&addr4, sizeof(addr4));
        if (error!=0) {
            NSLog(@"绑定失败");
            return NO;
        }
        //3、监听端口
        error = listen(sockaddr, 5);//开始监听第二个参数可以排队的最大连接个数
        if (error!=0) {
            NSLog(@"监听失败");
            return NO;
        }
        //4、轮询
        if (!queSerial) {
            queSerial = dispatch_queue_create("receive_queue", DISPATCH_QUEUE_SERIAL);
        }
        dispatch_async(queSerial, ^{
            [self receiveMessage];
        });
        return YES;
    }
    //轮询接收消息
    -(void)receiveMessage{
        while (true) {
            NSLog(@"currentThread:%@",[NSThread currentThread]);
            struct sockaddr_in aptsockaddr;
            socklen_t addrLen = sizeof(aptsockaddr);
            //4、获取连接的socket
            aptsocket = accept(sockaddr, (struct sockaddr *)&aptsockaddr, &addrLen);
            NSLog(@"aptsocket:%d",aptsocket);
            if (aptsocket != -1) {
                NSLog(@"accept success address:%ss, port:%d",inet_ntoa(aptsockaddr.sin_addr),ntohs(aptsockaddr.sin_port));
                char buffer[1024];
                ssize_t recvLen;
                size_t len = sizeof(buffer);
                do{
                    //5、接收客户端的消息
                    recvLen = recv(aptsocket, buffer, len, 0);
                    NSString *str = [NSString stringWithCString:buffer encoding:NSUTF8StringEncoding];
                    NSLog(@"receive:%@",str);
                    NSLog(@"buffer:%s",buffer);
                    [self sendMessage:@"回复你:哈哈"];
                }while(socket_flag);
            }
            close(aptsocket);
            break;//链接失败后跳出轮询
        }
    }
    //发送消息
    -(BOOL)sendMessage:(NSString *)message{
        if (sockaddr==0) {
            NSLog(@"链接已断开");
        }
        ssize_t sendLen = send(aptsocket, message.UTF8String, strlen(message.UTF8String), 0);
        NSLog(@"发送消息长度:%zd",sendLen);
        return sendLen>=0;
    }
    //断开链接
    -(void)closeSocket{
        NSLog(@"关闭链接");
        socket_flag = NO;
    }
    
    @end
    

    通过以上过程,对socket通信能有一个初步了解,可以利用socket通信搭建一套简单的聊天系统。

    长连接和短连接的区别
    长连接:建立连接->数据传输……保持连接……数据传输->关闭连接
    短连接:建立连接->数据传输->关闭连接……建立连接->数据传输->关闭连接

    相关文章

      网友评论

          本文标题:Socket

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