精华内容
下载资源
问答
  • 这种问题可以巧妙的在队列基础之上pend-post机制解决,即等待-释放机制。 当队列「满了」的时候,前来入队的task1可以选择pend一段时间或者永久等待,「一旦元素被task2出队」,调用post释放一个信号,「唤醒...

    关注+星标公众,不错过精彩内容

    转自 | Mculover666


    1. 知识点回顾

    队列(queue)是一种只能在一端插入元素、在另一端删除元素的数据结构,遵循先入先出(FIFO)的规则。

    环形队列(ring queue)可以方便的重复利用这段内存空间,同样遵循先入先出(FIFO)的规则。

    优先级队列(prio queue)不遵循FIFO,而是根据元素的优先级进行出队,优先级最高的先出队。

    「本文的所有内容都是基于这两个数据结构」,TencentOS-tiny中环形队列和优先级队列的实现和使用示例请阅读文章:

    2. 消息队列

    2.1. 什么是消息队列

    消息队列,Message Queue,顾名思义包含两部分:消息+队列,或者可以理解为消息的队列。

    ① 消息是什么?

    两个不同的任务之间传递数据时,这个数据就称之为消息,这个消息可以是一个整型值,浮点值,甚至一个结构体,一个指针……所以,在使用不同的RTOS的消息队列时,「一定要注意传递的是值还是该值的地址」

    传递值的缺点是值的长度有大有小,导致整个消息队列的长度有大有小。

    一个指针的长度是固定的4字节,传递值的时候,无论值是什么类型,只传递该值的地址。

    传递地址当然也有缺陷,当动态任务task1中定义了一个局部变量,然后把该局部变量的地址传给了task2,随即task1因为某种原因被销毁,内存回收,导致指向该局部变量的指针变为野指针,非常危险,不过不用慌,小问题,在编程的时候注意避免即可。

    「在TencentOS-tiny中,消息队列中传递的消息指的是地址,邮箱队列传递的消息是值」

    ② 队列是什么?

    消息队列如果底层使用环形队列存储消息,则成为消息队列,遵循:先送入的消息先被取出。

    消息队列如果底层使用优先级队列存储消息,则成为优先级消息队列,遵循:优先级最高的消息最先被取出。

    「在TencentOS-tiny中,这两种消息队列都有,下面一一讲述。」

    ③ pend-post机制

    无论是什么队列,都存在两种情况:当队列满了的时候,元素再入队会发生错误;当队列为空的时候,元素出队同样会发生错误。

    这种问题可以巧妙的在队列基础之上用pend-post机制解决,即等待-释放机制。

    当队列「满了」的时候,前来入队的task1可以选择pend一段时间或者永久等待,「一旦有元素被task2出队」,调用post释放一个信号,「唤醒等待中的task1」

    同样,当队列「空了」的时候,前来出队的task1可以选择pend一段时间或者永久等待,「一旦有元素被task2入队」,调用post释放一个信号,「唤醒等待中的task1」

    是不是很巧妙?

    接下来上源码!上Demo!一看便知~

    2.2. 消息队列的实现

    TencentOS-tiny中消息队列的实现在 tos_message_queue.htos_message_queue.c中。

    typedef struct k_message_queue_st {
        knl_obj_t   knl_obj;
    
        pend_obj_t  pend_obj;
        k_ring_q_t  ring_q;
    } k_msg_q_t;
    

    一个pend_obj对象用来实现pend-post机制,一个ring_q环形队列用来存储消息。

    是不是和我讲述的没错?学透了之后,其实一切都没有那么神秘的~

    再来看看从消息队列中获取消息的API实现:

    __API__ k_err_t tos_msg_q_pend(k_msg_q_t *msg_q, void **msg_ptr, k_tick_t timeout)
    {
        //省略了部分源码
        TOS_CPU_INT_DISABLE();
    
        if (tos_ring_q_dequeue(&msg_q->ring_q, msg_ptr, K_NULL) == K_ERR_NONE) {
            TOS_CPU_INT_ENABLE();
            return K_ERR_NONE;
        }
        
        pend_task_block(k_curr_task, &msg_q->pend_obj, timeout);
    
        TOS_CPU_INT_ENABLE();
        knl_sched();
    
        return err;
    }
    

    向消息队列中存放消息的API实现如下:

    __STATIC__ k_err_t msg_q_do_post(k_msg_q_t *msg_q, void *msg_ptr, opt_post_t opt)
    {
        //省略了部分源码
        TOS_CPU_INT_DISABLE();
    
        if (pend_is_nopending(&msg_q->pend_obj)) {
            err = tos_ring_q_enqueue(&msg_q->ring_q, &msg_ptr, sizeof(void*));
            if (err != K_ERR_NONE) {
                TOS_CPU_INT_ENABLE();
                return err;
            }
            TOS_CPU_INT_ENABLE();
            return K_ERR_NONE;
        }
    
        if (opt == OPT_POST_ONE) {
            msg_q_task_recv(TOS_LIST_FIRST_ENTRY(&msg_q->pend_obj.list, k_task_t, pend_list), msg_ptr);
        } else { // OPT_POST_ALL
            TOS_LIST_FOR_EACH_ENTRY_SAFE(task, tmp, k_task_t, pend_list, &msg_q->pend_obj.list) {
                msg_q_task_recv(task, msg_ptr);
            }
        }
    
        TOS_CPU_INT_ENABLE();
        knl_sched();
    
        return K_ERR_NONE;
    }
    

    从源码中可以看到,如果opt标志为 OPT_POST_ONE,表示唤醒一个,则唤醒该消息队列等待列表上任务优先级最高的那个;如果opt标志为 OPT_POST_ALL,则全部唤醒。

    2.3. 消息队列的使用示例

    #define MESSAGE_MAX     10
    
    uint8_t msg_pool[MESSAGE_MAX * sizeof(void *)];
    
    k_msg_q_t msg_q;
    
    void entry_task_receiver(void *arg)
    {
        k_err_t err;
        void *msg_received;
    
        while (K_TRUE) {
            err = tos_msg_q_pend(&msg_q, &msg_received, TOS_TIME_FOREVER);
            if (err == K_ERR_NONE) {
                printf("receiver: msg incoming[%s]\n", (char *)msg_received);
            }
        }
    }
    
    void entry_task_sender(void *arg)
    {
        char *msg_prio_0 = "msg 0 without priority";
        char *msg_prio_1 = "msg 1 without priority";
        char *msg_prio_2 = "msg 2 without priority";
    
        printf("sender: post a message 2 without priority\n");
        tos_msg_q_post(&msg_q, msg_prio_2);
    
        printf("sender: post a message 1 without priority\n");
        tos_msg_q_post(&msg_q, msg_prio_1);
    
        printf("sender: post a message 0 without priority\n");
        tos_msg_q_post(&msg_q, msg_prio_0);
    }
    

    执行结果如下:

    TencentOS-tiny Port on STM32L431RCT6 By Mculover666
    sender: post a message 2 without priority
    sender: post a message 1 without priority
    sender: post a message 0 without priority
    receiver: msg incoming[msg 2 without priority]
    receiver: msg incoming[msg 1 without priority]
    receiver: msg incoming[msg 0 without priority]
    

    3. 优先级消息队列

    3.1. 优先级消息队列的实现

    实现和消息队列类似,通过在优先级队列的基础上加上pend-post机制来实现。

    TencentOS-tiny中优先级消息队列的实现在tos_priority_message_queue.htos_priority_message_queue.c中。

    typedef struct k_priority_message_queue_st {
        knl_obj_t   knl_obj;
    
        pend_obj_t  pend_obj;
    
        void       *prio_q_mgr_array;
        k_prio_q_t  prio_q;
    } k_prio_msg_q_t;
    

    其中pend_obj用于挂载等待该优先级消息队列的任务,prio_q和prio_q_mgr_array合起来实现优先级队列。

    消息入队和消息出队的API实现与消息队列的实现思想一模一样,这里不再讲解。

    3.2. 优先级消息队列的使用示例

    #define MESSAGE_MAX     10
    
    uint8_t msg_pool[MESSAGE_MAX * sizeof(void *)];
    
    k_prio_msg_q_t prio_msg_q;
    
    void entry_task_receiver(void *arg)
    {
        k_err_t err;
        void *msg_received;
    
        while (K_TRUE) {
            err = tos_prio_msg_q_pend(&prio_msg_q, &msg_received, TOS_TIME_FOREVER);
            if (err == K_ERR_NONE) {
                printf("receiver: msg incoming[%s]\n", (char *)msg_received);
            }
        }
    }
    
    void entry_task_sender(void *arg)
    {
        char *msg_prio_0 = "msg with priority 0";
        char *msg_prio_1 = "msg with priority 1";
        char *msg_prio_2 = "msg with priority 2";
    
        printf("sender: post a message with priority 2\n");
        tos_prio_msg_q_post(&prio_msg_q, msg_prio_2, 2);
    
        printf("sender: post a message with priority 1\n");
        tos_prio_msg_q_post(&prio_msg_q, msg_prio_1, 1);
    
        printf("sender: post a message with priority 0\n");
        tos_prio_msg_q_post(&prio_msg_q, msg_prio_0, 0);
    }
    

    运行结果如下:

    TencentOS-tiny Port on STM32L431RCT6 By Mculover666
    sender: post a message with priority 2
    sender: post a message with priority 1
    sender: post a message with priority 0
    receiver: msg incoming[msg with priority 0]
    receiver: msg incoming[msg with priority 1]
    receiver: msg incoming[msg with priority 2]
    

    将第2节的结果和第3节的结果对比,就会发现同样的消息发送顺序,因为使用不同的消息队列,任务获取到的消息顺序截然不同。

    4. 邮箱队列

    4.1. 不同之处

    消息队列和邮箱队列的不同之处,在于底层队列每个元素类型不一样,看一眼源码便知。

    消息队列传递的消息是地址,所以在初始化消息队列的时候,环形队列中每个元素都是空指针类型:

    __API__ k_err_t tos_msg_q_create(k_msg_q_t *msg_q, void *pool, size_t msg_cnt)
    {
     //部分源码省略
    
     //重点:队列中每个元素类型大小是sizeof(void*)
        err = tos_ring_q_create(&msg_q->ring_q, pool, msg_cnt, sizeof(void *));
        if (err != K_ERR_NONE) {
            return err;
        }
    
        return K_ERR_NONE;
    }
    

    而邮箱队列传递的是值,所以在初始化底层用到的环形队列时,每个元素的大小是由用户指定的:

    __API__ k_err_t tos_mail_q_create(k_mail_q_t *mail_q, void *pool, size_t mail_cnt, size_t mail_size)
    {
     //省略了部分源码
     
     //重点:每个元素的大小是mail_size,由用户传入参数指定
        err = tos_ring_q_create(&mail_q->ring_q, pool, mail_cnt, mail_size);
        if (err != K_ERR_NONE) {
            return err;
        }
    
        return K_ERR_NONE;
    }
    

    4.2. 邮箱队列的实现

    这有什么好实现的~一个环形队列+pend-post对象即可。

    TencentOS-tiny中邮箱队列的实现在tos_mail_queue.htos_mail_queue.c中。

    typedef struct k_mail_queue_st {
        knl_obj_t   knl_obj;
    
        pend_obj_t  pend_obj;
        k_ring_q_t  ring_q;
    } k_mail_q_t;
    

    是不是没什么区别~至于操作的API,更没啥区别,不写了,划水划水。

    4.3. 邮箱队列的使用示例

    #define MAIL_MAX    10
    
    typedef struct mail_st {
        char   *message;
        int     payload;
    } mail_t;
    
    uint8_t mail_pool[MAIL_MAX * sizeof(mail_t)];
    
    k_mail_q_t mail_q;
    
    void entry_task_receiver_higher_prio(void *arg)
    {
        k_err_t err;
        mail_t mail;
        size_t mail_size;
    
        while (K_TRUE) {
            err = tos_mail_q_pend(&mail_q, &mail, &mail_size, TOS_TIME_FOREVER);
            if (err == K_ERR_NONE) {
                TOS_ASSERT(mail_size == sizeof(mail_t));
                printf("higher: msg incoming[%s], payload[%d]\n", mail.message, mail.payload);
            }
        }
    }
    
    void entry_task_receiver_lower_prio(void *arg)
    {
        k_err_t err;
        mail_t mail;
        size_t mail_size;
    
        while (K_TRUE) {
            err = tos_mail_q_pend(&mail_q, &mail, &mail_size, TOS_TIME_FOREVER);
            if (err == K_ERR_NONE) {
                TOS_ASSERT(mail_size == sizeof(mail_t));
                printf("lower: msg incoming[%s], payload[%d]\n", mail.message, mail.payload);
            }
        }
    }
    
    void entry_task_sender(void *arg)
    {
        int i = 1;
        mail_t mail;
    
        while (K_TRUE) {
            if (i == 2) {
                printf("sender: send a mail to one receiver, and shoud be the highest priority one\n");
                mail.message = "1st time post";
                mail.payload = 1;
                tos_mail_q_post(&mail_q, &mail, sizeof(mail_t));
            }
            if (i == 3) {
                printf("sender: send a message to all recevier\n");
                mail.message = "2nd time post";
                mail.payload = 2;
                tos_mail_q_post_all(&mail_q, &mail, sizeof(mail_t));
            }
            if (i == 4) {
                printf("sender: send a message to one receiver, and shoud be the highest priority one\n");
                mail.message = "3rd time post";
                mail.payload = 3;
                tos_mail_q_post(&mail_q, &mail, sizeof(mail_t));
            }
            if (i == 5) {
                printf("sender: send a message to all recevier\n");
                mail.message = "4th time post";
                mail.payload = 4;
                tos_mail_q_post_all(&mail_q, &mail, sizeof(mail_t));
            }
            tos_task_delay(1000);
            ++i;
        }
    }
    

    运行结果为:

    TencentOS-tiny Port on STM32L431RCT6 By Mculover666
    
    sender: send a mail to one receiver, and shoud be the highest priority one
    higher: msg incoming[1st time post], payload[1]
    
    sender: send a message to all recevier
    higher: msg incoming[2nd time post], payload[2]
    lower: msg incoming[2nd time post], payload[2]
    
    sender: send a message to one receiver, and shoud be the highest priority one
    higher: msg incoming[3rd time post], payload[3]
    
    sender: send a message to all recevier
    higher: msg incoming[4th time post], payload[4]
    lower: msg incoming[4th time post], payload[4]
    

    此示例主要演示了两点:1. 如何使用邮箱队列直接传递值;2. 唤醒一个等待任务和唤醒所有等待任务的区别。

    5. 优先级邮箱队列

    看到这里,这个不能再讲了吧~

    TencentOS-tiny中实现在tos_priority_mail_queue.ctos_priority_mail_queue.h中。

    可以自己尝试根据前面的demo,编写出一个使用优先级邮箱队列的demo,测试高优先级的邮件是否会被先收到,然后将结果与第4节的实验结果进行对比。

    越到文末我越浪,划水已经不能满足了,博主要去摸鱼~

    6. 总结

    按照惯例,对本文所讲的内容进行一个总结。

    本文主要讲述了用于任务间通信的一些内核对象,主要有四个:消息队列和优先级消息队列,邮箱队列和优先级邮箱队列。

    接下来列出一些重要的点:

    「在使用RTOS中的一些用于任务间通信的量时,要注意传递的是值还是地址。TencentOS-tiny中消息队列传输的是地址,而邮箱队列传递的是值。」

    「消息队列和邮箱队列基于环形队列实现,遵循FIFO规则;而优先级消息队列和优先级邮箱队列基于优先级队列实现,遵循按照元素优先级取出的规则。」

    最后来回答题目中的问题:任务间通信为什么不使用全局变量?

    ① 无论是消息队列还是邮箱队列,都是利用了全局变量可以被随意访问的特性,所以使用时都会被定义为全局变量。

    ② 普通全局变量可用于一些简单的任务间通信场合。

    ③ 相较于普通全局变量,加入队列机制可以存储多个消息,加入pend-post机制可以拥有任务等待和唤醒的机制,用于解决队列已满或队列为空的问题。

    ------------ END ------------

    推荐阅读:

    嵌入式专栏精选教程

    精选汇总 | STM32、单片机

    精选汇总 | RTOS、操作系统

    迎关注我的公众号回复“加群”按规则加入技术交流群,回复“1024”查看更多内容。

    欢迎关注我的视频号:

    点击“阅读原文”查看更多分享,欢迎点分享、收藏、点赞、在看。

    展开全文
  • 假设现在我们在长沙的一个网吧里面QQ给身在异地的女朋友发送了一条文本文本信息,这个消息是怎么发送过去的?他那边是怎么接收的? 我的推导大致是: 手指在键盘上打出文本按下回车----> 这些文本被编码应用...

    深入的推导TCP/IP协议的原理:

    1、自我想象通信是一个什么样的过程

    在先不看TCP/IP协议的情况下,我们先来推导一下现如今的网络通信是怎么实现的呢?

    假设现在我们在长沙的一个网吧里面用QQ给身在异地的女朋友发送了一条文本文本信息,这个消息是怎么发送过去的?他那边是怎么接收的?

    我的推导大致是:
    手指在键盘上打出文本按下回车---->
    这些文本被编码应用程序的代码监听到---->
    代码通过IO流将这些文本信息write为字节信息---->
    操作系统OS---->
    操作系统将这些字节信息传送给网卡---->
    网卡接收到这些字节信息,将这些字节信息包装,贴上数据类型,并附带好地址,然后转换成特定标识的电信号---->
    这种特殊电信号流入传输介质开始了自己的旅行---->
    中途这个电信号会经过许多中继站(扩大信号,防止信号衰减)---->
    履行完毕电信号通过介质进入异地女盆友的网卡---->
    网卡将电信号转换为字节数据包,并查看是不是自己的包裹(不是的话就退回线路,是的话就将这些数据包拆包,分清是什么类型的数据---->
    操作系统OS,识别这些字节,传输给QQ(应用程序)---->
    通过IO流read这些字节信息---->
    转换成他的女朋友能够识别的文本信息,并显示出来

    2、TCP/IP协议:

    前面已经自己胡乱推导了一下通信是一个怎么样的过程,下面让我们仔细看看TCP/IP协议:
    TCP/IP协议(传输控制协议/网际协议)其实是一个很多协议组成的协议簇它是借鉴OSI模型提出来的一个解决不同操作系统、不同芯片构架之间进行一个通信的网络协议,即共识

    TCP/IP协议分为四个层面:
    1、应用层:(HTTP、FTP、TELNET协议)
    2、运输层传输层:(TCP、UDP协议)
    3、网络层:(IP协议)
    4、数据链路层(硬件设施与网络接口)

    那么这四个层面到底都发挥着一个怎么样的作用呢?收先我们需要理解的是,通信其实是一台电脑的应用程序和另一台电脑上的应用程序的通信

    那么问题来了:
    1、首先,一个数据从一台电脑发送到另一台电脑怎么发?即怎么将这些数据精确发送到接收机上?
    2、电脑上收到这些数据之后,电脑上运行着这么多的应用程序或者说是端口,我怎么知道这个数据是发送到哪个端口或者应用程序的?
    3、找到对应的应用程序之后,我怎么知道发过来的数据是一个怎么样的数据,是超文本文件,还是远程登陆访问,还是邮件?

    那么着四个层面正是为了解决这些问题而提出来的协议!

    那么我们来分析这四个层面各有什么样的作用!
    数据链路层
    是一个对于局域网而言的一个数据传输协议,我们现在大多数都是用的以太网的组网形式,其他局域网的组网形式还有(令牌环网,FDDI等等),在以太网中规定,数据包只能通过一个网卡,发送到另一个网卡,每一个网卡有一个自己对应的MAC地址,如下图所示,在以太网中,在一个局域子网中,路由器将自己收到的数据包以广播的形式发送给此局域子网中的每一个设备的网卡,网卡解析数据包上面的MAC地址看与自己的是否相同,不相同的话就将数据包丢弃,不进行其它操作
    在这里插入图片描述
    网络层(IP协议,ARP协议,路由协议)

    IP协议:
    IP地址是用于用于识别网络中的特定计算机而分配的一个特定的地址,现在打开电脑的命令行窗口输入ipconfig会显示一个IPV4地址,意思就是IP的v4版本,用32bit存储,用点加十进制表示,如192.168.0.3此类的一共有2的32次方个地址,有A、B、C、D类之分(即他是用于标识网络中的一个特定计算机而分配的一个特有的IP地址
    在网络层引入IP协议,能够区分两台主机是否在同一个网络下,32bit地址分为两个部分,前面一部分是主机的网络地址,后面一部分是主机在子网中对应的地址,随着技术发展,大家还可以在这个命令行窗口看见一个子网掩码,子网掩码和IP地址相互运算,就可以的到计算机的MAC地址

    ARP协议:
    (百度百科的定义)
    即地址解析协议,是根据IP地址获取MAC地址的一个网络层协议。其工作原理如下:ARP首先会发起一个请求数据包,数据包的首部包含了目标主机的IP地址,然后这个数据包会在链路层进行再次包装,生成以太网数据包,最终由以太网广播给子网内的所有主机,每一台主机都会接收到这个数据包,并取出标头里的IP地址,然后和自己的IP地址进行比较,如果相同就返回自己的MAC地址,如果不同就丢弃该数据包。ARP接收返回消息,以此确定目标机的MAC地址;与此同时,ARP还会将返回的MAC地址与对应的IP地址存入本机ARP缓存中并保留一定时间,下次请求时直接查询ARP缓存以节约资源。

    路由协议:
    (百度百科的定义)
    首先通过IP协议来判断两台主机是否在同一个子网中,如果在同一个子网,就通过ARP协议查询对应的MAC地址,然后以广播的形式向该子网内的主机发送数据包;如果不在同一个子网,以太网会将该数据包转发给本子网的网关进行路由。网关是互联网上子网与子网之间的桥梁,所以网关会进行多次转发,最终将该数据包转发到目标IP所在的子网中,然后再通过ARP获取目标机MAC,最终也是通过广播形式将数据包发送给接收方。而完成这个路由协议的物理设备就是路由器,路由器扮演着交通枢纽的角色,它会根据信道情况,选择并设定路由,以最佳路径来转发数据包。

    (ARP协议和路由协议不知到怎么去组织自己的语言,就只能这样了。。)

    其实一句话说,网络层就是一个通过网络层找到特定的网络位置,找到特定局域网的路由器,路由器再以广播形式将数据包发送到内部组网中的通信设备上,这样就解决了第一个问题,精确地将一个数据包发送到了目标电脑

    运输层和传输层(UDP协议,TCP协议)

    现在数据包已经发送到我们地网卡了,现如今我接受的这个数据包要发送给计算机中运行的哪个应用程序上面呢?那么就有UDP协议和TCP协议来帮忙啦

    UDP协议: UDP协议为电脑上运行的应用程序或者说端口,都给了一个特定的标识,解析数据包的IP地址和MAC地址之后,操作系统读取数据包端口号找到UDP定义的对应的端口号,这样的话,数据包就找到了目标应用程序,
    在这里插入图片描述
    TCP协议:

    TCP协议其实就是一个更加完善的UDP协议,在UDP协议中,只有接收,但是接收的这个数据包是不是在传输过程会丢失数据呢?完不完整呢?他没有一个确认机制,而TCP协议添加了这个确认收到完整数据包的机制,在读取数据包的时候,如果数据包完整,TCP协议就会发送确认,如果发送端收不到确认,则会再次发送数据包过来,这样就建立了一个稳定的数据传输连接,比UDP协议更加可靠,TCP有一个三次招手建立连接和四次挥手断开连接(这个具体过程还得仔仔细细的研究)
    在这里插入图片描述
    应用层

    数据包已经找到了相应计算机下的特定应用程序,接下来就要读取数据了,那么这个数据怎么来读取呢?它也得遵循一定的协议,在应用层里,规范了很多的数据传输格式也就是不同数据传输的共识比如我给你发过来的是一个邮件,那么就是通过遵循SMTP协议来读取数据,又比如我要远程登陆你的电脑,那么此时在应用层的数据传输协议就会用到TELNET协议,还有传输超文本文件所遵循的HTTP协议等等,根据不同的协议,会对发送过来的数据包及使用不同的数据读取方法,实现与人们的直接交互!

    那么对于前面提出的三个问题,数据链路层和网络层解决了第一个问题
    运输层和传输层解决了第二个问题,应用层解决了第三个问题! TCP/IP协议从底层到交互,为我们提供了一个用于网络通信的规约!

    展开全文
  • 早期的 Java 网络相关的 API(java.net) 使用 Socket(套接字)进行网络通信,不过只支持阻塞函数使用。 要通过互联网进行通信,至少需要一对套接字: 运行于服务器端的 Server Socket。 运行于客户

    #前言
    老套路,学习某一门技术或者框架的时候,第一步当然是要了解下面这几样东西。

    1. 是什么?
    2. 有哪些特点?
    3. 有哪些应用场景?
    4. 有哪些成功使用的案例?

    为了让你更好地了解 Netty 以及它诞生的原因,先从传统的网络编程说起吧!

    还是要从 BIO 说起

    传统的阻塞式通信流程

    早期的 Java 网络相关的 API(java.net包) 使用 Socket(套接字)进行网络通信,不过只支持阻塞函数使用。

    要通过互联网进行通信,至少需要一对套接字:

    1. 运行于服务器端的 Server Socket。
    2. 运行于客户机端的 Client Socket

    Socket 网络通信过程如下图所示:

    Socket 网络通信过程简单来说分为下面 4 步:

    1. 建立服务端并且监听客户端请求
    2. 客户端请求,服务端和客户端建立连接
    3. 两端之间可以传递数据
    4. 关闭资源

    对应到服务端和客户端的话,是下面这样的。

    服务器端:

    1. 创建 ServerSocket 对象并且绑定地址(ip)和端口号(port): server.bind(new InetSocketAddress(host, port))
    2. 通过 accept()方法监听客户端请求
    3. 连接建立后,通过输入流读取客户端发送的请求信息
    4. 通过输出流向客户端发送响应信息
    5. 关闭相关资源

    客户端:

    1. 创建Socket 对象并且连接指定的服务器的地址(ip)和端口号(port):socket.connect(inetSocketAddress)
    2. 连接建立后,通过输出流向服务器端发送请求信息
    3. 通过输入流获取服务器响应的信息
    4. 关闭相关资源

    一个简单的 demo

    为了便于理解,我写了一个简单的代码帮助各位小伙伴理解。

    服务端:

    public class HelloServer {
        private static final Logger logger = LoggerFactory.getLogger(HelloServer.class);
    
        public void start(int port) {
            //1.创建 ServerSocket 对象并且绑定一个端口
            try (ServerSocket server = new ServerSocket(port);) {
                Socket socket;
                //2.通过 accept()方法监听客户端请求, 这个方法会一直阻塞到有一个连接建立
                while ((socket = server.accept()) != null) {
                    logger.info("client connected");
                    try (ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
                         ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream())) {
                       //3.通过输入流读取客户端发送的请求信息
                        Message message = (Message) objectInputStream.readObject();
                        logger.info("server receive message:" + message.getContent());
                        message.setContent("new content");
                        //4.通过输出流向客户端发送响应信息
                        objectOutputStream.writeObject(message);
                        objectOutputStream.flush();
                    } catch (IOException | ClassNotFoundException e) {
                        logger.error("occur exception:", e);
                    }
                }
            } catch (IOException e) {
                logger.error("occur IOException:", e);
            }
        }
    
        public static void main(String[] args) {
            HelloServer helloServer = new HelloServer();
            helloServer.start(6666);
        }
    }
    
    

    ServerSocketaccept() 方法是阻塞方法,也就是说 ServerSocket 在调用 accept()等待客户端的连接请求时会阻塞,直到收到客户端发送的连接请求才会继续往下执行代码,因此我们需要要为每个 Socket 连接开启一个线程(可以通过线程池来做)。

    上述服务端的代码只是为了演示,并没有考虑多个客户端连接并发的情况。

    客户端:

    /**
     * @author shuang.kou
     * @createTime 2020年05月11日 16:56:00
     */
    public class HelloClient {
    
        private static final Logger logger = LoggerFactory.getLogger(HelloClient.class);
    
        public Object send(Message message, String host, int port) {
            //1\. 创建Socket对象并且指定服务器的地址和端口号
            try (Socket socket = new Socket(host, port)) {
                ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
                //2.通过输出流向服务器端发送请求信息
                objectOutputStream.writeObject(message);
                //3.通过输入流获取服务器响应的信息
                ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
                return objectInputStream.readObject();
            } catch (IOException | ClassNotFoundException e) {
                logger.error("occur exception:", e);
            }
            return null;
        }
    
        public static void main(String[] args) {
            HelloClient helloClient = new HelloClient();
            helloClient.send(new Message("content from client"), "127.0.0.1", 6666);
            System.out.println("client receive message:" + message.getContent());
        }
    }
    
    

    发送的消息实体类

    /**
     * @author shuang.kou
     * @createTime 2020年05月11日 17:02:00
     */
    @Data
    @AllArgsConstructor
    public class Message implements Serializable {
    
        private String content;
    }
    
    

    首先运行服务端,然后再运行客户端,控制台输出如下:

    服务端:

    [main] INFO github.javaguide.socket.HelloServer - client connected
    [main] INFO github.javaguide.socket.HelloServer - server receive message:content from client
    
    

    客户端:

    client receive message:new content
    
    

    资源消耗严重的问题

    很明显,我上面演示的代码片段有一个很严重的问题:只能同时处理一个客户端的连接,如果需要管理多个客户端的话,就需要为我们请求的客户端单独创建一个线程。 如下图所示:

    对应的 Java 代码可能是下面这样的:

    new Thread(() -> {
       // 创建 socket 连接
    }).start();
    
    

    但是,这样会导致一个很严重的问题:资源浪费

    我们知道线程是很宝贵的资源,如果我们为每一次连接都用一个线程处理的话,就会导致线程越来越好,最好达到了极限之后,就无法再创建线程处理请求了。处理的不好的话,甚至可能直接就宕机掉了。

    很多人就会问了:那有没有改进的方法呢?

    线程池虽可以改善,但终究未从根本解决问题

    当然有! 比较简单并且实际的改进方法就是使用线程池。线程池还可以让线程的创建和回收成本相对较低,并且我们可以指定线程池的可创建线程的最大数量,这样就不会导致线程创建过多,机器资源被不合理消耗。

    ThreadFactory threadFactory = Executors.defaultThreadFactory();
    ExecutorService threadPool = new ThreadPoolExecutor(10, 100, 1, TimeUnit.MINUTES, new ArrayBlockingQueue<>(100), threadFactory);
    threadPool.execute(() -> {
         // 创建 socket 连接
     });
    
    

    但是,即使你再怎么优化和改变。也改变不了它的底层仍然是同步阻塞的 BIO 模型的事实,因此无法从根本上解决问题。

    为了解决上述的问题,Java 1.4 中引入了 NIO ,一种同步非阻塞的 I/O 模型。

    再看 NIO

    Netty 实际上就基于 Java NIO 技术封装完善之后得到一个高性能框架,熟悉 NIO 的基本概念对于学习和更好地理解 Netty 还是很有必要的!

    初识 NIO

    NIO 是一种同步非阻塞的 I/O 模型,在 Java 1.4 中引入了 NIO 框架,对应 java.nio 包,提供了 Channel , Selector,Buffer 等抽象。

    NIO 中的 N 可以理解为 Non-blocking,已经不在是 New 了(已经出来很长时间了)。

    NIO 支持面向缓冲(Buffer)的,基于通道(Channel)的 I/O 操作方法。

    NIO 提供了与传统 BIO 模型中的 SocketServerSocket 相对应的 SocketChannelServerSocketChannel 两种不同的套接字通道实现,两种通道都支持阻塞和非阻塞两种模式:

    1. 阻塞模式 : 基本不会被使用到。使用起来就像传统的网络编程一样,比较简单,但是性能和可靠性都不好。对于低负载、低并发的应用程序,勉强可以用一下以提升开发速率和更好的维护性
    2. 非阻塞模式 : 与阻塞模式正好相反,非阻塞模式对于高负载、高并发的(网络)应用来说非常友好,但是编程麻烦,这个是大部分人诟病的地方。所以, 也就导致了 Netty 的诞生。

    NIO 核心组件解读

    NIO 包含下面几个核心的组件:

    • Channel
    • Buffer
    • Selector
    • Selection Key

    这些组件之间的关系是怎么的呢?

    1. NIO 使用 Channel(通道)和 Buffer(缓冲区)传输数据,数据总是从缓冲区写入通道,并从通道读取到缓冲区。在面向流的 I/O 中,可以将数据直接写入或者将数据直接读到 Stream 对象中。在 NIO 库中,所有数据都是通过 Buffer(缓冲区)处理的。 Channel 可以看作是 Netty 的网络操作抽象类,对应于 JDK 底层的 Socket
    2. NIO 利用 Selector (选择器)来监视多个通道的对象,如数据到达,连接打开等。因此,单线程可以监视多个通道中的数据。
    3. 当我们将 Channel 注册到 Selector 中的时候, 会返回一个 Selection Key 对象, Selection Key 则表示了一个特定的通道对象和一个特定的选择器对象之间的注册关系。通过 Selection Key 我们可以获取哪些 IO 事件已经就绪了,并且可以通过其获取 Channel 并对其进行操作。

    Selector(选择器,也可以理解为多路复用器)是 NIO(非阻塞 IO)实现的关键。它使用了事件通知相关的 API 来实现选择已经就绪也就是能够进行 I/O 相关的操作的任务的能力。

    简单来说,整个过程是这样的:

    1. 将 Channel 注册到 Selector 中。
    2. 调用 Selector 的 select() 方法,这个方法会阻塞;
    3. 到注册在 Selector 中的某个 Channel 有新的 TCP 连接或者可读写事件的话,这个 Channel 就会处于就绪状态,会被 Selector 轮询出来。
    4. 然后通过 SelectionKey 可以获取就绪 Channel 的集合,进行后续的 I/O 操作。

    NIO 为啥更好?

    相比于传统的 BIO 模型来说, NIO 模型的最大改进是:

    1. 使用比较少的线程便可以管理多个客户端的连接,提高了并发量并且减少的资源消耗(减少了线程的上下文切换的开销)
    2. 在没有 I/O 操作相关的事情的时候,线程可以被安排在其他任务上面,以让线程资源得到充分利用。

    使用 NIO 编写代码太难了

    一个使用 NIO 编写的 Server 端如下,可以看出还是整体还是比较复杂的,并且代码读起来不是很直观,并且还可能由于 NIO 本身会存在 Bug。

    很少使用 NIO,很大情况下也是因为使用 NIO 来创建正确并且安全的应用程序的开发成本和维护成本都比较大。所以,一般情况下我们都会使用 Netty 这个比较成熟的高性能框架来做(Apace Mina 与之类似,但是 Netty 使用的更多一点)。

    重要角色 Netty 登场

    简单用 3 点概括一下 Netty 吧!

    1. Netty 是一个基于 NIO 的 client-server(客户端服务器)框架,使用它可以快速简单地开发网络应用程序。
    2. 它极大地简化并简化了 TCP 和 UDP 套接字服务器等网络编程,并且性能以及安全性等很多方面甚至都要更好。
    3. 支持多种协议如 FTP,SMTP,HTTP 以及各种二进制和基于文本的传统协议。

    用官方的总结就是:Netty 成功地找到了一种在不妥协可维护性和性能的情况下实现易于开发,性能,稳定性和灵活性的方法。

    Netty 特点

    根据官网的描述,我们可以总结出下面一些特点:

    • 统一的 API,支持多种传输类型,阻塞和非阻塞的。
    • 简单而强大的线程模型。
    • 自带编解码器解决 TCP 粘包/拆包问题。
    • 自带各种协议栈。
    • 真正的无连接数据包套接字支持。
    • 比直接使用 Java 核心 API 有更高的吞吐量、更低的延迟、更低的资源消耗和更少的内存复制。
    • 安全性不错,有完整的 SSL/TLS 以及 StartTLS 支持。
    • 社区活跃
    • 成熟稳定,经历了大型项目的使用和考验,而且很多开源项目都使用到了 Netty 比如我们经常接触的 Dubbo、RocketMQ 等等。

    使用 Netty 能做什么?

    这个应该是老铁们最关心的一个问题了,凭借自己的了解,简单说一下,理论上 NIO 可以做的事情 ,使用 Netty 都可以做并且更好。Netty 主要用来做网络通信 :

    1. 作为 RPC 框架的网络通信工具 : 我们在分布式系统中,不同服务节点之间经常需要相互调用,这个时候就需要 RPC 框架了。不同服务指点的通信是如何做的呢?可以使用 Netty 来做。比如我调用另外一个节点的方法的话,至少是要让对方知道我调用的是哪个类中的哪个方法以及相关参数吧!
    2. 实现一个自己的 HTTP 服务器 :通过 Netty 我们可以自己实现一个简单的 HTTP 服务器,这个大家应该不陌生。说到 HTTP 服务器的话,作为 Java 后端开发,我们一般使用 Tomcat 比较多。一个最基本的 HTTP 服务器可要以处理常见的 HTTP Method 的请求,比如 POST 请求、GET 请求等等。
    3. 实现一个即时通讯系统 : 使用 Netty 我们可以实现一个可以聊天类似微信的即时通讯系统,这方面的开源项目还蛮多的,可以自行去 Github 找一找。
    4. 消息推送系统 :市面上有很多消息推送系统都是基于 Netty 来做的。

    哪些开源项目用到了 Netty?

    我们平常经常接触的 Dubbo、RocketMQ、Elasticsearch、gRPC 等等都用到了 Netty。

    可以说大量的开源项目都用到了 Netty,所以掌握 Netty 有助于你更好的使用这些开源项目并且让你有能力对其进行二次开发。

    实际上还有很多很多优秀的项目用到了 Netty,Netty 官方也做了统计,统计结果在这里:netty.io/wiki/relate…

    ##最后
    大家看完有什么不懂的可以在下方留言讨论.
    谢谢你的观看。
    觉得文章对你有帮助的话记得关注我点个赞支持一下!
    earch、gRPC 等等都用到了 Netty。

    可以说大量的开源项目都用到了 Netty,所以掌握 Netty 有助于你更好的使用这些开源项目并且让你有能力对其进行二次开发。

    实际上还有很多很多优秀的项目用到了 Netty,Netty 官方也做了统计,统计结果在这里:netty.io/wiki/relate…

    [外链图片转存中…(img-CGXHJZtH-1622705128762)]

    ##最后
    大家看完有什么不懂的可以在下方留言讨论.
    谢谢你的观看。
    觉得文章对你有帮助的话记得关注我点个赞支持一下!

    展开全文
  • dao中存在是接口(interface)dao.impl中存在的是接口的具体...举个例子dao中public interface UserDAO {public List getUser();}dao.imple中public class JdbcUserDao implements UserDAO{@Overridepublic List ...

    dao中存在是接口(interface)

    dao.impl中存在的是接口的具体实现(class)

    至于好处,去查查接口的定义。

    更新。。。。

    举个例子

    dao中有

    public interface UserDAO {

    public List getUser();

    }

    dao.imple中

    public class JdbcUserDao implements UserDAO{

    @Override

    public List getUser() {

    //do sth.

    return null;

    }

    }

    在往后的某一天,需要用hibernate来操作数据库的时候。

    只要写一个 HibernateUserDao来实现UserDAO就行了。

    接口定义的是规范。并不关心具体实现。

    我的github上有一份自己对java各种关键字做的总结,其中关于接口在设计方面的描述是:

    接口作为系统与外界交互的窗口,接口体现是一种规范。对于接口的实现者而言,接口规定了实现者必须向外提供哪些服务(以方法的形式来提供);对于接口调用者

    而言,接口规定了调用者可以调用哪些服务,以及如何调用这些服务(就是如何来调用方法的)。当一个程序中使用接口时,接口是多个模块之间的耦合标准;当多

    个应用程序之间使用时,接口时多个程序之间的通信标准。

    从某种程度来说,接口类型整个系统中的“总纲”,它制定了系统之间各个模块应该遵循的标准,因此一个系统中的接口不应该经常改变。一旦接口改变,对整个系统

    甚至其他系统的影响将是辐射式的,导致系统中大部分类需要重写。

    展开全文
  • 《计算机网络由两部分组成,包括通信子网___A.计算机子网 B.资源...》由会员分享,可在线阅读,更多相关《计算机网络由两部分组成,包括通信子网___A.计算机子网 B.资源...(6页珍藏版)》请在人人文库网上搜索。1、...
  • Socket通信原理

    2021-05-14 00:12:54
    关注+星标公众号,不错过精彩内容编排 | strongerHuang微信公众号|嵌入式专栏网络通信与我们生活息息相关,特别是今天发达的智能手机、物联网这些都离不开网络通信。今天分享一下...
  • 最后,弄懂前面两个大前提,我们再回归到问题本身:这俩IP地址到底有啥用呢。 老规矩,咱们还是分4点来回答。 1、IP地址是啥 之前老杨的科普文章里也介绍过,有兴趣的伙伴可点开链接查看详细内容:网工必看!...
  • 1 RPC JAR 调用的对比 微服务把重复的代码集中在一起,如果把这些逻辑写在一个模块,将这个模块打成JAR,被别的需要的模块直接引入JAR,也可以达到省下重复代码的功效。什么场景该使用哪种实现怎么选择呢...
  • 即时通信工具简介

    2021-02-19 17:34:47
    即时通信是基于网络的一种新兴应用,它最基本的特征就是信息的即时传递用户的交互性,并可将音、视频通信、文件传输及网络聊天等业务集成为一体,为人们开辟了一种新型的沟通途径。简单地讲,即时通信是一种使人们...
  • 通信协议综述

    2021-07-10 11:16:05
    但是,你不一定知道,这段文字也是一种协议,是人类计算机沟通的协议,只有通过这种协议,计算机才知道我们想让它做什么。 协议的三要素 语法,就是这一段内容要符合一定的规则格式。例如,括号
  • ---------------------------------------网络通信的命令-----------------------------------------------一:write功能:给某个户发信息(对方必须登录,要不能是收不到的)场景:1:现在linux服务器2个用户在...
  • 51单片机——串口通信

    千次阅读 2021-06-17 23:28:18
    其实我压根不知道串口通信,我在这方面也是小白,只知道按照标准做就可以实现通信。 上图示是四孔串口,应该算是全双工通信的,复杂的9针串口,提供额外的口子可以调控发送速率等。 开发板原理图如下: 串口...
  • 与六类、七类等网线有啥区别?很多朋友看到标题可能会问,六类网线还没有普及,怎么八类网线就来了呢?是的,现在大多数人生活中,使用到的网络都是百兆千兆网络,匹配的线缆也是超五类网线六类网线,如果没有...
  • 通信协议定义

    千次阅读 2021-09-22 12:56:51
    首先明白通信协议,参考百度百科通信协议_百度百科给出的定义,通信协议是指双方实体完成通信或服务所必须遵循的规则约定。其实光看这个定义绝对云里雾里的,不知所云。下面按照我的理解进行分解吧。 通信...
  • 文章目录前言一、网络中进程之间通信方式二、Socket的基本使用1.Socket函数2.bind()函数3.listen()、connect()函数4.accept()函数5.read()、write()、close()函数三、QQ的基本实现C语言版本C++版本 前言 最近在看...
  • 在开发的过程中,http协议是主流的交互协议,抓是必不可少的调试方式,如果你想抓取pc端或者app的http协议, 提示网络异常或者 死活不走代理,那么你来对了。接下来,我们来探讨下,同样都是http请求,app的,...
  • 他从那时起,就日复一日的学习,并在 Github 做笔记的习惯,你看他的提交记录,每天都,一天都没拉下,就这样坚持了一年。 这个一年没有间断过的坚持,我是真的被震撼到,虽然我也经常肝文章,但是我也做不到...
  • 说到通信设计院,相信很多人都听说过。平常我们在新闻报道中,经常会看到各家设计院的名字出现,例如联通设计院、中移设计院,等等。最近各地校招启动,一些同学收到了设计院的offer,于是问我,...
  • 本课程包括四个部分:数据通信技术,计算机网络原理,计算机网络规划管理应用,网络操作系统与应用模式。数据通信技术是学习计算机网络理论的基础,计算机网络原理是本课程的核心部分,而计算机网络的规划管理...
  • EdgeXFoundry里都有啥

    2021-04-21 23:01:37
    EdgeXFoundry框架四个服务层两个基础系统服务,分别是设备服务层(Device Service),核心服务层(Core Service),支持服务层(Supporting Service)还有应用服务层(Application Services);以及安全服务(Security...
  • Restful已经得太多了,是不是种被接口压垮的感觉? 接口还没好,您就等着吧。摸鱼抓虾,问兄弟好了没,答还没好,今天事儿没干,1,2,3,4,周五好了,你这儿疯狂测试一堆问题,ok周末加加班,这就是常态。 RPC...
  • 其实对于 90%的企业来说,LoRa 确实没优势。除非企业想使用物联网的位置在农村、偏远 山区、独立的厂区等(比如智慧农场、智慧工厂),确实没有运营商 NB-IoT 网络的覆盖,无 可奈何才会想到自建 LoRa。 再就是...
  • 其中大多数都是已经学习的内容,只有SocketServerSocket属于陌生知识,而且此次实验的关键在于这两个类,所以我也将会简要介绍。 二、实验标题 基于GUI的网络通信程序设计 三、实验目的 1.掌握Java中GUI程序...
  • 为了解决这个问题,Netscape 公司制定了HTTPS协议,HTTPS可以将数据加密传输,也就是传输的是密文,即便黑客在传输过程中拦截到数据也无法破译,这就保证了网络通信的安全。 近年来各大公司对信息安全传输越来越重视...
  • 串口通信相关

    2021-04-15 22:24:37
    学习目标: 问:单片机串口通信,如何判断数据帧传输结束? 串行通信怎么判断一帧数据发送完毕?...帧数据是固定的周期的,判断时间间隔是个好方法,但不知道怎么弄.比如3-5MS还没有数据来,应该是一帧数据发送完成了
  • 有啥弊端? 这里浅谈一下微服务架构,主要还是在理解 Why :为什么需要服务化? https://weiyucloud.com/ 一、对微服务架构的理解 1.1 微服务架构 微服务架构,主要是多了个 “微”。亚马逊有个粗粗的定义:一个...
  • 一文搞懂串口通信

    2021-05-19 15:22:13
    研究之前我先是在网上搜了一些Qt串口通信开发相关的资料,大致看了一下大家的博文,稍微一个简单的了解,然后我先去问一下实验室的好兄弟, 简单沟通了一下,听听其他同学的想法,然后开干! 百度百科上对于...
  • IO通信机制

    2021-02-17 01:19:06
    IO指的是inputoutput 网络通信的本质是网络间的数据IO。只要IO,就会阻塞或非阻塞的问题,无论这个IO是网络的,还是硬盘的。原因在于程序是运行在系统上的,任何形式的IO操作都需要系统支持。 2.BIO(阻塞模式) ...
  • 一文总结GaussDB通信原理知识

    千次阅读 2021-02-07 11:18:47
    摘要:从发展历程到通信模型设计,到你了解一下GaussDB通信原理知识。 MPPDB通信库发展历程 ...由于每个DN只存储一部分数据,因此DN间也需要进行通信,采用stream线程TCP直连,每个连接都需要一

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 51,606
精华内容 20,642
关键字:

和通信包有啥用