2021-01-19

java.io流

IO

参考大佬的博客,写得很详细:

https://www.cnblogs.com/sxkgeek/p/9488703.html

https://www.cnblogs.com/restart30/p/8252109.html

https://blog.csdn.net/xxb2008/article/details/42424105#

https://blog.csdn.net/anxpp/article/details/51512200 感觉这个代码最贴近现实应用

BIO(同步阻塞 IO)

客户端一个请求对应一个线程。客户端上来一个请求(最开始的连接以及后续的IO请求),服务端新建一个线程去处理这个请求,由于线程总数是有限的(操作系统对线程总数的限制或者线程池的大小),所以,当达到最大值时给客户端的反馈就是无法响应,阻塞体现在服务端接收客户端连接请求被阻塞了,还有一种阻塞是在单线程处理某一个连接时,需要一直等待IO操作完成。同步体现在单个线程处理请求时调用read(write)方法需等待读取(写)操作完成才能返回。

缺点

  1. 默认单线程有阻塞,不支持并发。(两个阻塞
  2. 设置成多线程的话,浪费资源。(有多少连接,就需要多少线程,并且大部分只是连接但是不做实际操作)
ServerSocket server = null;  Socket socket = null;  InputStream inputStream = null;  try {   int port=8008;   server = new ServerSocket(port);   System.out.println("wait connection..");   //一直监听   while (true){     //阻塞一:等待连接 xxx.accept()    socket = server.accept();    System.out.println("connected..");    // 建立好连接后,从socket中获取输入流,并建立缓冲区进行读取    inputStream = socket.getInputStream();    byte[] bytes = new byte[1024];    int len;    //阻塞二:等待发送内容 xx.read()    System.out.println("wait data..");    len = inputStream.read(bytes);    System.out.println(new String(bytes,0,len, StandardCharsets.UTF_8));    System.out.println("data accept complete..");   }  }

NIO(同步非阻塞 IO)

客户端一个IO请求对应一个线程。过程是这样的,每个客户端一开始上来的连接会注册到selector中,selector会轮询注册上来的连接是否有IO请求,如果有IO请求,就创建一个线程处理该连接上的该次请求。非阻塞体现在服务端能够无限量(相对于BIO)的接收客户端的连接请求。同步体现在单个线程处理请求时调用read(write)方法需等待读取(写)操作完成才能返回。这种模式下,如果后端应用处理遇到资源争夺(数据库操作)而阻塞等,为提高请求的处理速度,可以在后端设立资源池或队列等,把对应的请求数据以及现场(哪个连接的哪个请求等)放入队列,前台线程立即返回处理别的IO请求。

改进一:

  1. 单线程情况下,解阻塞。ServerSocketChannel
  2. 存储链接信息,遍历是否有客户端发送消息

仍然具有缺点:性能瓶颈。连接数量超级多的时候,遍历很费时间。优化方案:由jvm交给os操作系统执行。

int port = 8008;//类似于ServerSocket,可以解阻塞ServerSocketChannel server = ServerSocketChannel.open();//形成端口SocketAddress socketAddress = new InetSocketAddress(port);//绑定端口server.bind(socketAddress);//设置不会阻塞server.configureBlocking(false);while (true){ //获取消息 for(SocketChannel socketChannel:list){  int len = socketChannel.read(byteBuffer);  //有消息  if(len>0){   System.out.println("=====reading"+ len);   //把消息从buffer中读取到byte   byteBuffer.flip();   //注意这里byte的长度不是1024了。而是读取多少,设置多少长度。或者改为一个个字节的读取   byte[] bytes = new byte[len];   while (byteBuffer.remaining()>0){    byteBuffer.get(bytes);   }   String message = new String(bytes, StandardCharsets.UTF_8);   System.out.println(message);  }else{   //如果断开连接 应该移除   //list.remove(socketChannel);  } } SocketChannel accept = server.accept(); //放入list,统一遍历去获取消息 if(accept!=null){  System.out.println("========加入一个连接");  list.add(accept); }}

改进二:

  1. 交给底层操作系统执行(c,c++执行)
    1. 即jni实现了java调用os操作系统函数的功能。 JNI是Java Native Interface的缩写
    2. java关键字:native ,如果有native则会调用操作系统的方法。
    3. 常见的有selector, epoll。 重点是epoll
  2. 注意:不同系统源码不一样。(所以下载的时候才会有版本区分,至少是原因之一吧)
    1. windows版本的java调用的是select
    2. linux版本java调用的是epoll
ServerSocketChannel serverChannel = ServerSocketChannel.open();// 设置通道为非阻塞serverChannel.configureBlocking(false);// 将该通道 对应的ServerSocket绑定到port端口// 注意:不是绑定ServerSocketChannel,而是绑定其对应的ServerSocketserverChannel.socket().bind(new InetSocketAddress(port));// 获得一个通道管理器Selector selector = Selector.open();//将通道管理器和该通道绑定,并为该通道注册SelectionKey.OP_ACCEPT事件,注册该事件后,//当该事件到达时,selector.select()会返回,如果该事件没到达selector.select()会一直阻塞。serverChannel.register(selector, SelectionKey.OP_ACCEPT);while (true) {   //当注册的事件到达时,方法返回;否则,该方法会一直阻塞   selector.select();   // 获得selector中选中的项的迭代器,选中的项为注册的事件   Iterator ite = this.selector.selectedKeys().iterator();   while (ite.hasNext()) {    SelectionKey key = (SelectionKey) ite.next();    // 删除已选的key,以防重复处理    ite.remove();    // 客户端请求连接事件    if (key.isAcceptable()) {     ServerSocketChannel server = (ServerSocketChannel) key.channel();     // 获得和客户端连接的通道:真正和客户端通信的通道     // 完成该操作意味着完成TCP三次握手,TCP物理链路正式建立     SocketChannel responeceChannel = server.accept();     // 设置成非阻塞     responeceChannel.configureBlocking(false);     responeceChannel.register(this.selector, SelectionKey.OP_READ);     // 获得了可读的事件    } else if (key.isReadable()) {     System.out.println("===========read key:"+key);     SocketChannel channel = (SocketChannel) key.channel();     // 创建读取的缓冲区     ByteBuffer buffer = ByteBuffer.allocate(512);     StringBuilder sb = new StringBuilder();     //如果长度超过byteBuffer一次性读取完     while ( channel.read(buffer)>0){      buffer.flip();      sb.append(new String(buffer.array())).append("+");     }     System.out.println("received : " + sb.toString());     }    }  }

NIO方法

ServerSocketChannel

类似于ServerSocket,重点在于提供了非阻塞的方法。configureBlocking(false).则建立的连接不是阻塞的

SocketChannel

类似于Socket。用于客户端和服务器端的交互。

并且区别在于socket通过获取inputStream和outputStream来实现交互(理解成单向的),

但是SocketChannel是双向的。可以并且只能通过xxxBuffer实现读写。

ByteBuffer我理解的是一个容器类。里面有一个很有意思的方法:flip,用于切换读写状态。

AIO(NIO2:异步非阻塞IO)

客户端一个IO请求对应一个线程。

过程同NIO,只是在读写IO时略有差异,对于read,方法调用后会立即返回,返回对应中有个回调方法,调用时java会告知操作系统缓冲区大小以及地址,操作系统把流里面的内容读入缓冲区后回调刚刚read返回的回调方法。对应write,方法调用后会立即返回,返回对应中有个回调方法,调用时将数据放入缓存区,操作系统往流里面写完数据后同样会回调刚刚write返回的回调方法。

阻塞是因为此时是通过select系统调用来完成的,而select函数本身的实现方式是阻塞的,而采用select函数有个好处就是它可以同时监听多个文件句柄,从而提高系统的并发性请求。

异步步体现在单个线程处理请求时调用read(write)方法会立即返回,返回对象有回调方法,底层OS完成IO操作后会回调该方法。

异步主要体现

  • AsynchronousSocketChannel
    • 类似SocketChannel
  • AsynchronousServerSocketChannel
    • 类似ServerSocketChannel
  • AsynchronousFileChannel
  • AsynchronousDatagramChannel

在AIO socket编程中,服务端通道是AsynchronousServerSocketChannel,这个类提供了一个open()静态工厂,一个bind()方法用于绑定服务端IP地址(还有端口号),另外还提供了accept()用于接收用户连接请求。在客户端使用的通道是AsynchronousSocketChannel,这个通道处理提供open静态工厂方法外,还提供了read和write方法。

在AIO编程中,发出一个事件(accept read write等)之后要指定事件处理类(回调函数),AIO中的事件处理类是CompletionHandler<V,A>,这个接口定义了如下两个方法,分别在异步操作成功和失败时被回调。

void completed(V result, A attachment);

void failed(Throwable exc, A attachment);

demo

开启和绑定

AsynchronousServerSocketChannel serverChannel;serverChannel = AsynchronousServerSocketChannel.open().bind(new InetSocketAddress(port), 100);	//port是端口号serverChannel.accept(this, new AcceptHandler());	//new AcceptHandler是实现的具体回调

回调方法需要具体实现

accept回调

 /**  * accept到一个请求时的回调  */private class AcceptHandler implements CompletionHandler<AsynchronousSocketChannel, AIOServer> {  @Override  public void completed(final AsynchronousSocketChannel client, AIOServer attachment) {   try {    System.out.println("远程地址:" + client.getRemoteAddress());    //tcp各项参数    client.setOption(StandardSocketOptions.TCP_NODELAY, true);    client.setOption(StandardSocketOptions.SO_SNDBUF, 1024);    client.setOption(StandardSocketOptions.SO_RCVBUF, 1024);    if (client.isOpen()) {     System.out.println("client.isOpen:" + client.getRemoteAddress());     final ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);     buffer.clear();     client.read(buffer, client, new ReadHandler(buffer));    }   } catch (Exception e) {    e.printStackTrace();   } finally {    attachment.serverChannel.accept(attachment, this);// 监听新的请求,递归调用。   }  }  @Override  public void failed(Throwable exc, AIOServer attachment) {   try {    exc.printStackTrace();   } finally {    attachment.serverChannel.accept(attachment, this);// 监听新的请求,递归调用。   }  } }

read回调

 /**  * Read到请求数据的回调  */ private class ReadHandler implements CompletionHandler<Integer, AsynchronousSocketChannel> {  private ByteBuffer buffer;  public ReadHandler(ByteBuffer buffer) {   this.buffer = buffer;  }  @Override  public void completed(Integer result, AsynchronousSocketChannel attachment) {   try {    if (result < 0) {// 客户端关闭了连接     AIOServer.close(attachment);    } else if (result == 0) {     System.out.println("空数据"); // 处理空数据    } else {     // 读取请求,处理客户端发送的数据     buffer.flip();     CharBuffer charBuffer = AIOServer.decoder.decode(buffer);     System.out.println(charBuffer.toString()); //接收请求     //响应操作,服务器响应结果     buffer.clear();     String res = "HTTP/1.1 200 OK" + "\r\n\r\n" + "hellworld";     buffer = ByteBuffer.wrap(res.getBytes());     attachment.write(buffer, attachment, new WriteHandler(buffer));//Response:响应。    }   } catch (Exception e) {    e.printStackTrace();   }  }  @Override  public void failed(Throwable exc, AsynchronousSocketChannel attachment) {   exc.printStackTrace();   AIOServer.close(attachment);  } }

write回调

 /**  * Write响应完请求的回调  */ private class WriteHandler implements CompletionHandler<Integer, AsynchronousSocketChannel> {  private ByteBuffer buffer;  public WriteHandler(ByteBuffer buffer) {   this.buffer = buffer;  }  @Override  public void completed(Integer result, AsynchronousSocketChannel attachment) {   buffer.clear();   AIOServer.close(attachment);  }  @Override  public void failed(Throwable exc, AsynchronousSocketChannel attachment) {   exc.printStackTrace();   AIOServer.close(attachment);  } }

NETTY

参考

netty介绍

netty概述

reactor模式

新手入门 《重点推荐

源码进阶

优点

  1. 设计优雅:适用于各种传输类型的统一 API 阻塞和非阻塞 Socket;基于灵活且可扩展的事件模型,可以清晰地分离关注点;高度可定制的线程模型 - 单线程,一个或多个线程池;真正的无连接数据报套接字支持(自 3.1 起)。
  2. 使用方便:详细记录的 Javadoc,用户指南和示例;没有其他依赖项,JDK 5(Netty 3.x)或 6(Netty 4.x)就足够了。
  3. 高性能、吞吐量更高:延迟更低;减少资源消耗;最小化不必要的内存复制。
  4. 安全:完整的 SSL/TLS 和 StartTLS 支持。、

使用场景

  1. 互联网行业:在分布式系统中,各个节点之间需要远程服务调用,高性能的 RPC 框架必不可少,Netty 作为异步高性能的通信框架,往往作为基础通信组件被这些 RPC 框架使用。典型的应用有:阿里分布式服务框架 Dubbo 的 RPC 框架使用 Dubbo 协议进行节点间通信,Dubbo 协议默认使用 Netty 作为基础通信组件,用于实现各进程节点之间的内部通信。

  2. 游戏行业:无论是手游服务端还是大型的网络游戏,Java 语言得到了越来越广泛的应用。Netty 作为高性能的基础通信组件,它本身提供了 TCP/UDP 和 HTTP 协议栈。

  3. 非常方便定制和开发私有协议栈,账号登录服务器,地图服务器之间可以方便的通过 Netty 进行高性能的通信。

  4. 大数据领域:经典的 Hadoop 的高性能通信和序列化组件 Avro 的 RPC 框架,默认采用 Netty 进行跨界点通信,它的 Netty Service 基于 Netty 框架二次封装实现。

Reactor模式

使用netty,不得不了解reactor模式(别问我为什么知道 = =)

在处理web请求时,通常有两种体系结构,分别为:thread-based architecture(基于线程)、event-driven architecture(事件驱动)

高性能设计

Netty 作为异步事件驱动的网络,高性能之处主要来自于其 I/O 模型和线程处理模型,前者决定如何收发数据,后者决定如何处理数据

I/O 模型

线程处理模型

thread-based architecture

基于线程的体系结构通常会使用多线程来处理客户端的请求,每当接收到一个请求,便开启一个独立的线程来处理。

请求很多的时候,线程开销很大。造成服务器负担

img

event-driven architecture

事件驱动体系结构是目前比较广泛使用的一种。这种方式会定义一系列的事件处理器来响应事件的发生,并且将服务端接受连接与对事件的处理分离。其中,事件是一种状态的改变。比如,tcp中socket的new incoming connection、ready for read、ready for write。

img

reactor

reactor设计模式是event-driven architecture的一种实现方式,处理多个客户端并发的向服务端请求服务的场景。每种服务在服务端可能由多个方法组成。reactor会解耦并发请求的服务并分发给对应的事件处理器来处理。目前,许多流行的开源框架都用到了reactor模式,如:netty、node.js等,包括java的nio。

netty架构

img

模块组件

Bootstrap、ServerBootstrap

Bootstrap 类是客户端程序的启动引导类,ServerBootstrap 是服务端启动引导类

Channel和Pipeline

Netty 网络通信的组件,能够用于执行网络 I/O 操作

提供一下channel类型:

NioSocketChannel,异步的客户端 TCP Socket 连接。NioServerSocketChannel,异步的服务器端 TCP Socket 连接。NioDatagramChannel,异步的 UDP 连接。NioSctpChannel,异步的客户端 Sctp 连接。NioSctpServerChannel,异步的 Sctp 服务器端连接,这些通道涵盖了 UDP 和 TCP 网络 IO 以及文件 IO。

在 Netty 中每个 Channel 都有且仅有一个 ChannelPipeline 与之对应,它们的组成关系如下:

img

ChannelPipeline是一个双向链表。入站事件会从链表 head 往后传递到最后一个入站的 handler,出站事件会从链表 tail 往前传递到最前一个出站的 handler,两种类型的 handler 互不干扰。

HandlerAdapter适配器

ChannelInboundHandlerAdapter 用于处理入站 I/O 事件。ChannelOutboundHandlerAdapter 用于处理出站 I/O 操作。ChannelDuplexHandler 用于处理入站和出站事件。

Selector

Netty 基于 Selector 对象实现 I/O 多路复用,通过 Selector 一个线程可以监听多个连接的 Channel 事件。

当向一个 Selector 中注册 Channel 后,Selector 内部的机制就可以自动不断地查询(Select) 这些注册的 Channel 是否有已就绪的 I/O 事件(例如可读,可写,网络连接完成等),这样程序就可以很简单地使用一个线程高效地管理多个 Channel 。

ByteBuf

对比NIO.ByteBuffer的优势:

  1. Pooling (池化,这点减少了内存复制和GC,提升效率)
  2. 可以自定义缓冲类型
  3. 通过一个内置的复合缓冲类型实现零拷贝
  4. 扩展性好,比如 StringBuffer
  5. 不需要调用 flip()来切换读/写模式
  6. 读取和写入索引分开
  7. 方法链
  8. 引用计数

创建和释放

原理:通过release方法减去 heapBuffer 的使用计数,Netty 会自动回收 heapBuffer

手动获取与释放ByteBuf

获取Java 堆中的缓冲区

ByteBuf heapBuffer = ctx.alloc().heapBuffer();

释放缓冲区(不管手动还是自动,其原理都是release)

ReferenceCountUtil.release(heapBuffer );
自动获取和释放 ByteBuf
方式一:TailHandler 自动释放
方式二:SimpleChannelInboundHandler 自动释放
方式三:HeadHandler 自动释放
总结
  1. 入站处理流程中,如果对原消息不做处理,默认会调用 ctx.fireChannelRead(msg) 把原消息往下传,由流水线最后一棒 TailHandler 完成自动释放。
  2. 如果将原消息转化为新的消息并调用 ctx.fireChannelRead(newMsg)往下传,那必须把原消息release掉。
  3. 或者是继承SimpleChannelInboundHandler自动处理。
  4. 出站处理过程中,申请分配到的 ByteBuf,通过 HeadHandler 完成自动释放。
  5. 多层的异常处理机制,有些异常处理的地方不一定准确知道ByteBuf之前释放了没有,可以在释放前加上引用计数大于0的判断避免异常;
  6. 总之,只要是在传递过程中,没有传递下去的ByteBuf就需要手动释放,避免不必要的内存泄露

缓冲区 Allocator 分配器

Netty提供了ByteBufAllocator的两种实现:PoolByteBufAllocator和UnpooledByteBufAllocator。前者将ByteBuf实例放入池中,提高了性能,将内存碎片减少到最小。这个实现采用了一种内存分配的高效策略,称为 jemalloc。它已经被好几种现代操作系统所采用。后者则没有把ByteBuf放入池中,每次被调用时,返回一个新的ByteBuf实例

缓冲区内存的类型

使用模式描述优点劣势
堆缓冲区数据存存储在JVM的堆空间中,又称为支撑数组,通过 hasArray 来判断是不是在堆缓冲区中没使用池化情况下能提供快速的分配和释放发送之前都会拷贝到直接缓冲区
直接缓冲区存储在物理内存中能获取超过jvm堆限制大小的空间; 写入channel比堆缓冲区更快释放和分配空间昂贵(使用系统的方法) ; 操作时需要复制一次到堆上
复合缓冲单个缓冲区合并多个缓冲区表示操作多个更方便-

使用:

//创建复合缓冲区CompositeByteBuf compBuf = Unpooled.compositeBuffer();//创建堆缓冲区ByteBuf heapBuf = Unpooled.buffer(8);//创建直接缓冲区ByteBuf directBuf = Unpooled.directBuffer(16);

ByteBuf 的四个逻辑部分

第一个部分是已经丢弃的字节,这部分数据是无效的;

第二部分是可读字节,这部分数据是 ByteBuf 的主体数据, 从 ByteBuf 里面读取的数据都来自这一部分;

第三部分的数据是可写字节,所有写到 ByteBuf 的数据都会写到这一段。

第四部分的字节,表示的是该 ByteBuf 最多还能扩容的大小。

img

ByteBuf 的三个指针

  • readerIndex(读指针)

    每读取一个字节,readerIndex 自增1 。一旦 readerIndex 与 writerIndex 相等,ByteBuf 不可读 。

  • writerIndex(写指针)

    每写一个字节,writerIndex 自增1。一旦增加到 writerIndex 与 capacity() 容量相等,表示 ByteBuf 已经不可写了

  • maxCapacity(最大容量)

    指示可以 ByteBuf 扩容的最大容量。当向 ByteBuf 写数据的时候,如果容量不足,可以进行扩容。

    capacity()扩容超过最大限度 maxCapacity 就会报错

img

ByteBuf 的引用计数

如果计数为0,则需要看是pooled还是Unpooled,如果是pool则放入池子,反之由GC回收。

  • 默认情况下,当创建完一个 ByteBuf 时,它的引用为1
  • 每次调用 retain()方法, 它的引用就加 1
  • 每次调用 release() 方法,是将引用计数减 1

ByteBuf 的浅层复制

ByteBuf 的浅层复制分为两种,有切片slice 浅层复制,和duplicate 浅层复制。

​ slice 只copy可读部分

​ duplicate copy全部

slice 切片浅层复制
public static void testSlice() { ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer(9, 100); print("===allocate ByteBuf(9, 100)===", buffer); buffer.writeBytes(new byte[]{1, 2, 3, 4}); print("===writeBytes(1,2,3,4)===", buffer); ByteBuf buffer1= buffer.slice(); print("===buffer slice===", buffer1);}

输出:

===========allocate ByteBuf(9, 100)============capacity(): 9maxCapacity(): 100readerIndex(): 0readableBytes(): 0isReadable(): falsewriterIndex(): 0writableBytes(): 9isWritable(): truemaxWritableBytes(): 100===========writeBytes(1,2,3,4)============capacity(): 9maxCapacity(): 100readerIndex(): 0readableBytes(): 4isReadable(): truewriterIndex(): 4writableBytes(): 5isWritable(): truemaxWritableBytes(): 96===========buffer slice============capacity(): 4maxCapacity(): 4readerIndex(): 0readableBytes(): 4isReadable(): truewriterIndex(): 4writableBytes(): 0isWritable(): falsemaxWritableBytes(): 0
duplicate() 浅层复制

工作架构图

img

BossGroup NioEventLoop 循环执行的任务包含 3 步:

1)轮询 Accept 事件;

2)处理 Accept I/O 事件,与 Client 建立连接,生成 NioSocketChannel,并将 NioSocketChannel 注册到某个 Worker NioEventLoop 的 Selector 上;

3)处理任务队列中的任务,runAllTasks。任务队列中的任务包括用户调用 eventloop.execute 或 schedule 执行的任务,或者其他线程提交到该 eventloop 的任务。

WorkerGroup NioEventLoop 循环执行的任务包含 3 步:

1)轮询 Read、Write 事件;

2)处理 I/O 事件,即 Read、Write 事件,在 NioSocketChannel 可读、可写事件发生时进行处理;

3)处理任务队列中的任务,runAllTasks。

通信协议(补充知识)

TCP

优点:

可靠,稳定 TCP的可靠体现在TCP在传递数据之前,会有三次握手来建立连接,而且在数据传递时,有确认、窗口、重传、拥塞控制机制,在数据传完后,还会断开连接用来节约系统资源。

缺点:

慢,效率低,占用系统资源高,易被攻击 TCP在传递数据之前,要先建连接,这会消耗时间,而且在数据传递时,确认机制、重传机制、拥塞控制机制等都会消耗大量的时间,而且要在每台设备上维护所有的传输连接,事实上,每个连接都会占用系统的CPU、内存等硬件资源。 而且,因为TCP有确认机制、三次握手机制,这些也导致TCP容易被人利用,实现DOS、DDOS、CC等攻击。

使用场景:

当对网络通讯质量有要求的时候,比如:整个数据要准确无误的传递给对方,这往往用于一些要求可靠的应用,比如HTTP、HTTPS、FTP等传输文件的协议,POP、SMTP等邮件传输的协议。 在日常生活中,常见使用TCP协议的应用如下: 浏览器,用的HTTP FlashFXP,用的FTP Outlook,用的POP、SMTP Putty,用的Telnet、SSH QQ文件传输 …………

UDP

优点:

快,比TCP稍安全 UDP没有TCP的握手、确认、窗口、重传、拥塞控制等机制,UDP是一个无状态的传输协议,所以它在传递数据时非常快。没有TCP的这些机制,UDP较TCP被攻击者利用的漏洞就要少一些。但UDP也是无法避免攻击的,比如:UDP Flood攻击

缺点:

不可靠,不稳定 因为UDP没有TCP那些可靠的机制,在数据传递时,如果网络质量不好,就会很容易丢包。

使用场景:

当对网络通讯质量要求不高的时候,要求网络通讯速度能尽量的快,这时就可以使用UDP。 比如,日常生活中,常见使用UDP协议的应用如下: QQ语音 QQ视频 TFTP

RTP

是用于Internet上针对多媒体数据流的一种传输层协议。它是建立在 UDP 协议上的.
RTP 不像http和ftp可完整的下载整个影视文件,它是以固定的数据率在网络上发送数据,客户端也是按照这种速度观看影视文件,当影视画面播放过后,就不可以再重复播放

RTCP

RTSP

RTSP 是一种双向实时数据传输协议,它允许客户端向服务器端发送请求,如回放、快进、倒退等操作

WebRTC

RTMP(Real Time Messaging Protocol)

基于TCP不会丢失

HLS

HLS 点播,基本上就是常见的分段HTTP点播,不同在于,它的分段非常小









原文转载:http://www.shaoqun.com/a/512276.html

跨境电商:https://www.ikjzd.com/

宝付:https://www.ikjzd.com/w/539

aca:https://www.ikjzd.com/w/1371


IO参考大佬的博客,写得很详细:https://www.cnblogs.com/sxkgeek/p/9488703.htmlhttps://www.cnblogs.com/restart30/p/8252109.htmlhttps://blog.csdn.net/xxb2008/article/details/42424105#https://blog.csdn.net/anxpp/article
米谷:米谷
pocket:pocket
2020年春节北京逛庙会要注意什么?:2020年春节北京逛庙会要注意什么?
2020年春运返程票什么时候买?:2020年春运返程票什么时候买?
海蜇 - :海蜇 -

No comments:

Post a Comment