博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
J2SE Socket SSL/TLS C/S模式编程
阅读量:5866 次
发布时间:2019-06-19

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

hot3.png

参考:

####一. SSLSocket是什么?

  1. JDK文档指出,SSLSocket扩展Socket并提供使用SSL或TLS协议的安全套接字。
  2. 这种套接字是正常的流套接字,但是它们在基础网络传输协议(如TCP)上添加了安全保护层。
  3. 具体安全方面的讨论见下一篇。本篇重点关注SSLSocket及相关几个类的使用。

####二.与SSLSocket相关的工具类

SSLSocket来自jsse(Java Secure Socket Extension) 与SSLSocket相关的工具类

  1. SSLContext: 此类的实例表示安全套接字协议的实现, 它是SSLSocketFactory、SSLServerSocketFactory和SSLEngine的工厂。
  2. SSLSocket: 扩展自Socket
  3. SSLServerSocket: 扩展自ServerSocket
  4. SSLSocketFactory: 抽象类,扩展自SocketFactory, SSLSocket的工厂
  5. SSLServerSocketFactory: 抽象类,扩展自ServerSocketFactory, SSLServerSocket的工厂
  6. KeyStore: 表示密钥和证书的存储设施
  7. KeyManager: 接口,JSSE密钥管理器
  8. TrustManager: 接口,信任管理器(?翻译得很拗口)
  9. X590TrustedManager: TrustManager的子接口,管理X509证书,验证远程安全套接字

####三.SSLContext的使用

public static void main(String[] args) throws Exception {      X509TrustManager x509m = new X509TrustManager() {          @Override          public X509Certificate[] getAcceptedIssuers() {              return null;          }          @Override          public void checkServerTrusted(X509Certificate[] chain,                  String authType) throws CertificateException {          }          @Override          public void checkClientTrusted(X509Certificate[] chain,                  String authType) throws CertificateException {          }      };      // 获取一个SSLContext实例      SSLContext s = SSLContext.getInstance("SSL");      // 初始化SSLContext实例      s.init(null, new TrustManager[] { x509m },              new java.security.SecureRandom());      // 打印这个SSLContext实例使用的协议      System.out.println("缺省安全套接字使用的协议: " + s.getProtocol());      // 获取SSLContext实例相关的SSLEngine      SSLEngine e = s.createSSLEngine();      System.out              .println("支持的协议: " + Arrays.asList(e.getSupportedProtocols()));      System.out.println("启用的协议: " + Arrays.asList(e.getEnabledProtocols()));      System.out.println("支持的加密套件: "              + Arrays.asList(e.getSupportedCipherSuites()));      System.out.println("启用的加密套件: "              + Arrays.asList(e.getEnabledCipherSuites()));  }

运行结果如下: 运行结果

SSLContext.getProtocol(): 返回当前SSLContext对象的协议名称SSLContext.init():  初始化当前SSLContext对象。 三个参数均可以为null。 详见JDK文档。SSLEngine.getSupportedProtocols()等几个方法可以返回些 Engine上支持/已启用的协议、支持/已启用的加密套件

####四. SSLSocket和SSLServerSocket的使用

这两个类的用法跟Socket/ServerSocket的用法比较类似。看下面的例子(主要为了验证SSLSocket的用法 ,I/O和多线程处理比较随意)

4.1 SSLServerSocket服务器创建 (1)新建一个SSLServerSocket,并开始监听来自客户端的连接

// 抛出异常  // javax.net.ssl.SSLException: No available certificate or key corresponds  // to the SSL cipher suites which are enabled.  public static void notOk() throws IOException {      SSLServerSocketFactory factory = (SSLServerSocketFactory) SSLServerSocketFactory              .getDefault();      SSLServerSocket server = (SSLServerSocket) factory              .createServerSocket(10000);      System.out.println("ok");      server.accept();  }

server.accept()处抛出异常, 提示缺少证书。与ServerSocket不同, SSLServerSocket需要证书来进行安全验证。

4.2使用keytool工具生成一个证书 步骤如下, 得到一个名为cmkey的证书文件

输入图片说明

重新完善上面的代码。 主要增加两个功能: 使用名为cmkey的证书初始化SSLContext, echo客户端的消息。 代码如下

// 启动一个ssl server socket  // 配置了证书, 所以不会抛出异常  public static void sslSocketServer() throws Exception {      // key store相关信息      String keyName = "cmkey";      char[] keyStorePwd = "123456".toCharArray();      char[] keyPwd = "123456".toCharArray();      KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());      // 装载当前目录下的key store. 可用jdk中的keytool工具生成keystore      InputStream in = null;      keyStore.load(in = Test2.class.getClassLoader().getResourceAsStream(              keyName), keyPwd);      in.close();      // 初始化key manager factory      KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory              .getDefaultAlgorithm());      kmf.init(keyStore, keyPwd);      // 初始化ssl context      SSLContext context = SSLContext.getInstance("SSL");      context.init(kmf.getKeyManagers(),              new TrustManager[] { new MyX509TrustManager() },              new SecureRandom());      // 监听和接收客户端连接      SSLServerSocketFactory factory = context.getServerSocketFactory();      SSLServerSocket server = (SSLServerSocket) factory              .createServerSocket(10002);      System.out.println("ok");      Socket client = server.accept();      System.out.println(client.getRemoteSocketAddress());      // 向客户端发送接收到的字节序列      OutputStream output = client.getOutputStream();      // 当一个普通 socket 连接上来, 这里会抛出异常      // Exception in thread "main" javax.net.ssl.SSLException: Unrecognized      // SSL message, plaintext connection?      InputStream input = client.getInputStream();      byte[] buf = new byte[1024];      int len = input.read(buf);      System.out.println("received: " + new String(buf, 0, len));      output.write(buf, 0, len);      output.flush();      output.close();      input.close();      // 关闭socket连接      client.close();      server.close();  }

这样就可以正常启动SSLServerSocket服务器

4.2 SSLSocket链接服务器 (1)我们先使用一个普通的Socket尝试连接服务器端

// 通过socket连接服务器  public static void socket() throws UnknownHostException, IOException {      Socket s = new Socket("localhost", 10002);      System.out.println(s);      System.out.println("ok");      OutputStream output = s.getOutputStream();      InputStream input = s.getInputStream();      output.write("alert".getBytes());      System.out.println("sent: alert");      output.flush();      byte[] buf = new byte[1024];      int len = input.read(buf);      System.out.println("received:" + new String(buf, 0, len));  }

结果客户端和服务器端都出错。 客户端的错误是接收到乱码。

输入图片说明

服务器则抛出异常 javax.net.ssl.SSLException: Unrecognized SSL message, plaintext connection?

4.3改成SSLSocket, 但是不使用证书。 客户端抛出sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

// 不使用证书, 通过ssl socket连接服务器  // 抛出异常, 提示找不到证书  public static void sslSocket() throws UnknownHostException, IOException {      SSLSocketFactory factory = (SSLSocketFactory) SSLSocketFactory              .getDefault();      SSLSocket s = (SSLSocket) factory.createSocket("localhost", 10002);      System.out.println("ok");      OutputStream output = s.getOutputStream();      InputStream input = s.getInputStream();      output.write("alert".getBytes());      System.out.println("sent: alert");      output.flush();      byte[] buf = new byte[1024];      int len = input.read(buf);      System.out.println("received:" + new String(buf, 0, len));  }

程序客户在不持有证书的情况下直接进行连接,服务器端会产生运行时异常javax.net.ssl.SSLHandshakeException: Received fatal alert: certificate_unknown,不允许进行连接。 我们可以指定像下面这样执行客户端,服务器端可以成功echo客户端的发出的字符串"alert"

java -Djavax.net.ssl.trustStore=cmkey Client

这里的cmkey即前面生成的证书文件。

4.4 使用SSLContext对SSLSocket初始化(仍旧不使用证书)

public static void sslSocket2() throws Exception {      SSLContext context = SSLContext.getInstance("SSL");                 // 初始化      context.init(null,              new TrustManager[] { new Test2.MyX509TrustManager() },              new SecureRandom());      SSLSocketFactory factory = context.getSocketFactory();      SSLSocket s = (SSLSocket) factory.createSocket("localhost", 10002);      System.out.println("ok");      OutputStream output = s.getOutputStream();      InputStream input = s.getInputStream();      output.write("alert".getBytes());      System.out.println("sent: alert");      output.flush();      byte[] buf = new byte[1024];      int len = input.read(buf);      System.out.println("received:" + new String(buf, 0, len));  }

服务器端可以成功echo客户端的发出的字符串"alert"。 完整代码见附件。

####五.SSLSocket加载证书(非原文部分) 对于想要在实现SSLSocket加载证书的SSLContext,可以看看这里

/**      * 获得SSLSocketFactory.      * @param password      *            密码      * @param keyStorePath      *            密钥库路径      * @param trustStorePath      *            信任库路径      * @return SSLSocketFactory      * @throws Exception      */      public static SSLContext getSSLContext(String password,              String keyStorePath, String trustStorePath) throws Exception {          // 实例化密钥库          KeyManagerFactory keyManagerFactory = KeyManagerFactory                  .getInstance(KeyManagerFactory.getDefaultAlgorithm());          // 获得密钥库          KeyStore keyStore = getKeyStore(password, keyStorePath);          // 初始化密钥工厂          keyManagerFactory.init(keyStore, password.toCharArray());          // 实例化信任库          TrustManagerFactory trustManagerFactory = TrustManagerFactory                  .getInstance(TrustManagerFactory.getDefaultAlgorithm());          // 获得信任库          KeyStore trustStore = getKeyStore(password, trustStorePath);          // 初始化信任库          trustManagerFactory.init(trustStore);          // 实例化SSL上下文          SSLContext ctx = SSLContext.getInstance("TLS");          // 初始化SSL上下文          ctx.init(keyManagerFactory.getKeyManagers(),                  trustManagerFactory.getTrustManagers(), null);          // 获得SSLSocketFactory          return ctx;      }

###这里我们使用一个常见的SSLTrustManager来构建我们的Socket

public class SSLTrustManager implements javax.net.ssl.TrustManager,            javax.net.ssl.X509TrustManager ,HostnameVerifier{        public java.security.cert.X509Certificate[] getAcceptedIssuers() {            return new X509Certificate[]{};        }        public boolean isServerTrusted(                java.security.cert.X509Certificate[] certs) {            return true;        }        public boolean isClientTrusted(                java.security.cert.X509Certificate[] certs) {            return true;        }        public void checkServerTrusted(                java.security.cert.X509Certificate[] certs, String authType)                throws java.security.cert.CertificateException {            return;        }        public void checkClientTrusted(                java.security.cert.X509Certificate[] certs, String authType)                throws java.security.cert.CertificateException {            return;        }            @Override        public boolean verify(String urlHostName, SSLSession session) { //允许所有主机            return true;        }   //封装   public static HttpURLConnection connectTrustAllServer(String strUrl) throws Exception {        return connectTrustAllServer(strUrl,null);    }   //封装   public static HttpURLConnection connectTrustAllServer(String strUrl,Proxy proxy) throws Exception {         javax.net.ssl.TrustManager[] trustAllCerts = new javax.net.ssl.TrustManager[1];         javax.net.ssl.TrustManager tm = new SSLTrustManager();         trustAllCerts[0] = tm;         javax.net.ssl.SSLContext sc = javax.net.ssl.SSLContext                 .getInstance("TLS");         sc.init(null, trustAllCerts, null);         javax.net.ssl.HttpsURLConnection.setDefaultSSLSocketFactory(sc                 .getSocketFactory());         HttpsURLConnection.setDefaultHostnameVerifier((HostnameVerifier) tm);        URL url = new URL(strUrl);        HttpURLConnection urlConn = null;        if(proxy==null)        {        	 urlConn = (HttpURLConnection) url.openConnection();        }else{        	 urlConn = (HttpURLConnection) url.openConnection(proxy);        }        urlConn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36");        return urlConn;    }   //用于双向认证   public static HttpURLConnection connectProxyTrustCA(String strUrl,Proxy proxy) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, FileNotFoundException, IOException, UnrecoverableKeyException, KeyManagementException   {	   HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {			@Override			public boolean verify(String s, SSLSession sslsession) {				return true;			}		});	   String clientKeyStoreFile = "D:/JDK8Home/twt/sslClientKeys";         String clientKeyStorePwd = "123456";         String catServerKeyPwd = "123456";         String serverTrustKeyStoreFile = "D:/JDK8Home/twt/sslClientTrust";         String serverTrustKeyStorePwd = "123456";         KeyStore serverKeyStore = KeyStore.getInstance("JKS");         serverKeyStore.load(new FileInputStream(clientKeyStoreFile), clientKeyStorePwd.toCharArray());         KeyStore serverTrustKeyStore = KeyStore.getInstance("JKS");         serverTrustKeyStore.load(new FileInputStream(serverTrustKeyStoreFile), serverTrustKeyStorePwd.toCharArray());         KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());         kmf.init(serverKeyStore, catServerKeyPwd.toCharArray());         TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());         tmf.init(serverTrustKeyStore);         SSLContext sslContext = SSLContext.getInstance("TLS");         sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom());         HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());	   URL url = new URL(strUrl);	   HttpURLConnection httpURLConnection = null;	   if(proxy==null)	   {		   httpURLConnection = (HttpURLConnection) url.openConnection();	   }else{		   httpURLConnection = (HttpURLConnection) url.openConnection(proxy);	   }	   httpURLConnection.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36");	   return httpURLConnection;   }   /**    * 用于单向认证    * server侧只需要自己的keystore文件,不需要truststore文件	* client侧不需要自己的keystore文件,只需要truststore文件(其中包含server的公钥)。    * 此外server侧需要在创建SSLServerSocket之后设定不需要客户端证书:setNeedClientAuth(false)    * @param strUrl    * @param proxy    * @return    * @throws KeyStoreException    * @throws NoSuchAlgorithmException    * @throws CertificateException    * @throws FileNotFoundException    * @throws IOException    * @throws UnrecoverableKeyException    * @throws KeyManagementException    */   public static HttpURLConnection connectProxyTrustCA2(String strUrl,Proxy proxy) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, FileNotFoundException, IOException, UnrecoverableKeyException, KeyManagementException   {	   HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {			@Override			public boolean verify(String s, SSLSession sslsession) {				return true;			}		});       String serverTrustKeyStoreFile = "D:/JDK8Home/twt/sslClientTrust";         String serverTrustKeyStorePwd = "123456";         KeyStore serverTrustKeyStore = KeyStore.getInstance("JKS");         serverTrustKeyStore.load(new FileInputStream(serverTrustKeyStoreFile), serverTrustKeyStorePwd.toCharArray());         TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());         tmf.init(serverTrustKeyStore);         SSLContext sslContext = SSLContext.getInstance("TLS");         sslContext.init(null, tmf.getTrustManagers(), null);         HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());	   URL url = new URL(strUrl);	   HttpURLConnection httpURLConnection = null;	   if(proxy==null)	   {		   httpURLConnection = (HttpURLConnection) url.openConnection();	   }else{		   httpURLConnection = (HttpURLConnection) url.openConnection(proxy);	   }	   httpURLConnection.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36");	   return httpURLConnection;   }   //用于双向认证   public static SSLSocket createTlsConnect(Socket socketClient) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, FileNotFoundException, IOException, UnrecoverableKeyException, KeyManagementException	{		    String protocol = "TLS";  	        String serverKey = "D:/JDK8Home/twt/sslServerKeys";  	        String serverTrust = "D:/JDK8Home/twt/sslServerTrust";  	        String serverKeyPwd = "123456";  //私钥密码	        String serverTrustPwd = "123456";  //信任证书密码	        String serverKeyStorePwd = "123456";  // keystore存储密码	        KeyStore keyStore = KeyStore.getInstance("JKS");    	        keyStore.load(new FileInputStream(serverKey),serverKeyPwd.toCharArray());  	        KeyStore tks = KeyStore.getInstance("JKS");	        tks.load(new FileInputStream(serverTrust), serverTrustPwd.toCharArray());	        KeyManagerFactory km = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());   	        km.init(keyStore, serverKeyStorePwd.toCharArray());	        TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());  	        tmf.init(tks);	        SSLContext sslContext = SSLContext.getInstance(protocol); 	        sslContext.init(km.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom());  //第一项是用来做服务器验证的	        SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();	        SSLSocket clientSSLSocket = (SSLSocket) sslSocketFactory.createSocket(socketClient,socketClient.getInetAddress().getHostAddress(),socketClient.getPort(), true);	        clientSSLSocket.setNeedClientAuth(false);	        clientSSLSocket.setUseClientMode(false);	        return clientSSLSocket;	}   /**    * 用于单向认证    * server侧只需要自己的keystore文件,不需要truststore文件	* client侧不需要自己的keystore文件,只需要truststore文件(其中包含server的公钥)。    * 此外server侧需要在创建SSLServerSocket之后设定不需要客户端证书:setNeedClientAuth(false)    * @param socketClient    * @return    * @throws KeyStoreException    * @throws NoSuchAlgorithmException    * @throws CertificateException    * @throws FileNotFoundException    * @throws IOException    * @throws UnrecoverableKeyException    * @throws KeyManagementException    */   public static SSLSocket createTlsConnect2(Socket socketClient) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, FileNotFoundException, IOException, UnrecoverableKeyException, KeyManagementException  	{  		    String protocol = "TLS";    	        String serverKey = "D:/JDK8Home/twt/sslServerKeys";    	        String serverKeyPwd = "123456";  //私钥密码  	        String serverKeyStorePwd = "123456";  // keystore存储密码  	        KeyStore keyStore = KeyStore.getInstance("JKS");      	        keyStore.load(new FileInputStream(serverKey),serverKeyPwd.toCharArray());    	        KeyManagerFactory km = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());     	        km.init(keyStore, serverKeyStorePwd.toCharArray());  	        SSLContext sslContext = SSLContext.getInstance(protocol);   	        sslContext.init(km.getKeyManagers(), null, new SecureRandom());  //第一项是用来做服务器验证的  	        SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();  	        SSLSocket clientSSLSocket = (SSLSocket) sslSocketFactory.createSocket(socketClient,socketClient.getInetAddress().getHostAddress(),socketClient.getPort(), true);  	        clientSSLSocket.setNeedClientAuth(false);  	        clientSSLSocket.setUseClientMode(false);  	        return clientSSLSocket;  	}	public static SSLSocket getTlsTrustAllSocket(Socket remoteHost,boolean isClient)	{		SSLSocket remoteSSLSocket = null;		SSLContext context = SSLTrustManager.getTrustAllSSLContext(isClient);		try {			remoteSSLSocket =  (SSLSocket) context.getSocketFactory().createSocket(remoteHost, remoteHost.getInetAddress().getHostName(),remoteHost.getPort(), true);			remoteSSLSocket.setTcpNoDelay(true);			remoteSSLSocket.setSoTimeout(5000);			remoteSSLSocket.setNeedClientAuth(false);			//remoteSSLSocket.setUseClientMode(isClient);		} catch (IOException e) {			e.printStackTrace();		}		return remoteSSLSocket;	}   public static SSLContext getTrustAllSSLContext(boolean isClient)   {	   javax.net.ssl.SSLContext sc = null;	   try {		javax.net.ssl.TrustManager[] trustAllCerts = new javax.net.ssl.TrustManager[1];		   javax.net.ssl.TrustManager tm = new SSLTrustManager();		   trustAllCerts[0] = tm;		    sc = javax.net.ssl.SSLContext		           .getInstance("TLS");		    if(isClient)		    {		    	sc.init(null, trustAllCerts, null);		    }else{		    	String protocol = "TLS";  	  	        String serverKeyPath = "D:/JDK8Home/twt/sslServerKeys";  	  	        String serverKeyPwd = "123456";  //私钥密码	  	        String serverKeyStorePwd = "123456";  // keystore存储密码	  	        KeyStore keyStore = KeyStore.getInstance("JKS");    	  	        keyStore.load(new FileInputStream(serverKeyPath),serverKeyPwd.toCharArray());  		    	KeyManagerFactory km = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());   		  	    km.init(keyStore, serverKeyStorePwd.toCharArray());		  	    KeyManager[] keyManagers = km.getKeyManagers();		  	    keyManagers = Arrays.copyOf(keyManagers, keyManagers.length+1);		  	    keyManagers[keyManagers.length-1] = new SSLTrustKeyManager();		  	    SSLContext sslContext = SSLContext.getInstance(protocol); 		  	    sslContext.init(keyManagers, null, new SecureRandom());  //第一项是用来做服务器验证的	  	        sc.init(km.getKeyManagers(), null, null);		    }	} catch (KeyManagementException e) {		e.printStackTrace();	} catch (NoSuchAlgorithmException e) {		e.printStackTrace();	} catch (UnrecoverableKeyException e) {		e.printStackTrace();	} catch (KeyStoreException e) {		e.printStackTrace();	} catch (CertificateException e) {		e.printStackTrace();	} catch (FileNotFoundException e) {		e.printStackTrace();	} catch (IOException e) {		e.printStackTrace();	}	   return sc;   }   public static class SSLTrustKeyManager extends X509ExtendedKeyManager   {	@Override	public String chooseClientAlias(String[] as, Principal[] aprincipal, Socket socket) 	{		System.out.println(Arrays.asList(as));		return null;	}	@Override	public String chooseServerAlias(String s, Principal[] aprincipal, Socket socket) {		System.out.println(s);		return null;	}	@Override	public X509Certificate[] getCertificateChain(String s) {		System.out.println(s);		return null;	}	@Override	public String[] getClientAliases(String s, Principal[] aprincipal) {		System.out.println(s);		return null;	}	@Override	public PrivateKey getPrivateKey(String s) {		System.out.println(s);		return null;	}	@Override	public String[] getServerAliases(String s, Principal[] aprincipal) {		System.out.println(s);		return null;	}   } }

转载于:https://my.oschina.net/ososchina/blog/675037

你可能感兴趣的文章
(转)IntelliJ IDEA java项目导入jar包,打jar包
查看>>
软件质量与测试--第六周作业 软件测试和评估
查看>>
实验4 颜色、字符串资源的使用
查看>>
Windows7配置python环境变量
查看>>
POJ 1654 Area 多边形面积 G++会WA
查看>>
apt 和 apt-get的区别
查看>>
学习园地
查看>>
Keras网络层之卷积层
查看>>
VS2017 调试不能命中断点问题
查看>>
7. Python运算符之逻辑、成员、身份运算符及优先级
查看>>
VirtualBox虚拟机网络设置(四种方式)
查看>>
通过构造器配置Bean
查看>>
thinkphp杂项功能(主干)
查看>>
jsp实现翻页功能
查看>>
PHP 比 Java 的开发效率高在哪?
查看>>
P1144 最短路计数
查看>>
c3p0
查看>>
以后在这里了
查看>>
brew组件实现原理
查看>>
js重写原型对象
查看>>