Java学习者论坛

 找回密码
 立即注册

QQ登录

只需一步,快速开始

手机号码,快捷登录

恭喜Java学习者论坛(https://www.javaxxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,购买链接:点击进入购买VIP会员
JAVA高级面试进阶视频教程Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程

Go语言视频零基础入门到精通

Java架构师3期(课件+源码)

Java开发全终端实战租房项目视频教程

SpringBoot2.X入门到高级使用教程

大数据培训第六期全套视频教程

深度学习(CNN RNN GAN)算法原理

Java亿级流量电商系统视频教程

互联网架构师视频教程

年薪50万Spark2.0从入门到精通

年薪50万!人工智能学习路线教程

年薪50万!大数据从入门到精通学习路线年薪50万!机器学习入门到精通视频教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程 MySQL入门到精通教程
查看: 368|回复: 0

[网络编程学习]java实现的端口映射器

[复制链接]
  • TA的每日心情
    开心
    2021-3-12 23:18
  • 签到天数: 2 天

    [LV.1]初来乍到

    发表于 2014-10-28 23:58:39 | 显示全部楼层 |阅读模式
    1.介绍:
          本文手把手的详解了jPortMap端口映射程序开发中的每一步,做为己运行在实际的企业项目中的应用, jPortMap程序较全面的展示了Thread、List、Vector、Socket、ServerSocket、Input/OutpuStream、File Read/Write、Properties等核心API的用法,是初学者快速进阶的一个优秀案例。

            在涉及内外网数据交换的网络应用系统开发中,我们经常需要做端口映射,比如放在外部网络主机上的程序要与内部网络上的某台机器建主TCP/IP连结,如下图(1)示:

         C机器可以与A机连通,但要与B机连通,由与不在同一网络,就无能为力了;这时,就需在A机器上做交换或是转发,来接通C与B之间的TCP/IP连结,即C机先与A机器建立Socket连结,A再与B机建立连结,然后由A在中间转发C与B通信的数据;B机器上可能运行着数据库,WebService等在Tcp/IP上通信的程序,而C机器必须访问这些服务。这里A机器就充当像现实生活中介绍人的角色,负责将C、B之间的通信数据流在Socket上转发;  
      
       
       
         
       

         
       
      
    图1
           因此,A机需实现端口转发功能,在Liunx上,可以通过配置IPTable由OS实现,在本例中,我们将开发一个java实现的端口转发程序jPortMap,此程序将运行在A机器上,以实现转发C与B之间通信的转发。

    2.源码下载及测试说明:

    从www.NetJava.cn上下载源代码解压后,可看到如下目录结构:



         现在,你可以修改一下jPortMap.cfg中的配置,比如,想通过本机的127.0.0.1地址上的8899端口转发到10.10.3.156,则这样配置:
    ##本地IP
    LocalIP.1 = 127.0.0.1
    ##本地端口
    LocalPort.1 = 8899
    ##目标IP
    DestHost.1 = 10.10.3.156
    ##目标端口
    DestPort.1 = 80
    ##客户端IP过滤表,*表示许可模糊匹配
    AllowClient.1 = *.*.*.*

    ,双击jPortMap.bat启动程序后,在你的IE里输入http://127.0.0.1:8899试试看:)
      3.jPortMap程序类的构成说明:
    jPortMap由Main.java、Server.java、Transfer.java、Route.java、SysLog.java五个类构成。

    类文件功能概要:
    Main.java:程序启动主类,负责从配置文件读取转发的配置参数,启动转发服务器;

    Server.java:其实是一个ServerSocket服务器,接受C机器进入的Socket连结请求,生成Transfer.对象,由Transfer负责在本机(A上)转发B和C之间的通信。

    Route.java:转发对象的数据模板类,用来将转发配置映射为java对象,以由Server,ransfer对象使用。

    Transfer.java:按字面意思,可理解为“传送者”,如在图示中,当C要通过A连结B时,是先连结到A机上,这里在C和A间生成一个socket对象,Transfer对象则使用这个生成的socket对象和这个传输任务的Route对象执行具体的转发任务。

    SysLog.java:jPortMap是一个服务器端程序,在运行中可能会出现错误,因此需要一个日志工具,日志工具在jPortMap中只有一个对象存在,负责记录每天程序运行的信息,如错误,警行,一般信息等。

    配置文件:
        cfgjPortMap.cfg:这是一个文本文件,其中存放jPortMap的配置数据,当程序启动时,主类会从中读取数据配置程序,以生成多个Route对象在内存中保持数据。

    4.Route.java解析:
    我们己经说明,Route类是转发对象配置数据的模板类,当jPortMap启运时,它需要知道如下配置:
    1. 有多少处转发任务(意味着要监听哪几个ServerSocket);
    2. jPortMap程序对每个转发任务要启动的监听ServerSocket端口及所绑定的IP地址;
    3. 每个转发任务的目标IP地址和端口;

        因此,jPortMap一但启动,可能会创建多个Route对象,而每个具体的Route对象则保布着一个转发任务的以上配置数据。
    另外,从安全方面着想,我们的jPortMap程序还需要对请求进入的连结进行安全管理,这里我们简单的用IP过滤的方法,即jPortMap中ServerSocekt监听到的进入连结请求会认证IP地址,如发现IP地址没有在许可的列表中,则断开这个请求;所以Route类还要保存每个任务对应的许可IP表;
    我们的Route.java源文件如下:
    1. /*
    2. * Route.java
    3. *
    4. * Created on 2006年12月28日, 下午12:36
    5. *
    6. * To change this template, choose Tools | Template Manager
    7. * and open the template in the editor.
    8. */
    9. package org.netjava.jportmap;
    10. /**
    11. *转发任务的配置数据对象模板
    12. *
    复制代码
    Company: www.NetJava.org * @author javafound */ public class Route { public Route() {} //jPortMap绑定的IP String LocalIP=""; //监听的端口 int LocalPort=0; //转发数据的目标机器IP String DestHost=""; //转发的目标端口 int DestPort=0; //这个转发上许可进入的IP列表 String AllowClient="";
    //重写的toString方法,输出具体Route对象的信息以便debug public String toString() { StringBuffer stb = new StringBuffer(); stb.append(" LocalADD " + LocalIP); stb.append(" :" + LocalPort); stb.append(" --->DestHost " + DestHost); stb.append(" :" + DestPort); stb.append(" (AllowClient) " + AllowClient); return stb.toString(); } }  
          可以比对cfgjPortMap.cfg(可用notepad打开)中的内容,Route类只需要据文本件中的配配生成多个Route对象或者说转发任务,再由其它对象来使用,因此,Route类的功能和结构很简单,就像映射表结构的javaBean一样,只是负责保存数据在内存中。

    5. SysLog.java解析:
    SysLog保存每天的日志信息到指定的目录下,简单的说就是提供方法供别的对象来调用,写内容到文件中:

    1. package org.netjava.jportmap;
    2. import java.io.*;
    3. import java.util.Calendar;

    4. /**
    5. * Title: 端口转发器
    6. * Description:日志工具类
    7. * Copyright: Copyright (c) 2005
    8. * Company: www.NetJava.org
    9. * @author javafound
    10. * @version 1.0
    11. */

    12. public class SysLog {
    13.     //记录输出一般信息
    14.   public  static void info(String s) {
    15.         writeToTodayLog("INFO  :", s);
    16.     }
    17.    ////记录警告信息
    18. public   static void warning(String s) {
    19.         writeToTodayLog("WARN:", s);
    20.     }
    21.    //记录错误信息
    22. public   static void severe(String s) {
    23.         writeToTodayLog("ERROR:", s);
    24.     }
    25. //输出到当天日志文件的具体实现
    26.     private static void writeToTodayLog(String flag, String msg) {
    27.         RandomAccessFile raf = null;
    28.         try {
    29.             Calendar now = Calendar.getInstance();
    30.             String yyyy = String.valueOf(now.get(java.util.Calendar.YEAR));
    31.             String mm = String.valueOf(now.get(Calendar.MONTH) + 1);
    32.             String dd = String.valueOf(now.get(Calendar.DAY_OF_MONTH));
    33.             String hh = String.valueOf(now.get(Calendar.HOUR_OF_DAY));
    34.             String ff = String.valueOf(now.get(Calendar.MINUTE));
    35.             String ss = String.valueOf(now.get(Calendar.SECOND));
    36.             mm = (1 == mm.length()) ? ("0" + mm) : mm;
    37.             dd = (1 == dd.length()) ? ("0" + dd) : dd;
    38.             hh = (1 == hh.length()) ? ("0" + hh) : hh;
    39.             ff = (1 == ff.length()) ? ("0" + ff) : ff;
    40.             ss = (1 == ss.length()) ? ("0" + ss) : ss;
    41.             String yyyymmdd = yyyy + mm + dd;
    42.             String hhffss=hh+ff+ss;
    43.             String path = System.getProperties().getProperty("user.dir")
    44.                           + File.separator + "log";
    45.             File p = new File(path);
    46.             if (!p.exists()) {
    47.                 p.mkdirs();
    48.             }
    49.             path += File.separator + "jPortMap_" + yyyymmdd + ".log";
    50.             File f = new File(path);
    51.             if (f.isDirectory()) {
    52.                 f.delete();
    53.             }
    54.             raf = new RandomAccessFile(f, "rw");
    55.             raf.seek(raf.length());
    56.             raf.writeBytes(hhffss+"  "+flag + " : " + msg + "
    57. ");
    58.             raf.close();
    59.         } catch (Exception ex) {
    60.             System.out.println("write file has error=" + ex);
    61.         }
    62.     }
    63.      /** Creates a new instance of SysLog
    64.       *做为一个工具类,一般不需要实例化,所以此处private
    65.       */
    66.     private SysLog() {}
    67. }
    复制代码
    说明:
    首先我们看到提供的三个公用静态方法:
    //记录一般信息
    public static void info(String s)

    ////记录警告信息
    public static void warning(String s)

    //记录错误信息
    public static void severe(String s)

          SysLog做为系统中的工具类,一般是不需要实例化的,所以只提供调用功能即可,这三个调用方法为其它对象提供了调用接口,分别输出不同类型的信息到目志中,而调用对象并不需要去关心具体日志的格式,日志文件命令,文件读写等问题----只需传入要记录的消息即可。

         System.getProperties()返回一个Properties对象,其实是一个Map接口的实现,其中存入格式为 名字:值 一一对应的表,系统的许多环境变量,如程序运行的当前目录user.dir,操作系统类型,java当前版本等都在其中存放。

         RandomAccessFile:在写日志时使用了这个类向日志文件中写入内容,其中seek(int length)可以指定跳过文件中内容的长度后再开始写入;这样我们的日志就不会丢失。

    6.Server.java解析:
          如其名,Server是一个转发服务器的实现类,我们的jPortMap可同时执行多个转发服务,所以每个Server对象都将做为一个独立的线程运行,在jPortMap.cfg中配置了几个转发任务,系统就会实例几个Route对象,并生成对应个数的的Server对象,每个Server对象使用自己的一个Route对象的数据在指定的端口启动监听服务,等待客户端(如前面图示则是C机器)发起的连结,接收到连结请求并通过IP验证后,这个Server对象则将具体的转发任务交给自己的一个Transfer对象去独立处理,而Server对象则继续运行,等待到来的连结请求。

          我们可以将这个Server理解为一个看门人的角色---使用ServerSocket监听指定端口,等待到来的连结,它只负责接待来客,并核查来客的身份,如核查通过,至于来客进的门怎么办,它不管-----由它所持有的另外一个对象Transfer类的一个实例去处理。解析代码如下:
    1. package org.netjava.jportmap;
    2. import java.net.*;
    3. import java.util.*;
    4. /**
    5. * Title: 端口转发器
    6. * Description:启动监听服务
    7. * Copyright: Copyright (c) 2005
    8. * Company: www.NetJava.org
    9. * @author javafound
    10. * @version 1.0
    11. */

    12. public class Server extends Thread {
    13.     //创建一个转发服务器
    14.     public Server(Route route, int id) {
    15.         this.route = route;
    16.         connectionQueue = new Vector();
    17.         myID = id;
    18.         start();
    19.     }
    20.     //关闭这个服务器:
    21.     public void closeServer() {
    22.         isStop = true;
    23.         if (null != myServer) {
    24.             closeServerSocket();
    25.         } while (this.connectionQueue.size() > 0) {
    26.             Transfer tc = (Transfer) connectionQueue.remove(0);
    27.             tc.closeSocket(tc.socket);
    28.             tc = null;
    29.         }
    30.     }
    31. //启动转发服务器的执行线程
    32.     public void run() {
    33.         SysLog.info(" start Transfer......:" + route.toString());
    34.         ServerSocket myServer = null;
    35.         try {
    36.             InetAddress myAD = Inet4Address.getByName(route.LocalIP);
    37.             myServer = new ServerSocket(route.LocalPort, 4, myAD);
    38.         } catch (Exception ef) {
    39.             SysLog.severe("Create Server " + route.toString() + " error:" + ef);
    40.             closeServerSocket();
    41.             return;
    42.         }
    43.         SysLog.info("Transfer Server : " + route.toString() + " created OK");
    44.         while (!isStop) {
    45.             String clientIP = "";
    46.             try {
    47.                 Socket sock = myServer.accept();
    48.                 clientIP = sock.getInetAddress().getHostAddress();
    49.                 if (checkIP(route, clientIP)) {
    50.                     SysLog.warning(" ransfer Server : " + route.toString() +
    51.                                    "  Incoming:" + sock.getInetAddress());
    52.                     sock.setSoTimeout(0);
    53.                     connCounter++;
    54.                     Transfer myt = new Transfer(sock, route);
    55.                     connectionQueue.add(myt);
    56.                 } else {
    57.                     SysLog.warning(" ransfer Server : " + route.toString() +
    58.                                    "  Refuse :" + sock.getInetAddress());
    59.                     closeSocket(sock);
    60.                 }
    61.             } catch (Exception ef) {
    62.                 SysLog.severe(" Transfer Server : " + route.toString() +
    63.                               " accept error" + ef);
    64.             }
    65.         }
    66.     }
    67.     //检测进入的IP是否己许可
    68.     private static boolean checkIP(Route route, String inIP) {
    69.         String[] inI = string2StringArray(inIP, ".");
    70.         String[] list = string2StringArray(route.AllowClient, ".");
    71.         if (inI.length != list.length) {
    72.             SysLog.severe(" Transfer Server Error Cfg AllowClient : " +
    73.                           route.toString());
    74.             return false;
    75.         }
    76.         for (int i = 0; i < inI.length; i++) {
    77.             if ((!inI[i].equals(list[i])) && !(list[i].equals("*"))) {
    78.                 System.out.println(": " + inI[i] + " :" + list[i]);
    79.                 return false;
    80.             }
    81.         }
    82.         return true;
    83.     }
    84.     /*
    85.      * @param srcString 原字符串
    86.      * @param separator 分隔符
    87.      * @return 目的数组
    88.      */
    89.     private static final String[] string2StringArray(String srcString,
    90.             String separator) {
    91.         int index = 0;
    92.         String[] temp;
    93.         StringTokenizer st = new StringTokenizer(srcString, separator);
    94.         temp = new String[st.countTokens()];
    95.         while (st.hasMoreTokens()) {
    96.             temp[index] = st.nextToken().trim();
    97.             index++;
    98.         }
    99.         return temp;
    100.     }

    101.   //关闭ServerSocket
    102.     private void closeServerSocket() {
    103.         try {
    104.             this.myServer.close();
    105.           } catch (Exception ef) {
    106.         }
    107.     }
    108. private void closeSocket(Socket s) {
    109.         try {
    110.             s.close();
    111.         } catch (Exception ef) {
    112.         }
    113.     }
    114.    
    115.     //服务器
    116.   private ServerSocket myServer = null;
    117.     //连结队列控制
    118.     private boolean isStop = false;
    119.             //
    120.     private Vector connectionQueue = null;
    121.     private int connCounter = 0;
    122.     // 路由对象
    123.     private Route route = null;
    124.     //连结的ID号,暂未用
    125.     private static int  myID = 0;
    126. }
    复制代码
      Server类关键功能是在一个独立的线程中执行监听任务,当我们实例化一个ServerSocket时,即绑定了本机的一个IP和端口,这个ServerSocket对象就在这个地址(由IP和端口组成)上通过调用accept()方法等待客户端连结,默认情况下,这个等待会一直持续,直到有一个连结进入----生成一个socket对象;
    而我们的ServerSocket.accept()是在一个wilhe循环中,这保证了监听服务器不会中途退出。

    7. Transfer.java解析
          在分析Server.java中我们看到,Server做为一个服务器,在与客户端建立连结后使用生成的Socket对象和自己的Routc对象来实例化一个Transfer,具体的传输工作就交给了Transfer对象完成。

        Server生成的Socket对象是机器C与A之间连结的一个代码,通过这个Socekt对象上的Input/OutPut Stream,可以让C与A之间通信----工作还只完成了一半,这里我们还需要建立A与B之间的Socket连结,这里就出现了两个Socket连结,分别是C与A间,我们叫SocketCA;A与B间我们假设叫做SocketAB; Transfer对象的任务就是行建立SocketAB,然后,将SocketCA的输入写入到SocketAB的输出流,将SocketAB的输出流写到SocketCA的输出流中,这样,就完成了C,B机器之间的数据转发。
    1. package org.netjava.jportmap;
    2. import java.net.*;
    3. import java.io.*;

    4. /**
    5. * Title: 端口转发器
    6. * Description: 对连结进行转发处理
    7. * Copyright: Copyright (c) 2005
    8. * Company: www.NetJava.org
    9. * @author javafound
    10. * @version 1.0
    11. */

    12. public class Transfer extends Thread {
    13.      /**
    14.      * 创建传输对象
    15.      * @param s Socket   :进入的socket
    16.      * @param route Route:转发配置
    17.      */
    18.     public Transfer(Socket s, Route route) {
    19.         this.route = route;
    20.         this.socket = s;
    21.         this.start();
    22.     }
    23.     // 执行操作的线程
    24.     public void run() {
    25.         Socket outbound = null;
    26.         try {
    27.             outbound = new Socket(route.DestHost, route.DestPort);
    28.             socket.setSoTimeout(TIMEOUT);
    29.             InputStream is = socket.getInputStream();
    30.             outbound.setSoTimeout(TIMEOUT);
    31.             OutputStream os = outbound.getOutputStream();
    32.             pipe(is, outbound.getInputStream(), os, socket.getOutputStream());
    33.         } catch (Exception e) {
    34.             SysLog.severe(" transfer error:" +route.toString()+ " :" + e);
    35.         } finally {
    36.             SysLog.warning("Disconnect :"+ route.toString());
    37.             closeSocket(outbound);
    38.             closeSocket(socket);
    39.         }
    40.     }
    41.    
    42. /**
    43. *传输的实现方法
    44. */
    45. private   void pipe(InputStream is0, InputStream is1,
    46.               OutputStream os0, OutputStream os1) {
    47.         try {
    48.             int ir;
    49.             byte bytes[] = new byte[BUFSIZ];
    50.             while (true) {
    51.                 try {
    52.                     if ((ir = is0.read(bytes)) > 0) {
    53.                         os0.write(bytes, 0, ir);
    54.                     } else if (ir < 0) {
    55.                         break;
    56.                     }
    57.                 } catch (InterruptedIOException e) {}
    58.                 try {
    59.                     if ((ir = is1.read(bytes)) > 0) {
    60.                         os1.write(bytes, 0, ir);
    61.                         // if (logging) writeLog(bytes,0,ir,false);
    62.                     } else if (ir < 0) {
    63.                         break;
    64.                     }
    65.                 } catch (InterruptedIOException e) {}
    66.             }
    67.         } catch (Exception e0) {
    68.             SysLog.warning(" Method pipe" + this.route.toString() + " error:" +
    69.                            e0);
    70.         }
    71.     }
    72.     //关闭socket
    73.      void closeSocket(Socket s) {
    74.         try {
    75.             s.close();
    76.         } catch (Exception ef) {
    77.         }
    78.     }
    79.    //传输任务的Route对象
    80. Route route = null;
    81.      // 传入数据用的Socket
    82. Socket socket;
    83.    //超时
    84.    static private int TIMEOUT = 1000;
    85.    //缓存
    86.    static private int BUFSIZ = 1024;
    87. }
    复制代码
    8.Main.java解析
    OK,至此己万事具备!我们需要一个启动主类,根据读入的配置文件数据来启动转发服务器,执行转发工作:
    1. package org.netjava.jportmap;
    2. import java.io.*;
    3. import java.util.*;
    4. import java.net.*;
    5. /**
    6. * Title: 端口转发器
    7. * Description:启动主类:读取配置,启动监听服务
    8. * Copyright: Copyright (c) 2005
    9. * Company: www.NetJava.org
    10. * @author javafound
    11. * @version 1.0
    12. */

    13. public class Main {
    14.   //start......
    15.     public static void main(String args[]) {
    16.         startService();
    17.     }
    18. //start
    19.     public static void startService() {
    20.         if (!loadCfgFile()) {
    21.             System.exit(1);
    22.         } while (serverList.size() > 0) {
    23.             Server ts =   serverList.remove(0);
    24.             ts.closeServer();
    25.         }
    26.         for (int i = 0; i < routeList.size(); i++) {
    27.             Route r = routeList.get(i);
    28.             Server server = new Server(r, i);
    29.             serverList.add(server);
    30.         }
    31.     }
    32. // 停止服务接口,备用其它模块调用
    33.     public static void stop() {
    34.         while (serverList.size() > 0) {
    35.             Server ts = serverList.remove(0);
    36.             ts.closeServer();
    37.         }
    38.     }
    39.     /**
    40.      *从配置文件读取数据,生成Route对象
    41.      * read cfg parameter
    42.      * @return boolean
    43.      */
    44.     private static boolean loadCfgFile() {
    45.         try {
    46.             String userHome = System.getProperties().getProperty("user.dir");
    47.             if (userHome == null) {
    48.                 userHome = "";
    49.             } else {
    50.                 userHome = userHome + File.separator;
    51.             }
    52.             userHome += "cfg" + File.separator + "jPortMap.cfg";
    53.             InputStream is = new FileInputStream(userHome);
    54.             Properties pt = new Properties();
    55.             pt.load(is);
    56.             //共有几个业务模块
    57.             int ServiceCount = Integer.parseInt(pt.getProperty("TransferCount"));
    58.             for (; ServiceCount > 0; ServiceCount--) {
    59.                 Route r = new Route();
    60.                 r.LocalIP = pt.getProperty("LocalIP." + ServiceCount).trim();
    61.                 r.LocalPort = Integer.parseInt(pt.getProperty("LocalPort." +
    62.                         ServiceCount).trim());
    63.                 r.DestHost = pt.getProperty("DestHost." + ServiceCount).trim();
    64.                 r.DestPort = Integer.parseInt(pt.getProperty("DestPort." +
    65.                         ServiceCount).trim());
    66.                 r.AllowClient = pt.getProperty("AllowClient." + ServiceCount).
    67.                                 trim();
    68.                 routeList.add(r);
    69.             }
    70.             is.close();
    71.             SysLog.info("ystem Read cfg file OK");
    72.         } catch (Exception e) {
    73.             System.out.println("找不到配置文件:"+e);
    74.             SysLog.severe("loadCfgFile false :" + e);
    75.             return false;
    76.         }
    77.         return true;
    78.     }
    79.     //Server服务器集合
    80.     private static List< Server> serverList = new ArrayList();
    81.     //Route集合
    82.     private static List< Route> routeList = new ArrayList();
    83. }
    复制代码
         Main类中需要注意的是loadCfgFile()方法,它用来读取当前目录下面cfg/jPortMap.cfg文件中的配置数据,如读取成功,返加ture值,如读取失败,程序测会退出。
    另外:
    //Server服务器集合
    private static List<Server> serverList = new ArrayList();

    //Route集合
    private static List<Route> routeList = new ArrayList();

    这两行代码,生成两个列表,来保存己启动的Server对象和Route对象。

         现在,我们只要启动Main类,jPortMap就开始运行了,同时会在log目录下行成每天的运行日志;当然,千万不要忘了cfg/目录下面jPortMap.cfg中配置转发的参数,配置的具体说明在该文件中有注解。
    源码目录结构图(NetBean中):

       9.改进设想:
           无论如何,这还是个比较简陋的程序!假如我们把配置改成XML格式、假如我们使用Thread Pool来执行任务、假如我们使用NIO、假如我们再做一套PL的UI界面….,您的任何建议,都会是对jPortMap走向完美的支持,请登陆www.NetJava.cn发表您的看法,发布您的创新!当然,www.NetJava.cn现在己增加了许多新东东让您欣赏!


    源码下载:http://file.javaxxz.com/2014/10/28/235839203.zip
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|Java学习者论坛 ( 声明:本站资料整理自互联网,用于Java学习者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2024-5-19 10:18 , Processed in 0.407505 second(s), 48 queries .

    Powered by Discuz! X3.4

    © 2001-2017 Comsenz Inc.

    快速回复 返回顶部 返回列表