12.1.1 网络基础知识
通信协议:计算机网络中实现通信的一些约定🌂
通信协议的组成:语义,语法,变换规则(时序)
国际标准化阻止ISO于1978年,提出”开放系统互连参考模型“,既OSI七层协议(物理层,数据链路层,网络层,传输层,会话层,表示层,应用层)
IP(网际互联协议)/TCP(传输控制协议),最早出现在UNIX操作系统中,该协议将网络分为四层(应用层,传输层,网络层,物理层+数据链路层)
<br>
12.1.2 IP地址和端口号
端口:一个16位整数,用于表述数据交给那个程序处理
同一台机器上,同一端口不能被两个程序使用
端口号范围0 --65535
-
0 到1023 —— 公认端口,他们紧密绑定一些特定的服务
-
1024 到 49151 —— 注册端口,松散绑定一些服务
-
49152 到 65535 —— 动态和私有端口
<br>
12.2 Java的基本网络支持
12.2.1 使用InetAddress
该类代表Ip地址,它的两个子类Inet4Address,InetAddress分别代表IPv4和IPv6
获取InetAddress实例
-
getByName(String):根据主机名获取对应的InetAdderss对象
-
getByAddress(byte【】):根据IP地址来获取对应的InetAddress对象
-
getLocalHost():获取本机IP地址对应的InetAddress实例
获取实例中的IP地址和主机名
-
String getCanonicalHostName():获取此IP地址的全限定域名
-
String getHostAddress():返回该InetAddress实例对应的饿IP地址,的字符串形式
-
String getHostName():获取此实例代表的主机名
测试通信方法
-
isReachable():用于测试是否可以到达该地址
-
该方法将尽最大努力试图到达主机,如果可以访问到,将发送类似ping命令的报文,如没有获取权限,它将试图在目标主机的端口7(Echo)上,建立TCP连接
-
<br>
12.2.2 使用URLDecoder和URLEncoder
这里讨论一个有趣的现象,打开浏览器,随便输入中文字符串来进行搜索,回车之后,中文字符将以get方式传输到搜索引擎的服务器,并且链接中的中文字符好像变成了一堆乱码的样子,并不是的,这一堆像乱码的东西,叫application/x-www-form-urlencoded MIME 字符串
当URL中包含非西欧字符的字符串时,系统会将这些,非西欧字符转换为application/x-www-form-urlencoded MIME字符串。下面称为特殊字符串
URLDecoder
-
decode(String,String):将特殊字符转换位普通字符串
URLEncoder
-
encode(String,String):将普通字符串转换位特殊字符串
这里的转换方式为,西欧字符串无需转换,中文字符每个占两字节,每个字节转换为2个十六进制的数字,所以每个中文字符转换为”%XX%XX“的形式,需要注意这里的转换字节数还和使用字符集有关
<br>
12.2.3 使用URL和URLConnection
URL(统一资源定位符),是指向互联网资源的指针
URL的组成 —— 协议名://主机:端口/资源
java中也提供了URL对应的类,就叫URL
获取URL类对象
-
通过构造方法
URL类的方法
-
String getFile():获取该URL资源名
-
String getHost():获取该URL主机名
-
String getPath():获取该URL的路径部分
-
int getPort():获取该URL的端口号
-
String getProtocol():获取该URL的协议名称
-
String getQuery():获取该URL的查询字符串部分
-
URLConnection openConnection():返回一个URLConnection对象,该对象代表了与URL所引用的远程对象的连接
-
InputStream openStream():打开与此URL的连接,并返回一个用于读取该URL资源的输入流(InputStream)
设置请求头的通用方法
setRequestproperty(String key,String value):设置URLConnection的key请求头字段的值为value
<br>
12.3 基于TCP协议的网络编程
java在网络通信的两端各建立一个Socket对象,通过该对象来生成虚拟链路,来实现TCP的可靠通信,同时使用该对象来产生IO流,来进行网络通信
<br>
12.3.2 使用ServerSocket创建TCP服务器端
服务器端的对象,使用serverSocket来表示,该对象包含一个方法
accept(),该方法如果接收到一个客户端的Socket连接请求,将返回一个与客户端Socket对应的Socket,否则该方法一直处于等待状态,线程也被阻塞
获取serverSocket对象
-
ServerSocket(int):用指定端口来创建一个ServerSocket
-
ServerSocket(int port,int backlog):增加一个用来改变连接队列长度的参数 backlog
-
ServerSocket(int port,int backlog,InetAddress localAddr):在机器存在多个IP地址的情况下,允许通过localAddr参数来指定,将ServerSocket绑定到指定的IP地址
<br>
12.3.3 使用Socket进行通信
获取Socket对象
-
Socket(InetAddress/String remoteAddress,int port):连接到远程主机,指定要连接主机的端口号,本地Socket使用默认IP地址,默认使用系统动态分配的端口
-
Socket(InetAddress/String remoteAddress,int port,InetAddress localAdde,int localPort):创建连接,连接到远程主机,该方法可以指定本地IP和本地端口
获取Socket的输入流和输出流
-
InputStream getInputStream():返回Socket对象对应的输入流
-
OutputStream getOutputStream():返回该Socket对象对应的输出流
指定超时时长
-
setSoTimeout(int):在超过int时间之后,抛出异常(SocketTimeoutException异常)
指定连接超时
-
connect(InetAddress实例,超时时间):连接到指定远程主机,指定超时时间
<br>
12.3.4 加入多线程
该操作,将每个客户端放在一个线程体中,让后让Serversocket的accept方法监听,只要监听到,则创建对应的线程,然后子线程单独处理每个客户端
需要注意的是,书中这里创建了一个ArrayList的集合,来保存创建的socket对象,之后每个线程向服务器发送信息时,服务器会向,连接到该服务器的所有客户端,转发该信息
<br>
12.3.5 记录用户信息
在这里使用了一个Map来记录用户名和对应的输入流,用于来实现私聊功能
之后使用了一个自定义接口,定义了成员变量来定义了一些特殊字符,这些特殊字符用作标识符,来区分不同的功能
<br>
12.3.6 半关闭的Socket
对于文件类的c/s模式,客户端可以在传完文件后直接关闭输入流,但对于网络的聊天类应用不能这样做,于是有了半关闭式Socket
-
shutdownInput():关闭该Socket的输入流,输出流正常
-
shutdownOutput():关闭该Socket的输出流,输入流正常
-
isInputShutdown():判断该Socket是否处于半读状态(read-half)
-
isOutputShutdown():判断该Socket是否处于半写状态(write-half)
注意,如果使用上面方法关闭流,则无法再打开该Socket,已被关闭的流
<br>
12.3.7 使用NIO实现非阻塞Socket通信
java的NIO为非阻塞式Socket通信提供了如下几个类
-
Selector:可调用该类的open()静态方法来创建Selector实例
Selector可同时监控多个SelectableChannel的IO状况,是非阻塞IO的核心,一个Selector实例有三个SelectionKey集合
-
所有SelectionKey集合,代表了注册在该Selector上的Channel,可通过keys()方法返回
-
被选择的SelectionKey集合,代表了所有可通过select(0方法获取的,需要IO处理的Channel,可通过selectedKeys()返回
-
被取消的SelectionKey集合,代表了所有被取消注册的Channel,在下一次执行select()方法是,该集合将会被cedilla删除
Selector方法
-
int select():监控所有这侧的Channel,当他们中间需要IO处理时,还方法返回,并将对应的SelectionKey加入被选择的SelectionKey,该方法返回CHannel的数量
-
int select(long timeout):设置超时时长
-
int selectNow():执行一个立即返回的select()操作,该方法不会阻塞线程
-
Selector wakeup():是一个还未返回的select()方法立刻返回
-
SelectableChannel:他代表可以支持非阻塞IO操作的Channel对象,可被注册到Selector上,这种注册关系由SelectionKey实例表示,Selector对象提供了一个select()方法,该方法允许应用程序用时监控多个IOChannel
-
register():将一个SelectableChannel注册到指定Selector上
-
SelectableChannel configureBlocking(boolean block):设置是否采用阻塞模式
-
boolean isBlocking():该Channel是否是阻塞模式(false为非阻塞)
-
int validOps():返回一个整数,表示这个Channel所支持的IO操作
serverSocketChannel代表一个ServerSocket,它只支持OP_ACCEPT操作
在SelectionKey中,用静态常量定义了4种IO操作
-
OP_READ(1)
-
OP_WRITE(4)
-
OP_CONNECT(8)
-
OP_AACCEPT(16)
这里的值,计算的非常巧妙,这里的值任意2个,3个,4个,进行按位或的结果,和相加的结果相等,同时他们相加的结果总是互不相同
所以系统可以根据validOps()方法的返回值,确定该SelectableChannel支持的操作
获取AelectableChannel的注册状态
-
boolean isRegistered():返回该Channel是否已注册在一个或多个Selector上
-
SelectionKey keyFor(Selector sel):返回该Channel和sel Selector之间的注册关系,如不存在注册关系,则返回null
-
SelectionKey:该对象代表了SelectableCHannel和Selector之间的注册关系
-
ServerSocketChannel:支持非组设操作,对应java.net.serverSocket类只支持OP_ACCEPT操作,
-
SocketChannel:支持非阻塞操作,对应java.netSocket这个类,支持OP_CONNECT、OP_READ 和 OP_WRITE 操作。这个类还实现了 ByteChannel 接口、ScatteringByteChannel 接口和GatheringByteChannel 接口,所以可以直接通过SocketChannel来读写ByteBuffer对象。
那么整理一下上面的工作流程,
1.服务器上的所有Channel都需要向Selector注册,
2.Selector则负责监视这些Socket的IO状态(通过select()方法)
3.只要当前select()方法返回值大于0,则使用selectedKeys()来获取需要处理的IO集合
书中接下来是使用NIO和AIO来实现聊天程序的具体代码实现和优化,实在繁琐,如有需要再回来看
后言
心野掉了就念不进书,就没心思干活,就只适合日复一日地坐在野地里发呆,在黄昏和夜晚的缝隙中一次又一次地消融。你就很难再回到真实的人世间,捡起上进心,努力去做一个世俗的成功者了。(陈春成 《夜晚的潜水艇》)