大话网络通信

极客拾光
• 阅读 723

1、术语

并发 vs 并行

  • 并发和并行是相关的概念,但有一些小的区别。并发意味着两个或多个任务正在取得进展,即使它们可能不会同时执行。例如,这可以通过时间切片来实现,其中部分任务按顺序执行,并与其他任务的部分混合。另一方面,当执行的任务可以真正同时进行时,就会出现并行
    简单说启动一个线程在一个core上就是并行,启动两个线程在一个core上就是并发

异步 vs 同步

  • 如果调用者在方法返回值或引发异常之前无法取得进展,则认为方法调用是同步的。另一方面,异步调用允许调用者在有限的步骤之后继续进行,并且可以通过一些附加机制 (它可能是已注册的回调、Future 或消息)来通知方法的完成
    简单来说Java API层来说的,如下 :
ExecutorService executorService = Executors.newFixedThreadPool(2);
        Future<Boolean> future = executorService.submit(new Callable<Boolean>() {
            @Override
            public Boolean call() throws Exception {
                System.out.println("执行业务逻辑");
                
                // 根据业务逻辑判断给定返回
                return true;
            }
        });

        future.get(); // 同步API,必须等到返回
        if(future.isDone()) {
            future.get();// 异步API,只有执行完,再get结果
        }  
  • 同步 API 可以使用阻塞来实现同步,但这不是必要的。CPU 密集型任务可能会产生类似 于阻塞的行为。一般来说,最好使用异步 API,因为它们保证系统能够进行

非阻塞 vs 阻塞

  • 如果一个线程的延迟可以无限期地延迟其他一些线程,这就是我们讨论的阻塞。一个很好的例子是,一个线程可以使用互斥来独占使用一个资源。如果一个线程无限期地占用资源(例如意外运行无限循环),则等待该资源的其他线程将无法进行。相反,非阻塞意味着没有线程能够无限期地延迟其他线程
  • 非阻塞操作优先于阻塞操作,因为当系统包含阻塞操作时,系统的总体进度并不能得到很好的保证

死锁 vs 饥饿 vs 活锁

  • 当多个线程在等待对方达到某个特定的状态以便能够取得进展时,就会出现死锁。由于没有其他线程达到某种状态,所有受影响的子系统都无法继续运行。死锁与阻塞密切相关,因为线程能够无限期地延迟其他线程的进程
  • 在死锁的情况下,没有线程可以取得进展,相反,当有线程可以取得进展,但可能有一个或多个线程不能取得进展时,就会发生饥饿。典型的场景是一个调度算法,它总是选择高优先级的任务而不是低优先级的任务。如果传入的高优先级任务的数量一直足够多,那么低优先级任务将永远不会完成
  • 活锁类似于死锁,因为没有线程取得进展。不同之处在于,线程不会被冻结在等待他人进展的状态中,而是不断地改变自己的状态。一个示例场景是,两个线程有两个相同资源可用时。他们每一个都试图获得资源,但他们也会检查对方是否也需要资源。 如果资源是由另一个线程请求的,他们会尝试获取该资源的另一个实例。在不幸的情 况下,两个线程可能会在两种资源之间“反弹”,从不获取资源,但总是屈服于另一种资源

2、BIO vs NIO

BIO

serverSocket.accept(),这里会阻塞
socket.getInputStream.read(),也会阻塞

虽然可以使用了线程池,因为read()方法的阻塞,其实线程池也是不能复用的,说白了,就是需要一个客户端一个线程进行服务

思考:那BIO就没有使用场景了吗?
其实不是,BIO在建立长连接的流式传输场景还是很有用的,比如说HDSF,客户端向DataNode传输数据使用的就是建立一个BIO的管道,流式上传数据的。此时引入一个问题,那HDFS DataNode就不考虑到线程阻塞么?是这样的,其实要知道你不可能多个客户端上传文件都是针对某个DataNode(NameNode会进行选择DataNode),所以线程阻塞的压力是会分摊的。NIO还是擅长小数据量的RPC请求,能接受百万客户端的连接

NIO

NIO中有三个重要组件 : Buffer(ByteBuffer主要使用)、Channel(双向通道,可读可写)和Selector(多路复用选择器)

  1. Buffer
    常用的就是 ByteBuffer,缓冲池,可以作为channel写的单位,也可以接受channel读取的返回里面重要的属性 :position、capacity、flip、limit和hasRemain

    每个channel都需要记录可能切分的消息,因为ByteBuffer不能被多个channel使用,因此需要为每个channel维护一个独立的ByteBuffer。ByteBuffer不能太大,比如一个ByteBuffer 1M的话,需要支持百万连接要1TB内存,因此需要设计大小可变的ByteBuffer
    1、首先分配一个较小的buffer,比如4k,如果发现不够的话,再分配8kb的buffer,将4kb buffer内容拷贝到8kb buffer,有点是消息连续容易处理,缺点是数据拷贝耗费性能
    2、多个数组组成buffer,一个数组不够,把多出来的内容写入新的数组,缺点不连续解析复杂,有点避免了拷贝引起的性能损耗

  2. FileChannel
    FileChannel在同一个JVM中是线程安全的,多个线程写也没有问题,但是在不同的JVM中同时写一个文件就会有问题需要的是 FileLock对文件进行加锁,有独占锁和共享锁
    channel.lock(0, Integer.MAX_VALUE, true),可以锁一定的区间

    RandomAccessFile 可以支持读写文件,Channel 本身是支持读写的,只是看源头是不是支持读写,比如说FileInputStream流获取的channel只能支持读,RandomAccessFile获取的流支持读写

    channel.force(true); 强制将os cache数据刷入到磁盘上

    from.transferTo(position,count,dest);从from channel写到dest channel,从position 开始写,写了count长度
    比如说从本地文件向网络中进行传输
    to.transferFrom(src,position,count); 比如说从网络中写到本地文件,from就是从外界到,src读取,写到to中

    transferTo & transferFrom 底层使用的是零拷贝,零拷贝简单来说其实不走应用层数据复制,但是也是有数据复制的,是在Linux内核层

  3. Selector & SocketChannel
    服务端 ServerSocketChannel
    是通过 ServerSocketChannel和Selector来获取多个连接,每个连接是一个SocketChannel
    将 ServerSocketChannel 注册到 Selector上,如果有连接,selector的select阻塞方法会有事件,生成SelectionKey,每个SelectionKey其实对应一个SocketChannel

    Selectionkey是可以attach对象的,也可以通过 Selectionkey 通过attachment进行对象的获取,这很重要,一般会创建一个对象并和SelectioKey进行关联

    照样是bind,监听OP_ACCEPT,进行isAcceptable、read和write事件(write事件是一次没有写完毕,继续要写)

    客户端 SocketChannel
    是进行connect,监听OP_CONNECT事件,isConnectable,read和write事件

    要注意,SelectionKey,每次迭代是需要删除的,否则重复请求,但是已经处理,就会有问题

    写数据的时候一定要注意,最好不要 while(buffer.hasRemaining()) 一直写,这样会阻塞网络带宽的,影响读取
    写一部分数据,然后关注SelectionKey.OP_WRITE事件,不断selector.select()继续写,写完毕取消写事件的关注

    socketChannel.write(buffer); 写一下,也不一定会把buffer中都写完毕
    if(buffer.hasRemaining()) {
        selectionKey.interestOps(selectionKey.interestOps() | SelectionKey.OP_WRITE);
        selectionKey.attach(buffer);
    }

3、零拷贝

传统IO问题
比如说要将本地磁盘文件往网络中写,磁盘 -> 内核缓冲区 -> 用户缓冲区 -> socket缓冲区 -> 网卡

读磁盘数据 : 用户态 -> 内核态
内核数据写到用户缓冲区 : 内核态 -> 用户态
网卡写数据 : 用户态 -> 内核态

4次数据复制,3次内核切换

通过DirectByteBuffer,MappedByteBuffer,为什么快?
因为他使用direct buffer的方式读写文件内容,称为内存映射。这种方式直接调用系统底层的缓存,没有JVM和系统之间的复制操作,所以效率大大的提升
将堆外内存映射到JVM内存中直接访问
减少一次数据拷贝,用户态与内核态的切换次数没有减少

Linux2.4
Java调用transferTo,要从Java程序的用户态到内核态,磁盘 -> 内核缓冲区 -> 网卡,一次内核切换,两次数据复制

4、Socket参数

SocketChannel参数
SO_RCVBUF和SO_SNDBUF : Socket参数,TCP数据接收缓冲区大小,发送和接受缓冲区,128kb或者256kb
CONNECT_TIMEOUT_MILLIS : 用户在客户端建立连接时,如果在指定毫秒内无法建立连接,会抛出timeout异常
TCP_NODELAY TCP参数,立即发送数据,默认值为Ture(关闭nagle算法)
SO_KEEPALIVE Socket参数,连接保活,默认值为False。启用该功能时,TCP会主动探测空闲连接的有效性(2个小时)
SO_REUSEADDR : 其实就是比如说ServerSocketChannel连接关闭了,此时跟其他客户端的连接都处于一个timeout状态,重启Netty Server,如果设置了
SO_REUSEADDR 为 true,则会让ServerSocketChannel重新地址端口绑定,否则失败

ServerSocketChannel参数
SO_BACKLOG Socket参数,服务端接受连接的队列长度,如果队列已满,客户端连接将被拒绝。默认值,Windows为200,其他为128
TCP三次握手是在ACCEPT之前发生的
1、第一次握手,client发送SYN到server,状态修改为SYN_SEND,server收到,状态修改为SYN_REVD,并将请求放入sync queu队列
2、第二次握手,server回复 SYN + ACK 给client,client收到,状态修改为ESTABLISHED,并发送给ACK给server
3、第三次握手,server收到ack,状态修改为 ESTABLISHED,将请求从sync queue放入accept queue

所以现在出现了半连接队列和全连接队列
在Centos Linux下对应着 /proc/sys/net/ipv4/tcp_max_syn_backlog(512),/proc/sys/net/core/somaxconn(128)

SO_BACKLOG 设置的是全连接

TCP SYNC FLOOD恶意DOS攻击方式就是建立大量的半连接状态的请求,然后丢弃

如感兴趣,点赞加关注哦!

点赞
收藏
评论区
推荐文章
blmius blmius
4年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
美凌格栋栋酱 美凌格栋栋酱
7个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Jacquelyn38 Jacquelyn38
4年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Stella981 Stella981
3年前
Python之time模块的时间戳、时间字符串格式化与转换
Python处理时间和时间戳的内置模块就有time,和datetime两个,本文先说time模块。关于时间戳的几个概念时间戳,根据1970年1月1日00:00:00开始按秒计算的偏移量。时间元组(struct_time),包含9个元素。 time.struct_time(tm_y
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
Go 并发
Go并发并发指的是同时处理多个任务的能力。并行指的是并行处理多个任务的能力。并行不一定加快运行速度,因为并行组件之间可能需要互相通信。Go中使用协程,信道来处理并发。协程Go中主要通过协程实现并发。协程是与其他函数或方法一起并发运行的函数或方法,协程可以看作是轻量级线程,但是创建成本更小,我们经常
Wesley13 Wesley13
3年前
JavaWeb 调用接口
JavaWeb 如何调用接口CreateTime2018年4月2日19:04:29Author:Marydon1.所需jar包!(https://oscimg.oschina.net/oscnet/0f139
Wesley13 Wesley13
3年前
Selenium2 Python 自动化测试实战学习笔记(八)
Python多线程分布式和并行是完全不同的概念,分布式只负责将一个测试脚本可调用不同的远程环境来执行;并行强调“同时”的概念,它可以借助多线程或多进程技术并行来执行脚本技术。10.1单进程的时代        在单线程的时代,当处理器要处理多个任务时,必须要对这些任务排一下执行顺序并按照这个顺序
Python进阶者 Python进阶者
1年前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这