网络编程

一、高并发互联网应用架构设计
架构的演变过程
1. 单一应用架构
简单,维护成本高
ORM
2. 垂直应用架构
各司其职
MVC
3. 分布式服务架构
Cross JVM | Cross Machine
RPC(Remote Procedure Call)
4. 流动计算架构
资源调度和服务治理理
SOA(service-oriented architecture)
高并发网站的设计原则
1. X轴:硬件(水平拓拓展,比性能的垂直提升成本低-摩尔定律律)、应用水平复制(应用无状态) 水
平扩展
2. Y轴:硬件、业务垂直拆分 各司其职 (泳道设计)
3. Z轴:x和y轴打包之后 物理理隔离
如何设计高效、高性能的应用服务?
在分布式服务架构中,我们需要Cross JVM或者Cross Machine传输数据,所以高效的RPC通信模型
(Socket+IO)是设计关键
二、Java中⽹网络(IO)模型
IO模型分类
1. BIO
传统IO 或者 Blocking IO
特点:面向流 Input | Output
2. NIO
New IO 或者 Non Blocking IO
特点:⾯面向缓冲区 Buffer(基于通道)
3. AIO(Async Non Blocking IO)
BIO
1. 方向 —— 输入、输出流(InputStream | OutputStream)
2. 类型 —— 字节、字符流(Reader | Writer)
3. 功能 —— 节点、过滤流(BufferedInputStream | BufferedOutputStream )
NIO
NIO主要有三⼤大核⼼心部分:
1. Channel
通道、双向、可读可写
Channel的主要实现类有:
FileChannel ⽂文件IO
DatagramChannel UDP
SocketChannel TCP Client
ServerSocketChannel TCP Server
2. Buffer
缓冲区
Buffer的主要实现类有:
除boolean外的其余七种基本类型(ByteBuffer、ShortBuffer、IntBuffer等)
3. Selector
NIO网络编程讲解
NIO Buffer详解
一个⽤用于特定基本类型的数据容器,除数据内容外,还包含以下属性:
1. capacity 缓冲区大小- 常量_不可变
2. limit 缓冲区允许读写操作的最大范围
3. position 缓冲区中下一个可读写元素的索引
注:所有可操作的数据在position和limit之间
操作图示:
示例例代码:

public class BufferTest {
public static void main(String[] args) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
System.out.println("初始化状态 ——> capacity:"+buffer.capacity() + "
limit:"+buffer.limit() + " position:"+buffer.position());
buffer.put("ab".getBytes());
System.out.println("存放两个字节 ——> capacity:"+buffer.capacity() + "
limit:"+buffer.limit() + " position:"+buffer.position());
buffer.flip();
测试结果:
NIO IO操作(⽂文件拷⻉贝)
System.out.println("调⽤用flip()⽅方法 ——> capacity:"+buffer.capacity() +
" limit:"+buffer.limit() + " position:"+buffer.position());
byte b = buffer.get();
System.out.println("读取到内容:" +(char)b);
System.out.println("读取⼀一个字节 ——> capacity:"+buffer.capacity() + "
limit:"+buffer.limit() + " position:"+buffer.position());
buffer.clear();
System.out.println("调⽤用清除⽅方法 ——> capacity:"+buffer.capacity() + "
limit:"+buffer.limit() + " position:"+buffer.position());
}
}

public class NIOFileCopy {
public static void main(String[] args) throws IOException {
FileInputStream inputStream = new
FileInputStream("f:\\background.jpg");
FileOutputStream outputStream = new
FileOutputStream("e:\\bg_copy.jpg");
// 1. 获取输⼊入通道
FileChannel inputStreamChannel = inputStream.getChannel();
BIO ⽹网路路编程
服务器器: ServerSocket
1. 初始化服务器器 ServerSocket,绑定监听端⼝口
2. 等待客户端连接 serverSocket.accept();
3. 处理理请求/响应 sockect.getInputStream(); / socket.getOutputStream();
4. 关闭资源
客户端: Socket
1. 初始化客户端 Socket,绑定服务器器IP/端口
2. 发起请求/获取响应 socket.getOutputStream(); / socket.getInputStream();
3. 关闭资源
客户端示例例代码:
// 2. 获取输出通道
FileChannel outputStreamChannel = outputStream.getChannel();
// 3. 创建缓冲区
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
while(true){
if( -1 == inputStreamChannel.read(byteBuffer)){
break;
}
// 4. 读取缓冲区的数据前 需要调用flip()
byteBuffer.flip();
outputStreamChannel.write(byteBuffer);
// 5. 清空缓冲区 恢复到初始化状态
byteBuffer.clear();
}
inputStream.close();
outputStream.close();
}
}



public class BIOClient {
public static void main(String[] args) throws IOException {
try {
// 1. 初始化客户端 指定连接服务器端口和IP
服务器示例例代码:
版本1: 单请求
Socket socket = new Socket("127.0.0.1", 8888);
// 2. 客户端发起请求
OutputStream outputStream = socket.getOutputStream();
outputStream.write(new String("Hello Server").getBytes());
socket.shutdownOutput();
// 3. 客户端处理理响应
InputStream inputStream = socket.getInputStream();
byte[] b = new byte[1024];
while (true) {
int flag = inputStream.read(b);
if (flag == -1) {
break;
}
System.out.println(new String(b));
}
socket.shutdownInput();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 单请求
* @throws IOException
*/
public static void start01() throws IOException {
// 1. 初始化服务器 绑定IP地址 启动监听端⼝口
ServerSocket serverSocket = new ServerSocket(8888);
System.out.println("服务器已启动,端口号:8888");
// 2. 等待客户端连接
System.out.println("正在等待客户端连接!");
Socket socket = serverSocket.accept();
1. 特点:accept()是⼀一个阻塞⽅方法,当有客户端连接时,返回Socket对象,通过Socket
对象的InputStream()或者OutputStream() 接受请求发送响应结果。单请求服务器模
型只能处理一个客户端的请求
2. 缺点:服务器必须能够处理多个客户端的请求和响应
版本2: 多请求
// 3. 获取请求数据
InputStream inputStream = socket.getInputStream();
// 4. 处理理请求
byte[] b = new byte[1024];
while(true){
int flag = inputStream.read(b);
if(flag == -1) {
break;
}
System.out.println(new String(b));
}
socket.shutdownInput();
//5. 发送响应
OutputStream outputStream = socket.getOutputStream();
outputStream.write("服务器器已收到消息!Hello Client".getBytes());
socket.shutdownOutput();
socket.close();
}
/**
* 多请求
* @throws IOException
*/
public static void start02() throws IOException, InterruptedException {
// 1. 初始化服务器器 绑定IP地址 启动监听端⼝口
ServerSocket serverSocket = new ServerSocket(8888);
System.out.println("服务器已启动,端⼝口号:8888");
1. 特点:通过循环可以不断的接受客户端请求
2. 缺点:请求的接受和IO处理在同⼀一个线程中,客户端请求本质上是串行处理,无法支
持高并发
版本3: 多请求多线程
while(true){
System.out.println("正在等待客户端连接!");
// 2. 等待客户端连接
Socket socket = serverSocket.accept();
System.out.println("----------------处理理请求中-------------------
-");
// 3. 获取请求数据
InputStream inputStream = socket.getInputStream();
// 4. 处理理请求
byte[] b = new byte[1024];
while(true){
int flag = inputStream.read(b);
if(flag == -1) {
break;
}
System.out.println(new String(b));
}
socket.shutdownInput();
// 模拟业务处理理 花费的时⻓长
Thread.sleep(5000);
//5. 发送响应
OutputStream outputStream = socket.getOutputStream();
outputStream.write("服务器已收到消息!Hello Client".getBytes());
socket.shutdownOutput();
socket.close();
}
}
/**
1. 特点:请求转发和IO处理理分离,主线程只负责接受请求,IO的处理理通过子线程完成。
实现服务器的高性能
* 多请求 多线程版
* @throws IOException
*/
public static void start03() throws IOException, InterruptedException {
// 1. 初始化服务器 绑定IP地址 启动监听端⼝口
ServerSocket serverSocket = new ServerSocket(8888);
while(true){
System.out.println("服务器已启动,端⼝口号:8888");
// 2. 等待客户端连接
final Socket socket = serverSocket.accept();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("----------------处理请求中-----------
---------");
try {
// 3. 获取请求数据
InputStream inputStream = socket.getInputStream();
// 4. 处理理请求
byte[] b = new byte[1024];
while(true){
int flag = inputStream.read(b);
if(flag == -1) {
break;
}
System.out.println(new String(b));
}
socket.shutdownInput();
//Thread.sleep(3000);
//5. 发送响应
OutputStream outputStream =
socket.getOutputStream();
outputStream.write("服务器已收到消息!Hello
Client".getBytes());
socket.shutdownOutput();
socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
}
2. 缺点:缺乏弹性伸缩能力,当客户端的访问量增加后,服务端的线程个数和客户端并
发访问数量1:1正比关系,线程是宝贵的系统资源,数量过多,会造成系统性能急剧
下降,导致服务器“宕机”
版本4: 多请求多线程优化版
/**
* 多请求 多线程优化版 (最终版)
* @throws IOException
*/
public static void start04()throws IOException, InterruptedException {
// 1. 初始化服务器 绑定IP地址 启动监听端口
ServerSocket serverSocket = new ServerSocket(8888);
// 初始化线程池对象
ExecutorService threadPool = Executors.newFixedThreadPool(5);
while(true) {
System.out.println("服务器已启动,端⼝号:8888");
// 2. 等待客户端连接
final Socket socket = serverSocket.accept();
threadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println("----------------处理理请求中-----------
---------"+Thread.currentThread().getId());
try {
// 3. 获取请求数据
InputStream inputStream = socket.getInputStream();
// 4. 处理理请求
byte[] b = new byte[1024];
while(true){
int flag = inputStream.read(b);
if(flag == -1) {
break;
}
1. 特点: 通过线程池有效的管理线程,避免线程的重复创建、销毁而导致的系统资源过
渡开销
2. 缺点:
a. 限制了了线程的数量,当有大量的并发请求时,超过最大线程数量的请求只能等待,
直到线程池中有空闲的线程可以复用
b. 对于BIO,当你发送一个请求的时候,你必须等待直到你获得返回结果。在服务端,
这个意味着,一个线程同一时间最多只能和一个传入的连接相关联(直到这个连接被
关闭,这个线程才能和其他连接关联)。先开线程,在线程中可能没有就绪的IO,导
致线程的利利用率不不⾼高。
NIO ⽹网络编程
使⽤用NIO API,替换BIO的多请求多线程版网络模型
System.out.println(new String(b));
}
socket.shutdownInput();
//Thread.sleep(3000);
//5. 发送响应
OutputStream outputStream =
socket.getOutputStream();
outputStream.write("服务器器已收到消息!Hello
Client".getBytes());
socket.shutdownOutput();
socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
}
package com.baizhi.bio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
/**
* 使⽤用NIO API,通过⾯面向通道的编程⽅方式,替换BIO的网络模型
*/
特点: 使用NIO的API,通过⾯面向通道的编程方式替换了了BIO的编程模型
缺点: 此模型跟BIO编程模型本质上没有区别(API差异),实际上也没有解决IO的阻塞问

Selector
public class NIOApiServer {
public static void main(String[] args) throws IOException {
// 1. 初始化服务器 绑定IP地址 启动监听端⼝口
ServerSocketChannel serverSocketChannel =
ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress(8888));
serverSocketChannel.configureBlocking(true);
while (true) {
System.out.println("服务器已启动,端⼝口号:8888");
// 2. 等待客户端连接 请求转发
SocketChannel socketChannel = serverSocketChannel.accept();
// IO处理理
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("处理理请求中~~~");
try {
// 3. 创建缓冲区
ByteBuffer byteBuffer =
ByteBuffer.allocate(1024);
// 4. IO处理理
while (true) {
int flag = socketChannel.read(byteBuffer);
if (flag == -1) {
break;
}
byteBuffer.flip();
socketChannel.write(byteBuffer);
byteBuffer.clear();
}
// 5. 关闭资源
socketChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
// serverSocketChannel.close();
}
}
在NIO使⽤用Channel管理理所有的IO操作,Selector用于管理理通道(注:需确保通道的操作都是非
阻塞的),Channel和Selector配合使⽤用
1. 通道需注册到选择器中(通道+关注的事件类型+附件信息)
2. 选择器中的通道必须是⾮非阻塞的
3. 常用的事件类型
a. SelectionKey.OP_CONNECT 连接就绪
b. SelectionKey.OP_ACCEPT 接受就绪
c. SelectionKey.OP_READ 读就绪
d. SelectionKey.OP_READ 读就绪

NIO服务器器示例例代码:
package com.baizhi.nio;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;

public class NIOServer {
public static void main(String[] args) throws IOException {
//1. 初始化NIO Server 绑定监听端⼝口 设置通道⾮非阻塞
ServerSocketChannel serverSocketChannel =
ServerSocketChannel.open();
// 注:选择器管理的通道 必须是非阻塞的
serverSocketChannel.configureBlocking(false);
serverSocketChannel.bind(new InetSocketAddress(8888));
//2. 创建通道选择器
Selector selector = Selector.open();
//3. 注册通道 事件类型 SelectionKey.OP_ACCEPT
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
//4. 轮询方式访问通道选择器
while(true){
System.out.println("服务器已启动!在8888端口等待客户端连
接......");
// 阻塞 直到有就绪的IO事件产⽣生
int i = selector.select();
if(i != 0){
// IO就绪 需要处理理的通道
Iterator<SelectionKey> iterator =
selector.selectedKeys().iterator();
while(iterator.hasNext()){
// 获取事件表中的通道
SelectionKey selectionKey = iterator.next();
// 接受请求事件
if(selectionKey.isAcceptable()){
System.out.println("-----------客户端已连接------
------");
ServerSocketChannel ssc = (ServerSocketChannel)
selectionKey.channel();
// 获取客户端连接通道
SocketChannel socketChannel = ssc.accept();
socketChannel.configureBlocking(false);
// 注册读事件
socketChannel.register(selector,SelectionKey.OP_READ);
}else if(selectionKey.isReadable()){
System.out.println("-----------获取请求中--------
----");
SocketChannel socketChannel = (SocketChannel)
selectionKey.channel();
// 初始化缓冲区
ByteBuffer byteBuffer =
ByteBuffer.allocate(1024);
ByteArrayOutputStream baos = new
ByteArrayOutputStream();
while(true){
int flag = socketChannel.read(byteBuffer);
NIO客户端示例例代码:
if(flag == -1){
break;
}
byteBuffer.flip();
while(byteBuffer.hasRemaining()){
baos.write(byteBuffer.get());
}
byteBuffer.clear();
}
System.out.println("收到客户端请求:"+new
String(baos.toByteArray()));
// 注册写事件
socketChannel.register(selector,SelectionKey.OP_WRITE);
}else if(selectionKey.isWritable()){
System.out.println("-----------处理理响应中--------
----");
SocketChannel socketChannel =
(SocketChannel)selectionKey.channel();
ByteBuffer byteBuffer =
ByteBuffer.allocate(1024);
byteBuffer.put("你好!客户端".getBytes());
byteBuffer.flip();
socketChannel.write(byteBuffer);
socketChannel.shutdownOutput();
socketChannel.close();
}
// 移除事件处理理完成的通道
iterator.remove();
}
}
}
}
}




public class NIOClient {
public static void main(String[] args) throws IOException {
//1. 初始化NIO Client 非阻塞 绑定服务器IP地址和端⼝口号
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("127.0.0.1",8888));
socketChannel.configureBlocking(false);
//2. 注册通道 到通道选择器
Selector selector = Selector.open();
//3. 注册连接事件
socketChannel.register(selector, SelectionKey.OP_WRITE);
while(true){
// 阻塞 直到IO就绪
System.out.println("----------");
int select = selector.select();
if(select > 0){
Iterator<SelectionKey> keys =
selector.selectedKeys().iterator();
while(keys.hasNext()){
SelectionKey selectionKey = keys.next();
// 处理连接事件 发送请求
if(selectionKey.isWritable()){
System.out.println("----------发起请求---------
");
SocketChannel channel = (SocketChannel)
selectionKey.channel();
ByteBuffer byteBuffer=ByteBuffer.wrap(" 愿得一
人心,白头不分离".getBytes());
channel.write(byteBuffer);
channel.shutdownOutput();
// 注册读事件
channel.register(selector,SelectionKey.OP_READ);
}else if(selectionKey.isReadable()){
SocketChannel channel = (SocketChannel)
selectionKey.channel();
ByteBuffer byteBuffer =
ByteBuffer.allocate(1024);
ByteArrayOutputStream baos = new
ByteArrayOutputStream();
while(true){
int flag = channel.read(byteBuffer);
if(flag == -1){
break;
}
byteBuffer.flip();
while(byteBuffer.hasRemaining()){
baos.write(byteBuffer.get());
}
byteBuffer.clear();
}
System.out.println("收到服务器响应:"+new
String(baos.toByteArray()));
channel.close();
return;
}
keys.remove();
}
}
}
}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值