`
code727
  • 浏览: 66694 次
  • 性别: Icon_minigender_1
  • 来自: 成都
社区版块
存档分类
最新评论

JAVA自定义网络通信协议

 
阅读更多

 

       JAVA默认提供了对file,ftp,gopher,http,https,jar,mailto,netdoc协议的支持。当我们要利用这些协议来创建应用时,主要会涉及到如下几个类:

      1.java.net.URL:URL资源

      2.java.net.URLConnection:各种URL资源连接器

    例如,当我们利用HTTP协议获取Web资源时,通常的过程如下:

 

URL url = new URL("http://www.163.com");
URLConnection conneciotn = url.openConnection();

 

 

        URL和URLConnection是如何做到对协议支持的呢?在它们的内部,主要涉及到了如下几个类:

        1.URLStreamHandler:协议的流处理器,负责对特定URL协议的解析,并创建符合当前协议的URLConnection;

        2.URLStreamHandlerFactory:协议处理工厂,负责为特定协议找到正确的URLStreamHandler。

        当利用URL对象创建资源时,其构造函数在去掉协议字段后将其传给URLStreamHandlerFactory,由该工厂来接受协议,为该协议找到并创建适当的URLStreamHandler实现类,最后存储在URL对象的一个字段中(即URL类中transient修饰的URLStreamHandler成员属性)。
       URLStreamHandler和URLConnection总是成对出现的。因此,若要实现对新协议的支持时,需同时实现这两个抽象类,分别负责对协议的解析,以及与服务器的交互(数据转换等)。

      另外,JAVA是如何识别当前URL协议该由哪个URLStreamHandler和URLConnection来处理的呢?在创建URL对象时,其内部调用了一个getURLStreamHandler(String protocol)静态方法,它将根据协议的名称来找到对应的URLStreamHandler实现类,其查找规则如下:

     1)检测是否创建了URLStreamHandlerFactory对象:如果创建,则直接使用createURLStreamHandler(String protocol)方法创建的协议处理器,否则进入步骤2);

     2)在java.protocol.handler.pkgs系统属性指定的包中查找与协议同名的子包和名为Handler的类,即负责处理当前协议的URLStreamHandler实现类必须在<包名>.<协议名定义的包>中,并且类名称必须为Handler。例如:com.company.net.protocol.rdp包中的Handler类将用于处理RDP协议。若仍未找到则进入步骤3);

    3)在JDK rt.jar中的sun.net.www.protocol.<name>包中查找Handler类。例如,若当前协议为ftp,则URL所使用协议处理器就应该为sun.net.www.protocol.ftp包中的Handler类。如下图:
         下面结合一个实例来说明如何开发一个新的网络协议。

          背景:senv(Server Environment Protocol)协议可让客户端连接远程服务器后发送出请求,请求的内容就是URL查询参数部分,该协议的默认端口为9527。例如:senv://192.168.1.101:9527?pro=os.name,java.version表示获取192.168.1.101这台主机的操作系统名和JRE的版本信息,如果没有查询参数,客户端将默认发送一个"?",表示获取所有的系统属性信息。

      
 1.Senv协议处理器

package com.daniele.appdemo.net.protocol.custom.senv;

import java.io.IOException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;

/**
 * <p>
 * 		自定义的senv协议处理器。
 *      由于senv协议的格式符合标准的URL格式:
 *      	protocol://username@hostname:port/path/filename?query#fragment
 *      因此,这个实现类只需实现父类中的openConnection()方法即可。否则,需重写父类方法
 *      protected void parseURL(URL u, String spec, int start, int limit),
 *      来重新正确的设置URL的各个属性值,例如:host,port,query等。
 * </p> 
 * @author  <a href="mailto:code727@gmail.com">Daniele</a>
 * @version 1.0.0, 2013-5-8
 * @see     
 * @since   AppDemo1.0.0
 */
public class Handler extends URLStreamHandler {

	/**
	 * <p>当URL根据协议找到该处理器并调用openConnection()方法后,返回负责处理该协议连接的连接器</p> 
	 * @author <a href="mailto:code727@gmail.com">Daniele</a> 
	 * @param u
	 * @return
	 * @throws IOException 
	 * @since AppDemo1.0.0
	 */
	@Override
	protected URLConnection openConnection(URL u) throws IOException {
		return new SenvURLConnection(u);
	}
	
}

 2.Senv协议连接器

package com.daniele.appdemo.net.protocol.custom.senv;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.URL;
import java.net.URLConnection;

import com.daniele.appdemo.util.StringUtils;

/**
 * <p>自定义senv协议连接器</p> 
 * @author  <a href="mailto:code727@gmail.com">Daniele</a>
 * @version 1.0.0, 2013-5-8
 * @see     
 * @since   AppDemo1.0.0
 */
public class SenvURLConnection extends URLConnection {
	
	/** senv协议的默认端口号 */
	public static final int DEFAULT_PORT = 9527;
	
	private Socket connection = null;

	public SenvURLConnection(URL url) {
		super(url);
	}
	
	/**
	 * <p>由于父类URLConnection中的getInputStream()方法不提供输入流的获取实现逻辑,因此这里需要重写此方法</p> 
	 * @author <a href="mailto:code727@gmail.com">Daniele</a> 
	 * @return
	 * @throws IOException 
	 * @since AppDemo1.0.0 
	 */
	@Override
	public synchronized InputStream getInputStream() throws IOException {
		if (!connected)
			this.connect();
		return connection.getInputStream();
	}
	
	/**
	 * <p>senv协议连接操作</p> 
	 * @author <a href="mailto:code727@gmail.com">Daniele</a> 
	 * @throws IOException 
	 * @since AppDemo1.0.0 
	 */
	@Override
	public synchronized void connect() throws IOException {
		if (!connected) {
			int port = url.getPort();
			if (port < 1 || port > 65535)
				port = DEFAULT_PORT;
			this.connection = new Socket(url.getHost(), port);
			connected = true;
			// 连接后立即发送请求
			sendRequest(url);
		}
	}
	
	/**
	 * <p>发送senv协议请求</p> 
	 * @author <a href="mailto:code727@gmail.com">Daniele</a> 
	 * @param u
	 * @throws IOException 
	 * @since AppDemo1.0.0
	 */
	protected void sendRequest(URL u) throws IOException {
		OutputStream outputStream = this.connection.getOutputStream();
		
		String queryString = u.getQuery();
		
		/*
		 *  将URL的查询参数部分发送给服务器,由服务器负责解析查询后返回结果。
		 *  当参数参数部分为空时,则发送一个"?",表示查询服务器系统环境的所有信息。
		 */
		outputStream.write(StringUtils.isNotNullOrBlank(queryString)? queryString.getBytes() : "?".getBytes());
		outputStream.flush();
	}

}

 

3.协议处理器工厂

package com.daniele.appdemo.net.protocol.factory;

import java.net.URLStreamHandler;
import java.net.URLStreamHandlerFactory;

/**
 * <p>
 *       自定义协议的处理器工厂,负责针对每种自定义的协议而返回它们各自对应的协议处理器
 *       如果要用上述的查找规则1来安装协议处理器时,则需要用到这个类
 *</p> 
 * @author  <a href="mailto:code727@gmail.com">Daniele</a>
 * @version 1.0.0, 2013-5-9
 * @see     
 * @since   AppDemo1.0.0
 */
public class CustomProtocolFactory implements URLStreamHandlerFactory {

	public URLStreamHandler createURLStreamHandler(String protocol) {
		if ("senv".equalsIgnoreCase(protocol))
			return new com.daniele.appdemo.net.protocol.custom.senv.Handler();
		return null;
	}

}

 

4.处理Senv协议的服务器

package com.daniele.appdemo.net.protocol.test;

import java.io.IOException;
import java.net.InetAddress;
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.nio.charset.CharsetDecoder;
import java.util.Iterator;

import org.apache.log4j.Logger;

import com.daniele.appdemo.util.StringUtils;
import com.daniele.appdemo.util.SystemUtils;

/**
 * <p>处理Senv协议的服务器
 *    1.接收客户端请求
 *	  2.发送响应结果
 *  </p> 
 * @author  <a href="mailto:code727@gmail.com">Daniele</a>
 * @version 1.0.0, 2013-5-10
 * @see     
 * @since   AppDemo1.0.0
 */

public class SenvProtocolServer {
	
	private static final Logger logger = Logger.getLogger(SenvProtocolServer.class);
	
	/** Senv协议的请求参数标识 */
	public static final String REQUEST_PARAM_MARK = "pro=";
	
	/** Senv协议服务的默认端口号 */
	private static final int DEFAULT_PORT = 9527;

	/** 服务器的IP或主机名 */
	private String host;
	
	/** 绑定了Senv协议服务的端口号 */
	private int port = 9527;
	
	/** 当前就绪的服务端通道 */
	private ServerSocketChannel serverChannel;
	
	/** 当前就绪的客户端通道 */
	private SocketChannel clientChannel;
	
	/** 服务端的事件注册器 */
	private Selector selector;
	
	/**
	 * <p>启动Senv协议服务器</p> 
	 * @author <a href="mailto:code727@gmail.com">Daniele</a> 
	 * @throws IOException 
	 * @since AppDemo1.0.0
	 */
	public void start() throws IOException {
		
		serverChannel = ServerSocketChannel.open();
		
		if (port < 1 || port > 65535)
			port = DEFAULT_PORT;
		
		if (StringUtils.isNotNullOrBlank(host)) {
			serverChannel.socket().bind(new InetSocketAddress(InetAddress.getByName(host), port));
			logger.info("Start server " + host + ":" + port);
		} else {
			serverChannel.socket().bind(new InetSocketAddress(port));
			logger.info("Start server on port " + port);
		}
		serverChannel.configureBlocking(false);
		selector = Selector.open();
        serverChannel.register(selector, SelectionKey.OP_ACCEPT); 
        handle();
	}
	
	/**
	 * <p>处理Senv协议请求</p> 
	 * @author <a href="mailto:code727@gmail.com">Daniele</a> 
	 * @throws IOException 
	 * @since AppDemo1.0.0
	 */
	protected void handle() throws IOException {
		while (true) {
			selector.select();
			Iterator<SelectionKey> keySetIterator = selector.selectedKeys().iterator();
			SelectionKey cuurentKey = null;
			while (keySetIterator.hasNext()) {
				// 获取当前就绪通道的键对象
				cuurentKey = keySetIterator.next();
				// 避免同一个就绪通道被重复处理
				keySetIterator.remove();
				try {
					if (cuurentKey.isAcceptable()) {
						serverChannel = (ServerSocketChannel) cuurentKey.channel();
						clientChannel = serverChannel.accept();
						if (clientChannel != null) {
							logger.info("Receive request from " 
									+ clientChannel.socket().getInetAddress().getHostAddress() + ":"
									+ clientChannel.socket().getLocalPort());
							clientChannel.configureBlocking(false);
							clientChannel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
						} 
					} else {
						clientChannel = (SocketChannel) cuurentKey.channel();
						if (cuurentKey.isReadable()) 
							writeResponse();
					}
				} catch (IOException e) {
					if (clientChannel != null && clientChannel.isOpen())
						try {
							/*
							 *  为防止服务端在读写客户端信息时,客户端由于某种原因被意外关闭引起服务端也被强制关闭的情况发生。
							 *  需在catch块中也需要对客户端的通道做关闭处理, 从而防止服务端也被强制关闭的严重问题。
							 *  另外,对就绪通道的读写过程需单独的在一个try...catch块中。
							 */
							clientChannel.close();
						} catch (IOException ioe) {
							ioe.printStackTrace();
						}
				} 
			}
		}
	}
	
	/**
	 * <p>读取客户端请求</p> 
	 * @author <a href="mailto:code727@gmail.com">Daniele</a> 
	 * @return 
	 * @throws IOException 
	 * @throws  
	 * @since AppDemo1.0.0
	 */
	protected String readRequest() throws IOException {
		StringBuffer request = new StringBuffer();
		CharsetDecoder decoder = Charset.forName("UTF-8").newDecoder();
		ByteBuffer buffer = ByteBuffer.allocate(1024);
		while (clientChannel.read(buffer) > 0) {
			buffer.flip();
			request.append(decoder.decode(buffer).toString());
			buffer.clear();
		}
		return request.toString();
	}
	
	/**
	 * <p>向客户端返回响应结果</p> 
	 * @author <a href="mailto:code727@gmail.com">Daniele</a> 
	 * @throws IOException 
	 * @since AppDemo1.0.0
	 */
	protected void writeResponse() throws IOException {
		String request = readRequest();
		int start = -1;
		// 如果发送的请求为"?"或请求中无指定的参数时,则查询所有的系统环境属性
		if ("?".equals(request) || 
				(start = request.toLowerCase().indexOf(REQUEST_PARAM_MARK)) < 0) {
			clientChannel.write(ByteBuffer.wrap(SystemUtils.formatSystemProperties().getBytes()));
		} else {
			// 获取请求参数值
			String queryValueString = request.substring(start + REQUEST_PARAM_MARK.length());
			if (StringUtils.isNullOrBlank(queryValueString))
				clientChannel.write(ByteBuffer.wrap(SystemUtils.formatSystemProperties().getBytes()));
			else {
				int index = queryValueString.indexOf("&");
				if (index > -1)
					/*
					 *  如果请求参数值里出现了"&"字符,
					 *  则说明这个字符后面的内容则认为是其它一些请求参数的内容,
					 *  因此不对这部分内容作处理
					 */
					queryValueString = queryValueString.substring(0, index);
				clientChannel.write(ByteBuffer.wrap(SystemUtils.formatSystemProperties(queryValueString.split(",")).getBytes()));
			}
		}
		/*
		 *  响应内容被发送出去之后添加换行标识,
		 *  目的是让客户端的BufferedReader对象调用readLine()方法后能将当前行的内容读取出来
		 */
		clientChannel.write(ByteBuffer.wrap("\n".getBytes()));
		
		/*
		 *  发送完响应信息后马上关闭与客户端之间的通道。
		 *  目的在于让客户端读取完这些响应之后,就立即释放掉资源,从而让读操作不会一直处于阻塞状态
		 */
		clientChannel.close();
	}
	public String getHost() {
		return host;
	}

	public void setHost(String host) {
		this.host = host;
	}

	public int getPort() {
		return port;
	}

	public void setPort(int port) {
		this.port = port;
	}

	public static void main(String[] args) {
		SenvProtocolServer server = new SenvProtocolServer();
		server.setHost("192.168.1.101");
		try {
			server.start();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

}

 

 4.Senv协议请求客户端

/**
 * <p>
 * 	  Senv协议请求的客户端,主要功能分为:
 *    1.在创建第一次创建URL对象之前,添加对自定义协议的支持
 *    2.发送请求
 *    3.展示响应数据
 * </p> 
 * @author  <a href="mailto:code727@gmail.com">Daniele</a>
 * @version 1.0.0, 2013-5-9
 * @see     
 * @since   AppDemo1.0.0
 */

public class SenvProtocolClient {
	
	public static void main(String[] args) {
		BufferedReader reader = null;
		try {
			// 配置协议处理器查找规则一
			if (StringUtils.isNullOrBlank(System.getProperty("java.protocol.handler.pkgs"))) {
				// 设置各个协议包所在的父包路径
				System.setProperty("java.protocol.handler.pkgs", "com.daniele.appdemo.net.protocol.custom");
			}
			/*
                         * 配置协议处理器查找规则二
                         * 这种方式在整个应用范围之内只能被执行一次。
                         * 如果多于一次则会出现"java.lang.Error: factory already defined"这样的错误。但不会受规则一的限制.
                         */ 
//			URL.setURLStreamHandlerFactory(new CustomProtocolFactory());
			
			URL url = new URL("senv://192.168.1.101:9527/");
			reader = new BufferedReader(new InputStreamReader(url.openConnection().getInputStream()));
			String result = "";
			while ((result = reader.readLine()) != null)
				System.out.println(result);
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				if (reader != null)
					reader.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

}

 

package com.daniele.appdemo.util;

import java.util.Enumeration;
import java.util.Properties;

/**
 * <p>运行环境工具类</p> 
 * @author  <a href="mailto:code727@gmail.com">Daniele</a>
 * @version 1.0.0, 2013-5-9
 * @see     
 * @since   AppDemo1.0.0
 */

public class SystemUtils {
	
	private static Properties properties = null;
	
	static {
		properties = System.getProperties();
	}
	
	/**
	 * <p>返回格式化后的所有系统属性信息</p> 
	 * @author <a href="mailto:code727@gmail.com">Daniele</a> 
	 * @return 
	 * @since AppDemo1.0.0
	 */
	@SuppressWarnings("unchecked")
	public static String formatSystemProperties() {
		StringBuffer formatResult = new StringBuffer();
		Enumeration<String> names = (Enumeration<String>) properties.propertyNames();
		while (names.hasMoreElements()) {
			String name = names.nextElement();
			formatResult.append(name).append("=")
				.append(properties.getProperty(name)).append("\n");
		}
		int length = 0;
		return (length = formatResult.length()) > 0 ? 
				formatResult.substring(0, length - 1) : "";
	}
	
	/**
	 * <p>返回格式化后的所有指定的系统属性信息</p> 
	 * @author <a href="mailto:code727@gmail.com">Daniele</a> 
	 * @param propertyKeys
	 * @return 
	 * @since AppDemo1.0.0
	 */
	public static String formatSystemProperties(String[] propertyKeys) {
		StringBuffer formatResult = new StringBuffer();
		if (propertyKeys != null && propertyKeys.length > 0) {
			for (String key : propertyKeys) 
				formatResult.append(key).append("=")
					.append(properties.getProperty(key)).append("\n");
		}
		int length = 0;
		return (length = formatResult.length()) > 0 ? 
				formatResult.substring(0, length - 1) : "";
	}

}
package com.daniele.appdemo.util;

public class StringUtils {
	
	public static boolean isNullOrBlank(String str) {
		return str == null || str.length() == 0;
	}
	
	public static boolean isNotNullOrBlank(String str) {
		return !isNullOrBlank(str);
	}

}

运行时,依次启动SenvProtocolServer和SenvProtocolClient类即可。

 

  • 大小: 7.8 KB
分享到:
评论

相关推荐

    java socket通信自定义消息协议

    总的来说,Java Socket通信自定义消息协议涉及网络编程基础、数据结构设计以及异常处理等多方面知识。通过这个过程,我们可以灵活地构建满足特定需求的通信系统,实现客户端和服务器间的高效交互。

    Mina自定义协议通信的示例

    在IT行业中,网络通信是核心领域之一,而Mina(Mindterm In Action Network Application)是一个高性能、跨平台的Java框架,常用于构建网络应用程序,如服务器和客户端。本示例聚焦于Mina中的自定义协议通信,这允许...

    网络通信基础第五式——实现自定义字节流协议的KTM

    在IT行业中,网络通信是计算机系统之间交换信息的基础。这篇名为“网络通信基础第五式——实现自定义字节流协议的KTM”的...通过阅读博客文章和相关源码,我们可以学习到如何为特定应用设计高效、可靠的网络通信协议。

    Java URL自定义私有网络协议

    Java URL自定义私有网络协议允许开发者扩展Java的标准网络功能,创建自己的通信协议,这在某些特定场景下非常有用,例如在需要与非标准服务端交互或者实现特定数据传输格式时。通过理解和利用Java的URL处理机制,...

    java自定义通讯协议.doc

    ### Java自定义通讯协议知识点详解 #### 一、概述 在现代软件开发中,尤其是在分布式系统和网络应用中,自定义通讯协议的设计是一项至关重要的任务。通过定义一套规范化的通讯规则,可以确保不同组件之间高效、可靠...

    java实现485自由协议通讯

    自由协议则是指在没有预定义的通信协议下,开发者根据实际需求自行设计的数据交换格式。 在"java实现485自由协议通讯"的项目中,开发者已经创建了一个Java程序,可以实现通过RS-485接口进行自定义协议的数据传输。...

    自定义Sample或者协议完成测试

    自定义Sample通常指的是开发者根据项目需求创建的示例代码或测试用例,而自定义协议则可能指的是网络通信中使用的非标准数据交换格式。在这个过程中,我们需要理解以下几个关键知识点: 1. **自定义Sample**:在...

    软件工程java 基于通信协议

    在软件工程领域,Java 和 C++ 语言常常被用于构建复杂的系统,特别是在...通过这个实验,学生将深入理解网络通信的核心原理,并掌握在Java和C++中实现通信协议的技能,这对他们的软件工程职业生涯来说是极其宝贵的。

    呕心沥血的java复杂聊天室(包括自定义应用层协议、CS多线程、多客户端登录、上下线提醒等等)

    在网络通信中,应用层协议是位于TCP/IP模型最顶层的协议,它定义了应用程序如何通过网络交换数据。在Java聊天室项目中,自定义应用层协议可能涉及到消息格式的定义,如消息头、消息体、消息类型编码等。设计一个...

    Mavlink通信协议Java版本

    **Mavlink通信协议Java版本**是针对无人机爱好者和开发者的一款重要工具,它基于Mavlink协议,并已转化为适用于Java编程环境的实现。Mavlink协议是无人机领域广泛使用的轻量级、高效的数据通信协议,它能确保飞行器...

    Java与网络通信程序设计

    - URL通信通过HTTP等协议直接访问资源,而Socket通信底层直接使用TCP协议,可以自定义通信协议。 - URL通信通常适用于简单的请求-响应模型,Socket更适合需要复杂交互和数据传输的场景。 2. 使用Socket通信过程:...

    java实现CMPP协议

    CMPP协议主要用于短信中心(SMSC)与短信网关之间的通信,支持大量并发的短信发送和接收操作,是移动运营商内部的重要通信协议。本文将详细介绍Java实现CMPP协议的相关知识点。 首先,CMPP协议主要由一系列的命令...

    JAVA网络通信系统的研究与开发源代码

    该系统基于客户端和服务端之间的互联,提供了一系列的客户端和服务端间的通信方式,包括TCP协议,UDP协议,并通过自定义通信协议,实现客户端和服务端之间的数据传输和处理。该系统可用于多种领域,包括游戏、即时...

    Java自定义协议进行socket连接的简单认证和消息.doc

    在Java编程中,自定义协议进行Socket连接的简单认证和消息传输是一种常见的网络通信方法,特别是在需要特定格式或逻辑的通信场景下。本文件描述了一个简单的三步认证过程,通过Socket连接实现客户端与服务器端之间的...

    Netty4.0学习笔记系列之五:自定义通讯协议

    在本篇“Netty4.0学习笔记系列之五:自定义通讯协议”中,我们将深入探讨如何在Netty框架下构建和实现自己的通信协议。Netty是一个高性能、异步事件驱动的网络应用框架,广泛应用于Java领域的服务器开发,如网络游戏...

    Java网络通信之HttpClient

    本篇将详细探讨`Java网络通信之HttpClient`的相关知识点,以及如何通过`HttpClientTest`进行实践操作。 首先,让我们了解什么是Apache HttpClient。HttpClient是一个开放源代码的Java库,它允许开发人员执行HTTP和...

    netty自定义数据包协议

    Netty 是一个高性能、异步...总结来说,通过在Netty中自定义解码器和编码器,我们可以有效地处理网络通信中的拆包和粘包问题,确保数据的正确传输和解析。同时,理解并掌握这个过程对于开发高性能的网络应用至关重要。

    java CSMA/CD 协议模拟

    你还需要实现一个机制来模拟数据在介质上传输的过程,这可能涉及使用`Socket`或自定义的通信协议。 在实际项目中,可能还需要考虑其他因素,如网络拓扑、数据包错误检测与纠正、服务质量(QoS)等。通过模拟,可以...

    Java2网络协议技术内幕(附源码)

    该书内容涵盖广泛,从基础的TCP/IP协议栈到复杂的HTTP、FTP、SMTP等应用层协议,全面解析了Java在网络通信中的实现细节。 首先,Java的Socket编程是网络协议的基础,书中会详细介绍如何使用Java Socket API进行TCP...

    基于ssl的Java的socket网络通信

    在IT行业中,网络通信是应用程序之间交互的基础,而Java作为一种广泛应用的编程语言,提供了丰富的库和API来实现网络通信。本篇文章将详细讲解基于SSL(Secure Socket Layer)的Java Socket网络通信,帮助开发者理解...

Global site tag (gtag.js) - Google Analytics