1、常规技术:Spring系统、ORM组件、服务支持
数据库表的CRUD处理(重复且大量的编写),这种开发好像不是开发的感觉
2、未来的开发人才到底应该具备哪些技能 —— 架构师
A、可以完成项目,同时可以很好的沟通;
B、掌握各种常规的开发技术,并且掌握一些服务组件的使用(需要有好的运维);
C、良好的代码设计能力 —— 代码重用与标准设定;
D、非常清楚底层通讯机制,并且可以根据实际的业务需求,进行底层通讯协议的定义。
3、网络通讯的核心思想:请求-响应
网络七层模型:
应用层、表示层、回话层、传输层(数据段)、网络层(数据包)、数据链路层(数据帧)、物理层(比特流)
TCP协议是整个现代项目开发的基础,包括HTTP协议也都是在TCP基础上实现的。
案例:采用一个标准的ECHO程序
客户端输入一个内容,随后服务器端接收到之后进行数据的返回,在数据前面追加有"【ECHO】"的信息。
“telnet 主机名称 端口号”,主要是进行TCP协议的通讯,而对于服务器端是如何实现的并不关注。
BIO程序实现
【JDK 1.0】实现BIO程序开发:同步阻塞IO操作,每一个线程都只会管理一个客户端的连接,这种操作的本质是存在有程序阻塞的问题。
线程的资源总是在不断的进行创建,并且所有的数据接收里面(Scanner、PrintStrean简化了),网络的数据都是长度限制的,
传统的数据是需要通过字节数组的形式搬砖完成的。
BIO:是需要对数据采用蚂蚁搬家的模式完成的
程序问题:性能不高,多线程的利用率不高,如果大规模的用户访问,有可能会造成服务器端资源的耗尽。
服务器端
/**
* 实现服务端的编写开发,采用BIO(阻塞模式)实现开发的基础结构
*/
public class EchoServer {
public static void main(String[] args) throws Exception{
new EchoServerHandle();
}
}
class EchoServerHandle implements AutoCloseable{
ServerSocket serverSocket;
public EchoServerHandle() throws Exception{
// 进行服务器端Socket的启动
this.serverSocket = new ServerSocket(ServerInfo.PORT);
System.out.println("ECHO服务器端已经启动,该服务在" + ServerInfo.PORT + "端口上监听...");
this.clientConnect();
}
private void clientConnect() throws Exception{
boolean serverFlag = true;
if(serverFlag){
Socket client = this.serverSocket.accept(); // 等待客户端的连接
new Thread(() -> {
try {
// 服务端的输入为客户端的输出
Scanner scan = new Scanner(client.getInputStream());
// 服务端的输出为客户端的输入
PrintStream out = new PrintStream(client.getOutputStream());
// 设置分隔符
scan.useDelimiter("\n");
boolean clientFlag = true;
while(clientFlag){
if(scan.hasNext()){// 现在有内容
String inputData = scan.next(); // 获得输入数据
inputData = inputData.replaceAll("\r|\n", "");
System.out.println(inputData);
if("exit".equalsIgnoreCase(inputData)){// 信息结束
// 这里一定需要提供一个换行的机制,否则Scanner不好读取
out.println("[ECHO] Bye Bye ...");
clientFlag = false; // 结束循环
}else{
// 回应信息
out.println("[ECHO] " + inputData);
}
}
}
client.close();
}catch (Exception e){}
}).start();//启动线程
}
}
@Override
public void close() throws Exception {
this.serverSocket.close();
}
}
客户端
/**
* 实现客户端的编写开发
*/
public class EchoClient {
public static void main(String[] args) {
try(EchoClientHandle echo = new EchoClientHandle()) {
}catch (Exception e){}
}
}
class EchoClientHandle implements AutoCloseable{
private Socket client;
public EchoClientHandle() throws Exception{
this.client = new Socket(ServerInfo.ECHO_SERVER_HOST, ServerInfo.PORT);
System.out.println("已经成功的连接到了服务器,可以进行消息的发送处理");
this.accessServer();
}
/**
* 数据交互处理
* @throws Exception
*/
private void accessServer() throws Exception{
// 服务器端的输出为客户端的输入
Scanner scan = new Scanner(this.client.getInputStream());
// 向服务端发送内容
PrintStream out = new PrintStream(this.client.getOutputStream());
scan.useDelimiter("\n");
boolean flag = true;
while (flag){
String data = InputUtil.getString("请输入要发送的数据信息:");
out.println(data); // 先把内容发送到服务器端上
if("exit".equalsIgnoreCase(data)) {
flag = false; // 结束循环
}
if(scan.hasNext()){
System.out.println(scan.next());
}
}
}
@Override
public void close() throws Exception {
this.client.close();
}
}
NIO程序实现
【JDK 1.4】提供了一个java.nio的开发包,从这一时代开始,Jav提升了IO的处理效率,同时也提升了网络模型的处理效率, 同时NIO里面采用的是同步非阻塞IO操作。NIO的出现在当时来讲,给Java带来了一个最伟大的通讯利器(已经接近了底层的传输性能)。
NIO里面提倡使用Buffer来代替传统的数组操作(蚂蚁搬家),可以减少数组的操作复杂度,利用缓存数据的保存于方便的清空操作进行处理。
Reactor模型提倡的是:公共注册,统一操作,所以会提供有一系列的Channel(通道)。
服务器端
/**
* 基于NIO实现数据的交互处理模式
*/
public class EchoServer {
public static void main(String[] args) throws Exception {
new EchoServerHandle();
}
}
/**
* 实现一个专门用于客户端请求处理的线程对象
*/
class ClientChannelThread implements Runnable{
private SocketChannel channel;
private boolean flag = true; // 循环处理的标志
public ClientChannelThread(SocketChannel channel){
this.channel = channel;
System.out.println("客户端与服务器端连接成功,可以进行数据的交互操作了...");
}
/**
* 真正的通讯处理的核心需要通过run()方法来进行操作
*/
@Override
public void run(){
// NIO是基于Buffer缓冲操作实现的功能,需要将输入的内容保存到缓存中
ByteBuffer buffer = ByteBuffer.allocate(50); // 开辟一个50大小的缓存空间
try {
while(this.flag){
// 先清空缓存操作,可以进行缓存空间的重复使用
buffer.clear();
// 服务器端读取客户端发送来的数据
int readCount = this.channel.read(buffer);
System.out.println(readCount);
// buffer.flip();
// 将缓存区中保存的内容转为字节数组之后进行存储
String readMessage = new String(buffer.array(), 0, readCount).trim();
System.out.println("[服务器端接收消息] " + readMessage); // 输出一下提示信息
// 在进行整个通讯过程里,分隔符是一个绝对重要的概念,如果不能够很好的处理分隔符,那么无法进行有效的通讯
String writeMessage = "[ECHO] " + readMessage + "\n"; // 进行消息的回应处理
if("exit".equalsIgnoreCase(readMessage)){
writeMessage = "[ECHO] Bye Bye ..."; // 结束消息
this.flag = false;
// 现在我们的数据是在字符串中,如果要回应消息,需要将内容保存到Buffer中
}
buffer.clear(); // 将已经处理完的内容清除
buffer.put(writeMessage.getBytes()); // 保存回应信息
buffer.flip(); // 重置缓冲区
this.channel.write(buffer);
}
this.channel.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 定义服务器端的处理类
*/
class EchoServerHandle implements AutoCloseable{
private ExecutorService executor;
// 服务器端的通讯通道
private ServerSocketChannel serverChannel;
//
private Selector selector;
// 客户端的通讯通道
private SocketChannel clientChannel;
public EchoServerHandle() throws Exception {
// 当前执行的线程只允许有5个
this.executor = Executors.newFixedThreadPool(5);
// 打开服务器的连接通道
this.serverChannel = ServerSocketChannel.open();
// 设置为非阻塞模式
this.serverChannel.configureBlocking(false);
// 绑定端口
this.serverChannel.bind(new InetSocketAddress(ServerInfo.PORT));
// NIO 中的Reactor模型重点在于所有的Channel需要向Selector之中注册
// 获取Selector的实例
this.selector = Selector.open();
// 注册
this.serverChannel.register(this.selector, SelectionKey.OP_ACCEPT);//服务器端需要进行接收
System.out.println("服务器端程序启动,改程序在" + ServerInfo.PORT + "端口上进行监听...");
this.clientHandle();
}
public void clientHandle() throws Exception{
int keySelect = 0; // 保存当前的状态
while ((keySelect = this.selector.select())>0){// 需要进行连接等待
// 获取全部的连接通道信息
Set<SelectionKey> selectionKeys = this.selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while(iterator.hasNext()){
// 获取每一个通道
SelectionKey selectionKey = iterator.next();
if(selectionKey.isAcceptable()){ // 该通道为接收状态
this.clientChannel = this.serverChannel.accept(); // 等待连接
if(this.clientChannel != null){// 当前已经有连接了
// 线程池将该连接提交给客户端的通道线程去处理
this.executor.submit(new ClientChannelThread(this.clientChannel));
}
}
// 移出此通道
iterator.remove();
}
}
}
@Override
public void close() throws Exception {
this.executor.shutdown(); // 关闭线程池
this.serverChannel.close(); // 关闭服务端通道
}
}
客户端
/**
* 进行NIO客户端的连接访问
*/
public class EchoClient{
public static void main(String[] args) throws Exception{
new EchoClientHandle();
}
}
class EchoClientHandle implements AutoCloseable{
private SocketChannel clientChannel;
public EchoClientHandle() throws Exception{
// 创建一个客户端的通道实例
this.clientChannel = SocketChannel.open();
// 设置要连接的主机信息,包括主机名称以及端口号
this.clientChannel.connect(new InetSocketAddress(ServerInfo.ECHO_SERVER_HOST, ServerInfo.PORT));
this.accessServer();
}
/**
* 访问服务器端
*/
public void accessServer() throws Exception{
ByteBuffer buffer = ByteBuffer.allocate(50); // 开辟缓冲区
boolean flag = true;
while (flag){
buffer.clear();
String msg = InputUtil.getString("请输入要发送的内容:");
buffer.put(msg.getBytes());
buffer.flip();
this.clientChannel.write(buffer); // 发送出具内容
// 当消息发送过去之后还需要进行返回内容的接收处理
buffer.clear();
// 将内容读取到缓冲区,并返回个数
int readCount = this.clientChannel.read(buffer);
buffer.flip();
System.out.println(new String(buffer.array(), 0, readCount));
if("exit".equalsIgnoreCase(msg)){
flag = false;
}
}
}
@Override
public void close() throws Exception {
this.clientChannel.close();
}
}
AIO程序实现
【JDK 1.7】AIO,异步非阻塞IO通讯,需要采用大量的回调处理模式,所以需要使用:
public interface CompletionHandler<V, A>
服务器端
/**
* 基于AIO实现数据的交互处理模式
*/
public class EchoServer {
public static void main(String[] args) throws Exception{
new Thread(new AIOServerThread()).start();
}
}
/**
* 1、实现客户端连接回调的处理操作
*/
class AcceptHandler implements CompletionHandler<AsynchronousSocketChannel, AIOServerThread>{
@Override
public void completed(AsynchronousSocketChannel result, AIOServerThread attachment) {
System.out.println("客户端与服务器端连接成功,可以进行数据的交互操作了...");
// 接收连接对象
attachment.getServerChannel().accept(attachment, this);
ByteBuffer buffer = ByteBuffer.allocate(50);
result.read(buffer, buffer, new EchoHandler(result));
}
@Override
public void failed(Throwable exc, AIOServerThread attachment) {
System.out.println("服务器端客户端连接失败...");
attachment.getLatch().countDown(); // 恢复执行
}
}
/**
* 2、实现客户端的回应处理操作
*/
class EchoHandler implements CompletionHandler<Integer, ByteBuffer>{
// 客户端的通道
private AsynchronousSocketChannel clientChannel;
private boolean exit = false; // 进行操作结束的标志
public EchoHandler(AsynchronousSocketChannel clientChannel){
this.clientChannel = clientChannel;
}
@Override
public void completed(Integer result, ByteBuffer buffer) {
buffer.flip();
String readMessage = new String(buffer.array(), 0, buffer.remaining()).trim();
System.out.println("[服务器端接收到消息内容]" + readMessage);
String resultMessage = "[ECHO] " + readMessage + "\n";//回应消息
if("exit".equalsIgnoreCase(readMessage)){
resultMessage = "[ECHO] Bye Bye..." + "\n";
}
this.echoWrite(resultMessage);
}
private void echoWrite(String result){
ByteBuffer buffer = ByteBuffer.allocate(50);
buffer.put(result.getBytes());
buffer.flip();
this.clientChannel.write(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() {
@Override
public void completed(Integer result, ByteBuffer buffer) {
if(buffer.hasRemaining()){ // 当前有数据
EchoHandler.this.clientChannel.write(buffer, buffer, this);
}else {
if (EchoHandler.this.exit == false){//需要继续操作
ByteBuffer readBuffer = ByteBuffer.allocate(50);
EchoHandler.this.clientChannel.read(readBuffer, readBuffer, new EchoHandler(EchoHandler.this.clientChannel));
}
}
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
try {
EchoHandler.this.clientChannel.close();
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
try {
this.clientChannel.close();// 关闭通道
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* AIO线程处理类
*/
class AIOServerThread implements Runnable{
private AsynchronousServerSocketChannel serverChannel;
private CountDownLatch latch; // 进行线程等待操作
public AIOServerThread() throws Exception{
this.latch = new CountDownLatch(1); // 设置一个线程等待个数
this.serverChannel = AsynchronousServerSocketChannel.open();
// 绑定服务端口
this.serverChannel.bind(new InetSocketAddress(ServerInfo.PORT));
System.out.println("服务器端程序启动,改程序在" + ServerInfo.PORT + "端口上进行监听...");
}
public AsynchronousServerSocketChannel getServerChannel(){
return serverChannel;
}
public CountDownLatch getLatch(){
return latch;
}
@Override
public void run(){
// 等待客户端的连接
this.serverChannel.accept(this, new AcceptHandler());
try {
this.latch.await(); // 进入等待时机
} catch (Exception e) {
e.printStackTrace();
}
}
}
客户端
/**
* 基于AIO实现客户端的连接访问
*/
public class EchoClient {
public static void main(String[] args) throws Exception{
AIOClientThread client = new AIOClientThread();
new Thread(client).start();
while (client.sendMessage(InputUtil.getString("请输入要发送的信息:"))){
}
}
}
/**
* 客户端读操作
*/
class ClientReadHandler implements CompletionHandler<Integer, ByteBuffer>{
private AsynchronousSocketChannel clientChannel;
private CountDownLatch latch;
public ClientReadHandler(AsynchronousSocketChannel clientChannel, CountDownLatch latch){
this.clientChannel = clientChannel;
this.latch = latch;
}
@Override
public void completed(Integer result, ByteBuffer buffer) {
buffer.flip();
String receiveMessage = new String(buffer.array(), 0, buffer.remaining());
System.out.println(receiveMessage);
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
try {
this.clientChannel.close();
} catch (Exception e) {
e.printStackTrace();
}
this.latch.countDown();
}
}
/**
* 客户端写操作
*/
class ClientWriteHandler implements CompletionHandler<Integer, ByteBuffer>{
private AsynchronousSocketChannel clientChannel;
private CountDownLatch latch;
public ClientWriteHandler(AsynchronousSocketChannel clientChannel, CountDownLatch latch){
this.clientChannel = clientChannel;
this.latch = latch;
}
@Override
public void completed(Integer result, ByteBuffer buffer) {
if(buffer.hasRemaining()){
this.clientChannel.write(buffer, buffer, this);
}else {
ByteBuffer readBuffer = ByteBuffer.allocate(50);
this.clientChannel.read(readBuffer, readBuffer, new ClientReadHandler(this.clientChannel, this.latch));
}
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
try {
this.clientChannel.close();
} catch (Exception e) {
e.printStackTrace();
}
this.latch.countDown();
}
}
class AIOClientThread implements Runnable{
private AsynchronousSocketChannel clientChannel;
private CountDownLatch latch;
public AIOClientThread() throws Exception{
// 打开客户端的连接通道
this.clientChannel = AsynchronousSocketChannel.open();
// 连接服务端
this.clientChannel.connect(new InetSocketAddress(ServerInfo.ECHO_SERVER_HOST, ServerInfo.PORT));
this.latch = new CountDownLatch(1);
}
@Override
public void run(){
try {
this.latch.await();
this.clientChannel.close();
}catch (Exception e){}
}
/**
* 进行消息的发送处理
* @param msg 输入的交互信息
* @return 是否停止交互的处理
*/
public boolean sendMessage(String msg) throws Exception{
ByteBuffer buffer = ByteBuffer.allocate(50);
buffer.put(msg.getBytes());
buffer.flip();
this.clientChannel.write(buffer, buffer, new ClientWriteHandler(this.clientChannel, this.latch));
if("exit".equalsIgnoreCase(msg)){
return false;
}else {
return true;
}
}
}
总结:BIO(同步阻塞IO):在进行处理的时候是通过一个线程进行操作,并且IO实现通讯的时候采用的是阻塞模式;
例如:水壶烧水,在BIO的世界里,烧水这一过程你需要从头一直监视到结尾
NIO(同步非阻塞IO):不断的进行烧水状态的判断,同时你可以做其他的事情;
AIO(异步非阻塞IO):烧水的过程你不用关注,如果水一旦烧好了,就会给你一个反馈。
以上所讲解的程序都属于最为基础的通讯模型,但是如果你真的只是依靠这样的开发操作去编写程序,那么基本上就决定你的项目八成会失败。
真实的项目之中如果要想实现这些通讯的操作一般要考虑:粘包和拆包、序列化、HTTP协议如何去写、WebSocket的定义实现。都需要你去精通这些协议。
为了简化这些操作在实际的项目里面,强烈建议,这些底层功能都通过Netty包装。