一、网络编程模型 1.编程模型前置知识:
Java基础知识;BIO网络编程知识;多线程编程知识;
模型:对事务共性的抽象;
编程模型:对编程共性的抽象;
2.BIO网络模型实际的问题 具体的解决方案 抽象出一套
依靠这个模型来解决这一类问题;
阻塞式 I/O 模型
如果客户端没有发起请求,服务的会一直存在并等待连接 弹性伸缩能力差
每一个对服务端的连接就需要开启一个线程连接数很可能超过服务器所能负载的最大线程数 多线程耗资源
创建,销毁,维护大量线程以及线程切换都非常消耗系统资源 3.NIO网络模型
非阻塞 IO 模型
服务器端提供一个单线程的 selector 来统一管理所有客户端接入的连接并负责监听每个连接所关心的事件 弹性伸缩能力加强
服务器端一个线程处理所有客户端的连接请求客户端的个数与服务器端的线程数呈 M 比 1 的关系 单线程节省资源
避免了线程的频繁创建和销毁同时也避免了多个线程之间上下文的切换,提高了执行效率 二、BIO下的TCP编程通信
1.服务器端serverserver.java
import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public void start() throws IOException {
//1.服务器端监听建立连接请求
ServerSocket serverSocket = new ServerSocket(8888);
System.out.println("服务端即将启动,等待客户端的连接...");
//2.客户端发起建立连接请求
int count = 0;
Socket socket = null;
while(true){//循环监听客户端的连接
//调用accept()方法监听,等待客户端连接以获取socket实例
socket = serverSocket.accept();
//3.服务器端启动新线程
//4.线程响应客户端
//5.等待客户单再次请求
//创建新线程
Thread thread = new Thread(new ServerThread(socket));
thread.start();
count++;
System.out.println("服务器端被连接过的次数:"+count);
InetAddress address = socket.getInetAddress();
System.out.println("当前客户端IP为:"+address.getHostAddress());
}
}
public static void main(String[] args) throws IOException {
Server server = new Server();
server.start();
}
}
ServerThread.java
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
public class ServerThread implements Runnable{
Socket socket = null;
public ServerThread(Socket socket){
this.socket = socket;
}
@Override
public void run() {
InputStream is = null;
InputStreamReader isr = null;
BufferedReader br = null;
OutputStream os = null;
PrintWriter pw = null;
//与客户端建立通信,获取输入流,读取客户端提供的信息
try {
is = socket.getInputStream();
isr = new InputStreamReader(is,"UTF-8");
br = new BufferedReader(isr);
String data = null;
while((data = br.readLine())!=null){
System.out.println("我是服务器,客户端提交的信息为:"+data);
socket.shutdownInput();//关闭输入流
//获取输出流,响应客户端的请求
os = socket.getOutputStream();
pw = new PrintWriter(os);
pw.write("服务器端响应成功");
pw.flush();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
//关闭资源和相关socket
try{
if(pw!=null){
pw.close();
}
if(os!=null){
os.close();
}
if(is!=null){
is.close();
}
if(isr!=null){
isr.close();
}
if(br!=null){
br.close();
}
if(socket!=null){
socket.close();
}
}catch(Exception e){
e.printStackTrace();
}
}
}
}
2.客户端client
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
public class Client {
public static void main(String[] args) {
try{
Socket socket = new Socket("localhost",8888);
OutputStream os = socket.getOutputStream();
PrintWriter pw = new PrintWriter(os);
pw.write("用户名:jinxueling;密码:123");
pw.flush();
socket.shutdownOutput();
InputStream is = socket.getInputStream();
InputStreamReader isr = new InputStreamReader(is,"UTF-8");
BufferedReader br = new BufferedReader(isr);
String data = null;
while((data = br.readLine())!=null){
System.out.println("我是客户端,服务器端响应的数据为:"+data);
}
socket.close();
}catch (UnknownHostException e) {
e.printStackTrace();
}catch(IOException e){
e.printStackTrace();
}
}
}
三、NIO下的聊天室编程
1NIO网络编程详解
NIO 核心
Channel:通道Buffer:缓冲区Selector:选择器或多路复用器 1.1. Channel
特点:
双向性非阻塞性操作唯一性
实现:
文件类:FileChannelUDP 类:DatagramChannelTCP 类:ServerSocketChannel / SocketChannel
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress(8000));
SocketChannel socketChannel = serverSocketChannel.accept();
SocketChannel socketChannel = SocketChannel.open(
new InetSocketAddress("127.0.0.1", 8000));
1.2 Buffer
作用:读写 Channel 中数据
本质:一块内存区域
属性:
Capacity:容量Position:位置Limit:上限Mark:标记
用法:
ByteBuffer.allocate(10);
ByteBuffer.put("abc".getBytes(Charset.forName("UTF-8")));
ByteBuffer.flip();
ByteBuffer.get();
ByteBuffer.mark();
ByteBuffer.get(); ByteBuffer.reset();
ByteBuffer.clear();1.3 Selector
作用:I/O 事件就绪选择
地位:NIO 网络编程的基础之一
Selector selector = Selector.open(); serverSocketChannel.register(selector, SelectionKey.OP_READ); int selectNum = selector.select(); SetselectionKeys = selector.selectedKeys();
SelectionKey 提供四种就绪状态常量:
连接就绪:connect接受就绪:accept读就绪:read写就绪:write 1.4 NIO编程步骤
第一步:创建 Selector第二步:创建 ServerSocketChannel,并绑定监听端口第三步:将 Channel 设置为非阻塞模式第四步:将 Channel 注册到 Selector 上,监听连接事件第五步:循环调用 Selector 的 select 方法,检测就绪情况第六步:调用 selectedKeys 方法获取就绪 channel 集合第七步:判断就绪事件种类,调用业务处理方法第八步:根据业务需要决定是否再次注册监听事件,重复执行第三步操作 2.NIO网络编程聊天室实战 2.1 服务器端
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.Set;
public class NioServer {
public void start() throws IOException {
//1.创建selector
Selector selector = Selector.open();
//2.通过serversocketchannel创建channel通道
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//3.为channel绑定监听端口
serverSocketChannel.bind(new InetSocketAddress(8000));
//4.设置channel为非阻塞模式
serverSocketChannel.configureBlocking(false);
//5.将channel注册到selector上监听连接事件
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("服务器启动成功");
//6.循环等待新接入的连接
for (;;){
//获取可用的channel数量
int readChannels = selector.select();
if (readChannels==0){
continue;
}
//获取可用的channel地址集合
Set selectionKeySet = selector.selectedKeys();
Iterator iterator = selectionKeySet.iterator();
while (iterator.hasNext()){
//selectionKey实例
SelectionKey selectionKey= (SelectionKey) iterator.next();
//移除set中当前selectionKey实例
iterator.remove();
//7.根据对应状态,调用对应方法处理业务逻辑
if (selectionKey.isAcceptable()){
//如果是接入事件
acceptHandler(serverSocketChannel,selector);
}
if (selectionKey.isReadable()){
//如果是可读事件
readHandler(selectionKey,selector);
}
}
}
}
private void acceptHandler(ServerSocketChannel serverSocketChannel,
Selector selector) throws IOException {
//如果是接入事件,创建socketchannel
SocketChannel socketChannel = serverSocketChannel.accept();
//将socketchannel设置为非阻塞工作模式
socketChannel.configureBlocking(false);
//将channel注册到selector上,监听可读事件
socketChannel.register(selector,SelectionKey.OP_READ);
//回复客户端提示信息
socketChannel.write(StandardCharsets.UTF_8.encode("你与聊天室李其他人都不是朋友关系,请注意隐私安全"));
}
private void readHandler(SelectionKey selectionKey,Selector selector) throws IOException {
//从selectionkey中获取到已经就绪的channel
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
//创建buffer
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
//循环读取客户端请求信息
StringBuilder request = new StringBuilder();
while (socketChannel.read(byteBuffer)>0){
//切换buffer为读模式
byteBuffer.flip();
//读取buffer中的内容
request.append(StandardCharsets.UTF_8.decode(byteBuffer));
}
//将channel再次注册到selector上,监听其它的可读事件
socketChannel.register(selector,SelectionKey.OP_READ);
//将客户端发送的请求信息,广播到其它客户端
if (request.length()>0){
boardCast(selector,socketChannel,request.toString());
}
}
private void boardCast(Selector selector, SocketChannel sourceChannel, String request) {
//获取到所有已接入的客户端channel
Set selectionKeySet = selector.keys();
//循环向所有channel广播信息
selectionKeySet.forEach(selectionKey -> {
Channel targetChannel= selectionKey.channel();
//剔除发消息的客户端
if (targetChannel instanceof SocketChannel && targetChannel != sourceChannel){
try {
//将消息发送到targetChannel客户端
((SocketChannel) targetChannel).write(StandardCharsets.UTF_8.encode(request));
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
public static void main(String[] args) throws IOException {
NioServer nioServer = new NioServer();
nioServer.start();
}
}
2.2 客户端
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;
public class NioClient {
public void start(String nickname) throws IOException {
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8000));
System.out.println("客户端启动成功");
Selector selector = Selector.open();
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
new Thread(new NioClientHandler(selector)).start();
Scanner scanner = new Scanner(System.in);
while (scanner.hasNextLine()){
String request = scanner.nextLine();
if (request!=null&&request.length()>0){
socketChannel.write(StandardCharsets.UTF_8.encode(nickname+" : " + request));
}
}
}
}
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.Set;
public class NioClientHandler implements Runnable{
private final Selector selector;
public NioClientHandler(Selector selector) {
this.selector = selector;
}
@Override
public void run() {
try {
for (;;){
int readChannels = selector.select();
if (readChannels==0){
continue;
}
Set selectionKeySet = selector.selectedKeys();
Iterator iterator = selectionKeySet.iterator();
while(iterator.hasNext()){
SelectionKey selectionKey = iterator.next();
iterator.remove();
if (selectionKey.isReadable()){
readHander(selectionKey,selector);
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
private void readHander(SelectionKey selectionKey,Selector selector) throws IOException {
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
StringBuilder response = new StringBuilder();
while(socketChannel.read(byteBuffer)>0){
byteBuffer.flip();
response.append(StandardCharsets.UTF_8.decode(byteBuffer));
}
socketChannel.register(selector,SelectionKey.OP_READ);
if (response.length()>0){
System.out.println(response);
}
}
}
3.NIO网络编程缺陷
麻烦
NIO 类库和 API 繁杂 心累
可靠性能力补齐,工作量和难度都非常大 有坑
Selector 空轮询,导致 CPU 占用率 100%



