Java Socke 探究
Java中的Socket可以分为普通Socket和NioSocket两种。
普通Socket的用法
Java中的网络通信是通过Socket实现的,Socket分为ServerSocket和Socket两大类,ServerSocket用于服务端,可以通过accept方法监听请求,监听到请求后返回Socket,Socket用于具体完成数据传输,客户端直接使用Socket发起请求并传输数据。
一个简单的交互介绍ServerSocket及Socket的使用:
1. 创建ServerSocket。
ServerSocket的构造方法一共有5个。最方便的是传入一个端口参数的方法。
2. 调用创建出来的ServerSocket的accept方法进行监听
accept方法是阻塞方法,也就是说调用accept方法后程序会停下来等待连接请求,在接收到请求之前程序将不会继续执行,当接收到请求之后,accept方法会返回一个Socket。
3. 使用accept方法返回的Socket与客户端进行通信。
服务端代码示例:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.Date;public class Server {public static void main(String[] args) {try {//创建一个ServeSocket,设置端口为8080ServerSocket serverSocket = new ServerSocket(8080);//运行Socket监听,等待请求 此方法会阻塞线程,当有请求时才会继续执行Socket socket = serverSocket.accept();//接收到请求之后使用Socket进行通信,创建BufferedReader用于读取请求的数据BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));PrintWriter out = new PrintWriter(socket.getOutputStream());String line = in.readLine();System.out.println(line);//创建PrintlnWriter,用于发送数据out.println("已经接受到了数据");out.flush();System.out.println("Server关闭" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.sss").format(new Date()));//关闭资源out.close();in.close();socket.close();serverSocket.close();} catch (IOException e) {e.printStackTrace();} finally {}}
}
客户端代码示例:
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.Date;/*** 客户端* @author sanchan*/
public class Client {public static void main(String[] args) {//需要先启动Server否则报错java.net.ConnectException: Connection refusedtry {String msg="你好,ServerSocket!";//创建一个Socket,与本机8080端口连接Socket socket=new Socket("127.0.0.1",8080);//使用Socket创建PrintWriter和BufferedReader进行数据的读写PrintWriter out=new PrintWriter(socket.getOutputStream());out.println(msg);out.flush();BufferedReader in=new BufferedReader(new InputStreamReader(socket.getInputStream()));String line=in.readLine();System.out.println(line);//关闭资源in.close();out.close();socket.close();System.out.println("client关闭"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.sss").format(new Date()));} catch (Exception e) {e.printStackTrace();} finally {}}
}
NioSocket的使用
nio(new IO)是JDK1.4新增加的IO模式,nio在底层采用了与新的处理方式大大的提高了Java IO的效率。Socket也属于IO的一种,nio提供了相对应的类:ServerSocketChannel和SocketChannel,分别对应原来的ServerSocket和Socket。
理解nio三基础:
1. Buffer
2. Channel
3. Selector
小故事里有大智慧:
试想一下如果电商是只要有订单就派人直接取货去送货【这种模式就相当于之前的Socket方式】,而不是现在的快递模式:将货物统一到中转站,分拣员按照配送范围分配快递员,然后快递员统一送货【这种模式就相当于NioSocket模式】。那么你得多长时间收到你得快递/(ㄒoㄒ)/~~
Buffer就是货物,Channel就是快递员,Selector就是中转站的分拣员。
NioSocket使用步骤:
1. 创建ServerSocketChannel并设置相应参数
SerSocketChannel可以使用自身的静态工厂方法open创建。
每个ServerSocketChannel对应一个ServerSocket,可以调用其socket方法来获取【不过如果使用该ServerSocket监听请求就又回到原来的 普通Socket 模式了,一般只用于使用bind方法绑定端口】。
ServerSocketChannel可以通过`SelectableChannel configureBlocking(boolean block)` 方法来设置是否采用阻塞模式。设置非阻塞模式之后可以调用register方法注册Selector【阻塞模式不可以使用Selector】
2. 创建Selector并注册Selector到ServerSocketChannel上
Selector可以使用自身的静态工厂方法open创建。
创建后通过上面所说的Channel的register方法注册到ServerSocketChannel或者SocketChannel上。
3.调用Selector的select方法等待请求
通过select方法等待请求,select方法可传入代表最长等待时间的long型参数。在设定时间内接收到相应操作的请求则返回可以处理请求的数量,否则在超时后返回0,程序继续执行。如果传入0或者使用无参的重载方法,则会采用阻塞模式直到有相应操作的请求出现。
4. 使用Selector接收请求并处理
接收到请求后Selector调用selectedKeys返回SelectionKey的Set集合。
5. 使用SelectionKey获取到Channel、Selector和操作类型并进行具体操作。
SelectionKey保存了处理当前请求的Channel和Selector,并提供了不同的操作类型。前面提到的Channel注册Selector的register方法参数中第二个参数就是SelectionKey定义的。共有四种:
SelectionKey.OP_ACCEPT //请求操作
SelectionKey.OP_CONNECT //链接操作
SelectionKey.OP_READ //读操作
SelectionKey.OP_WRITE //写操作
只有在register方法中注册了对应的操作Selector才会关心相应类型操作的请求。
Selector和Channel是多对多关系。
Selector是按不同的操作类型进行分拣,将分拣结果保存在SelectionKey中,可分别通过SelectionKey的channel、selector方法来获取对应的Channel和Selector。可以使用SelectionKey的isAcceptable、isConnectable、isReadable和isWritable方法来判断是什么类型的操作。
Buffer是专门用于存储数据,有四个极为重要的属性:
capacity:容量。
Buffer最多可以保存元素的数量,创建时设置,使用过程中不可修改。limit:可以使用的上限。
刚创建Buffer时limit等于capacity。如果给limit设置【不能超过capacity】之后,limit就成了最大可访问的值。
例如,一个Buffer的capacity为100,表示最多可以保存100个数据,只写入20个之后就要读取,在读取时limit就会设置为20。
position:当前所操作元素所在索引位置。
position从0开始,随着get和put方法自动更新。mark:用来暂时保存position的值。
position保存到mark之后就可以修改并进行相关的操作,操作完成后可以通过reset方法将mark的值恢复到position。
mark默认值为-1,且其值必须小于position的值。
例如,Buffer中一共保存了20个数据,position为10,现在想读取15到20之间的数据,这时就可以调用Buffer的mark方法将目前的position保存到mark中,然后调用Buffer的position(15)将position指向第15个元素,这时就可以读取。读取完成之后使用Buffer的reset就可以将position恢复到10.
如果调用Buffer的position方法时传入的值小于mark当前的值,则会将mark设为-1。
这四个属性大小关系:mark<=position<=limit<=capacity
我们将前面的普通Socket示例的服务端改写一下:
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
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;
import java.util.Iterator;/*** @author sanchan* @since 1.0*/
public class NIOServer {public static void main(String[] args) {/*** 启动监听* 当监听到请求时根据SelectionKey的操作类型交给内部类Handler进行处理*/try {//创建ServerSocketChannelServerSocketChannel ssc = ServerSocketChannel.open();//设置监听8080端口ssc.socket().bind(new InetSocketAddress(8080));//设置为非阻塞模式ssc.configureBlocking(false);//为ServerSocketChannel注册SelectorSelector selector = Selector.open();ssc.register(selector, SelectionKey.OP_ACCEPT);//创建HandlerHandler handler = new Handler(1024);while (true) {//等待请求,每次等待阻塞3s,超过3秒后线程继续运行,如果传入0或使用无参重载方法,将一直阻塞if (selector.select(3000) == 0) {System.out.println("等待请求超时~~~~~");continue;}System.out.println("处理请求~~~~~");//获取等待处理的SelectionKeyIterator<SelectionKey> keyIterator = selector.selectedKeys().iterator();while (keyIterator.hasNext()) {SelectionKey key = keyIterator.next();try {//根据不同请求操作选择对应的处理方法if (key.isAcceptable()) {handler.handleAccept(key);}if (key.isReadable()) {handler.handleRead(key);}} catch (IOException e) {e.printStackTrace();//如果异常就说明连接结束,移除keyIterator.remove();continue;}}}} catch (IOException e) {e.printStackTrace();} finally {}}/*** 请求处理类*/private static class Handler {private int bufferSize = 1024;private String localCharset = "UTF-8";public Handler() {}public Handler(int bufferSize) {this(bufferSize, null);}public Handler(String localCharset) {this(-1, localCharset);}public Handler(int bufferSize, String localCharset) {if (bufferSize > 0)this.bufferSize = bufferSize;if (localCharset != null)this.localCharset = localCharset;}/*** 处理请求操作** @param key* @throws IOException*/public void handleAccept(SelectionKey key) throws IOException {//获取ChannelSocketChannel sc = ((ServerSocketChannel) key.channel()).accept();//设置非阻塞sc.configureBlocking(false);//注册读操作的Selectorsc.register(key.selector(), SelectionKey.OP_READ, ByteBuffer.allocate(bufferSize));}/*** 处理读操作** @param key* @throws IOException*/public void handleRead(SelectionKey key) throws IOException {//获取ChannelSocketChannel sc = ((SocketChannel) key.channel());//获取ByteBuffer/*** Buffer专门用于存储数据,有四个极为重要的属性:* 1. capacity:容量。* Buffer最多可以保存元素的数量,创建时设置,使用过程中不可修改。* 2. limit:可以使用的上限。* 刚创建Buffer时limit等于capacity。如果给limit设置【不能超过capacity】之后,limit就成了最大可访问的值。* 例如,一个Buffer的capacity为100,表示最多可以保存100个数据,只写入20个之后就要读取,在读取时limit就会设置为20。* 3. position:当前所操作元素所在索引位置。* position从0开始,随着get和put方法自动更新。* 4. mark:用来暂时保存position的值。* position保存到mark之后就可以修改并进行相关的操作,操作完成后可以通过reset方法将mark的值恢复到position。* mark默认值为-1,且其值必须小于position的值。* 例如,Buffer中一共保存了20个数据,position为10,现在想读取15到20之间的数据,这时就可以调用Buffer的mark方法将目前的position保存到mark中,然后调用Buffer的position(15)将position指向第15个元素,这时就可以读取。读取完成之后使用Buffer的reset就可以将position恢复到10.* 如果调用Buffer的position方法时传入的值小于mark当前的值,则会将mark设为-1。*/ByteBuffer buffer = (ByteBuffer) key.attachment();//重置ByteBuffer。设置limit=capacity、position=0、mark=-1buffer.clear();//没有获取到内容则关闭if (sc.read(buffer) == -1) {sc.close();} else {/*** flip()作用:* 在保存数据时保存一个数据position加1,保存完成后要读取数据* 就得设置limit=position,position=0**/buffer.flip();//返回数据到客户端String receivedString = Charset.forName(localCharset).newDecoder().decode(buffer).toString();System.out.println("从客户端获取到了数据:" + receivedString);String sendString = "服务端已经获取到了数据:" + receivedString;buffer = ByteBuffer.wrap(sendString.getBytes(localCharset));sc.write(buffer);//关闭SocketChannelsc.close();}}}
}
我是广告
本人的直播课程在 7 月份就要开始了,希望小伙伴们支持一下,现在报名有优惠噢
https://segmentfault.com/l/15...
https://segmentfault.com/l/15...
Java Socke 探究相关推荐
- 【Java】探究Java实现多接口时同名方法冲突问题
问题由来 今天与朋友们聊天谈到C++的多继承问题,朋友觉得非常麻烦,特别是遇到方法重复的时候. 这时,我突然想到既然Java通过多接口的implement代替了复杂的多继承,那如果两个甚至多个接口存在 ...
- 【Java】探究Java方法的参数传递是值传递还是引用传递
测试思路 每个更改形参的方法,返回值都是void,不同方法的参数设置不同类型. 注意在方法内测地址的时候在改之前测一下,才能看出传入参数是不是传了地址.(注意反正OS的内存地址是虚拟的,JVM中的也是 ...
- 【Java】探究自增运算符++的原理
荐读 C语言里++能随便用吗 j=j++到底是什么 a++ + ++a到底是什么 i++与++i 看下面的两组代码: public class IntegerPlusTest {public stat ...
- 【Java】探究Java数组的本质
问题引入 Java中的数组是封装好的,我们直观地是看不到实现的细节的,但这是否意味着数组是Java中的基本类型? 答案是否定的. 众所周知,Java为了便于开发者使用,提供了8种基本类型:long.i ...
- java深入探究13-js,ajax
链接:http://pan.baidu.com/s/1c2D0cAs 密码:uwm6 1.js 1)三种基本类型: var num=100; var str="哈哈"; var f ...
- JAVA编码格式探究
转载1:http://www.blogjava.net/rabbit/archive/2008/03/27/189009.html 谢谢分享 java中的String类是按照unicode进行编 ...
- java深入探究07-jsp
RequestDispatcher 是web资源包装类 <jsp:include>只能实现固定jsp文件名 他可以翻译为:RequestDispatcher(filename).inclu ...
- Java多线程探究-死锁原因
进程死锁及解决办法 一.要点提示 (1) 掌握死锁的概念和产生死锁的根本原因. (2) 理解产生死锁的必要条件--以下四个条件同时具备:互斥条件.不可抢占条件.占有且申请条件.循环等待条件. (3) ...
- android java json_探究Android系统中解析JSON数据的方式
前言喜欢在前言里讲一下自己的现状,或许能有共鸣的同学,更多的是留给自己一个纪念,几个月或者几年再回来看的时候还是会很有感慨.今天说说语言,json这种数据格式之前我做服务器端的时候天天接触,天真的以为 ...
最新文章
- python入门(三)-- 基本运算符
- 网站SEO中内页标签该如何进行优化?
- leetcode算法题--摆动序列★
- java使用动态代理来实现AOP(日志记录)的实例代码
- python 排名函数_一个危险的Python函数,不推荐使用
- Git 分支 - rebase 变基
- C#数据结构(一)----线性表
- PAT_B_1058_Java(20分)
- Berttransformer
- motorola 企业移动解决方案
- 函数相关之函数参数08
- Hive——元数据表含义
- 利用matlab编程实现主成分分析,利用Matlab编程进行主成分分析
- Dilated Convolution(空洞卷积、膨胀卷积)详解
- 用友U8打开起初采购入库单报错
- 如何将Photoshop图层复制到其他文档
- 如何删除PDF水印?PDF删除水印怎么操作
- 查看linux系统CPU和内存命令
- Modbus Slave学习笔记
- Swift Markup Formatting Syntax