1.简介

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

如果大家使用过 Unix/Linux 系统下的 socket 接口,那么对 socket 编程的过程应该有一些了解。对于 TCP 服务端,接口调用的顺序为socket() -> bind() -> listen() -> accept() -> 其他操作 -> close(),客户端的顺序为socket() -> connect() -> 其他操作 -> close()。如下图所示:


* 图片来源于《深入理解计算机系统》

如上所示,直接调用操作系统 socket 相关接口还是比较麻烦的。所以我们的 Java 语言对上面的步骤进行了封装,方便使用。比如我们今天要讲的套接字通道就比原生的接口好用的多。好了,关于 socket 的简介先说到这,接下进入正题吧。

2 通道类型

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

类型 说明
DatagramChannel UDP 网络套接字通道
SocketChannel TCP 网络套接字通道
ServerSocketChannel TCP 服务端套接字通道

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

3.基本操作

3.1 打开通道

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

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

3.2 关闭通道

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

1
2
3
4
5
6
7
8
9
10
11
12
13
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 中读取数据。示例如下:

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

写操作

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

1
2
3
4
5
6
7
8
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,我们来看一下该方法的使用示例:

1
2
3
4
5
6
7
8
9
10
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 资源。

关于 IO 模型相关的知识,大家可以参考我之前的一篇文章I/O模型简述 ,这里不再赘述。另外,大家还需要去参考一下权威资料《UNIX网络编程卷 第1卷:套接口API》第6章关于 IO 模型的介绍,那一章除了对5种 IO 模型进行了介绍,还介绍了同步与异步的概念,值得一读。好了,本节就先说到这里。

3.5 实例演示

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
package wetalk;import static wetalk.WeTalkUtils.recvMsg;
import static wetalk.WeTalkUtils.sendMsg;import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Scanner;/*** WeTalk 服务端* @author coolblog.xyz* @date 2018-03-22 12:43:26*/
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();}
}

上面的代码基本上进行了逐步注释,应该不难理解,这里就不啰嗦了。上面有两个方法没有贴代码,就是sendMsgrecvMsg,由于通用操作,在下面的客户端代码里也可以使用,所以这里做了封装。封装代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package wetalk;import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;/*** 工具类** @author coolblog.xyz* @date 2018-03-22 13:13:41*/
public class WeTalkUtils {private static final int BUFFER_SIZE = 128;public static void sendMsg(SocketChannel channel, String msg) throws IOException {ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);buffer.put(msg.getBytes());buffer.flip();channel.write(buffer);}public static String recvMsg(SocketChannel channel) throws IOException {ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);channel.read(buffer);buffer.flip();byte[] bytes = new byte[buffer.limit()];buffer.get(bytes);return new String(bytes);}
}

工具类的代码比较简单,没什么好说的。接下来再来看看客户端的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
package wetalk;import static wetalk.WeTalkUtils.recvMsg;
import static wetalk.WeTalkUtils.sendMsg;import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SocketChannel;
import java.util.Scanner;/*** WeTalk 客户端* @author coolblog.xyz* @date 2018-03-22 12:38:21*/
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();}
}

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

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

4.总结

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

好了,本文到这里就结束了,有错误的地方欢迎大家指出来。最后谢谢大家的阅读,祝周末愉快。

参考

  • 《深入理解计算机系统》
  • 《UNIX网络编程卷 第1卷:套接口API》
  • https://www.zhihu.com/question/27991975/answer/69041973
  • https://zhuanlan.zhihu.com/p/27365009
  • 本文链接: https://www.tianxiaobo.com/2018/03/25/Java-NIO之套接字通道/

from: 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 NIO之套接字通道相关推荐

  1. java套接字客户端_使用Java从客户端套接字读取数据(Read data from a client socket in Java)...

    使用Java从客户端套接字读取数据(Read data from a client socket in Java) 我编写了从客户端套接字发送/接收数据的代码. 发送数据步骤已成功完成,但是当我想从套 ...

  2. Java网络编程套接字

    文章目录 1.网络编程基础 2.什么是网络编程 3.网络编程中的基本概念 3.1.发送端和接收端 3.2 请求和响应 3.3 客户端和服务端 3.4 常见的客户端服务端模型 4.Socket套接字 4 ...

  3. (积累)java里的套接字

    上计算机网络实验课,有个作业是关于java套接字编程的,总结一下吧! 1. 建立一个服务端套接字: ServerSocket s = new ServerSocket(12343);     // 默 ...

  4. Java 创建带有套接字的简单代理服务器示例

    在这些示例中,您可以找到创建简单代理套接字服务器的不同方法.由于多种原因,这对您很有用: 捕获客户端和服务器之间的流量. 限制上传/下载带宽,以了解您的网站是如何加载慢速连接的. 查看网络出现问题时系 ...

  5. Java NIO 系列教程 (十一) Datagram 通道

    Java NIO中的DatagramChannel是一个能收发UDP包的通道.因为UDP是无连接的网络协议,所以不能像其它通道那样读取和写入.它发送和接收的是数据包. 打开 DatagramChann ...

  6. java使用原始套接字技术进行数据包截获_Linux零拷贝技术,看完这篇文章就懂了...

    本文讲解 Linux 的零拷贝技术,云计算是一门很庞大的技术学科,融合了很多技术,Linux 算是比较基础的技术,所以,学好 Linux 对于云计算的学习会有比较大的帮助. 为什么需要零拷贝 传统的 ...

  7. 关于Java中数据报套接字DatagramSocket中connet()方法说明

    大家都知道UDP协议是面向无连接的,但是用于UDP的DatagramSocket类中却有connet()方法,实在令人费解.刚才有学生问起来,研究了下才发现这个方法的真正用法! connet()方法共 ...

  8. java socket ascii_TCP套接字上的ASCII - java

    有人可以给我传递通过TCP发送Ascii消息的示例吗(在网上找不到示例) 谢谢, 射线. 参考方案 写入和读取回显服务器的Here's an example. 简化摘录: Socket echoSoc ...

  9. java无法获取套接字_无法从套接字获取更多数据

    因为第一次遇到这个问题,所以以下方法,都来源于网上. 版本:oracle11g 遇到这个问题,可以查看oracle日志,分析问题的原因. oracle数据库的最常用问题定位日志是alert日志,ora ...

最新文章

  1. IDE神器intellij idea的基本使用
  2. c++ requests网络请求库
  3. springboot util 测试类怎么写_SpringBoot入门建站全系列(九)文件上传功能与下载方式...
  4. [蓝桥杯][2019年第十届真题]后缀表达式(正解!!)
  5. Baidu与Google地图API初探
  6. SaaS软件的应用弱化了不同规模企业的竞争差距
  7. 短视频直播带货APP源码 全开源原生直播APP源码
  8. java 万年历_java万年历代码仅供参考
  9. SOTA级发丝抠图模型PP-Matting重磅开源,支持多场景精细化分割!
  10. matlab 小波分析实例,小波分析MATLAB实例
  11. 正则匹配特殊符号及标点符号
  12. plt.text函数用法
  13. Revit导出PDF格式图纸流程及“批量导出图纸”
  14. 真菌多样性分析ITS序列
  15. 摄像模组中光学相关知识(三)
  16. 快看影视大全隐私政策
  17. 简述配置php运行环境的大致步骤,简述手工安装、配置PHP运行环境的大致步骤。...
  18. 美国东北大学计算机专业排名,美国研究生院 计算机工程排名:美国东北大学研究生计算机专业排名怎么样...
  19. 从零开始入门创作游戏——游戏对象的脚本编辑
  20. Hessian/Burlap: is an unknown class in WebappClassLoader

热门文章

  1. Linux服务器集群系统(四)--转
  2. 机器学习-数据科学库(第四天)
  3. 【Python】Pycharm
  4. 【NLP】Transformer详解
  5. 《Credit Risk Scorecard》第五章: Development Database Creation
  6. 乌镇现场·帅初:公有链的未来——链上校验,链下计算
  7. PANDAS 数据合并与重塑(concat篇) 原创 2016年09月13日 19:26:30 47784 pandas作者Wes McKinney 在【PYTHON FOR DATA ANALYS
  8. Java Review - 创建线程和线程池时建议指定与业务相关的名称
  9. Java 8 - 02 Lambda Expression
  10. 并发编程-22J.U.C组件拓展之Fork/Join框架