BIO,Socket网络编程入门代码示例,NIO网络编程入门代码示例,AIO 网络编程

                             BIO,Socket网络编程入门代码示例

1.BIO服务器端程序

package cn.itcast.bio;

import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

//BIO 服务器端程序
public class TCPServer {
	public static void main(String[] args) throws Exception {
		//1.创建ServerSocket对象
		ServerSocket ss=new ServerSocket(9999); //端口号

		while (true) {
			//2.监听客户端
			System.out.println("来呀");
			Socket s = ss.accept(); //阻塞
			System.out.println("来呀");
			//3.从连接中取出输入流来接收消息
			InputStream is = s.getInputStream(); //阻塞
			byte[] b = new byte[10];
			is.read(b);
			String clientIP = s.getInetAddress().getHostAddress();
			System.out.println(clientIP + "说:" + new String(b).trim());
			//4.从连接中取出输出流并回话
			//OutputStream os = s.getOutputStream();
			//os.write("没钱".getBytes());
			//5.关闭
			//s.close();
		}
	}
}

2.BIO 客户端程序

package cn.itcast.bio;

import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;

//BIO 客户端程序
public class TCPClient {
	public static void main(String[] args) throws Exception {
		while (true) {
			//1.创建Socket对象
			Socket s = new Socket("127.0.0.1", 9999);
			//2.从连接中取出输出流并发消息
			OutputStream os = s.getOutputStream();
			System.out.println("请输入:");
			Scanner sc = new Scanner(System.in);
			String msg = sc.nextLine();
			os.write(msg.getBytes());
			//3.从连接中取出输入流并接收回话
			InputStream is = s.getInputStream(); //阻塞
			byte[] b = new byte[20];
			is.read(b);
			System.out.println("老板说:" + new String(b).trim());
			//4.关闭
			s.close();
		}
	}
}

 

                               

                            NIO网络编程入门代码示例

1.操作文件

package cn.itcast.nio.file;

import org.junit.Test;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

//通过NIO实现文件IO
public class TestNIO {

    @Test  //往本地文件中写数据
    public void  test1() throws  Exception{
        //1. 创建输出流
        FileOutputStream fos=new FileOutputStream("basic.txt");
        //2. 从流中得到一个通道
        FileChannel fc=fos.getChannel();
        //3. 提供一个缓冲区
        ByteBuffer buffer=ByteBuffer.allocate(1024);
        //4. 往缓冲区中存入数据
        String str="hello,nio";
        buffer.put(str.getBytes());
        //5. 翻转缓冲区
        buffer.flip();
        //6. 把缓冲区写到通道中
        fc.write(buffer);
        //7. 关闭
        fos.close();
    }

    @Test  //从本地文件中读取数据
    public void test2() throws  Exception{
        File file=new File("basic.txt");
        //1. 创建输入流
        FileInputStream fis=new FileInputStream(file);
        //2. 得到一个通道
        FileChannel fc=fis.getChannel();
        //3. 准备一个缓冲区
        ByteBuffer buffer=ByteBuffer.allocate((int)file.length());
        //4. 从通道里读取数据并存到缓冲区中
        fc.read(buffer);
        System.out.println(new String(buffer.array()));
        //5. 关闭
        fis.close();
    }

    @Test  //使用NIO实现文件复制
    public void test3() throws  Exception{
        //1. 创建两个流
        FileInputStream fis=new FileInputStream("basic.txt");
        FileOutputStream fos=new FileOutputStream("c:\\test\\basic.txt");

        //2. 得到两个通道
        FileChannel sourceFC=fis.getChannel();
        FileChannel destFC=fos.getChannel();

        //3. 复制
        destFC.transferFrom(sourceFC,0,sourceFC.size());

        //4. 关闭
        fis.close();
        fos.close();
    }

}

 

                                           NIO

网络 IO

概述和核心 API

前面在进行文件 IO 时用到的 FileChannel 并不支持非阻塞操作,学习 NIO 主要就是进行网络 IO,Java NIO 中的网络通道是非阻塞 IO 的实现,基于事件驱动,非常适用于服务器需要维持大量连接,但是数据交换量不大的情况,例如一些即时通信的服务等等....

在 Java 中编写 Socket 服务器,通常有以下几种模式:

  1. 一个客户端连接用一个线程,优点:程序编写简单;缺点:如果连接非常多,分配的线  程也会非常多,服务器可能会因为资源耗尽而崩溃。
  2. 把每一个客户端连接交给一个拥有固定数量线程的连接池,优点:程序编写相对简单,  可以处理大量的连接。确定:线程的开销非常大,连接如果非常多,排队现象会比较严重。
  3. 使用 Java 的 NIO,用非阻塞的 IO 方式处理。这种模式可以用一个线程,处理大量的客户端连接。

1.Selector(选择器),能够检测多个注册的通道上是否有事件发生,如果有事件发生,便获取事件然后针对每个事件进行相应的处理。这样就可以只用一个单线程去管理多个通道,也就是管理多个连接。这样使得只有在连接真正有读写事件发生时,才会调用函数来进行读写,   就大大地减少了系统开销,并且不必为每个连接都创建一个线程,不用去维护多个线程,并且避免了多线程之间的上下文切换导致的开销。

该类的常用方法如下所示:

  1. public static Selector open(),得到一个选择器对象
  2. public int select(long timeout),监控所有注册的通道,当其中有 IO 操作可以进行时,将对应的 SelectionKey 加入到内部集合中并返回,参数用来设置超时时间
  3. public Set<SelectionKey> selectedKeys(),从内部集合中得到所有的 SelectionKey
  4.  
  5. SelectionKey,代表了 Selector 和网络通道的注册关系,一共四种:
  6. int OP_ACCEPT:有新的网络连接可以 accept,值为 16
  7. int OP_CONNECT:代表连接已经建立,值为 8
  8. int OP_READ 和 int OP_WRITE:代表了读、写操作,值为 1 和 4
  9. 该类的常用方法如下所示:
  10. public abstract Selector selector(),得到与之关联的 Selector 对象
  11. public abstract SelectableChannel channel(),得到与之关联的通道
  12. public final Object attachment(),得到与之关联的共享数据
  13. public abstract SelectionKey interestOps(int ops),设置或改变监听事件
  14. public final boolean isAcceptable(),是否可以 accept
  15. public final boolean isReadable(),是否可以读
  16. public final boolean isWritable(),是否可以写

1.ServerSocketChannel,用来在服务器端监听新的客户端 Socket 连接,常用方法如下所示:

  1. public static ServerSocketChannel open(),得到一个 ServerSocketChannel 通道
  2. public final ServerSocketChannel bind(SocketAddress local),设置服务器端端口号
  3. public final SelectableChannel configureBlocking(boolean block),设置阻塞或非阻塞模式, 取值 false 表示采用非阻塞模式
  4. public SocketChannel accept(),接受一个连接,返回代表这个连接的通道对象
  5. public final SelectionKey register(Selector sel, int ops),注册一个选择器并设置监听事件

 

4.SocketChannel,网络 IO 通道,具体负责进行读写操作。NIO 总是把缓冲区的数据写入通道,或者把通道里的数据读到缓冲区。常用方法如下所示:

  1. public static SocketChannel open(),得到一个 SocketChannel 通道
  2. public final SelectableChannel configureBlocking(boolean block),设置阻塞或非阻塞模式, 取值 false 表示采用非阻塞模式
  3. public boolean connect(SocketAddress remote),连接服务器
  4. public boolean finishConnect(),如果上面的方法连接失败,接下来就要通过该方法完成连接操作
  5. public int write(ByteBuffer src),往通道里写数据
  6. public int read(ByteBuffer dst),从通道里读数据
  7. public final SelectionKey register(Selector sel, int ops, Object att),注册一个选择器并设置监听事件,最后一个参数可以设置共享数据
  8. public final void close(),关闭通道

 

入门案例

API 学习完毕后,接下来我们使用 NIO 开发一个入门案例,实现服务器端和客户端之间的数据通信(非阻塞)。

 

//网络服务器端程序public class NIOServer {
public static void main(String[] args) throws Exception{
//1. 得到一个 ServerSocketChannel 对象 老大
ServerSocketChannel serverSocketChannel=ServerSocketChannel.open();
//2.  得到一个 Selector 对象	间谍
Selector selector=Selector.open();
//3. 绑定一个端口号
serverSocketChannel.bind(new InetSocketAddress(9999));
//4. 设置非阻塞方式
serverSocketChannel.configureBlocking(false);
//5. 把 ServerSocketChannel 对象注册给 Selector 对象
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
//6. 干活
while(true){
//6.1 监控客户端
if(selector.select(2000)==0){ //nio 非阻塞式的优势
System.out.println("Server:没有客户端搭理我,我就干点别的事"); continue;
}
//6.2   得到 SelectionKey,判断通道里的事件Iterator<SelectionKey> keyIterator=selector.selectedKeys().iterator(); while(keyIterator.hasNext()){
SelectionKey key=keyIterator.next(); if(key.isAcceptable()){ //客户端连接请求事件
System.out.println("OP_ACCEPT");
SocketChannel socketChannel=serverSocketChannel.accept(); socketChannel.configureBlocking(false); socketChannel.register(selector,SelectionKey.OP_READ, ByteBuffer.allocate(1024));
}
if(key.isReadable()){  //读取客户端数据事件SocketChannel channel=(SocketChannel) key.channel(); ByteBuffer buffer=(ByteBuffer) key.attachment(); channel.read(buffer);
System.out.println("客户端发来数据:"+new String(buffer.array()));
}
// 6.3 手动从集合中移除当前 key,防止重复处理
keyIterator.remove();
}
}
}
}

上面代码用 NIO 实现了一个服务器端程序,能不断接受客户端连接并读取客户端发过来的数据。

//网络客户端程序public class NIOClient {
public static void main(String[] args) throws Exception{
//1. 得到一个网络通道
SocketChannel channel=SocketChannel.open();
//2. 设置非阻塞方式
channel.configureBlocking(false);
//3. 提供服务器端的 IP 地址和端口号
InetSocketAddress address=new InetSocketAddress("127.0.0.1",9999);
//4. 连接服务器端
if(!channel.connect(address)){
while(!channel.finishConnect()){ //nio 作为非阻塞式的优势
System.out.println("Client:连接服务器端的同时,我还可以干别的一些事情");
}
}
//5. 得到一个缓冲区并存入数据
String msg="hello,Server";
ByteBuffer writeBuf = ByteBuffer.wrap(msg.getBytes());
//6. 发送数据channel.write(writeBuf); System.in.read();
}

上面代码通过 NIO 实现了一个客户端程序,连接上服务器端后发送了一条数据,运行效果如下图所示:

网络聊天案例:

刚才我们通过 NIO 实现了一个入门案例,基本了解了 NIO 的工作方式和运行流程,接下来我们用 NIO 实现一个多人聊天案例,具体代码如下所示:

public class ChatServer { private Selector selector;
private ServerSocketChannel listenerChannel; private static final int PORT = 9999; //服务器端口

public ChatServer() { try {
// 得到选择器
selector = Selector.open();
// 打开监听通道
listenerChannel = ServerSocketChannel.open();
// 绑定端口
listenerChannel.bind(new InetSocketAddress(PORT));
// 设置为非阻塞模式
listenerChannel.configureBlocking(false);
// 将选择器绑定到监听通道并监听 accept 事件listenerChannel.register(selector, SelectionKey.OP_ACCEPT); printInfo("Chat Server is ready.......");
} catch (IOException e) { e.printStackTrace();
}
}


public void start() { try {
while (true) { //不停轮询
int count = selector.select();//获取就绪 channel if (count > 0) {
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator(); while (iterator.hasNext()) {
SelectionKey key = iterator.next();
// 监听到 accept
if (key.isAcceptable()) {
SocketChannel sc = listenerChannel.accept();
//非阻塞模式sc.configureBlocking(false);
// 注 册 到 选 择 器 上 并 监 听 read sc.register(selector, SelectionKey.OP_READ);
System.out.println(sc.getRemoteAddress().toString().substring(1)+"上线了...");
key.interestOps(SelectionKey.OP_ACCEPT);
}
//监听到 read
if (key.isReadable()) {
readMsg(key); //读取客户端发来的数据





}
} else {

}
//一定要把当前 key 删掉,防止重复处理
iterator.remove();

System.out.println("独自在寒风中等候...");
}
}
} catch (IOException e) { e.printStackTrace();
}
}

private void readMsg(SelectionKey key) { SocketChannel channel = null;
try {
// 得到关联的通道
channel = (SocketChannel) key.channel();
//设置 buffer 缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
//从通道中读取数据并存储到缓冲区中int count = channel.read(buffer);
//如果读取到了数据if (count > 0) {
//把缓冲区数据转换为字符串
String msg = new String(buffer.array()); printInfo(msg);
//将关联的 channel 设置为 read,继续准备接受数据key.interestOps(SelectionKey.OP_READ); BroadCast(channel, msg); //向所有客户端广播数据
}
buffer.clear();
} catch (IOException e) { try {
//当客户端关闭 channel 时,进行异常如理printInfo(channel.getRemoteAddress().toString().substring(1) + "下线了..."); key.cancel(); //取消注册
channel.close(); //关闭通道
} catch (IOException e1) {
e1.printStackTrace();
}
}
}


public void BroadCast(SocketChannel except, String msg) throws IOException { System.out.println("发送广播...");
//广播数据到所有的 SocketChannel 中
for (SelectionKey key : selector.keys()) { Channel targetchannel = key.channel();
//排除自身
if (targetchannel instanceof SocketChannel && targetchannel != except) { SocketChannel dest = (SocketChannel) targetchannel;
//把数据存储到缓冲区中
ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes());
//往通道中写数据dest.write(buffer);
}
}
}


private void printInfo(String str) { //往控制台打印消息
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); System.out.println("[" + sdf.format(new Date()) + "] -> " + str);
}


public static void main(String[] args) { ChatServer server = new ChatServer(); server.start();
}

上述代码使用 NIO 编写了一个聊天程序的服务器端,可以接受客户端发来的数据,并能把

数据广播给所有客户端。

public class ChatClient {
private final String HOST = "127.0.0.1"; //服务器地址private int PORT = 9999; //服务器端口
private Selector selector;
private SocketChannel socketChannel; private String userName;

public ChatClient() throws IOException {
//得到选择器
selector = Selector.open();
//连接远程服务器
socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", PORT));
//设置非阻塞socketChannel.configureBlocking(false);
// 注 册 选 择 器 并 设 置 为 read socketChannel.register(selector, SelectionKey.OP_READ);
//得到客户端 IP 地址和端口信息,作为聊天用户名使用
userName = socketChannel.getLocalAddress().toString().substring(1); System.out.println("---------------Client(" + userName + ") is ready---------------");
}

//向服务器端发送数据
public void sendMsg(String msg) throws Exception {
//如果控制台输入 bye 就关闭通道,结束聊天
if (msg.equalsIgnoreCase("bye")) { socketChannel.close(); socketChannel = null;
return;
}
msg = userName + "说: " + msg; try {
//往通道中写数据socketChannel.write(ByteBuffer.wrap(msg.getBytes()));
} catch (IOException e) { e.printStackTrace();
}
}

//从服务器端接收数据public void receiveMsg() {
try {
int readyChannels = selector.select(); if (readyChannels > 0) { //有可用通道
Set selectedKeys = selector.selectedKeys(); Iterator keyIterator = selectedKeys.iterator(); while (keyIterator.hasNext()) {
SelectionKey sk = (SelectionKey) keyIterator.next(); if (sk.isReadable()) {
//得到关联的通道
SocketChannel sc = (SocketChannel) sk.channel();
//得到一个缓冲区
ByteBuffer buff = ByteBuffer.allocate(1024);
//读取数据并存储到缓冲区sc.read(buff);
//把缓冲区数据转换成字符串
String msg = new String(buff.array()); System.out.println(msg.trim());
}
keyIterator.remove(); //删除当前 SelectionKey,防止重复处理
}
} else {
System.out.println("人呢?都去哪儿了?没人聊天啊...");
}
} catch (IOException e) { e.printStackTrace();
}
}
}

上述代码通过 NIO 编写了一个聊天程序的客户端,可以向服务器端发送数据,并能接收服

​​​​

务器广播的数据。

public class TestChat {
public static void main(String[] args) throws Exception {
//创建一个聊天客户端对象
ChatClient chatClient = new ChatClient();


new Thread() { //单独开一个线程不断的接收服务器端广播的数据public void run() {
while (true) {
chatClient.receiveMsg(); try { //间隔 3 秒
Thread.currentThread().sleep(3000);
} catch (InterruptedException e) { e.printStackTrace();
}
}
}
}.start();


Scanner scanner = new Scanner(System.in);
//在控制台输入数据并发送到服务器端while (scanner.hasNextLine()) {
String msg = scanner.nextLine(); chatClient.sendMsg(msg);
}
}
}

上述代码运行了聊天程序的客户端,并在主线程中发送数据,在另一个线程中不断接收服务   器端的广播数据,该代码运行一次就是一个聊天客户端,可以同时运行多个聊天客户端,聊   天效果如下图所示:

 

                                     AIO 编程

 

JDK 7 引入了 Asynchronous I/O,即 AIO。在进行 I/O 编程中,常用到两种模式:Reactor 和 Proactor。Java 的 NIO 就是 Reactor,当有事件触发时,服务器端得到通知,进行相应的处理。

AIO 即 NIO2.0,叫做异步不阻塞的 IO。AIO 引入异步通道的概念,采用了 Proactor 模式, 简化了程序编写,一个有效的请求才启动一个线程,它的特点是先由操作系统完成后才通知服务端程序启动线程去处理,一般适用于连接数较多且连接时间较长的应用。

目前 AIO 还没有广泛应用,并且也不是本课程的重点内容,这里暂不做讲解。

IO 对比总结

IO 的方式通常分为几种:同步阻塞的 BIO、同步非阻塞的 NIO、异步非阻塞的 AIO。

  1. BIO 方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4 以前的唯一选择,但程序直观简单易理解。
  2. NIO 方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4 开始支持。
  3. AIO 方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用 OS 参与并发操作,编程比较复杂,JDK7 开始支持。

     举个例子再理解一下:

  1. 同步阻塞:你到饭馆点餐,然后在那等着,啥都干不了,饭馆没做好,你就必须等着!
  2. 同步非阻塞:你在饭馆点完餐,就去玩儿了。不过玩一会儿,就回饭馆问一声:好了没  啊!
  3. 异步非阻塞:饭馆打电话说,我们知道您的位置,一会给你送过来,安心玩儿就可以了,   类似于现在的外卖。

代码示例下载:

链接:https://pan.baidu.com/s/1ANpIDnCSrpkSK_Y-1TT5eQ 
提取码:xq66 
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_无往而不胜_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值