一、简介

  前面一篇文章讲了文件通道,本文继续来说说另一种类型的通道 – 套接字通道。在展开说明之前,咱们先来聊聊套接字的由来。套接字即 socket,最早由伯克利大学的研究人员开发,所以经常被称为Berkeley sockets。UNIX 4.2BSD 内核版本中加入了 socket 的实现,此后,很多操作系统都提供了自己的 socket 接口实现。通过 socket 接口,我们就可以与不同地址的计算机实现通信。

  如果大家使用过 Unix/Linux 系统下的 socket 接口,那么对 socket 编程的过程应该有一些了解。

  对于 TCP 服务端,接口调用的顺序为socket() -> bind() -> listen() -> accept() -> 其他操作 -> close(),客户端的顺序为socket() -> connect() -> 其他操作 -> close()

  如下图所示:

      

二、通道类型

  Java 套接字通道包含三种类型,分别是:

    1. SocketChannel:TCP 网络套接字通道

    2. ServerSocketChannel:TCP 服务端套接字通道

    3. DatagramChannel:UDP 网络套接字通道

Java 套接字通道类型对应于两种通信协议 TCP 和 UDP,这个大家应该都知道。本文将介绍 TCP 网络套接字通道的使用,并在最后实现一个简单的聊天功能。至于 UDP 类型的通道,大家可以自己看看。

三、基本操作

3.1 打开通道

  SocketChannel 和 ServerSocketChannel 都是抽象类,所以不能直接通过构造方法创建通道。这两个类均是使用 open 方法创建通道,如下:

SocketChannel socketChannel = SocketChannel.open();
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

3.2 关闭通道

  SocketChannel 和 ServerSocketChannel 均提供了 close 方法,用于关闭通道。示例如下:

SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("www.coolblog.xyz", 80));
// do something...
socketChannel.close(); /*******************************************************************/ ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(8080));
SocketChannel socketChannel = serverSocketChannel.accept();
// do something...
socketChannel.close();
serverSocketChannel.close();

3.3 读写操作

读操作

通过使用 SocketChannel 的 read 方法,并配合 ByteBuffer 字节缓冲区,即可以从 SocketChannel 中读取数据。示例如下:

ByteBuffer buffer = ByteBuffer.allocate(32);
int num = socketChannel.read(buffer);

写操作

读取数据使用的是 read 方法,那么写入自然也就是 write 方法了。NIO 通道是面向缓冲的,所以向管道中写入数据也需要和缓冲区配合才行。示例如下

String data = "Test data..."

ByteBuffer buffer = ByteBuffer.allocate(32);
buffer.clear();
buffer.put(data.getBytes()); bbuffer.flip();
channel.write(buffer);

3.4 非阻塞模式

  与文件通道不同,套接字通道可以运行在非阻塞模式下。在此模式下,调用 connect(),read() 和 write() 等方法时,进程/线程会立即返回

  设置非阻塞模式的方法为configureBlocking,我们来看一下该方法的使用示例:

SocketChannel socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
socketChannel.connect(new InetSocketAddress("www.coolblog.xyz", 80)); // 这里要循环检测是否已经连接上
while(!socketChannel.finishConnect()){
// do something
} // 连接建立起来后,才能进行读取或写入操作

  由于在非阻塞模式下,调用 connect 方法会立即返回。如果在连接未建立起来的情况下,从管道中读取,或向管道写入数据,会触发 NotYetConnectedException 异常。

  所以要进行循环检测,以保证连接完成建立。如果代码按照上面那样去写,会引发另外一个问题。非阻塞模式虽然不会阻塞线程,但是在方法返回后,还要进行循环检测,线程实际上还是被阻塞。出现这个问题的原因是和 Java NIO 套接字通道的 IO 模型有关,套接字通道采用的是“同步非阻塞”式 IO 模型,用户发起一个 IO 操作后,即可去做其他事情,不用等待 IO 完成。但是 IO 是否已完成,则需要用户自己时不时的去检测,这样实际上还是会浪费 CPU 资源。

3.5 实例演示

  本节用一个简单的例子来演示套接字通道的使用,这个例子演示了一个客户端与服务端互相聊天的场景。首先服务端会监听某个端口,等待客户端来连接。客户端连接后,由客户端先向服务端发送消息,然后服务端再回复一条消息。这样,客户端和服务端就能你一句我一句的聊起来了。背景先介绍到这,我们来看看代码实现吧,首先看看服务端的代码:

服务端

package com.nio.wetalk;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Scanner;
import static com.nio.wetalk.WeTalkUtils.recvMsg;
import static com.nio.wetalk.WeTalkUtils.sendMsg; public class WeTalkServer {
private static final String EXIT_MARK = "exit"; private int port; WeTalkServer(int port) {
this.port = port;
}
public void start() throws IOException {
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.socket().bind(new InetSocketAddress(port));
System.out.println("服务端已启动,正在监听 " + port + " 端口......");
SocketChannel channel = ssc.accept();
System.out.println("接受来自" + channel.getRemoteAddress().toString().replace("/", "") + " 请求");
Scanner sc = new Scanner(System.in);
while (true) {
// 等待并接收客户端发送的消息
String msg = recvMsg(channel);
System.out.println("\n客户端:");
System.out.println(msg + "\n"); // 输入信息
System.out.println("请输入:");
msg = sc.nextLine();
if (EXIT_MARK.equals(msg)) {
sendMsg(channel, "bye~");
break;
} // 回复客户端消息
sendMsg(channel, msg);
} // 关闭通道
channel.close();
ssc.close();
} public static void main(String[] args) throws IOException {
new WeTalkServer(8080).start();
}
}

客户端

package com.nio.wetalk;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SocketChannel;
import java.util.Scanner;
import static com.nio.wetalk.WeTalkUtils.recvMsg;
import static com.nio.wetalk.WeTalkUtils.sendMsg; //客户端
public class WeTalkClient {
private static final String EXIT_MARK = "exit"; private String hostname; private int port; WeTalkClient(String hostname, int port) {
this.hostname = hostname;
this.port = port;
}
public void start() throws IOException {
// 打开一个套接字通道,并向服务端发起连接
SocketChannel channel = SocketChannel.open();
channel.connect(new InetSocketAddress(hostname, port)); Scanner sc = new Scanner(System.in);
while (true) {
// 输入信息
System.out.println("请输入:");
String msg = sc.nextLine();
if (EXIT_MARK.equals(msg)) {
sendMsg(channel, "bye~");
break;
} // 向服务端发送消息
sendMsg(channel, msg); // 接受服务端返回的消息
msg = recvMsg(channel);
System.out.println("\n服务端:");
System.out.println(msg + "\n");
} // 关闭通道
channel.close();
}
public static void main(String[] args) throws IOException {
new WeTalkClient("localhost", 8080).start();
}
}

工具类

package com.nio.wetalk;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
//工具类
public class WeTalkUtils {
private static final int BUFFER_SIZE = 128; public static void sendMsg(SocketChannel channel, String msg) throws IOException {
ByteBuffer byteBuffer = ByteBuffer.allocate(BUFFER_SIZE);
byteBuffer.put(msg.getBytes());
byteBuffer.flip();
channel.write(byteBuffer);
} public static String recvMsg(SocketChannel channel) throws IOException {
ByteBuffer byteBuffer = ByteBuffer.allocate(BUFFER_SIZE);
channel.read(byteBuffer); byteBuffer.flip();
byte[] bytes = new byte[byteBuffer.limit()];
byteBuffer.get(bytes);
return new String(bytes);
}
}

客户端做的事情也比较简单,首先是打开通道,然后连接服务单。紧接着进入 while 循环,然后就可以和服务端愉快的聊天了。

上面的代码和叙述都没啥意思,最后我们还是来看看上面代码的运行效果,一图胜前言。

四、总结

  到这里,关于套接字通道的相关内容就讲完了,不知道大家有没有看懂。本文仅从使用的角度分析了套接字通道的用法,至于套接字通道的实现,这并不是本文关注的重点。实际上,我在上一篇文章中就说过,Java 所提供的很多类实际上是对操作系统层面上一些系统调用做了一层包装。所以大家在学习 Java 的同时,还应该去了解底层的一些东西,这样才算是知其然,又知其所以然。

感谢:http://www.tianxiaobo.com/2018/03/25/Java-NIO%E4%B9%8B%E5%A5%97%E6%8E%A5%E5%AD%97%E9%80%9A%E9%81%93/

java输入输出 -- Java NIO之套接字通道的更多相关文章

  1. Java NIO之套接字通道

    1.简介 前面一篇文章讲了文件通道,本文继续来说说另一种类型的通道 -- 套接字通道.在展开说明之前,咱们先来聊聊套接字的由来.套接字即 socket,最早由伯克利大学的研究人员开发,所以经常被称为B ...

  2. Java NIO SocketChannel套接字通道

    原文链接:http://tutorials.jenkov.com/java-nio/socketchannel.html 在Java NIO体系中,SocketChannel是用于TCP网络连接的套接 ...

  3. java输入输出 -- Java NIO之选择器

    一.简介 前面的文章说了缓冲区,说了通道,本文就来说说 NIO 中另一个重要的实现,即选择器 Selector.在更早的文章中,我简述了几种 IO 模型.如果大家看过之前的文章,并动手写过代码的话.再 ...

  4. mycat->oracle报java.sql.SQLException: 无法从套接字读取更多的数据

    今天下午,测试环境清算的时候又出现了之前的一个异常,这次把错误信息全部打出来了,java.sql.SQLException: 无法从套接字读取更多的数据,是使用mycat连接oracle的,如下所示: ...

  5. 【Android 应用开发】Android 网络编程 API笔记 - java.net 包 权限 地址 套接字 相关类 简介

    Android 网络编程相关的包 : 9 包, 20 接口, 103 类, 6 枚举, 14异常; -- Java包 : java.net 包 (6接口, 34类, 2枚举, 12异常); -- An ...

  6. Java学习笔记(3)----网络套接字服务器多线程版本

    本例给出一个客户端程序和一个服务器程序.客户端向服务器发送数据.服务器接收数据,并用它来生成一个结果,然后将这个结果返回给客户端.客户端在控制台上显示结果.在本例中,客户端发送的数据是圆的半径,服务器 ...

  7. Android 网络编程 API笔记 - java.net 包 权限 地址 套接字 相关类 简介

    Android 网络编程相关的包 : 9 包, 20 接口, 103 类, 6 枚举, 14异常; -- Java包 : java.net 包 (6接口, 34类, 2枚举, 12异常); -- An ...

  8. java基础----->TCP和UDP套接字编程

    这里简单的总结一下TCP和UDP编程的写法,另外涉及到HttpUrlConnection的用法 . TCP套接字 一.项目的流程如下说明: .客户输入一行字符,通过其套接字发送到服务器. .服务器从其 ...

  9. Java NIO(五)套接字通道

    Socket通道 Socket通道和文件通道有着不一样的特征: Socket通道类可以运行于非阻塞模式,并且是可选的.这两个特征可以激活大程序(如网络服务和中间件组件)巨大的可伸缩性和灵活性,因此再也 ...

随机推荐

  1. (转)glances用法

    借鉴:https://www.ibm.com/developerworks/cn/linux/1304_caoyq_glances/index.html glances 可以为 Unix 和 Linu ...

  2. 线程wait和notify方法的demo详解

    wait和notify都是针对某个线程而言的: package com.roocon.thread.t1; public class NewThread implements Runnable { @ ...

  3. LUA table中函数的调用

    1 lua中函数作为表中元素时有三种定义方式 采用‘:’来定义,实际上隐藏了一个形参的声明,这个形参会截获调用函数时的第一个实参并把它赋值给self 2 调用方式,点号和冒号 functb:hello ...

  4. DELPHI搭建centos开发环境

    DELPHI搭建centos7开发环境 关闭防火墙 搭建开发环境,还是直接关闭LINUX防火墙,省事. 否则,使用到的网络端口号,都要在防火墙开放,麻烦. systemctl disable fire ...

  5. NLP 文本预处理

    1.不同类别文本量统计,类别不平衡差异 2.文本长度统计 3.文本处理,比如文本语料中简体与繁体共存,这会加大模型的学习难度.因此,他们对数据进行繁体转简体的处理. 同时,过滤掉了对分类没有任何作用的 ...

  6. 超详细Qt5.9.5移植攻略

    本文就来介绍下如何将Qt5.9.5移植到ARM开发板上. 以imx6开发板为例,使用Ubuntu14.04虚拟机作为移植环境. 准备工作 1.主机环境:Ubuntu14.04: 开发板:启扬IAC-I ...

  7. [Java复习] 缓存Cache part1

    1. 在项目中是如何使用缓存的?为什么要用?不用行不行?用了可能会有哪些不良后果? 结合项目业务,主要两个目的:高性能和高并发.缓存走内存,天然支持高并发. 不良后果: 缓存与DB双写不一致 缓存雪崩 ...

  8. ISO/IEC 9899:2011 条款5——5.2.1 字符集

    5.2.1 字符集 1.两个字符集和它们相关联的依次顺序应该被定义:写在源文件中的集合(源字符集),以及在执行环境中被解释的集合(执行字符集).每个集合此外被划分为一个基本字符集,其内容由本子条款给出 ...

  9. 使用注解注入properties中的值的简单示例

    spring使用注解注入properties中的值的简单示例   1.在web项目的src目录下新建setting.properties的文件,内容如下: version=1 2.在spring的xm ...

  10. Qt编写自定义控件52-颜色下拉框

    一.前言 这个控件写了很久了,元老级别的控件之一,开发之初主要是自己的好几个项目要用到,比如提供一个颜色下拉框设置对应的曲线或者时间颜色,视频监控项目中经常用到的OSD标签设置,这个控件的难度系数接近 ...