Java 中的 IO 与 socket 编程 [ 复习 ]
一、Unix IO 与 IPC
Unix IO:Open-Read or Write-Close
IPC:open socket - receive and send to socket - close socket
IPC 全称是 InterProcess Communication。
当消息发出后,消息进入 SendQ队列 一直等待 sending socket 处理,才真正发出(一直等待是阻塞的)。当消息到达时,消息进入RecvQ队列 一直等待 receiving socket 处理(同前)。
底层的 TCP 协议关联的 RecvQ 或者 SendQ 队列就是一个操作系统缓冲区。
TCP/UDP 基于 IPC。Udp 是一种 datagram communication protocol,是 connectionless protocol,意味着每次发送 datagrams 时,需要额外发送local socket descriptor 和 receiving socket's address.。
Tcp 是一种 stream communication protocol 你可以通过直接构建 Tcp 通道建立连接,在通道中的 socket pairs 可以互相访问。
二、Java 中的 IO 编程
基于流的
public static void simpleIo() throws IOException {
InputStream input = new BufferedInputStream(new FileInputStream("/home/lg/Pictures/wallpapers/2047.png"));
OutputStream out = new BufferedOutputStream(new FileOutputStream("dem.png"));
byte[] buf = new byte[1024];
while (input.read(buf) > 0) {
out.write(buf);
}
out.flush();
}
基于字符的
public static void simpleIo() throws IOException {
BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
PrintWriter out = new PrintWriter(System.out, true);
String myLine;
while ((myLine = input.readLine()) != null) {
out.println(myLine);
}
}
// new String(char[] chars, "utf-8") 字节序列到字符串的转换
* PrintWriter 与 BufferedWriter 区别
1. PrintWriter的print、println方法可以接受任意类型的参数,而BufferedWriter的write方法只能接受字符、字符数组和字符串;
2. PrintWriter的println方法自动添加换行,BufferedWriter需要显示调用newLine方法;
3. PrintWriter的方法不会抛异常,若关心异常,需要调用checkError方法看是否有异常发生;
4. PrintWriter构造方法可指定参数,实现自动刷新缓存(autoflush);
5. PrintWriter的构造方法更广。
三、Java 中的 socket 编程
服务端
public class Server {
public static void main(String[] args) {
{
ServerSocket myServer = null;
Socket acceptSocket = null;
BufferedReader input = null;
PrintWriter output = null;
try {
myServer = new ServerSocket(4000);
while (true) {
acceptSocket = myServer.accept();
input = new BufferedReader(new InputStreamReader(acceptSocket.getInputStream()));
output = new PrintWriter(acceptSocket.getOutputStream(), true);
String rLine;
while ((rLine = input.readLine()) != null) {
System.out.println("服务器接受到一条消息:\n" + rLine + "\n---------------\n\n");
if (rLine.equals("hello server")) {
System.out.println("然后打了个招呼\n---------------\n\n");
output.println("hello client");
} else {
System.out.println("然后嘲讽了一波\n---------------\n\n");
output.println("233333");
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
客户端
public class Client {
private static Socket myClient;
//静态初始化时处理异常
static {
try {
myClient = new Socket("localhost", 4000);
} catch (IOException e) {
e.printStackTrace();
}
}
private static void sendMessage() {
try {
PrintWriter output = new PrintWriter(myClient.getOutputStream(), true);
BufferedReader readConsole = new BufferedReader(new InputStreamReader(System.in));
String cline;
while ((cline = readConsole.readLine()) != null) {
output.println(cline);
System.out.println("消息发送成功!\n---------------\n\n");
}
} catch (IOException e) {
e.printStackTrace();
}
}
private static void receiveMessage() {
try {
BufferedReader input = new BufferedReader(new InputStreamReader(myClient.getInputStream()));
// 这里省略掉了 OutputStream(System.out), 直接使用打印替代
String rLine;
while ((rLine = input.readLine()) != null) {
System.out.println("客户端收到来自服务器的消息:\n" + rLine + "\n---------------\n\n");
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
new Thread(Client::sendMessage).start();
new Thread(Client::receiveMessage).start();
}
}
说明:
客户端使用了两个线程:一个用于发送消息,另一个用于接受消息。服务端只考虑了一个 socket 连接,因此并没有用多线程。实际上,服务端通常需要同时处理多个 socket 连接,因此可考虑多线程。建立一个线程池,对于每个 socket 连接,分派一个线程去处理。
四、NIO 与 socket 编程
1. 处理流程对比
普通的 BIO,消息要等待处理,需要先放入操作系统的 Socket 缓冲区(阻塞的SendQ与RecvQ队列),这样一直等待,直到 sending socket 或 receiving socket 就位后,才进行处理。
而 NIO,是 消息进入到操作系统的 Socket 缓冲区,直接复制到 Buffer (ByteBuffer.allocate)中,抑或直接进入与系统底层关联的缓冲区(ByteBuffer.allocateDirector)。不用一直等待 sending socket 或 receiving socket 就续就进行处理,是非阻塞的。
需要说明的是等待就绪的阻塞是不使用CPU的,是在“空等”;而真正的读写操作的阻塞是使用CPU的,真正在"干活",而且这个过程非常快,属于memory copy,带宽通常在1GB/s级别以上,可以理解为基本不耗时。
下图是几种常见I/O模型的对比:(图片来自:UNIX网络编程 -- I/O复用:select和poll函数)

(摘自 Linux IO模式及 select、poll、epoll详解 )
- BIO,同步阻塞IO,在IO执行的两个阶段都被block了。如果连接少,他的延迟是最低的,因为一个线程只处理一个连接,适用于少连接且延迟低的场景,比如说数据库连接。
- NIO,同步非阻塞IO,用户进程需要不断的主动询问kernel数据好了没有。
- 多路复用IO,就是我们说的select,poll,epoll,有些地方也称这种IO方式为event driven IO。
当用户进程调用了select,那么整个进程会被block,而同时,kernel会“监视”所有select负责的socket,当任何一个socket中的数据准备好了,select就会返回。这个时候用户进程再调用read操作,将数据从kernel拷贝到用户进程。注意与 nio 的关系:在IO multiplexing Model中,实际中,对于每一个socket,一般都设置成为non-blocking,但是,如上图所示,整个用户的process其实是一直被block的。只不过process是被select这个函数block,而不是被socket IO给block。 - 信号驱动IO,这种IO模型主要用在嵌入式开发,不参与讨论。
- 异步IO,用户进程发起read操作之后,立刻就可以开始去做其它的事,当数据拷贝到用户内存,当这一切都完成之后,kernel会给用户进程发送一个signal,告诉它read操作完成了。
2.线程模型对比
NIO一个重要的特点是:socket主要的读、写、注册和接收函数,在等待就绪阶段都是非阻塞的,真正的I/O操作是同步阻塞的(消耗CPU但性能非常高)。
回忆BIO模型,之所以需要多线程,是因为在进行I/O操作的时候,一是没有办法知道到底能不能写、能不能读,只能"傻等",即使通过各种估算,算出来操作系统没有能力进行读写,也没法在socket.read()和socket.write()函数中返回,这两个函数无法进行有效的中断。所以除了多开线程另起炉灶,没有好的办法利用CPU。
NIO的读写函数可以立刻返回,这就给了我们不开线程利用CPU的最好机会:如果一个连接不能读写(socket.read()返回0或者socket.write()返回0),我们可以把这件事记下来,记录的方式通常是在Selector上注册标记位,然后切换到其它就绪的连接(channel)继续进行读写。
3.事件模型
NIO的主要事件有几个:读就绪、写就绪、有新连接到来。Selector 主要围绕这几个事件做分发。注册所有感兴趣的事件处理器,单线程轮询选择就绪事件,执行事件处理器。
服务端
public class Server {
private static Selector selector;
static {
try {
selector = Selector.open();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
sscRegister();
}
private static void sscRegister() {
try {
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.configureBlocking(false);
ssc.socket().bind(new InetSocketAddress("localhost", 8000));
ssc.register(selector, SelectionKey.OP_ACCEPT);
dispatcher(selector);
} catch (IOException e) {
e.printStackTrace();
}
}
private static void dispatcher(Selector selector) {
try {
while (true) {
int select = selector.select();
if (select == 0) {
System.out.println("返回 0 表示 SelectedKeys 未更新");
continue;
}
Iterator<SelectionKey> selectedIterator = selector.selectedKeys().iterator();
while (selectedIterator.hasNext()) {
SelectionKey key = selectedIterator.next();
selectedIterator.remove(); // 只是从 SelectedKeys 中移除,而并不是从整个选择器的键集中移除。
if (!key.isValid()) {
continue;
}
if (key.isConnectable()) {
System.out.println(233);
}
if (key.isAcceptable()) {
accept(key);
}
if (key.isReadable()) {
read(key);
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
private static void accept(SelectionKey key) {
ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
try {
SocketChannel socketChannel;
if ((socketChannel = serverSocketChannel.accept()) != null) { // 服务端非阻塞时,直接返回 null
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
}
} catch (IOException e) {
e.printStackTrace();
}
}
private static void read(SelectionKey key) {
SocketChannel channel = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
try {
if (channel.read(buffer) > 0) {
System.out.println("客户端:" + new String(buffer.array()));
buffer.clear();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
buffer.clear();
}
}
}
客户端
public class Client {
private static void clientSendMsg() {
try {
SocketChannel client = SocketChannel.open(new InetSocketAddress("localhost", 8000));
System.out.println("已向服务器发出请求!");
String msg = "hello" + Thread.currentThread().getName();
ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes());
client.write(buffer);
buffer.flip();
Thread.sleep(5);
client.close();
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
new Thread(Client::clientSendMsg, "Thread-A").start();
}
}
参考文章
https://tech.meituan.com/nio.html?utm_source=tool.lu
https://www.ibm.com/developerworks/cn/java/j-lo-javaio/index.html
https://www.bysocket.com/?p=615 (大小端模式)
Java 中的 IO 与 socket 编程 [ 复习 ]的更多相关文章
- java中的IO操作总结
一.InputStream重用技巧(利用ByteArrayOutputStream) 对同一个InputStream对象进行使用多次. 比如,客户端从服务器获取数据 ,利用HttpURLConnect ...
- Java中的IO流(二)
上一篇<Java中的IO流(一)>把学习IO流的字符流作了一下记录,本篇把字节流记录一下. 一,Java中的字节流 Java中的字节流的操作方式与字符流的操作方式大致相同,连方法名都是类似 ...
- Java NIO学习系列六:Java中的IO模型
前文中我们总结了linux系统中的5中IO模型,并且着重介绍了其中的4种IO模型: 阻塞I/O(blocking IO) 非阻塞I/O(nonblocking IO) I/O多路复用(IO multi ...
- Java中的IO操作和缓冲区
目录 Java中的IO操作和缓冲区 一.简述 二.IO流的介绍 什么是流 输入输出流的作用范围 三.Java中的字节流和字符流 字节流 字符流 二者的联系 1.InputStreamReader 2. ...
- Java中的IO流 - 入门篇
前言 大家好啊,我是汤圆,今天给大家带来的是<Java中的IO流-入门篇>,希望对大家有帮助,谢谢 由于Java的IO类有很多,这就导致我刚开始学的时候,感觉很乱,每次用到都是上网搜,结果 ...
- java中的io系统详解 - ilibaba的专栏 - 博客频道 - CSDN.NET
java中的io系统详解 - ilibaba的专栏 - 博客频道 - CSDN.NET 亲,“社区之星”已经一周岁了! 社区福利快来领取免费参加MDCC大会机会哦 Tag功能介绍—我们 ...
- java中的IO流
Java中的IO流 在之前的时候我已经接触过C#中的IO流,也就是说集中数据固化的方式之一,那么我们今天来说一下java中的IO流. 首先,我们学习IO流就是要对文件或目录进行一系列的操作,那么怎样操 ...
- Java中的IO流总结
Java中的IO流总结 1. 流的继承关系,以及字节流和字符流. 2. 节点流FileOutputStream和FileInputStream和处理流BufferedInputStream和Buffe ...
- 深入理解Java中的IO
深入理解Java中的IO 引言: 对程序语言的设计者来说,创建一个好的输入/输出(I/O)系统是一项艰难的任务 < Thinking in Java > 本文的目录视图如下: ...
随机推荐
- Taints 与 Tolerations
节点亲和性是描述Pods如何分配到一个或一组节点的策略,亲和性的相关资料可以参考Kubernetes中的亲和性与反亲和性.与亲和性规则不同, Taints 描述节点拒绝一个或一组Pods的策略.其实现 ...
- 12、Java函数接口
注:新版本接口中Iterable已换成Iterator
- matplotlib、PIL、cv2图像操作 && caffe / tensorflow 通道顺序
用python进行图像处理中分别用到过matplotlib.pyplot.PIL.cv2三种库,这三种库图像读取和保存方法各异,并且图像读取时顺序也有差异,如plt.imread和PIL.Image. ...
- shell编程学习笔记(八):Shell中if判断的使用
一.if的语法: 1.单分支语句结构 if [ 条件表达式 ]; then 指令 fi 2.双分支语句结构 if [ 条件表达式 ]; then 指令一 else 指令二 fi 3.多分支语句结构 i ...
- Win10远程桌面提示你的凭据不工作的处理方法
需要确保在组策略编辑器(Win+R 输入 gpedit.msc )中计算机配置->Windows设置->安全设置->本地策略->安全选项->右侧的网络访问:本地帐户的共享 ...
- PhpStorm配置SVN的完整方法
1.安装SVN时注意选择“command line client tools"默认是不安装的 2.设置系统环境变量 3.在PhpStorm上设置如下 4.然后通过VCS就可以上传导入你的工程 ...
- 【iOS】ARC-MRC下的单例及其应用
单例的应用十分普遍,单例模式使一个类仅仅有一个实例. *易于供外界訪问. *方便控制实例个数,节约系统资源. *OC中的常见单例: 如:UIApplication, NSNotificationCe ...
- server后台TCP连接存活问题
公司的server后台部署在某一个地方,接入的是用户的APP,而该地方的网络信号较差,导致了server后台在执行一段时间后用户无法接入,那边的同事反馈使用netstat查看系统.存在较多的TCP连接 ...
- SublimeText3追踪函数工具CTags设置及使用
第一步:在 ST3 安装 CTags 插件 1. 在 ST3 快捷键 Crtl+Shift+P 然后输入 pci ,选择“ Package Control: Install Package ”启动安装 ...
- V-Charts中使用extend属性定制词云图
[本文出自天外归云的博客园] 简介 在Vue中使用E-Charts可以用V-Charts,词云图在V-Charts官网中介绍比较简单,如果想更多定制的话,官网上说要在extend属性中进行扩展. V- ...