博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Java Socke 探究
阅读量:5949 次
发布时间:2019-06-19

本文共 10523 字,大约阅读时间需要 35 分钟。

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,设置端口为8080            ServerSocket 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 refused        try {            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是专门用于存储数据,有四个极为重要的属性:

  1. capacity:容量。

    Buffer最多可以保存元素的数量,创建时设置,使用过程中不可修改。

  2. limit:可以使用的上限。

    刚创建Buffer时limit等于capacity。如果给limit设置【不能超过capacity】之后,limit就成了最大可访问的值。

例如,一个Buffer的capacity为100,表示最多可以保存100个数据,只写入20个之后就要读取,在读取时limit就会设置为20。

  1. position:当前所操作元素所在索引位置。

    position从0开始,随着get和put方法自动更新。

  2. 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 {            //创建ServerSocketChannel            ServerSocketChannel ssc = ServerSocketChannel.open();            //设置监听8080端口            ssc.socket().bind(new InetSocketAddress(8080));            //设置为非阻塞模式            ssc.configureBlocking(false);            //为ServerSocketChannel注册Selector            Selector selector = Selector.open();            ssc.register(selector, SelectionKey.OP_ACCEPT);            //创建Handler            Handler handler = new Handler(1024);            while (true) {                //等待请求,每次等待阻塞3s,超过3秒后线程继续运行,如果传入0或使用无参重载方法,将一直阻塞                if (selector.select(3000) == 0) {                    System.out.println("等待请求超时~~~~~");                    continue;                }                System.out.println("处理请求~~~~~");                //获取等待处理的SelectionKey                Iterator
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 { //获取Channel SocketChannel sc = ((ServerSocketChannel) key.channel()).accept(); //设置非阻塞 sc.configureBlocking(false); //注册读操作的Selector sc.register(key.selector(), SelectionKey.OP_READ, ByteBuffer.allocate(bufferSize)); } /** * 处理读操作 * * @param key * @throws IOException */ public void handleRead(SelectionKey key) throws IOException { //获取Channel SocketChannel 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=-1 buffer.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); //关闭SocketChannel sc.close(); } } }}

我是广告

本人的直播课程在 7 月份就要开始了,希望小伙伴们支持一下,现在报名有优惠噢

转载地址:http://qlsxx.baihongyu.com/

你可能感兴趣的文章
Learn in FCC(3)
查看>>
RunLoop--
查看>>
chrome 2行换行省略号 ... text-ellipse
查看>>
架构,改善程序复用性的设计~目录(附核心原代码)
查看>>
Unix编程艺术阅读笔记
查看>>
nginx配置location总结及rewrite规则写法
查看>>
12月该知道的
查看>>
[3D]第一人称相机类Camera
查看>>
zookeeper启动报错(数据目录权限不对)
查看>>
VM 监控信息布局
查看>>
Python Pandas -- Series
查看>>
Python3 GUI开发(PyQt)安装和配置
查看>>
在UnrealEngine中用Custom节点实现径向模糊
查看>>
Linux 套接字通信笔记(一)
查看>>
汉诺塔——各种编程范式的解决
查看>>
poi读写Excel文件
查看>>
Android闹钟开发与展示Demo
查看>>
静态库介绍与简单演练及同名资源冲突解决(.a格式的静态库)
查看>>
layoutSubviews
查看>>
67. Add Binary
查看>>