java网络通信不止UDP,TCP

预备知识

多线程

实现多线程

线程池

IO流

核心功能就是读和写
扩展功能对什么读写,怎么读写,如何优化读写

网络基础

IP

IP规定网络上所有的设备都必须有一个独一无二的IP地址,就好比是邮件上都必须注明收件人地址,邮递员才能将邮件送到。同理,每个IP信息包都必须包含有目的设备的IP地址,信息包才可以正确地送到目的地。同一设备不可以拥有多个IP地址,所有使用IP的网络设备至少有一个唯一的IP地址

OSI模型

TCP/IP模型

网络通信

基本思路:

  1. 存在着两台主机client,和server
  2. 两方通过ip和端口号确定对方位置
  3. 通过socket传递信息

TCP
TCP协议全称是传输控制协议是一种面向连接的、可靠的、基于字节流的传输层通信协议,由 IETF 的RFC 793定义。TCP 是面向连接的、可靠的流协议。,
• 它充分地实现了数据传输时各种控制功能,可以进行丢包时的重发控制,还可以对次序乱掉的分包进行顺序控制。而这些在 UDP 中都没有。
• 此外,TCP 作为一种面向有连接的协议,只有在确认通信对端存在时才会发送数据,从而可以控制通信流量的浪费。
• 根据 TCP 的这些机制,在 IP 这种无连接的网络上也能够实现高可靠性的通信( 主要通过检验和、序列号、确认应答、重发控制、连接管理以及窗口控制等机制实现)客户端创建socket,服务端接受socket,然后再通过socket传递信息
• 用到的主要的类:
• Socket
• ServerSocket
• I/O相关
• 类之间的关系
• InetSocketAddress继承SocketAddress,作用是存储ip地址和端口号
• Inet4Address与Inet6Address继承InetAddress,作用存储p地址
创建

private static class InetSocketAddressHolder {
        // The hostname of the Socket Address
        private String hostname;
        // The IP address of the Socket Address
        private InetAddress addr;
        // The port number of the Socket Address
        private int port;
}
//要端口和ip地址,InetAddress提供ip地址,port提供端口

分析

Socket构造方法做了些什么:

  1. 创建一个SocksSocketImpl实例
  2. 调用bind()绑定本地端口和地址(如果不为空)实际上是调用的SocksSocketImpl的bind方法
  3. 调用connect()连接远程主机和端口
    大多数会调用两个私有的构造函数,空实现以及传入Proxy构造对象时需要手动调用connect(),进行连接
    package-private一般是为了功能的实现
    ServerSocket构造方法做了些什么
  4. 创建一个SocksSocketImpl实例
  5. 调用bind()绑定本地监听端口(必须绑定)和地址以及确定backlog(无法绑定抛出异常)
  6. 参数port指定服务器要绑定的端口(即服务器要监听的端口,注意端口号为零时自动分配),参数backlog指定客户连接请求队列的长度,参数bindAddr指定服务器要绑定的IP地址。
  7. 默认构造方法:允许服务器在绑定到特定端口之前,先设置ServerSocket的一些选项。因为一旦服务器与特定端口绑定,有些选项就不能再改变了。
public ServerSocket(int port, int backlog, InetAddress bindAddr)

设置Socket选项
·TCP_NODELAY:表示立即发送数据。
·SO_RESUSEADDR:表示是否允许重用Socket所绑定的本地地址。
·SO_TIMEOUT:表示接收数据时的等待超时时间。
·SO_LINGER:表示当执行Socket的close()方法时,是否立即关闭底层的Socket。
·SO_SNDBUF:表示发送数据的缓冲区的大小。
·SO_RCVBUF:表示接收数据的缓冲区的大小。
·SO_KEEPALIVE:表示对于长时间处于空闲状态的Socket,是否要自动把它关闭。
·OOBINLINE:表示是否支持发送1字节的TCP紧急数据。
设置ServerSocket选项
·SO_TIMEOUT:表示等待客户连接的超时时间。
·SO_REUSEADDR:表示是否允许重用服务器所绑定的地址。
·SO_RCVBUF:表示接收数据的缓冲区的大小
服务
• 客户端:对象创建成功并且连接到远程主机后可以获取一些信息,已经通过getOutPutStream()与getInputStream()获取I/o流对象读入和写入信息
• 服务器:监听接口接受客户端的socket对象,再从中获取信息
关闭
可以以一个标志作为是否结束的判断(比如输入bye)
半关闭:
调用Socket的close()方法关闭Socket后,它的输出流和输入流也都被关闭。有的时候,可能仅仅希望关闭输出流或输入流之一
shutdownInput():关闭输入流。shutdownOutput():关闭输出流。
完全关闭:
为了确保关闭,要将socket.close放在finally中(实现了java.lang.Auto Closable接口。这意味着如果在try代码块中打开或创建了这些类的实例,那么即使程序没有显式地关闭它们,Java虚拟机也会在退出try代码块时自动关闭它们,释放相关的资源)
服务器同时与多个客户端连接
方式1:为每个客户分配一个工作线程。
方式2:创建一个线程池,由其中的工作线程来为客户服务.
方式3:利用JDK的Java类库中现成的线程池,由它的工作线程来为客户服务。
单独分配线程

  1. 主线程负责接收客户端的连接,创造工作线程负责通信
  2. 每个线程负责一个客户端
    自定义线程池
    使用jdk内置线程池
    java.util.concurrent
    线程池的缺点
    1.死锁
    2.系统资源不足
    3.并发错误
    4.线程泄漏
    5.任务过载

UDP

• UDP 不提供复杂的控制机制,利用 IP 提供面向无连接的通信服务。
• 并且它是将应用程序发来的数据在收到的那一刻,立即按照原样发送到网络上的一种机制。即使是出现网络拥堵的情况,UDP 也无法进行流量控制等避免网络拥塞行为。
• 此外,传输途中出现丢包,UDP 也不负责重发。
• 甚至当包的到达顺序出现乱序时也没有纠正的功能。
• 如果需要以上的细节控制,不得不交由采用 UDP 的应用程序去处理。
• UDP 常用于一下几个方面:1.包总量较少的通信(DNS、SNMP等);2.视频、音频等多媒体通信(即时通信);3.限定于 LAN 等特定网络中的应用通信;4.广播通信(广播、多播)。
• 客户端通过DataPacket保存信息,DataSocket发送packet信息,服务端通过监听端口接受Packet

DataPacket

• 发送信息的packet必须要指定地址和端口,接受信息的packet则不必指定
• 数据报中只能存放字节形式的数据,在发送方,需要把其他格式的数据转换为字节序列,在接收方,需要把字节序列转换为原来格式的数据(可以利用ByteArrayOutputStream和DataOuputStream)
• 可以利用ByteArrayInputStream和DataInputStream来把字节序列转换为原来格式的数据

DataSocket

DatagramSocket类负责接收和发送数据报。在客户程序中,一般由操作系统为DatagramSocket类分配本地端口,这种端口也被称为匿名端口。在服务器程序中,一般由程序显式地为DatagramSocket类指定本地端口。

• 每个DatagramSocket对象都会与一个本地端口绑定(在构造方法中就自动绑定,不提供则随机分配),在此端口监听发送过来的数据报。在recieve()方法中还会进一步检查.
• connect()是可选的 ,但与TCP的连接不一样,前者不建立TCP意义上的连接,而是限制当前DatagramSocket或DatagramChannel只对参数指定的远程主机和UDP端口收发数据报。

组播

单播:提供点对点的通信。发送者每次发送的数据有着唯一的目的地址,只被一个接收者接收。前面介绍的TCP Socket和UDP Socket都只支持单播。
组播:发送者每次发送的数据可以被小组内的所有接收者接收。
广播:发送者每次发送的数据可以被传播范围内的所有接收者接收

原理

组播地址:
组播组内的所有主机共享同一个地址,这种地址被称为组播地址,组播地址是范围在224.0.0.0 ~239.255.255.255之间的IP地址。此范围内的所有地址的前4个二进制位都是“1110”。组播地址也被称为D类IP地址,
使用UDP:
大多数组播数据为音频或视频,这些数据一般都很大,即便部分数据在传输途中被丢失,接收方也仍然能识别信号。因此组播数据通过UDP发送,虽然不可靠,但比面向连接的TCP的传输速度快3倍以上。
TTL(Time to live)
这也是与单播UDP不同的地方

好处

如图:在从主机1到路由器2的途中数据只需要传输一次,(单播对数据进行了不必要的复制,浪费了许多网络带宽。如果采用组播,则可以大大提高传输效率,路由器会动态决定组播数据的路由,只在必要时才复制数据)。

实现

java.net.MulticastSocket具有组播的功能,它是DatagramSocket的子类

非阻塞通信

什么是阻塞

放弃CPU,暂停运行,只有等到导致阻塞的原因消除,才能恢复运行;或者被其他线程中断,该线程会退出阻塞状态,并且抛出InterruptedException(比如烧水,一直等水烧开后再做其他事)

阻塞的原因

线程阻塞

1.线程执行了Thread.sleep(int n)方法,线程放弃CPU,睡眠n ms,然后恢复运行。
2.线程要执行一段同步代码,由于无法获得相关的同步锁,只好进入阻塞状态,等到获得了同步锁,才能恢复运行。
3. 线程执行了一个对象的wait()方法,进入阻塞状态,只有等到其他线程执行了该对象的notify()或notifyAll() 方法,才可能将其唤醒。
4. 线程执行I/O操作或进行远程通信时,会因为等待相关的资源而进入阻塞状态。例如当线程执行System.in.read() 方法时,如果用户没有向控制台输入数据,则该线程会一直等读到了用户的输入数据才从read()方法返回。

客户端阻塞

  1. 请求与服务器建立连接时,即当线程执行Socket的带参数的构造方法,或执行Socket的connect()方法时,会进 入阻塞状态,直到连接成功,此线程才从Socket的构造方法或connect()方法返回。
  2. 线程从Socket的输入流读入数据时,如果没有足够的数据,就会进入阻塞状态,直到读到了足够的数据,或者到达 输入流的末尾,或者出现了异常,才从输入流的read()方法返回或异常中断.
  3. 输入流中有多少数据才算足够呢?这要看线程执行的read()方法的类。
    (1)int read():只要输入流中有1字节,就算足够。
    (2)int read(byte[] buff):只要输入流中的字节数目与参数buff数组的长度相同,就算足够。
    (3)String readLine():只要输入流中有1行字符串,就算足够。
  4. 线程向Socket的输出流写一批数据时,可能会进入阻塞状态,等到输出了所有的数据,或者出现异常,才从输出流 的write()方法返回或异常中断。
  5. 如果调用Socket的setSoLinger()方法设置了关闭Socket的延迟时间,那么当线程执行Socket的close()方法 时,会进入阻塞状态,直到底层Socket发送完所有剩余数据,或者超过了setSoLinger()方法设置的延迟时间,才从close()方法返回。

服务端阻塞

  1. 线程执行ServerSocket的accept()方法,等待客户的连接,直到接收到了客户连接,才从accept()方法返回。
  2. 线程从Socket的输入流读入数据时,如果输入流没有足够的数据,就会进入阻塞状态。
  3. 线程向Socket的输出流写一批数据时,可能会进入阻塞状态,等到输出了所有的数据,或者出现异常,才从输出流 的write()方法返回或异常中断。

实现

java.nio.channels包提供了支持非阻塞通信的类

·ServerSocketChannel:ServerSocket的替代类,支持阻塞通信与非阻塞通信。
·SocketChannel:Socket的替代类,支持阻塞通信与非阻塞通信。
·Selector:为ServerSocketChannel监控接收连接就绪事件,为 SocketChannel监控连接就绪、读就绪和写就绪事件。
·SelectionKey:代表ServerSocketChannel以及SocketChannel向Selector注册事件的句柄。当一个SelectionKey对象位于Selector对象的selected-keys集合中,就表示与这个SelectionKey对象相关的事件发生了。

posted @ 2020-11-03 07:58  茕祇  阅读(64)  评论(0编辑  收藏  举报