1、软件结构

C/S结构 :全称为Client/Server结构,是指客户端和服务器结构。常见程序有QQ、迅雷等软件。

  • 特点: 客户端和服务器是分开的,需要下载客户端,对网络要求相对低, 服务器压力小,开发和维护成本高,相对稳定

B/S结构 :全称为Browser/Server结构,是指浏览器和服务器结构。常见浏览器有谷歌、火狐等。

特点:没有客户端,只有服务器,不需要下载客户端,直接通过浏览器访问, 对网络要求相对高, 服务器压力很大,相对不稳定,开发和维护成本低,

两种架构各有优势,但是无论哪种架构,都离不开网络的支持。网络编程,就是在一定的协议下,实现两台计算机在网络中的通信的程序。

2、网络编程三要素

  • 协议
  • IP地址
  • 端口号

协议

**网络通信协议:**通信协议是计算机必须遵守的规则,只有遵守这些规则,计算机之间才能进行通信。这就好比在道路中行驶的汽车一定要遵守交通规则一样,协议中对数据的传输格式、传输速率、传输步骤等做了统一规定,通信双方必须同时遵守,最终完成数据交换。

java.net 包中提供了两种常见的网络协议的支持:

  • TCP:传输控制协议 (Transmission Control Protocol)。TCP协议是面向连接的通信协议,即传输数据之前,在发送端和接收端建立逻辑连接,然后再传输数据,它提供了两台计算机之间可靠无差错的数据传输。
  • TCP协议特点: 面向连接,传输数据安全,传输速度低

完成三次握手,连接建立后,客户端和服务器就可以开始进行数据传输了。由于这种面向连接的特性,TCP协议可以保证传输数据的安全,所以应用十分广泛,例如下载文件、浏览网页等

  • UDP:用户数据报协议(User Datagram Protocol)。UDP协议是一个面向无连接的协议。传输数据时,不需要建立连接,不管对方端服务是否启动,直接将数据、数据源和目的地都封装在数据包中,直接发送。每个数据包的大小限制在64k以内。它是不可靠协议,因为无连接,所以传输速度快,但是容易丢失数据。日常应用中**,例如视频会议、QQ聊天等。**
  • UDP特点: 面向无连接,传输数据不安全,传输速度快

IP地址

  • IP地址:指互联网协议地址(Internet Protocol Address),俗称IP。IP地址用来给一个网络中的计算机设备做唯一的编号。假如我们把“个人电脑”比作“一台电话”的话,那么“IP地址”就相当于“电话号码”。

**IP地址分类 **

  • IPv4:是一个32位的二进制数,通常被分为4个字节,表示成a.b.c.d 的形式,例如192.168.65.100 。其中a、b、c、d都是0~255之间的十进制整数,那么最多可以表示42亿个。

  • IPv6:由于互联网的蓬勃发展,IP地址的需求量愈来愈大,但是网络地址资源有限,使得IP的分配越发紧张。有资料显示,全球IPv4地址在2011年2月分配完毕。

    为了扩大地址空间,拟通过IPv6重新定义地址空间,采用128位地址长度,每16BIT一组,分成8组十六进制数,表示成ABCD:EF01:2345:6789:ABCD:EF01:2345:6789,号称可以为全世界的每一粒沙子编上一个网址,这样就解决了网络地址资源数量不够的问题。

常用命令

  • 查看本机IP地址,在控制台输入:
ipconfig

检查网络是否连通

ping IP地址

特殊的IP地址

  • 本机IP地址:127.0.0.1localhost

端口号

网络的通信,本质上是两个进程(应用程序)的通信。每台计算机都有很多的进程,那么在网络通信时,如何区分这些进程呢?

如果说IP地址可以唯一标识网络中的设备,那么端口号就可以唯一标识设备中的进程(应用程序)了。

  • **端口号:用两个字节表示的整数,它的取值范围是0~65535。其中,0~1023之间的端口号用于一些知名的网络服务和应用,普通的应用程序需要使用1024以上的端口号。如果端口号被另外一个服务或应用所占用,会导致当前程序启动失败。

利用协议+IP地址+端口号 三元组合,就可以标识网络中的进程了,那么进程间的通信就可以利用这个标识与其它进程进行交互。

3、InetAddress类

InetAddress类的概述

  • 一个该类的对象就代表一个IP地址对象。

InetAddress类的方法

  • static InetAddress getLocalHost() 获得本地主机IP地址对象
  • static InetAddress getByName(String host) 根据IP地址字符串或主机名获得对应的IP地址对象
  • String getHostName();获得主机名
  • String getHostAddress();获得IP地址字符串
public class InetAddressDemo01 {public static void main(String[] args) throws Exception {// 获取本地ip地址对象InetAddress ip1 = InetAddress.getLocalHost();System.out.println("ip1: "+ip1);// DESKTOP-U8Q5F96/192.168.0.100// 获取百度ip地址对象InetAddress ip2 = InetAddress.getByName("www.baidu.com");System.out.println("ip2:"+ip2);// www.baidu.com/182.61.200.7// 获得本地的主机名String hostName = ip1.getHostName();System.out.println("hostName:"+hostName);// DESKTOP-U8Q5F96// 获得本地的ip地址String hostAddress = ip1.getHostAddress();System.out.println("hostAddress:"+hostAddress);//192.168.0.100}
}

4、TCP通信程序

TCP协议是面向连接的通信协议,即在传输数据前先在发送端和接收器端建立逻辑连接,然后再传输数据。它提供了两台计算机之间可靠无差错的数据传输。TCP通信过程如下图所示:

TCP协议相关的类

  • Socket : 一个该类的对象就代表一个客户端程序。

    • Socket(String host, int port) 根据ip地址字符串和端口号创建客户端Socket对象
      * 注意事项:只要执行该方法,就会立即连接指定的服务器程序,如果连接不成功,则会抛出异常。
      如果连接成功,则表示三次握手通过。
    • OutputStream getOutputStream(); 获得字节输出流对象
    • InputStream getInputStream();获得字节输入流对象
    • void close();关闭Socket, 会自动关闭相关的流,关闭通过Socket获得流,也会关闭Socket
  • ServerSocket : 一个该类的对象就代表一个服务器端程序。
    • ServerSocket(int port); 根据指定的端口号开启服务器。
    • Socket accept(); 等待客户端连接并获得与客户端关联的Socket对象 如果没有客户端连接,该方法会一直阻塞
    • void close();关闭ServerSocket,一般不关闭

TCP通信案例

TCP客户端代码

/*
TCP客户端代码实现步骤* 创建客户端Socket对象并指定服务器地址和端口号* 调用Socket对象的getOutputStream方法获得字节输出流对象* 调用字节输出流对象的write方法往服务器端输出数据* 调用Socket对象的getInputStream方法获得字节输入流对象* 调用字节输入流对象的read方法读取服务器端返回的数据* 关闭Socket对象断开连接。
*/
public class Client {public static void main(String[] args) throws Exception{// 创建Socket对象,指定服务器ip和端口号Socket socket = new Socket("127.0.0.1",6666);while (true) {// 通过socket对象获得输出流OutputStream os = socket.getOutputStream();// 写出数据Scanner sc = new Scanner(System.in);System.out.println("请输入向服务器发送的数据:");String str = sc.nextLine();os.write(str.getBytes());// 通过Socket对象获得输入流InputStream is = socket.getInputStream();// 定义一个byte数组,用来存储读取到的字节数据byte[] bys = new byte[1024];int len = is.read(bys);// 打印数据System.out.println(new String(bys,0,len));}// 关闭流,释放资源//socket.close();}
}

服务端代码实现

/**TCP服务器端代码实现步骤* 创建ServerSocket对象并指定端口号(相当于开启了一个服务器)* 调用ServerSocket对象的accept方法等待客端户连接并获得对应Socket对象* 调用Socket对象的getInputStream方法获得字节输入流对象* 调用字节输入流对象的read方法读取客户端发送的数据* 调用Socket对象的getOutputStream方法获得字节输出流对象* 调用字节输出流对象的write方法往客户端输出数据* 关闭Socket和ServerSocket对象*/
public class Server {public static void main(String[] args) throws Exception{// 创建ServerSocket对象,并指定端口号ServerSocket ss = new ServerSocket(6666);// 调用accept()方法等待客户端连接,连接成功返回Socket对象Socket socket = ss.accept();while (true) {// 通过Socket对象获得输入流InputStream is = socket.getInputStream();// 定义一个byte数组,用来存储读取到的字节数据byte[] bys = new byte[1024];int len = is.read(bys);// 打印数据System.out.println(new String(bys,0,len));// 通过socket对象获得输出流OutputStream os = socket.getOutputStream();// 写出数据Scanner sc = new Scanner(System.in);System.out.println("请输入向客户端发送的数据:");String str = sc.nextLine();os.write(str.getBytes());}// 关闭资源//socket.close();//ss.close();// 服务器一般不关闭}
}

模拟B\S服务器
浏览器工作原理是遇到图片会开启一个线程进行单独的访问,因此在服务器端加入线程技术

public class Demo {public static void main(String[] args) throws Exception {// 通过读取浏览器端的请求信息,获取浏览器需要访问的页面的路径// 1.创建ServerSocket对象,指定端口号为9999ServerSocket ss = new ServerSocket(9999);while (true) {// 2.调用accept()方法,接收请求,建立连接,返回Socket对象Socket socket = ss.accept();new Thread(new Runnable() {@Overridepublic void run() {try {// 3.通过返回的Socket对象获取字节输入流,关联连接通道InputStream is = socket.getInputStream();// 4.把字节输入流转换为字符输入流InputStreamReader isr = new InputStreamReader(is);// 5.创建字符缓冲输入流BufferedReader br = new BufferedReader(isr);// 6.使用字符缓冲输入流读取第一行数据String line = br.readLine();// 7.使用空格对读取到的第一行数据进行分割String[] arr = line.split(" ");// 8.获取分割后数组中索引为1的元素,对其进行截取String path = arr[1].substring(1);System.out.println("浏览器需要访问的页面路径是:" + path);// 服务器把浏览器需要访问的页面响应给浏览器// 9.创建一个字节输入流,关联数据源文件路径FileInputStream fis = new FileInputStream(path);// 10.通过Socket对象获得输出流,关联连接通道OutputStream os = socket.getOutputStream();// 11.定义一个变量,用来存储读取到的字节数据byte[] bys = new byte[8192];int len;// 响应页面的时候需要同时把以下响应过去给浏览器os.write("HTTP/1.1 200 OK\r\n".getBytes());os.write("Content-Type:text/html\r\n".getBytes());os.write("\r\n".getBytes());// 12.循环读取while ((len = fis.read(bys)) != -1) {// 13.在循环中,写出数据给浏览器os.write(bys, 0, len);}// 关闭Socket对象,释放资源fis.close();socket.close();} catch (IOException e) {}}}).start();}}/*** 1.读取到浏览器端的请求信息** @return* @throws IOException*/private static Socket method01() throws IOException {//  1.读取到浏览器端的请求信息// 1.1 创建ServerSocket对象,指定端口号为9999ServerSocket ss = new ServerSocket(9999);// 1.2 调用accept()方法,接收请求,建立连接,返回Socket对象Socket socket = ss.accept();// 1.3 通过返回的Socket对象获取输入流,关联连接通道InputStream is = socket.getInputStream();// 1.4 使用输入流去读取数据byte[] bys = new byte[8192];int len = is.read(bys);// 1.5 打印读取到的数据System.out.println(new String(bys, 0, len));/*GET /day12/web/index.html HTTP/1.1Host: localhost:9999Connection: keep-aliveCache-Control: max-age=0Upgrade-Insecure-Requests: 1User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36Sec-Fetch-User: ?1Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,**;q=0.8,application/signed-exchange;v=b3Sec-Fetch-Site: noneSec-Fetch-Mode: navigateAccept-Encoding: gzip, deflate, brAccept-Language: zh-CN,zh;q=0.9Cookie: Idea-7071e3d8=cc177568-5581-4562-aeac-26fcb6ca7e56*/return socket;}
}

5、NIO

几个关键词

  • 同步与异步(synchronous/asynchronous):同步是一种可靠的有序运行机制,当我们进行同步操作时,后续的任务是等待当前调用返回,才会进行下一步;而异步则相反,其他任务不需要等待当前调用返回,通常依靠事件、回调等机制来实现任务间次序关系
  • 阻塞与非阻塞:在进行阻塞操作时,当前线程会处于阻塞状态,无法从事其他任务,只有当条件就绪才能继续,比如ServerSocket新连接建立完毕,或者数据读取、写入操作完成;而非阻塞则是不管IO操作是否结束,直接返回,相应操作在后台继续处理

在Java1.4之前的I/O系统中,提供的都是面向流的I/O系统,系统一次一个字节地处理数据,一个输入流产生一个字节的数据,一个输出流消费一个字节的数据,面向流的I/O速度非常慢,而在Java 1.4中推出了NIO,这是一个面向块的I/O系统,系统以块的方式处理数据,每一个操作在一步中产生或者消费一个数据,按块处理要比按字节处理数据快的多。

在 Java 7 中,NIO 有了进一步的改进,也就是 NIO 2,引入了异步非阻塞 IO 方式,也有很多人叫它 AIO(Asynchronous IO)。异步 IO 操作基于事件和回调机制,可以简单理解为,应用操作直接返回,而不会阻塞在那里,当后台处理完成,操作系统会通知相应线程进行后续工作。

NIO之所以是同步,是因为它的accept/read/write方法的内核I/O操作都会阻塞当前线程

首先,我们要先了解一下NIO的三个主要组成部分:Buffer(缓冲区)、Channel(通道)、Selector(选择器)

Buffer类(缓冲区)

概述:Buffer是一个抽象类,它是对某种基本类型的数组进行了封装。

作用: 在NIO中,就是通过 Buffer 来读写数据的。所有的数据都是用Buffer来处理的,它是NIO读写数据的中转池, 通常使用字节数组。

Buffer主要有如下几种:

  • ByteBuffer
  • CharBuffer
  • DoubleBuffer
  • FloatBuffer
  • IntBuffer
  • LongBuffer
  • ShortBuffer

创建ByteBuffer

  • ByteBuffer类内部封装了一个byte[]数组,并可以通过一些方法对这个数组进行操作。

  • 创建ByteBuffer对象

    • 方式一:在堆中创建缓冲区:allocate(int capacity)
public static void main(String[] args) {//创建堆缓冲区ByteBuffer byteBuffer = ByteBuffer.allocate(10);
}

  • 方式二: 在系统内存创建缓冲区:allocatDirect(int capacity)
public static void main(String[] args) {//创建直接缓冲区ByteBuffer byteBuffer = ByteBuffer.allocateDirect(10);
}
- 在堆中创建缓冲区称为:间接缓冲区
- 在系统内存创建缓冲区称为:直接缓冲区- 间接缓冲区的创建和销毁效率要高于直接缓冲区
- 间接缓冲区的工作效率要低于直接缓冲区
  • 方式三:通过数组创建缓冲区:wrap(byte[] arr)
   public static void main(String[] args) {byte[] byteArray = new byte[10];ByteBuffer byteBuffer = ByteBuffer.wrap(byteArray);}

此种方式创建的缓冲区为:间接缓冲区

添加数据-put

  • public ByteBuffer put(byte b):向当前可用位置添加数据。

  • public ByteBuffer put(byte[] byteArray):向当前可用位置添加一个byte[]数组

  • public ByteBuffer put(byte[] byteArray,int offset,int len):添加一个byte[]数组的一部分

容量-capacity

Buffer的容量(capacity)是指:Buffer所能够包含的元素的最大数量。定义了Buffer后,容量是不可变的。

public static void main(String[] args) {ByteBuffer b1 = ByteBuffer.allocate(10);System.out.println("容量:" + b1.capacity());//10。之后不可改变byte[] byteArray = {97, 98, 99, 100};ByteBuffer b2 = ByteBuffer.wrap(byteArray);System.out.println("容量:" + b2.capacity());//4。之后不可改变
}

限制-limit

  • 限制limit是指:第一个不应该读取或写入元素的index索引。缓冲区的限制(limit)不能为负,并且不能大于容量。

  • 有两个相关方法:

    • public int limit():获取此缓冲区的限制。
    • public Buffer limit(int newLimit):设置此缓冲区的限制。

位置-position

  • 位置position是指:当前可写入的索引。位置不能小于0,并且不能大于"限制"。

  • 有两个相关方法:

    • public int position():获取当前可写入位置索引。
    • public Buffer position(int p):更改当前可写入位置索引。

注意: 字节缓冲数组能操作的范围就是position位置到limit位置:[position,limit)

标记-mark

  • 标记mark是指:当调用缓冲区的reset()方法时,会将缓冲区的position位置重置为该索引。

  • 相关方法:

    • public Buffer mark():设置此缓冲区的标记为当前的position位置。
    • public Buffer reset() : 将此缓冲区的位置重置为以前标记的位置。

其它方法

- public int remaining():获取position与limit之间的元素数。
- public boolean isReadOnly():获取当前缓冲区是否只读。
- public boolean isDirect():获取当前缓冲区是否为直接缓冲区。
- public Buffer rewind():重绕此缓冲区。- 将position位置设置为:0- 限制limit不变。- 丢弃标记。
- public Buffer clear():还原缓冲区的状态。- 将position设置为:0- 将限制limit设置为容量capacity;- 丢弃标记mark。
- public Buffer flip():缩小limit的范围。- 将limit设置为当前position位置;- 将当前position位置设置为0;- 丢弃标记。

6、Channel(通道)

Channel 的概述

Channel(通道):Channel是一个接口,可以通过它读取和写入数据, 可以把它看做是IO中的流,不同的是:Channel是双向的, Channel对象既可以调用读取的方法, 也可以调用写出的方法 。

输入流: 读

输出流: 写

Channel: 读,写

Channel 的分类

在Java NIO中的Channel主要有如下几种类型:

  • FileChannel:从文件读取数据的 输入流和输出流
  • DatagramChannel:读写UDP网络协议数据 Datagram
  • SocketChannel:读写TCP网络协议数据 Socket
  • ServerSocketChannel:可以监听TCP连接 ServerSocket

FileChannel类的基本使用

获取FileChannel类的对象

  • java.nio.channels.FileChannel (抽象类):用于读、写文件的通道。

  • FileChannel是抽象类,我们可以通过FileInputStream和FileOutputStream的getChannel()方法方便的获取一个它的子类对象。

FileInputStream fi=new FileInputStream(new File(src));
FileOutputStream fo=new FileOutputStream(new File(dst));
//获得传输通道channel
FileChannel inChannel=fi.getChannel();
FileChannel outChannel=fo.getChannel();

使用FileChannel类完成文件的复制

  • 我们将通过CopyFile这个示例让大家体会NIO的操作过程。CopyFile执行三个基本的操作:创建一个Buffer,然后从源文件读取数据到缓冲区,然后再将缓冲区写入目标文件。
public static void main(String[] args) throws Exception{FileInputStream fis = new FileInputStream("day19\\aaa\\a.txt");FileOutputStream fos = new FileOutputStream("day19\\aaa\\aCopy1.txt");// 获得FileChannel管道对象FileChannel c1 = fis.getChannel();FileChannel c2 = fos.getChannel();// 创建ByteBuffer数组ByteBuffer b = ByteBuffer.allocate(1000);// 循环读取数据while ((c1.read(b)) != -1){// 读取的字节会填充postion到limit位置之间// 重置 postion为0,limit为postion的位置b.flip();// 写出数据c2.write(b);// 会把postion到limit之间的数据写出// 还原b.clear();// positon为:0  limit为: capacity 用于下次读取}// 释放资源c2.close();c1.close();fos.close();fis.close();/*byte[] bys = new byte[8192];int len;while ((len = fis.read(bys)) != -1){fos.write(bys,0,len);}fos.close();fis.close();*/}

FileChannel结合MappedByteBuffer实现高效读写

MappedByteBuffer类的概述

  • 上例直接使用FileChannel结合ByteBuffer实现的管道读写,但并不能提高文件的读写效率。

  • ByteBuffer有个抽象子类:MappedByteBuffer,它可以将文件直接映射至内存,把硬盘中的读写变成内存中的读写, 所以可以提高大文件的读写效率。

  • 可以调用FileChannel的map()方法获取一个MappedByteBuffer,map()方法的原型:

    MappedByteBuffer map(MapMode mode, long position, long size);

    说明:将节点中从position开始的size个字节映射到返回的MappedByteBuffer中。

复制2GB以下的文件

  • 复制d:\b.rar文件,此文件大概600多兆,复制完毕用时不到2秒。此例不能复制大于2G的文件,因为map的第三个参数被限制在Integer.MAX_VALUE(字节) = 2G。
public static void main(String[] args) throws Exception{//java.io.RandomAccessFile类,可以设置读、写模式的IO流类。//"r"表示:只读--输入流,只读就可以。RandomAccessFile r1 = new RandomAccessFile("day19\\aaa\\a.txt","r");//"rw"表示:读、写--输出流,需要读、写。RandomAccessFile r2 = new RandomAccessFile("day19\\aaa\\aCopy2.txt","rw");// 获得FileChannel管道对象FileChannel c1 = r1.getChannel();FileChannel c2 = r2.getChannel();// 获取文件的大小long size = c1.size();// 直接把硬盘中的文件映射到内存中MappedByteBuffer b1 = c1.map(FileChannel.MapMode.READ_ONLY, 0, size);MappedByteBuffer b2 = c2.map(FileChannel.MapMode.READ_WRITE, 0, size);// 循环读取数据for (long i = 0; i < size; i++) {// 读取字节byte b = b1.get();// 保存到第二个数组中b2.put(b);}// 释放资源c2.close();c1.close();r2.close();r1.close();}
  • map()方法的第一个参数mode:映射的三种模式,在这三种模式下得到的将是三种不同的MappedByteBuffer:三种模式都是Channel的内部类MapMode中定义的静态常量,这里以FileChannel举例:
    1). FileChannel.MapMode.READ_ONLY:得到的镜像只能读不能写(只能使用get之类的读取Buffer中的内容);

    2). FileChannel.MapMode.READ_WRITE:得到的镜像可读可写(既然可写了必然可读),对其写会直接更改到存储节点;

    3). FileChannel.MapMode.PRIVATE:得到一个私有的镜像,其实就是一个(position, size)区域的副本罢了,也是可读可写,只不过写不会影响到存储节点,就是一个普通的ByteBuffer了!!

  • 为什么使用RandomAccessFile?

    1). 使用InputStream获得的Channel可以映射,使用map时只能指定为READ_ONLY模式,不能指定为READ_WRITE和PRIVATE,否则会抛出运行时异常!

    2). 使用OutputStream得到的Channel不可以映射!并且OutputStream的Channel也只能write不能read!

    3). 只有RandomAccessFile获取的Channel才能开启任意的这三种模式!

复制2GB以上的文件

 public static void main(String[] args) throws Exception{//java.io.RandomAccessFile类,可以设置读、写模式的IO流类。//"r"表示:只读--输入流,只读就可以。RandomAccessFile r1 = new RandomAccessFile("H:\\课堂资料.zip","r");//"rw"表示:读、写--输出流,需要读、写。RandomAccessFile r2 = new RandomAccessFile("H:\\课堂资料2.zip","rw");// 获得FileChannel管道对象FileChannel c1 = r1.getChannel();FileChannel c2 = r2.getChannel();// 获取文件的大小long size = c1.size();// 每次期望复制500Mint everySize = 1024*1024*500;// 总共需要复制多少次long count = size % everySize == 0 ? size/everySize : size/everySize+1;// 开始复制for (long i = 0; i < count; i++) {// 每次开始复制的位置long start = everySize*i;// 每次复制的实际大小long trueSize = size - start > everySize ? everySize : size - start;// 直接把硬盘中的文件映射到内存中MappedByteBuffer b1 = c1.map(FileChannel.MapMode.READ_ONLY, start, trueSize);MappedByteBuffer b2 = c2.map(FileChannel.MapMode.READ_WRITE, start, trueSize);// 循环读取数据for (long j = 0; j < trueSize; j++) {// 读取字节byte b = b1.get();// 保存到第二个数组中b2.put(b);}}// 释放资源c2.close();c1.close();r2.close();r1.close();}

ServerSocketChannel和SocketChannel创建连接

SocketChannel创建连接

  • 客户端:SocketChannel类用于连接的客户端,它相当于:Socket。

    1). 先调用SocketChannel的open()方法打开通道:

SocketChannel socket = SocketChannel.open()

2). 调用SocketChannel的实例方法connect(SocketAddress add)连接服务器:

 socket.connect(new InetSocketAddress("localhost", 8888));

ServerSocketChanne创建连接

  • 服务器端:ServerSocketChannel类用于连接的服务器端,它相当于:ServerSocket。

  • 调用ServerSocketChannel的静态方法open()就可以获得ServerSocketChannel对象, 但并没有指定端口号, 必须通过其套接字的bind方法将其绑定到特定地址,才能接受连接。

ServerSocketChannel serverChannel = ServerSocketChannel.open()

调用ServerSocketChannel的实例方法bind(SocketAddress add):绑定本机监听端口,准备接受连接。

​ 注:java.net.SocketAddress(抽象类):代表一个Socket地址。

​ 我们可以使用它的子类:java.net.InetSocketAddress(类)

​ 构造方法:InetSocketAddress(int port):指定本机监听端口。

serverChannel.bind(new InetSocketAddress(8888));

调用ServerSocketChannel的实例方法accept():等待连接。
服务器端等待连接(默认-阻塞模式)

public class Server {public static void main(String[] args) throws Exception{ServerSocketChannel serverChannel = ServerSocketChannel.open();serverChannel.bind(new InetSocketAddress(8888));System.out.println("【服务器】等待客户端连接...");SocketChannel accept = serverChannel.accept();System.out.println("后续代码......");}
}

可以通过ServerSocketChannel的configureBlocking(boolean b)方法设置accept()是否阻塞**
服务器:

public class Server {public static void main(String[] args)  throws IOException{//创建对象//ServerSocket ss = new ServerSocket(8888);//创建ServerSocketChannel ssc = ServerSocketChannel.open();//服务器绑定端口ssc.bind(new InetSocketAddress(8888));//连接上客户端SocketChannel sc = ssc.accept();//服务器端接受数据//创建数组ByteBuffer buffer = ByteBuffer.allocate(1024);//接收数据int len = sc.read(buffer);//打印结构System.out.println(new String(buffer.array(),0,len));//关闭资源sc.close();}
}

客户端:

public class Client {public static void main(String[] args) {//创建对象//Socket s = new Socket("127.0.0.1",8888);//创建对象SocketChannel sc = SocketChannel.open();//连接服务器sc.connect(new InetSocketAddress("127.0.0.1",8888));//客户端发数据//创建数组ByteBuffer buffer = ByteBuffer.allocate(1024);//数组中添加数据buffer.put("你好啊~".getBytes());//切换buffer.flip();//发出数据sc.write(buffer);//关流sc.close();}
}

7、Selector(选择器)

多路复用的概念

选择器Selector是NIO中的重要技术之一。它与SelectableChannel联合使用实现了非阻塞的多路复用。使用它可以节省CPU资源,提高程序的运行效率。

"多路"是指:服务器端同时监听多个“端口”的情况。每个端口都要监听多个客户端的连接。

  • 服务器端的非多路复用效果

  • 如果不使用“多路复用”,服务器端需要开很多线程处理每个端口的请求。如果在高并发环境下,造成系统性能下降。

  • 服务器端的多路复用效果
    - 使用了多路复用,只需要一个线程就可以处理多个通道,降低内存占用率,减少CPU切换时间,在高并发、高频段业务环境下有非常重要的优势

多路复用的意思就是一个Selector可以监听多个服务器端口。

选择器Selector的获取和注册

Selector选择器的概述和作用

概述: Selector被称为:选择器,也被称为:多路复用器,可以把多个Channel注册到一个Selector选择器上, 那么就可以实现利用一个线程来处理这多个Channel上发生的事件,并且能够根据事件情况决定Channel读写。这样,通过一个线程管理多个Channel,就可以处理大量网络连接了, 减少系统负担, 提高效率。因为线程之间的切换对操作系统来说代价是很高的,并且每个线程也会占用一定的系统资源。所以,对系统来说使用的线程越少越好。

作用: 一个Selector可以监听多个Channel发生的事件, 减少系统负担 , 提高程序执行效率 .

Selector选择器的获取

Selector selector = Selector.open();

注册Channel到Selector

通过调用 channel.register(Selector sel, int ops)方法来实现注册:

channel.configureBlocking(false);// 设置非阻塞
SelectionKey key =channel.register(selector,SelectionKey.OP_READ);

register()方法的第二个参数:是一个int值,意思是在通过Selector监听Channel时对什么事件感兴趣。可以监听四种不同类型的事件,而且可以使用SelectionKey的四个常量表示:

  1. 连接就绪–常量:SelectionKey.OP_CONNECT

  2. 接收就绪–常量:SelectionKey.OP_ACCEPT (ServerSocketChannel在注册时只能使用此项)

  3. 读就绪–常量:SelectionKey.OP_READ

  4. 写就绪–常量:SelectionKey.OP_WRITE

注意:

  1. 对于ServerSocketChannel在注册时,只能使用OP_ACCEPT,否则抛出异常。
  2. ServerSocketChannel要设置成非阻塞
  • 案例演示; 服务器创建3个通道,同时监听3个端口,并将3个通道注册到一个选择器中
public class Test2 {public static void main(String[] args) throws Exception{/*把多个Channel注册到一个选择器上*/// 获取ServerSocketChannel服务器通道对象ServerSocketChannel ssc1 = ServerSocketChannel.open();// 绑定端口号ssc1.bind(new InetSocketAddress(7777));// 获取ServerSocketChannel服务器通道对象ServerSocketChannel ssc2 = ServerSocketChannel.open();// 绑定端口号ssc2.bind(new InetSocketAddress(8888));// 获取ServerSocketChannel服务器通道对象ServerSocketChannel ssc3 = ServerSocketChannel.open();// 绑定端口号ssc3.bind(new InetSocketAddress(9999));// 设置非阻塞ssc1.configureBlocking(false);ssc2.configureBlocking(false);ssc3.configureBlocking(false);// 获取Selector选择器对象Selector selector = Selector.open();// 把服务器通道的accept()交给选择器来处理// 注册Channel到Selector选择器上ssc1.register(selector, SelectionKey.OP_ACCEPT);ssc2.register(selector,SelectionKey.OP_ACCEPT);ssc3.register(selector,SelectionKey.OP_ACCEPT);}
}

Selector的常用方法

Selector的select()方法:

  • 作用: 服务器等待客户端连接的方法

  • 阻塞问题:

    • 在连接到第一个客户端之前,会一直阻塞
    • 当连接到客户端后,如果客户端没有被处理,该方法会计入不阻塞状态
    • 当连接到客户端后,如果客户端有被处理,该方法又会进入阻塞状态

Selector的selectedKeys()方法

  • 获取已连接的所有通道集合

Selector的keys()方法

  • 获取已注册的所有通道集合
public class Server2 {public static void main(String[] args) throws Exception {/*需求: 使用Selector进行多路复用,监听3个服务器端口分析:1.创建3个服务器Channel对象,并绑定端口号2.把3个服务器Channel对象设置成非阻塞3.获得Selector选择器4.把3个个服务器Channel对象对象注册到同一个Selector选择器上,指定监听事件5.死循环去等待客户端的连接6.获取所有被连接的服务器Channel对象的Set集合7.循环遍历所有被连接的服务器Channel对象8.处理客户端的请求- 问题: Selector把所有被连接的服务器对象放在了一个Set集合中,但是使用完后并没有删除,导致在遍历集合时,遍历到已经没用的对象,出现了异常- 解决办法: 使用完了,应该从集合中删除,由于遍历的同时不能删除,所以使用迭代器进行遍历*/// 1.创建3个服务器Channel对象,并绑定端口号ServerSocketChannel ssc1 = ServerSocketChannel.open();ssc1.bind(new InetSocketAddress(7777));ServerSocketChannel ssc2 = ServerSocketChannel.open();ssc2.bind(new InetSocketAddress(8888));ServerSocketChannel ssc3 = ServerSocketChannel.open();ssc3.bind(new InetSocketAddress(9999));// 2.把3个服务器Channel对象设置成非阻塞ssc1.configureBlocking(false);ssc2.configureBlocking(false);ssc3.configureBlocking(false);// 3.获得Selector选择器Selector selector = Selector.open();// 4.把3个个服务器Channel对象对象注册到同一个Selector选择器上,指定监听事件ssc1.register(selector, SelectionKey.OP_ACCEPT);ssc2.register(selector, SelectionKey.OP_ACCEPT);ssc3.register(selector, SelectionKey.OP_ACCEPT);// 5.死循环去等待客户端的连接while (true) {// 服务器等待客户端连接System.out.println(1);selector.select();// 6.获取所有被连接的服务器Channel对象的Set集合Set<SelectionKey> keySet = selector.selectedKeys();// 7.循环遍历所有被连接的服务器Channel对象,获取每一个被连接的服务器Channel对象Iterator<SelectionKey> it = keySet.iterator();// 迭代器的快捷键: ititwhile (it.hasNext()){// 遍历出来的SelectionKeySelectionKey key = it.next();// 8.由于SelectionKey是对Channel的封装,所以我们得根据key获取被连接的服务器Channel对象ServerSocketChannel ssc = (ServerSocketChannel)key.channel();// 9.处理客户端的请求// 9.1 获取连接的客户端对象SocketChannel sc = ssc.accept();// 9.2 创建ByteBuffer缓冲数组ByteBuffer b = ByteBuffer.allocate(1024);// 9.3 读取数据int len = sc.read(b);// 把读取到的字节数据存储到b缓冲数组中,返回读取到的字节个数// 9.4 打印输出System.out.println(new String(b.array(), 0, len));// 10. 释放资源sc.close();// 用完了就得删除it.remove();}}}
}

Selector把所有被连接的服务器对象放在了一个Set集合中,但是使用完后并没有删除,导致在遍历集合时,遍历到已经没用的对象,出现了异常

8、NIO2-AIO(异步、非阻塞)

- 同步:调用方法之后,必须要得到一个返回值。- 异步:调用方法之后,没有返回值,但是会有回调函数。回调函数指的是满足条件之后会自动执行的方法- 阻塞:如果没有达到方法的目的,就一直停在这里【等待】。  - 非阻塞:不管有没有达到目的,都直接【往下执行】。

AIO相关类和方法介绍

AIO是异步IO的缩写,虽然NIO在网络操作中,提供了非阻塞的方法,但是NIO的IO行为还是同步的。对于NIO来说,我们的业务线程是在IO操作准备好时,得到通知,接着就由这个线程自行进行IO操作,IO操作本身是同步的。

但是对AIO来说,则更加进了一步,它不是在IO准备好时再通知线程,而是在IO操作已经完成后,再给线程发出通知。因此AIO是不会阻塞的,此时我们的业务逻辑将变成一个回调函数,等待IO操作完成后,由系统自动触发。

与NIO不同,当进行读写操作时,只须直接调用API的read或write方法即可。这两种方法均为异步的,对于读操作而言,当有流可读取时,操作系统会将可读的流传入read方法的缓冲区,并通知应用程序;对于写操作而言,当操作系统将write方法传递的流写入完毕时,操作系统主动通知应用程序。 即可以理解为,read/write方法都是异步的,完成后会主动调用回调函数。 在JDK1.7中,这部分内容被称作NIO.2---->AIO,主要在Java.nio.channels包下增加了下面四个异步通道:

  • AsynchronousSocketChannel
  • AsynchronousServerSocketChannel
  • 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);

AIO 同步连接同步读(没有意义)

public static void main(String[] args) throws Exception {//创建对象AsynchronousServerSocketChannel assc = AsynchronousServerSocketChannel.open();//绑定端口assc.bind(new InetSocketAddress(8888));//获取连接//Future里面放的就是方法的结果//********同步********System.out.println("准备连接客户端");Future<AsynchronousSocketChannel> future = assc.accept();//Future方法需要调用get()方法获取真正的返回值AsynchronousSocketChannel sc = future.get();System.out.println("连接上了客户端");//读取客户端发来的数据ByteBuffer buffer = ByteBuffer.allocate(1024);//读取//以前返回的是读取到的个数,真正的个数就在Future里面放着//********同步********System.out.println("准备读取数据");Future<Integer> future2 = sc.read(buffer);//获取真正的返回值Integer len = future2.get();System.out.println("读取到了数据");//打印System.out.println(new String(buffer.array(),0,len));}

AIO 异步非阻塞连接和异步读

//异步非阻塞连接和读取public static void main(String[] args) throws IOException {//创建对象AsynchronousServerSocketChannel assc  = AsynchronousServerSocketChannel.open();//绑定端口assc.bind(new InetSocketAddress(8000));//异步非阻塞连接!!!!//第一个参数是一个附件System.out.println(1);assc.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {@Overridepublic void completed(AsynchronousSocketChannel s, Object attachment) {//如果连接客户端成功,应该获取客户端发来的数据//completed()的第一个参数表示的是Socket对象.System.out.println(5);//创建数组ByteBuffer buffer = ByteBuffer.allocate(1024);//异步非阻塞读!!!!!System.out.println(3);s.read(buffer, null, new CompletionHandler<Integer, Object>() {@Overridepublic void completed(Integer len, Object attachment) {//读取成功会自动调用这个方法//completed()方法的第一个参数是read()读取到的实际个数//打印数据System.out.println(6);System.out.println(new String(buffer.array(),0,len));}@Overridepublic void failed(Throwable exc, Object attachment) {}});System.out.println(4);}@Overridepublic void failed(Throwable exc, Object attachment) {}});System.out.println(2);//让程序别结束写一个死循环while(true){}}

java 网络编程和NIO相关推荐

  1. Java网络编程与NIO学习总结

    #Java网络编程与NIO学习总结 这篇总结主要是基于我之前Java网络编程与NIO系列文章而形成的的.主要是把重要的知识点用自己的话说了一遍,可能会有一些错误,还望见谅和指点.谢谢 #更多详细内容可 ...

  2. Java网络编程和NIO详解开篇:Java网络编程基础

    老曹眼中的网络编程基础 转自:https://mp.weixin.qq.com/s/XXMz5uAFSsPdg38bth2jAA 我们是幸运的,因为我们拥有网络.网络是一个神奇的东西,它改变了你和我的 ...

  3. Java网络编程与NIO详解14:Tomcat 常见面试题汇总

    1.Tomcat的缺省端口是多少,怎么修改? 1)找到Tomcat目录下的conf文件夹 2)进入conf文件夹里面找到server.xml文件 3)打开server.xml文件 4)在server. ...

  4. Java网络编程和NIO详解1:JAVA 中原生的 socket 通信机制

    JAVA 中原生的 socket 通信机制 转载自:https://github.com/jasonGeng88/blog 当前环境 jdk == 1.8 知识点 socket 的连接处理 IO 输入 ...

  5. java网络编程3 -- NIO一些简单说明

    2019独角兽企业重金招聘Python工程师标准>>> 什么是NIO NIO是相对有BIO而言的,就是非阻塞性IO . 什么叫非阻塞性 ?我举一个简单的例子: 比如,你客户端发送了一 ...

  6. 【Java 网络编程】NIO Buffer 简介 ( 概念 | 数据传输 | 标记 | 位置 | 限制 | 容量 | 标记 | 重置 | 清除 | 翻转 | 重绕 | 链式操作 )

    文章目录 I. Buffer 简介 II. Buffer 属性 III. Buffer 数据读写 IV. Buffer 标记 mark() 和重置 reset() V. Buffer 清除 翻转 重绕 ...

  7. Java网络编程与NIO详解13:epoll、poll、select面试题汇总

    文章目录 一.文件描述符与IO模型 二.端口和地址复用 三.select 四.poll 五.epoll 六.相关面试题 1.epoll读到一半又有新事件来了怎么办? 一.文件描述符与IO模型   文件 ...

  8. NIO详解(一):java网络编程IO总结(BIO、NIO、AIO)

    1.基本概念 在Java网络通信中,最基本的概念就是Socket编程了.Socket又称"套接字" 向网络发出请求或者应答网络请求. Socket 和ServerSocket类库位 ...

  9. connect: 网络不可达_Java网络编程和NIO详解1:JAVA 中原生的 socket 通信机制

    本文转自:https://github.com/jasonGeng88/blog 本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https ...

最新文章

  1. 零基础学习Python需要注意的几个点,Python培训机构排名
  2. 使用spring-loaded开源项目,实现java程序和web应用的热部署
  3. Springmvc的静态资源映射配置
  4. 《Android UI基础教程》——1.2节Android 应用程序的基本结构
  5. 央行“意外“降息 专家称贷款买房者首先受益
  6. excel vba编程代码大全_实战VBA代码一键提取EXCEL中的所有公式!
  7. Android快速开发框架XUtils
  8. 设计模式:简单工厂模式(C++)【看不懂算我输】
  9. Google AI面试题
  10. jquery's json url
  11. Win10 Build9926 更新问题解决
  12. JAVA中解决Filter过滤掉css,js,图片文件等问题
  13. WebResponse 跨域访问
  14. kubernetes ingress-nginx原理
  15. 信息学奥赛一本通(C++)版在线评测系统网址
  16. 大学物理公式和名词整理
  17. 新医药与生命健康专题讲座
  18. 切片函数python_python切片操作
  19. 好用的项目管理工具,选择Tracup的N种理由
  20. 微信web开发者工具、网易云音乐、为知笔记等软件崩溃无法打开等问题的解决

热门文章

  1. P8架构师:看源码是Java高级程序员的必经之路
  2. 考计算机证书需要学什么,考计算机二级证书需要学哪些内容
  3. linux库--静态库、动态库
  4. 京东js加密 nloginpwd 破解
  5. 联系人按照拼音首字母排序
  6. [FlashPlyaer] FP版本20.0.267对Win10的64位系统的不兼容问题
  7. 在linux中本地安装blender LTS
  8. Mysql crash
  9. teradata 查看 表定义_teradata建表DDL
  10. 推荐:远程主机探测技术FAQ集 - 扫描篇 - 黑鹰安全网新闻中心