本篇包含了入门小栗子以及一些问题的思考

BIO

package com.demo.bio;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner; /**
* 问题:开启多个客户端,只有服务端发送足够条数的消息,客户端才会收到
*/
public class Server { public static void main(String[] args) throws Exception {
new Server().startServer();
} public void startServer() throws IOException {
ServerSocket serverSocket = new ServerSocket(9999); while (true){
Socket client = serverSocket.accept();
System.err.println("Client:" + client.getInetAddress().getHostAddress());
OutputStream out = client.getOutputStream();
PrintWriter writer = new PrintWriter(new OutputStreamWriter(out, "UTF-8"), true);
writer.println("Hello!We are already connected!say 'bye' to close"); new Thread(new SocketReadThread(client)).start();
new Thread(new SocketWriteThread(client)).start();
} }
} /**
* 读线程
*/
class SocketReadThread implements Runnable{ private Socket socket; public SocketReadThread(Socket socket) {
this.socket = socket;
} @Override
public void run() {
try {
InputStream in = socket.getInputStream();
Scanner scanner = new Scanner(in, "UTF-8");
boolean bye = false;
while (!bye && scanner.hasNextLine()){
String line = scanner.nextLine();
System.out.println("Client Msg[" + socket + "]:" + line);
if(line.trim().equals("bye")){
bye = true;
}
}
in.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
} }
} /**
* 写线程
*/
class SocketWriteThread implements Runnable{ private Socket socket; public SocketWriteThread(Socket socket) {
this.socket = socket;
} @Override
public void run() {
try {
OutputStream out = socket.getOutputStream();
PrintWriter writer = new PrintWriter(new OutputStreamWriter(out, "UTF-8"), true);
Scanner scanIn = new Scanner(System.in);
while (true){
String line = scanIn.nextLine();
writer.println(line);
if (socket.isClosed()){
break;
}
}
} catch (IOException e) {
e.printStackTrace();
} }
}
package com.demo.bio;

import java.io.*;
import java.net.Socket;
import java.util.Scanner; /**
* 客户端
*/
public class Client { public static void main(String[] args) throws Exception {
Socket socket = new Socket("127.0.0.1", 9999);
OutputStream out = socket.getOutputStream();
PrintWriter writer = new PrintWriter(new OutputStreamWriter(out, "UTF-8"), true); new Thread(new SocketReceiveThread(socket)).start();
Scanner scanIn = new Scanner(System.in);
while (!socket.isClosed()){
String line = scanIn.nextLine();
writer.println(line);
if(line.trim().equals("bye")){
socket.close();
}
}
} }
class SocketReceiveThread implements Runnable{ private Socket socket; public SocketReceiveThread(Socket socket) {
this.socket = socket;
} @Override
public void run() {
try {
InputStream in = socket.getInputStream();
Scanner scanner = new Scanner(in, "UTF-8");
boolean bye = false;
while (!bye && scanner.hasNextLine()){
String line = scanner.nextLine();
System.out.println("Server Msg:" + line);
if(line.trim().equals("bye")){
bye = true;
}
}
scanner.close();
} catch (IOException e) {
e.printStackTrace();
} }
}

BIO没什么难的,同步阻塞。上面实现的主要就是服务器和客户端你一句我一句,巴拉巴拉巴拉

NIO

我要实现一个客户端服务器通信的例子,我的第一个版本

package com.demo.nio;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Iterator;
import java.util.Scanner;
import java.util.Set; /**
* 问题:启动服务器,没有启动客户端的时候,阻塞在selector.select();直到有客户端连接才会向下走。
* 启动客户端:获取到客户端的消息,并读取显示;然后写一条数据给客户端;然后进入了写操作模块,等待写入,阻塞。
* 这个时候,客户端已经经过了读取操作,并且没有读到数据,也进入了写操作模块,等待写入,阻塞。这就解释了为什么客户端收不到服务器的第一条消息。
* 客户端写入:客户端输入数据,发送给服务器,离开写操作模块,进入下一轮循环,然后进入读操作模块,读取到服务器的第一条消息并显示。
* 服务器接收:此时服务器并没有收到客户端的消息,因为此时还在写操作模块阻塞,所以想要读取到数据,就要向客户端发送数据,以离开写操作模块,进入下一轮循环。
* 这就解释了:为什么要先写入才能读取的数据。
*/
public class Server { private boolean isFirst = true;
private ServerSocketChannel ssc = null;
private Selector selector = null; public Server(int port) throws IOException {
ssc = ServerSocketChannel.open();
selector = Selector.open();
InetSocketAddress inetAddress = new InetSocketAddress(InetAddress.getLocalHost(), port); ssc.socket().bind(inetAddress);
ssc.configureBlocking(false);
ssc.register(selector, SelectionKey.OP_ACCEPT);
listener(selector);
} private void listener(Selector selector) throws IOException{
while(true){
System.out.println("等待客户端连接...");
selector.select();
System.out.println("捕获客户端连接...");
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectedKeys.iterator(); while (iterator.hasNext()) {
SelectionKey key = iterator.next();
//连接事件
if(key.isAcceptable()){
ServerSocketChannel channel = (ServerSocketChannel) key.channel();
channel.accept().configureBlocking(false).register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
//System.out.println(channel.toString() + "-已连接");
}
//读数据
if(key.isReadable()){
SocketChannel channel = (SocketChannel) key.channel();
ByteBuffer bf = ByteBuffer.allocate(1024);
channel.read(bf);
System.out.println("来自客户端数据:" + new String(bf.array()));
// 只有第一次通信返回消息
if(isFirst){
isFirst = false;
ByteBuffer bst = ByteBuffer.wrap("Hi!".getBytes());
channel.write(bst);
}
}
//写数据
if(key.isWritable()){
Scanner scanner = new Scanner(System.in);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String msg = sdf.format(new Date()) + "\t" + scanner.nextLine();
SocketChannel channel = (SocketChannel) key.channel();
ByteBuffer bst = ByteBuffer.wrap(msg.getBytes());
channel.write(bst);
// key.interestOps(key.interestOps() & ~SelectionKey.OP_WRITE);// 取消写就绪,否则会一直触发写就绪
}
iterator.remove(); } }
} public static void main(String[] args) {
try {
Server server = new Server(9999);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} }
package com.demo.nio;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Iterator;
import java.util.Scanner;
import java.util.Set; public class Client { private SocketChannel sc = null;
private Selector selector = null; public Client(int port) throws IOException {
sc = SocketChannel.open();
selector = Selector.open();
sc.connect(new InetSocketAddress(InetAddress.getLocalHost(), port));
sc.configureBlocking(false);
sc.register(selector, SelectionKey.OP_CONNECT | SelectionKey.OP_READ | SelectionKey.OP_WRITE);
ByteBuffer bf = ByteBuffer.wrap("Hello".getBytes());
sc.write(bf); listener(selector);
} private void listener(Selector selector) throws IOException{
while(true){
selector.select();
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectedKeys.iterator(); while(iterator.hasNext()){
SelectionKey key = iterator.next();
if(key.isReadable()){
SocketChannel channel = (SocketChannel) key.channel();
ByteBuffer dst = ByteBuffer.allocate(1024);
channel.read(dst);
System.out.println("来自服务器:" + new String(dst.array()));
}
if(key.isWritable()){
Scanner scanner = new Scanner(System.in);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String msg = sdf.format(new Date()) + "\t" + scanner.nextLine();
SocketChannel channel = (SocketChannel) key.channel();
ByteBuffer bst = ByteBuffer.wrap(msg.getBytes());
channel.write(bst);
}
iterator.remove();
} }
} public static void main(String[] args) {
try {
Client client = new Client(9999);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} }

上面例子的问题在注释里已经详细描述了,不信可以运行一下,下面是修正版,把写操作放在一个独立的线程里

package com.demo.nio;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Iterator;
import java.util.Scanner;
import java.util.Set; /**
* 修正版
*/
public class ServerRevision { private boolean isFirst = true;
private ServerSocketChannel ssc = null;
private Selector selector = null; public ServerRevision(int port) throws IOException {
ssc = ServerSocketChannel.open();
selector = Selector.open();
InetSocketAddress inetAddress = new InetSocketAddress(InetAddress.getLocalHost(), port); ssc.socket().bind(inetAddress);
ssc.configureBlocking(false);
ssc.register(selector, SelectionKey.OP_ACCEPT);
listener(selector);
} private void listener(Selector selector) throws IOException{
while(true){
System.out.println("等待客户端连接...");
selector.select();
System.out.println("捕获客户端连接...");
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectedKeys.iterator(); while (iterator.hasNext()) {
SelectionKey key = iterator.next();
//连接事件
if(key.isAcceptable()){
ServerSocketChannel channel = (ServerSocketChannel) key.channel();
channel.accept().configureBlocking(false).register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
//System.out.println(channel.toString() + "-已连接");
}
//读数据
if(key.isReadable()){
SocketChannel channel = (SocketChannel) key.channel();
ByteBuffer bf = ByteBuffer.allocate(1024);
channel.read(bf);
System.out.println("来自客户端数据:" + new String(bf.array()));
// 只有第一次通信返回消息
if(isFirst){
isFirst = false;
ByteBuffer bst = ByteBuffer.wrap("Hi!".getBytes());
channel.write(bst);
}
}
//写数据
if(key.isWritable()){
System.out.println("[服务器]写就绪...");
new Thread(new DealWrite(key)).start();
key.interestOps(key.interestOps() & ~SelectionKey.OP_WRITE);// 取消写就绪,否则会一直触发写就绪
}
iterator.remove(); } }
} public static void main(String[] args) {
try {
ServerRevision server = new ServerRevision(9999);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} } class DealWrite implements Runnable{ private SelectionKey key; public DealWrite(SelectionKey key) {
this.key = key;
} @Override
public void run() {
while (true){
Scanner scanner = new Scanner(System.in);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String msg = sdf.format(new Date()) + "\t" + scanner.nextLine();
SocketChannel channel = (SocketChannel) key.channel();
ByteBuffer bst = ByteBuffer.wrap(msg.getBytes());
try {
channel.write(bst);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
package com.demo.nio;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Iterator;
import java.util.Scanner;
import java.util.Set; /**
* 修正版
*/
public class ClientRevision { private SocketChannel sc = null;
private Selector selector = null; public ClientRevision(int port) throws IOException {
sc = SocketChannel.open();
selector = Selector.open();
sc.connect(new InetSocketAddress(InetAddress.getLocalHost(), port));
sc.configureBlocking(false);
sc.register(selector, SelectionKey.OP_CONNECT | SelectionKey.OP_READ | SelectionKey.OP_WRITE);
ByteBuffer bf = ByteBuffer.wrap("Hello".getBytes());
sc.write(bf); listener(selector);
} private void listener(Selector selector) throws IOException{
while(true){
selector.select();
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectedKeys.iterator(); while(iterator.hasNext()){
SelectionKey key = iterator.next();
if(key.isReadable()){
SocketChannel channel = (SocketChannel) key.channel();
ByteBuffer dst = ByteBuffer.allocate(1024);
channel.read(dst);
System.out.println("来自服务器:" + new String(dst.array()));
}
if(key.isWritable()){
System.out.println("[客户端]写就绪...");
new Thread(new DealWrite(key)).start();
key.interestOps(key.interestOps() & ~SelectionKey.OP_WRITE);// 取消写就绪,否则会一直触发写就绪
}
iterator.remove();
} }
} public static void main(String[] args) {
try {
ClientRevision client = new ClientRevision(9999);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} }

目前只是测试了服务器-客户端一对一的通信,不知道一个服务器对多个客户端会出什么bug

NIO稍微有些复杂吧,不过核心的就三个Selector、Channel、Buffer,NIO是同步非阻塞的。

Java IO 与 NIO 服务器&客户端通信小栗子的更多相关文章

  1. 如何解读 Java IO、NIO 中的同步阻塞与同步非阻塞?

    原文链接:如何解读 Java IO.NIO 中的同步阻塞与同步非阻塞? 一.前言 最近刚读完一本书:<Netty.Zookeeper.Redis 并发实战>,个人觉得 Netty 部分是写 ...

  2. JAVA基础知识之网络编程——-TCP/IP协议,socket通信,服务器客户端通信demo

    OSI模型分层 OSI模型是指国际标准化组织(ISO)提出的开放系统互连参考模型(Open System Interconnection Reference Model,OSI/RM),它将网络分为七 ...

  3. Java IO 和 NIO

    昨天面试问到了有关Java NIO的问题,没有答上来.于是,在网上看到了一篇很有用的系列文章讲Java IO的,浅显易懂.后面的备注里有该系列文章的链接.内容不算很长,需要两个小时肯定看完了,将该系列 ...

  4. java IO和NIO 的区别

    Java NIO和IO的主要区别 下表总结了Java NIO和IO之间的主要差别. IO                NIO 面向流            面向缓冲 阻塞IO           非 ...

  5. java的服务端与客户端通信(2)

    一.Socket连接与HTTP连接   1.1Socket套接字 套接字(socket)是通信的基石,是支持TCP/IP协议的网络通信的基本操作单元.它是网络通信过程中端点的抽象表示,包含进行网络通信 ...

  6. java的服务端与客户端通信(1)

    一.理解socket 1.1什么是socket? socket通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄.应用程序通常通过"套接字"向网络 ...

  7. 漫谈Java IO之 NIO那些事儿

    前面一篇中已经介绍了基本IO的使用以及最简单的阻塞服务器的例子,本篇就来介绍下NIO的相关内容,前面的分享可以参考目录: 网络IO的基本知识与概念 普通IO以及BIO服务器 NIO的使用与服务器Hel ...

  8. Java IO、NIO、AIO知识总结

    本文主要讲述下自己对IO的理解,对IO的用法和细则可能没有顾虑到. 本文的理解基于以下几篇文章,他们对各自部分都讲的很细,对我理解IO提供了很大帮助. https://www.cnblogs.com/ ...

  9. 关于Java IO与NIO知识都在这里

    由于内容比较多,我下面放的一部分是我更新在我的微信公众号上的链接,微信排版比较好看,更加利于阅读.每一篇文章下面我都把文章的主要内容给列出来了,便于大家学习与回顾. Java面试通关手册(Java学习 ...

随机推荐

  1. 开发(三)ESP32 硬件配置

    https://github.com/espressif/arduino-esp32

  2. vue使用插件的流程

    1.引入vue 2.引入插件 3.通过vue.use()调用 例子:使用router插件 import Vue from "vue"; import VueRouter from ...

  3. 服务器负载过高问题分析-不是cpu高负载也不是IO负载如何处理(阿里 几乎是必考题)

    关于top命令 经常问load average 参考:load average 定义(网易面试) jvm dump的使用 参考:Jvm dump jstack jmap jstat 介绍与使用(内存与 ...

  4. 划水嘶吼misc

    划水嘶吼misc [题目描述]: 开局一张图,flag全靠编 [题目writeup]: 瞅半天,瞅到二维码 然后用potplayer按帧查找到skr二维码 通过PS自动调整对比度,扫出key1,2,3 ...

  5. Spring Boot通过Configuration配置多数据源

    本文结合SpringBoot + MyBatis + MySql进行多数据源配置,DataSource信息采用自定义dataSource.properties进行配置. 1.文件结构如下: 2.1 p ...

  6. SpringMVC(十五):Dispatcher的重要组件之一MultipartResolver(StandardServletMultipartResolver和CommonsMultipartResolver)的用法

    MultipartResolver组件 从Spring官网上可以看到MultipartResolver接口的定义信息: public interface MultipartResolver A str ...

  7. 【Laravel基础】laravel基础之相关概念,自定义服务提供者:Contracts, ServiceContainer, ServiceProvider, Facades关系

    Contracts, ServiceContainer, ServiceProvider, Facades关系 概念 Contracts 合同,契约,也就是接口,定义一些规则,每个实现此接口的都要实现 ...

  8. kotlin基础 密封类

    密封类 密封类用来表示受限的类继承结构:当一个值为有限几种的类型, 而不能有任何其他类型时.在某种意义上,他们是枚举类的扩展:枚举类型的值集合 也是受限的,但每个枚举常量只存在一个实例,而密封类 的一 ...

  9. ThinkPHP中文字段问题

    转自: https://www.baidu.com/link?url=Ohc9epgQgkNYLwnHqP-jZ9RfIQWW50-iz8-ZMIPLdtCIJHnUpYwQnDLmXzi7Fa110 ...

  10. 解决bootstrap-table表头filter-control select控件被遮挡显示不全的问题

    [本文出自天外归云的博客园] 在使用bootstrap-table的extension——filter-control时(对应表格的data-filter-control="true&quo ...