java网络编程基础——TCP网络编程二
1、半关闭的Socket
前面的服务器和客户端通信时总是以行为最小数据单位,但是在某些协议里,通信的数据单位可能是多行的,当出现多行数据时就
出现一个问题:Socket输出流如何表示输出数据已经结束。
在IO中,如果表示输出已经结束,可以通过关闭输出流来实现,但在网络通信中则不同通过关闭输出流表示输出已经结束,
因为如果关闭,对应的Socket也将随之关闭,这样会导致程序无法再从Socket对应输出流中获取数据了。
在这种情况下,socket提供了两个半关闭的方法:
shutdownInput():关闭Socket的输入流,程序还可以通过Socket输出流输出数据
shutdownOutput():关闭Socket的输出流,程序还可以通过Socket的输入流读取数据
package rdb.com.net.server;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
public class Server1 {
public static void main(String[] args){ ServerSocket serverSocket = null;
PrintStream ps = null;
Scanner scan = null;
Socket socket = null;
try {
serverSocket = new ServerSocket(30000);
while(true){
socket = serverSocket.accept();
ps = new PrintStream(socket.getOutputStream());
ps.println("服务端第一行数据");
ps.println("服务端第二行数据");
//关闭输出流,表明socket输出已经结束
socket.shutdownOutput();
//false,关闭输出流或关闭输入流 是不会关闭Socket
System.out.println(socket.isClosed()); scan = new Scanner(socket.getInputStream());
while(scan.hasNextLine()){
System.out.println(scan.nextLine());
}
}
} catch (IOException e) {
e.printStackTrace();
}finally{
try {
if(scan != null){scan.close();}
if(socket != null){socket.close();}
if(serverSocket != null){serverSocket.close();} } catch (IOException e) {
e.printStackTrace();
}
} }
}
package rdb.com.net.server;
import java.io.IOException;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;
public class Client1 { public static void main(String[] args) {
Socket socket = null ;
PrintStream ps = null;
Scanner scan = null;
try {
socket = new Socket("127.0.0.1", 30000);
ps = new PrintStream(socket.getOutputStream());
ps.println("客户端第一行数据");
ps.println("客户端第二行数据");
//关闭输出流
socket.shutdownOutput();
System.out.println(socket.isClosed());
scan = new Scanner(socket.getInputStream());
while(scan.hasNextLine()){
System.out.println(scan.nextLine());
} } catch (IOException e) {
e.printStackTrace();
}finally{
try {
if(scan != null){scan.close();}
if(socket != null){socket.close();} } catch (IOException e) {
e.printStackTrace();
}
}
}
}
当使用上面两个方法关闭输入或者输出流后,Socket无法再次打开输入或者输出流,这种做法不适合保持持久通信状态的交互式应用。
适用一些一站式通信协议,如HTTP协议-----客户端请求服务器,服务器发送数据完成后无需再次发送数据,客户端只需读取响应数据,读取后Socket连接也就被关闭了
2、NIO实现非阻塞Socket通信

JDK1.4开始Java提供了NIO API开发高性能的网络服务器。
Java NIO为非阻塞式Socket提供了如下几个类:
Selector:是SelectableChannel对象的多路复用器,Selector实现了通过一个线程管理多个Channel,从而管理多个网络连接的目的。
Selector可以同时监控多个SelectableChannel的IO状况,是非阻塞的核心,一个Selector实例有三个SelectionKey集合:
1)所有的SelectionKey集合:代表注册在该Selector上的Channel,这个集合可以通过keys()方法返回
2)被选择的SelectionKey集合:代表了所有可通过select()方法获取的、需要进行IO处理的Channel,可以通过selectKeys()返回。
3)被取消的SelectionKey集合:代表了所有取消注册关系的Channel,在下一次执行select()方法时,这些Channel对应的SelectionKey会被彻底删除。
Selector还提供了一系列select()相关方法:
1)int select():监控所有注册的channel,当其中有需要处理IO操作时,该方法放回,并将对应的SelectionKey加入选择的SelectionKey集合中,该方法返回这些Channel的数量。
2)int select(long timeout):可以设置超时时长的select()操作
3)int selectNow():执行一个立即返回的select()操作,相对与无参的select()操作,该方法不会阻塞线程。
4)Selector wakeup():使一个未返回的select()立即返回。
SelectableChannel:代表可以支持非阻塞IO操作的Channel对象,它可以被注册到Selector上,这种注册关系由SelectionKey实例表示。SelectableChannel对象支持阻塞和非阻塞两种模式,默认是阻塞模式,必须在非阻塞模式下才可以使用非阻塞IO操作。
1)register():将Channel注册到指定Selector上。
2)SelectableChannel configureBlocking(boolean block):设置是否采用阻塞模式。
3)boolean isBlocking():返回Channel是否是阻塞模式。
4)int validOps():返回一个整数值,表示该Channel所支持的的IO操作。(不同的SelectableChannel 支持的操作不一样)
SelectionKey:该对象代表了SelectableChannel和Selector之间的注册关系,其中用静态常量定义了4种IO操作:
1)OP_READ:读
2)OP_WRITE:写
3)OP_CONNECT:连接
4)OP_ACCEPT:接收
下面使用NIO实现简单聊天室
package chatServer;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.Channel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
public class NServer { static final int PORT = 30000;
private Selector selector = null;
//定义编码、解码对象
private Charset charset = Charset.forName("UTF-8");
public void init() {
try {
//获取Selector对象
selector = Selector.open();
//获取未绑定的ServerSocketChannel对象
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
InetSocketAddress isc = new InetSocketAddress("127.0.0.1", PORT);
//将ServerSocketChannel邦到指定ip,端口
serverSocketChannel.bind(isc);
//设置serverSocketChannel以非阻塞方式工作
serverSocketChannel.configureBlocking(false);
//将serverSocketChannel注册到Selector
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); while(selector.select() > 0) {
//依次处理Selector上每个已选择的SelectionKey
for(SelectionKey sk : selector.selectedKeys()) {
//移除正在处理的SelectionKey
selector.selectedKeys().remove(sk);
//如果sk对应的channel包含客户端的连接请求
if(sk.isAcceptable()) {
//接受连接,产生服务端的SocketChannel.
SocketChannel socketChannel = serverSocketChannel.accept();
//设置非阻塞模式
socketChannel.configureBlocking(false);
//SocketChannel注册到Selector
socketChannel.register(selector, SelectionKey.OP_READ);
//sk对应的channel设置为accept,准备接收其他请求你
sk.interestOps(SelectionKey.OP_ACCEPT);
}
//如果sk对应的channel可读
if(sk.isReadable()) {
SocketChannel sc = (SocketChannel) sk.channel();
ByteBuffer buff = ByteBuffer.allocate(1024);
String content = "" ;
try {
while(sc.read(buff)>0) {
buff.flip();
content += charset.decode(buff);
}
System.out.println("读取数据:" + content);
//将sk对应的Channel设置成READ,准备下一次读取
sk.interestOps(SelectionKey.OP_READ);
} catch (Exception e) {
//删除SelectionKey
sk.cancel();
if(sk.channel() != null) {
sk.channel().close();
}
} //如果读取到消息,将消息发送到各个客户端
if(content.length() > 0) {
for(SelectionKey key : selector.keys()) {
Channel targetChannel = key.channel();
//剔除ServerSocketChannel
if(targetChannel instanceof SocketChannel) {
//将读取到的内容写入Channel中
((SocketChannel) targetChannel).write(charset.encode(content));
}
}
}
}
}
} } catch (IOException e) {
e.printStackTrace();
}
} public static void main(String[] args) {
NServer nServer = new NServer();
nServer.init();
}
}
package charClient;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Scanner;
public class NClient { static final int PORT = 30000;
private Selector selector = null;
private SocketChannel socketChannel ;
private Charset charset = Charset.forName("UTF-8");
public void init() {
try {
//检测SocketChannel的Selector对象
selector = Selector.open();
InetSocketAddress isa = new InetSocketAddress("127.0.0.1", PORT);
//创建连接到指定主机的SocketChannel
socketChannel = SocketChannel.open(isa);
//设置非阻塞
socketChannel.configureBlocking(false);
//注册channel,监听读
socketChannel.register(selector, SelectionKey.OP_READ); //启动线程,读取服务器来的数据
new NClientThread(selector).start();
//创建键盘输入流
Scanner scan = new Scanner(System.in);
while(scan.hasNextLine()) {
String line = scan.nextLine();
socketChannel.write(charset.encode(line));
}
} catch (IOException e) {
e.printStackTrace();
}
} public static void main(String[] args) {
NClient nClient = new NClient();
nClient.init();
}
} package charClient; import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset; public class NClientThread extends Thread{
private Selector selector = null;
private Charset charset = Charset.forName("UTF-8");
public NClientThread(Selector selector) {
this.selector = selector ;
} @Override
public void run() {
try {
while(selector.select() > 0) {
for(SelectionKey sk : selector.selectedKeys()) {
//移除正在处理的SelectionKey
selector.selectedKeys().remove(sk);
if(sk.isReadable()) {
SocketChannel sc = (SocketChannel) sk.channel();
ByteBuffer buff = ByteBuffer.allocate(1024);
String content = "" ;
while(sc.read(buff) > 0) {
buff.flip();
content += charset.decode(buff);
}
System.out.println("聊天信息:"+content);
//为下一次读做准备
sk.interestOps(SelectionKey.OP_READ);
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
package chatServer;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.Channel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
public class NServer { static final int PORT = 30000;
private Selector selector = null;
//定义编码、解码对象
private Charset charset = Charset.forName("UTF-8");
public void init() {
try {
//获取Selector对象
selector = Selector.open();
//获取未绑定的ServerSocketChannel对象
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
InetSocketAddress isc = new InetSocketAddress("127.0.0.1", PORT);
//将ServerSocketChannel邦到指定ip,端口
serverSocketChannel.bind(isc);
//设置serverSocketChannel以非阻塞方式工作
serverSocketChannel.configureBlocking(false);
//将serverSocketChannel注册到Selector
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); while(selector.select() > 0) {
//依次处理Selector上每个已选择的SelectionKey
for(SelectionKey sk : selector.selectedKeys()) {
//移除正在处理的SelectionKey
selector.selectedKeys().remove(sk);
//如果sk对应的channel包含客户端的连接请求
if(sk.isAcceptable()) {
//接受连接,产生服务端的SocketChannel.
SocketChannel socketChannel = serverSocketChannel.accept();
//设置非阻塞模式
socketChannel.configureBlocking(false);
//SocketChannel注册到Selector
socketChannel.register(selector, SelectionKey.OP_READ);
//sk对应的channel设置为accept,准备接收其他请求你
sk.interestOps(SelectionKey.OP_ACCEPT);
}
//如果sk对应的channel可读
if(sk.isReadable()) {
SocketChannel sc = (SocketChannel) sk.channel();
ByteBuffer buff = ByteBuffer.allocate(1024);
String content = "" ;
try {
while(sc.read(buff)>0) {
buff.flip();
content += charset.decode(buff);
}
System.out.println("读取数据:" + content);
//将sk对应的Channel设置成READ,准备下一次读取
sk.interestOps(SelectionKey.OP_READ);
} catch (Exception e) {
//删除SelectionKey
sk.cancel();
if(sk.channel() != null) {
sk.channel().close();
}
} //如果读取到消息,将消息发送到各个客户端
if(content.length() > 0) {
for(SelectionKey key : selector.keys()) {
Channel targetChannel = key.channel();
//剔除ServerSocketChannel
if(targetChannel instanceof SocketChannel) {
//将读取到的内容写入Channel中
((SocketChannel) targetChannel).write(charset.encode(content));
}
}
}
}
}
} } catch (IOException e) {
e.printStackTrace();
}
} public static void main(String[] args) {
NServer nServer = new NServer();
nServer.init();
}
}
java网络编程基础——TCP网络编程二的更多相关文章
- java网络编程基础——TCP网络编程一
		
基于TCP协议的网络编程 TCP/IP协议是一种可靠的网络协议,它的通信的两端各自建立一个Socket,从而在通信的两端之间形成网络虚拟链路. Java使用Socket对象来代表两端的通信端口,并通过 ...
 - java网络编程基础——TCP网络编程三
		
AIO实现非阻塞通信 java7 NIO2 提供了异步Channel支持,这种异步Channel可以提供更高效的IO,这种基于异步Channel的IO被称为异步IO(Asynchronous IO) ...
 - 【网络编程1】网络编程基础-TCP、UDP编程
		
网络基础知识 网络模型知识 OSI七层模型:(Open Systems Interconnection Reference Model)开放式通信系统互联参考模型,是国际标准化组织(ISO)提出的一个 ...
 - 【Linux网络编程】TCP网络编程中connect()、listen()和accept()三者之间的关系
		
[Linux网络编程]TCP网络编程中connect().listen()和accept()三者之间的关系 基于 TCP 的网络编程开发分为服务器端和客户端两部分,常见的核心步骤和流程如下: conn ...
 - Spark编程基础_RDD初级编程
		
摘要:Spark编程基础_RDD初级编程 RDD(Resilient Distributed Dataset)叫做弹性分布式数据集,是Spark中最基本的数据抽象,它代表一个不可变.可分区.里面的元素 ...
 - java网络编程基础——基本网络支持
		
基本网络支持 java.net包主要为网络编程提供支持. 1.InetAddress InetAddress类代表IP地址,还有两个子类:Inet4Address.Inet6Address. pack ...
 - Linux socket网络编程基础 tcp和udp
		
Socket TCP网络通信编程 首先,服务器端需要做以下准备工作: (1)调用socket()函数.建立socket对象,指定通信协议. (2)调用bind()函数.将创建的socket对象与当前主 ...
 - Python黑客编程基础3网络数据监听和过滤
		
网络数据监听和过滤 课程的实验环境如下: • 操作系统:kali Linux 2.0 • 编程工具:Wing IDE • Python版本:2.7.9 • 涉及 ...
 - 网络编程基础:网络基础之网络协议、socket模块
		
操作系统(简称OS)基础: 应用软件不能直接操作硬件,能直接操作硬件的只有操作系统:所以,应用软件可以通过操作系统来间接操作硬件 网络基础之网络协议: 网络通讯原理: 连接两台计算机之间的Intern ...
 
随机推荐
- 开发掉坑(二)前端静态资源 Uncaught SyntaxError: Unexpected token <
			
某天,有同学反馈后台管理系统出现静态资源无法加载的问题. 复现如下: 进入首页. 点击侧边栏某个子功能,静态资源可正常访问到. 等待10分钟左右,点击侧边栏其他子功能,无法访问到静态资源. 查看控制台 ...
 - 『动善时』JMeter基础 — 43、JMeter对数据库的查询操作
			
目录 1.使用"用户自定义变量"实现参数化 2. 在SQL Query中使用占位符传递参数 (1)传递的参数值是常量 (2)传递的参数值是变量 3.Variables names参 ...
 - WordPress安全篇(1):WordPress网站启用HTTPS详细教程
			
以前我们浏览网页使用的都是HTTP协议,HTTP使用明文传输,所以传输过程中很容易遭受黑客窃取.篡改数据,很不安全.在WordPress网站上启用HTTPS协议访问后,能大大提升站点的安全性,启用HT ...
 - 使用NDepend衡量代码的SOLID程度
			
SOLID是面向对象的软件开发中的5条准则,也是开发人员可以提升自己代码质量的准则.那么如何衡量自己的代码是否符合SOLID准则呢?NDepend这款工具也许可以帮得上忙.本文将介绍一些NDepend ...
 - 【VBA】查找字符串
			
老婆饼里有老婆吗 Sub test() aaa = "老婆饼里有老婆吗" If InStr(aaa, "老婆") <> 0 Then Debug.p ...
 - WEB安全新玩法 [1] 业务安全动态加固平台
			
近年来,信息安全体系建设趋于完善,以注入攻击.跨站攻击等为代表的传统 Web 应用层攻击很大程度上得到了缓解.但是,Web 应用的业务功能日益丰富.在线交易活动愈加频繁,新的安全问题也随之呈现:基于 ...
 - OpenCV随笔
			
创建一个窗口#zeros(shape,dtype=float,order='C')#shape:形状,dtype:数据类型,可选参数,默认numpy.float64img = np.zeros((50 ...
 - React-Antd4的Form表单校验
			
之前很少用react做项目,最近入职新公司,用的react,在自己的摸索过程中,慢慢会记录一些使用方法.今天简单记录一下使用antd 4.0版本的Form表单校验,直接代码. 需要购买阿里云产品和服务 ...
 - 用transform和rem哪个好
			
个人觉得电脑端的用transform好,毕竟电脑端的项目基本都会固定屏幕比列,16:9.28:9.32:9的 一个固定的设计稿就能很好的适配. 移动端用rem比较好,移动端的屏幕比列太杂,使用rem自 ...
 - vscode 配置 Pug Compile Hero Pro 插件步骤
			
这个随笔主要介绍 vscode 配置 Pug Compile Hero Pro 插件的步骤,实现快速使用less 以及 scss 等的编程语言 第一步 当然是安装我们的插件啦! 在插件商店里 搜 Sa ...