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是专门用于存储数据,有四个极为重要的属性:

  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 {//创建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 探究相关推荐

  1. 【Java】探究Java实现多接口时同名方法冲突问题

    问题由来 今天与朋友们聊天谈到C++的多继承问题,朋友觉得非常麻烦,特别是遇到方法重复的时候. 这时,我突然想到既然Java通过多接口的implement代替了复杂的多继承,那如果两个甚至多个接口存在 ...

  2. 【Java】探究Java方法的参数传递是值传递还是引用传递

    测试思路 每个更改形参的方法,返回值都是void,不同方法的参数设置不同类型. 注意在方法内测地址的时候在改之前测一下,才能看出传入参数是不是传了地址.(注意反正OS的内存地址是虚拟的,JVM中的也是 ...

  3. 【Java】探究自增运算符++的原理

    荐读 C语言里++能随便用吗 j=j++到底是什么 a++ + ++a到底是什么 i++与++i 看下面的两组代码: public class IntegerPlusTest {public stat ...

  4. 【Java】探究Java数组的本质

    问题引入 Java中的数组是封装好的,我们直观地是看不到实现的细节的,但这是否意味着数组是Java中的基本类型? 答案是否定的. 众所周知,Java为了便于开发者使用,提供了8种基本类型:long.i ...

  5. java深入探究13-js,ajax

    链接:http://pan.baidu.com/s/1c2D0cAs 密码:uwm6 1.js 1)三种基本类型: var num=100; var str="哈哈"; var f ...

  6. JAVA编码格式探究

    转载1:http://www.blogjava.net/rabbit/archive/2008/03/27/189009.html    谢谢分享 java中的String类是按照unicode进行编 ...

  7. java深入探究07-jsp

    RequestDispatcher 是web资源包装类 <jsp:include>只能实现固定jsp文件名 他可以翻译为:RequestDispatcher(filename).inclu ...

  8. Java多线程探究-死锁原因

    进程死锁及解决办法 一.要点提示 (1) 掌握死锁的概念和产生死锁的根本原因. (2) 理解产生死锁的必要条件--以下四个条件同时具备:互斥条件.不可抢占条件.占有且申请条件.循环等待条件. (3)  ...

  9. android java json_探究Android系统中解析JSON数据的方式

    前言喜欢在前言里讲一下自己的现状,或许能有共鸣的同学,更多的是留给自己一个纪念,几个月或者几年再回来看的时候还是会很有感慨.今天说说语言,json这种数据格式之前我做服务器端的时候天天接触,天真的以为 ...

最新文章

  1. python入门(三)-- 基本运算符
  2. 网站SEO中内页标签该如何进行优化?
  3. leetcode算法题--摆动序列★
  4. java使用动态代理来实现AOP(日志记录)的实例代码
  5. python 排名函数_一个危险的Python函数,不推荐使用
  6. Git 分支 - rebase 变基
  7. C#数据结构(一)----线性表
  8. PAT_B_1058_Java(20分)
  9. Berttransformer
  10. motorola 企业移动解决方案
  11. 函数相关之函数参数08
  12. Hive——元数据表含义
  13. 利用matlab编程实现主成分分析,利用Matlab编程进行主成分分析
  14. Dilated Convolution(空洞卷积、膨胀卷积)详解
  15. 用友U8打开起初采购入库单报错
  16. 如何将Photoshop图层复制到其他文档
  17. 如何删除PDF水印?PDF删除水印怎么操作
  18. 查看linux系统CPU和内存命令
  19. Modbus Slave学习笔记
  20. Swift Markup Formatting Syntax

热门文章

  1. 浪潮世科和浪潮软件什么关系_社交图形浪潮
  2. 黑客宣言_情感设计宣言
  3. 怎样开发一个 Node.js 命令行工具包
  4. 复杂性思维中文第二版 附录 A、算法分析
  5. 安卓操作sqlite3,增删改查
  6. oracle 约束 Oracle 10g学习系列(5)
  7. 高性能网站建设的最佳实践(二)
  8. python第三周测试_python第三周小测
  9. Accoridion折叠面板
  10. 用链表和数组实现HASH表,几种碰撞冲突解决方法