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

  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...

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/289779.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

codeforces 600D Area of Two Circles' Intersection

分相离&#xff0c;内含&#xff0c;想交三种情况讨论一下。 主要是精度和数据范围的问题&#xff0c;首先数据用long double&#xff0c;能用整型判断就不要用浮点型。 题目中所给的坐标&#xff0c;半径是整型的&#xff0c;出现卡浮点判断的情况还是比较少的。 最后算三角型…

PHP进程退出信号_一文吃透 PHP 进程信号处理

背景前两周老大给安排了一个任务&#xff0c;写一个监听信号的包。因为我司的项目是运行在容器里边的&#xff0c;每次上线&#xff0c;需要重新打包镜像&#xff0c;然后启动。在重新打包之前&#xff0c;Dokcer会先给容器发送一个信号&#xff0c;然后等待一段超时时间(默认1…

GitHub Copilot 现已登陆 Visual Studio!

激动人心的好消息来了&#xff0c;GitHub 在3月29日发布博客&#xff0c;宣布 Github Copilot 现在可以在 Visual Studio 中使用。我们知道 Visual Studio 的 IntelliCode 本身已经很智能了, 现在又迎来了 Copilot, 编程体验将进入新的篇章。如何安装? 首先&#xff0c;您…

经典实用SQL语句大全汇总

目 录 1.随机取3条记录 2.随机选记录 3.删除重复记录 4.创建数据库 5.列出表里的所有的列名 6.选择从10到15的记录 7.压缩数据库

java之写接口回调编程经验改进

1、问题 在一个类里面数据的变化需要在另外一个类里面动态得到&#xff0c;比如在我的异步任务里面下载的数据&#xff0c;需要在UI界面的ProgressDialog里面动态显示&#xff0c;我们需要在异步任务里面写个接口&#xff0c;然后接口里面有一些函数&#xff0c;至于在Progres…

Mac Ubuntu ----端口被占用

Mac下使用lsof&#xff08;list open files&#xff09;来查看端口占用情况&#xff0c;lsof 是一个列出当前系统打开文件的工具。 使用 lsof 会列举所有占用的端口列表&#xff1a; 1$ lsof使用less可以用于分页展示&#xff0c;如&#xff1a; 1$ lsof | less也可以使用 -i 查…

iOS 9音频应用播放音频之音量设置与声道设置

iOS 9音频应用播放音频之音量设置与声道设置 iOS 9音频应用音量设置 音量又称响度、音强&#xff0c;是指人耳对所听到的声音大小强弱的主观感受&#xff0c;其客观评价尺度是声音的振幅大小。在iOS 9音频应用的应用中&#xff0c;经常会出现播放的音乐音量过大或者过小。此时i…

nginx配置文件中的location中文详解

location 语法:location [|~|~*|^~] /uri/ { … }默认:否 上下文:server 这个指令随URL不同而接受不同的结构。你可以配置使用常规字符串和正则表达式。如果使用正则表达式&#xff0c;你必须使用 ~* 前缀选择不区分大小写的匹配或者 ~ 选择区分大小写的匹配。 确定 哪个locati…

php fpm工作原理,什么是phpfpm的工作原理?

什么是phpfpm的工作原理&#xff1f;发布时间&#xff1a;2020-07-13 15:12:53来源&#xff1a;亿速云阅读&#xff1a;181作者&#xff1a;Leah什么是phpfpm的工作原理&#xff1f;针对这个问题&#xff0c;这篇文章详细介绍了相对应的分析和解答&#xff0c;希望可以帮助更多…

C#对象映射器之Mapster

简介Mapster是一个快&#xff0c;小巧&#xff0c;功能强大的对象映射.Net框架例子我有两个Model类且他们的属性一致&#xff0c;我们将 SourceObjectTest赋值给DestObjectTest该怎么做&#xff1f;SourceObjectTest sourceObject new SourceObjectTest(); sourceObject.Name …

如何关闭Struts2的webconsole.html

出于安全目的&#xff0c;在禁用了devMode之后&#xff0c;仍然不希望其他人员看到webconsole.html页面&#xff0c;则可以直接删除webconsole.html 的源文件&#xff0c; 它的位置存在于&#xff1a; 我们手工删除 struts2-core-*.jar\org\apache\struts2\interceptor\debuggi…

UIView 的基础

UIView•什么是控件&#xff1f;-屏幕上的所有UI元素都叫做控件&#xff0c;也有人叫做视图、组件-按钮&#xff08;UIButton&#xff09;、文本&#xff08;UILabel&#xff09;都是控件•控件的共同属性有哪些&#xff1f;-尺寸-位置-背景色-......-•苹果将控件的共同属性都…

记录平时编程或者阅读英文文档的时候不认识的英文单词

一、目的 英语虽然过了4级&#xff0c;但是还是很多英文单词不认识&#xff0c;为了以后能更好的阅读英文文档和函数的英文注释&#xff0c;记录自己不认识的英文单词&#xff0c;方便以后复习&#xff0c;这里后面会持续更新&#xff0c;因为放在网上这个平台不会弄丢。 二、…

php本地的调试安装,教你本地安装、运行、调试PHP程序

安装工具对学习PHP的新手来说&#xff0c;WINDOWS下环境配置是一件很困难的事&#xff1b;至少对于我来说本地调试PHP程序比登天还要困难&#xff0c;繁荣拖沓的各种程序。以前我博客程序用的是PJBLOG&#xff0c;本地的IIS就完全可以搞定&#xff0c;但是自从换了WordPress之后…

如何从一个 C# 的 dump 中挖到机器相关的信息?

前段时间有位朋友问我&#xff0c;如何从 dump 中提取出哪些和机器相关的信息&#xff1f;比如&#xff1a;机器内存大小&#xff0c;cpu核数&#xff0c;机器名&#xff0c;机器的环境变量 等等。那如何提取到里面的信息呢&#xff1f;当然我也没说全部可以提取的到。。。这里…

9.3磁盘及文件系统管理详解

练习&#xff1a;4 调整其预留百分比为3%#tune2fs -m 3 -L DATA /dev/sda75 以重新挂载此文件系统为不更新访问时间戳&#xff0c;并验证其效果#stat /backup/inittab# cat /backup/inittab# stat# mount -o remount,noatime /backup# cat # stat6 对此文件系统强行做一次检测e…

LeetCode() Word Search II

超时&#xff0c;用了tire也不行&#xff0c;需要再改。 class Solution {class TrieNode { public:// Initialize your data structure here.TrieNode() {for(int i0;i<26;i)next[i]NULL;isString false;}TrieNode *next[26];bool isString; };class Trie { public:Trie()…

java之通过FileChannel实现文件复制

1、FileChanel介绍 Java NIO FileChannel是连接文件的通道&#xff0c;从文件中读取数据和将数据写入文件。Java NIO FileChannel类是NIO用于替代使用标准Java IO API读取文件的方法。 FileInputStream的getChannel方法获取的文件通道是只读的&#xff0c;当然通过FileOutput…

Python版的Hello World

print “Hello World” 或者print("Hello World")