当柳上原的风吹向天际的时候...

真正的快乐来源于创造

  BlogJava :: 首页 :: 联系 :: 聚合  :: 管理
  368 Posts :: 1 Stories :: 201 Comments :: 0 Trackbacks

#

此版是 http://www.blogjava.net/heyang/archive/2010/12/31/342057.html  的改进版本。

改善功能:
1.XML文件存储。
2.在英文操作系统下乱码的问题。

已有功能:
1.基于TCP的通信功能。
2.基于RSA+AES的对信息的混合加密。
3.通过服务器转发客户端间消息。
4.用户的注册,登录,登出等功能。

注意:
本程序用到了AES加密,请确认http://www.blogjava.net/heyang/archive/2010/12/01/339515.html 一文中提到的两个文件是否已经拷贝到了对应的两处目录。

下载地址:
http://www.box.net/shared/o8z5rcrff5


posted @ 2011-01-21 17:17 何杨 阅读(172) | 评论 (1)编辑 收藏

以前,我们习惯用以下方式在XML中保存中文:
        try {
            OutputFormat format 
= OutputFormat.createPrettyPrint();
            format.setEncoding(
"GBK");    // 指定XML编码        
            XMLWriter writer = new XMLWriter(new FileWriter("c:\\1.xml"
),format);
            
            Document document 
= DocumentHelper.createDocument();
            Element root 
= document.addElement("users");

            
for(String user:userMap.keySet()){
                Element userElm
=root.addElement("user");
                userElm.addElement(
"name").addText(user);
                userElm.addElement(
"pswd").addText(userMap.get(user));
            }
           
            writer.write(document);
            writer.close();
        } 
catch (Exception e) {
            System.out.println(
"无法将注册用户信息存储到文件中,原因为"+e.getMessage());
            e.printStackTrace();
        }

在中文操作系统下,这样的方案是可行的,但是在英文操作系统下中文就会变成问号。最好使用如下的修正方案:
        try {
            OutputStreamWriter osw 
= new OutputStreamWriter(new FileOutputStream("c:\\1.xml"),"UTF-8");   
            OutputFormat format 
= OutputFormat.createPrettyPrint();
            format.setEncoding(
"UTF-8");    // 指定XML编码       
            XMLWriter writer = new
 XMLWriter(osw,format);
            
            Document document 
= DocumentHelper.createDocument();
            Element root 
= document.addElement("users");

            
for(String user:userMap.keySet()){
                Element userElm
=root.addElement("user");
                userElm.addElement(
"name").addText(user);
                userElm.addElement(
"pswd").addText(userMap.get(user));
            }
           
            writer.write(document);
            writer.close();
        } 
catch (Exception e) {
            System.out.println(
"无法将注册用户信息存储到文件中,原因为"+e.getMessage());
            e.printStackTrace();
        }

两端代码的变化区域在粗体部分,请注意区分。
posted @ 2011-01-21 11:35 何杨 阅读(757) | 评论 (0)编辑 收藏

注:下文代码主要来自参考书籍,本人稍稍修改了一下。

泛型栈类:
package com.heyang;

/**
 * 栈数据结构
 * 说明:
 * 作者:何杨(heyang78@gmail.com)
 * 创建时间:2011-1-15 上午07:51:09
 * 修改时间:2011-1-15 上午07:51:09
 
*/
public class MyStack<T>{
    
private int size;    // 大小
    private T[] datas;    // 数据
    private int top;    // 栈顶元素下标
    
    @SuppressWarnings(
"unchecked")
    
public MyStack(int size){
        
this.size=size;
        datas
= (T[])new Object[this.size]; 
        top
=-1;
    }
    
    
/**
     * 压栈
     
*/
    
public void push(T t){
        datas[
++top]=t;
    }
    
    
/**
     * 出栈
     * 
     * 说明:
     * 
@return
     * 创建时间:2011-1-15 上午08:01:15
     
*/
    
public T pop(){
        
return datas[top--];
    }
    
    
/**
     * 取得栈顶元素
     * 
     * 说明:
     * 
@return
     * 创建时间:2011-1-15 上午08:02:02
     
*/
    
public T getTopItem(){
        
return datas[top];
    }
    
    
/**
     * 查看栈是否为空
     * 
     * 说明:
     * 
@return
     * 创建时间:2011-1-15 上午08:02:38
     
*/
    
public boolean isEmpty(){
        
return top==-1;
    }
}

括号检查类:
package com.heyang;

/**
 * 表达式中括号检查类
 * 说明:
 * 作者:何杨(heyang78@gmail.com)
 * 创建时间:2011-1-15 上午08:05:25
 * 修改时间:2011-1-15 上午08:05:25
 
*/
public class BracketChecker{
    
private String input;        // 输入:待检查的表达式
    private boolean isValid;    // 是否检查通过
    private String checkResult;        // 输出:检查结果
    
    
/**
     * 构造函数
     * 
@param input
     
*/
    
public BracketChecker(String input){
        
this.input=input;
        
this.isValid=false;
        
this.checkResult="";
        
        check();
    }
    
    
/**
     * 执行检查
     * 
     * 说明:
     * 创建时间:2011-1-15 上午08:09:25
     
*/
    
private void check(){
        
int length=input.length();
        MyStack
<Character> stack=new MyStack<Character>(length);
        
        
for(int i=0;i<length;i++){
            
char c=input.charAt(i);
            
            
switch(c){
                
case '{':
                
case '[':
                
case '(':
                    stack.push(c);
                    
break;
                
case '}':
                
case ']':
                
case ')':
                    
if(stack.isEmpty()==false){
                        
char top=stack.pop();
                        
                        
if( (c=='}' && top!='{'|| (c==']' && top!='['|| (c==')' && top!='(') ){
                            isValid
=false;
                            checkResult
="经检查,表达式'"+input+"'中,位于第"+(i+1)+"的字符‘"+c+"’没有对应的匹配项";
                            
return;
                        }
                    }
                    
else{
                        isValid
=false;
                        checkResult
="经检查,表达式'"+input+"'中,位于第"+(i+1)+"的字符‘"+c+"’没有对应的匹配项";
                        
return;
                    }
                    
break;
                
default:
                    
break;
            }
        }
        
        
if(stack.isEmpty()==false){
            isValid
=false;
            checkResult
="经检查,表达式'"+input+"'中右括号缺失,匹配不完整";
            
return;
        }
        
else{
            isValid
=true;
            checkResult
="经检查,表达式'"+input+"'中括号匹配无误.";
            
return;
        }
    }

    
/**
     * 括号是否匹配
     * 
     * 说明:
     * 
@return
     * 创建时间:2011-1-15 上午08:39:38
     
*/
    
public boolean isValid() {
        
return isValid;
    }

    
/**
     * 取得检查结果
     * 
     * 说明:
     * 
@return
     * 创建时间:2011-1-15 上午08:39:27
     
*/
    
public String getCheckResult() {
        
return checkResult;
    }
    
    
public static void main(String[] args){
        String[] arr
={"1+(2/3-4","[1+(2/3-4)]*5","{[1+(2/3-4)]*5+(6+2*3)}*7","{[1+(2/3-4]*5+(6+2*3)}*7"};
        
        
for(String str:arr){
            BracketChecker c
=new BracketChecker(str);
            System.out.println(c.getCheckResult());
        }
    }
}

检查结果:
经检查,表达式'1+(2/3-4'中右括号缺失,匹配不完整
经检查,表达式
'[1+(2/3-4)]*5'中括号匹配无误.
经检查,表达式
'{[1+(2/3-4)]*5+(6+2*3)}*7'中括号匹配无误.
经检查,表达式
'{[1+(2/3-4]*5+(6+2*3)}*7'中,位于第11的字符‘]’没有匹配项


参考书籍:SAMS的《Java数据结构与算法》第四章

posted @ 2011-01-15 08:49 何杨 阅读(730) | 评论 (0)编辑 收藏

注:以下是Swing GUI处理的一个小小技巧,对此无兴趣者请退散。

在Swing中有这样一种状况:即长时间运行的事件回调,当它运行时,其余的GUI是没有响应的。如果这会持续较长的一段时间,它可能会让使用者感到挫折和困惑。下面一段程序就展示了这一现象,其粗体部分的本意是每隔一秒刷新标签中的文字,但是结果是按钮事件响应完毕后,标签上显示最后一段文字:
package com.heyang;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;

public class MyFrame extends JFrame{
    
private static final long serialVersionUID = -5100794608937579830L;
    
    
private JLabel msgLbl;
    
private JButton cmdBtn;
    
    
public MyFrame(){
        setTitle(
"MyFrame");
        
        msgLbl
=new JLabel("提示文字");
        cmdBtn
=new JButton("刷新文本");
        
        
this.setLayout(new BorderLayout());
        
this.add(msgLbl,BorderLayout.NORTH);
        
this.add(cmdBtn,BorderLayout.CENTER);
        
        
// 设置大小,位置
        setSizeAndCentralizeMe(300200);
        
        
// 点击窗口右上角的关闭按钮关闭窗口,直接退出程序
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        
        
// 按钮事件注册
        cmdBtn.addActionListener(new ActionListener() {
            
public void actionPerformed(ActionEvent e) {
                runCmd();
            }
        });
        
        setVisible(
true);
    }
    
    
private void runCmd(){
        msgLbl.setText(
"温故而知新,可以为师矣。");
        longTimeProcess(
10);
        
        msgLbl.setText(
"由,汝知之乎!知之为知之,不知为不知,是知也。");
        longTimeProcess(
10);
        
        msgLbl.setText(
"见贤思齐焉,见不贤而内自省也");
        longTimeProcess(
10);
        
        msgLbl.setText(
"士不可以不弘毅,任重而道远。");
        longTimeProcess(
10);
        
        msgLbl.setText(
"岁寒,然后知松柏之后凋也。");
        longTimeProcess(
10
);
    }
    
    
/**
     * 模拟一个长时处理,以100毫秒为单位
     * 
     * 说明:
     * 
@param mSeconds
     * 创建时间:2011-1-9 下午12:02:28
     
*/
    
private void longTimeProcess(int mSeconds){
        
try{
            Thread.sleep(mSeconds
*100);
        }
        
catch(Exception e){
            
        }
    }
    
    
private void setSizeAndCentralizeMe(int width, int height) {
        Dimension screenSize 
= Toolkit.getDefaultToolkit().getScreenSize();
        
this.setSize(width, height);
        
this.setLocation(screenSize.width / 2 - width / 2, screenSize.height
                
/ 2 - height / 2);
    }
    
    
public static void main(String[] args){
        
new MyFrame();
    }
}

要达到预期的效果,Swing建议:让长时间运行的任务在独立的线程中运行会好很多,这样能够让GUI有适当响应。修改后的代码如下:
package com.heyang;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;

public class MyFrame extends JFrame{
    
private static final long serialVersionUID = -5100794608937579830L;
    
    
private JLabel msgLbl;
    
private JButton cmdBtn;
    
    
public MyFrame(){
        setTitle(
"MyFrame");
        
        msgLbl
=new JLabel("提示文字");
        cmdBtn
=new JButton("刷新文本");
        
        
this.setLayout(new BorderLayout());
        
this.add(msgLbl,BorderLayout.NORTH);
        
this.add(cmdBtn,BorderLayout.CENTER);
        
        
// 设置大小,位置
        setSizeAndCentralizeMe(300200);
        
        
// 点击窗口右上角的关闭按钮关闭窗口,直接退出程序
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        
        
// 按钮事件注册
        cmdBtn.addActionListener(new ActionListener() {
            
public void actionPerformed(ActionEvent e) {
                runCmd();
            }
        });
        
        setVisible(
true);
    }
    
    
private void runCmd(){
        new Thread(){
            
public void
 run(){
                msgLbl.setText(
"温故而知新,可以为师矣。");
                longTimeProcess(
10);
                
                msgLbl.setText(
"由,汝知之乎!知之为知之,不知为不知,是知也。");
                longTimeProcess(
10);
                
                msgLbl.setText(
"见贤思齐焉,见不贤而内自省也");
                longTimeProcess(
10);
                
                msgLbl.setText(
"士不可以不弘毅,任重而道远。");
                longTimeProcess(
10);
                
                msgLbl.setText(
"岁寒,然后知松柏之后凋也。");
                longTimeProcess(
10);
            }
        }.start();

    }
    
    
/**
     * 模拟一个长时处理,以100毫秒为单位
     * 
     * 说明:
     * 
@param mSeconds
     * 创建时间:2011-1-9 下午12:02:28
     
*/
    
private void longTimeProcess(int mSeconds){
        
try{
            Thread.sleep(mSeconds
*100);
        }
        
catch(Exception e){
            
        }
    }
    
    
private void setSizeAndCentralizeMe(int width, int height) {
        Dimension screenSize 
= Toolkit.getDefaultToolkit().getScreenSize();
        
this.setSize(width, height);
        
this.setLocation(screenSize.width / 2 - width / 2, screenSize.height
                
/ 2 - height / 2);
    }
    
    
public static void main(String[] args){
        
new MyFrame();
    }
}

以上代码达到了预期效果,其中起关键作用的代码就是以上粗体部分,它将长时处理放到了另一个线程中运行。
这种做法不是唯一解决之道,Sun提供的SwingWorker类可以帮你达到目的,只是要繁琐一些。这样的技巧在耗时检查,与服务器交互和复杂图形处理中都能有所应用。

参考书籍:
O'REILLY 《Java 线程》一书。

最后感谢您看到这里。

何杨,2011年1月9日14:20:37


posted @ 2011-01-09 14:08 何杨 阅读(1011) | 评论 (3)编辑 收藏

注意:
1.NIO主要用于服务端,若用来客户端相对于它带来的额外复杂性有点不值得。
2.用于编码解码字符集请根据您的机器缺省字符集进行调整,如GB2312,GBK,UTF-8,UTF-16等,若出现乱码请更换之。
3.BufferSize请根据您可能发送接收的最大字符串长度进行相应调整。

代码:
package com.heyang.biz.server.test.nio;

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;


/**
 * NIO 服务器
 * 说明:
 * 作者:何杨(heyang78@gmail.com)
 * 创建时间:2011-1-3 下午12:09:02
 * 修改时间:2011-1-3 下午12:09:02
 
*/
public class NIOServer{
    
// 本地字符集
    private static final String LocalCharsetName = "gb2312";

    
// 本地服务器监听的端口
    private static final int Listenning_Port=8888;
    
    
// 缓冲区大小
    private static final int Buffer_Size=1024;
    
    
// 超时时间,单位毫秒
    private static final int TimeOut=3000;
    
    
public static void main(String[] args) throws Exception{
        
// 创建一个在本地端口进行监听的服务Socket信道.并设置为非阻塞方式
        ServerSocketChannel serverChannel=ServerSocketChannel.open();
        serverChannel.socket().bind(
new InetSocketAddress(Listenning_Port));
        serverChannel.configureBlocking(
false);
        
        
// 创建一个选择器并将serverChannel注册到它上面
        Selector selector=Selector.open();
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);
        
        
while(true){
            
// 等待某个信道就绪
            if(selector.select(TimeOut)==0){
                System.out.println(
".");
                
continue;
            }
            
            
// 获得就绪信道的键迭代器
            Iterator<SelectionKey> keyIter=selector.selectedKeys().iterator();
            
            
// 使用迭代器进行遍历就绪信道
            while(keyIter.hasNext()){
                SelectionKey key
=keyIter.next();
                
                
// 这种情况是有客户端连接过来,准备一个clientChannel与之通信
                if(key.isAcceptable()){
                    SocketChannel clientChannel
=((ServerSocketChannel)key.channel()).accept();
                    clientChannel.configureBlocking(
false);
                    clientChannel.register(key.selector(), SelectionKey.OP_READ,ByteBuffer.allocate(Buffer_Size));
                }
                
                
// 客户端有写入时
                if(key.isReadable()){
                    
// 获得与客户端通信的信道
                    SocketChannel clientChannel=(SocketChannel)key.channel();
                    
                    
// 得到并重置缓冲区的主要索引值
                    ByteBuffer buffer=(ByteBuffer)key.attachment();
                    buffer.clear();
                    
                    
// 读取信息获得读取的字节数
                    long bytesRead=clientChannel.read(buffer);
                    
                    
if(bytesRead==-1){
                      
// 没有读取到内容的情况
                      clientChannel.close();
                    }
                    
else{
                      
// 将缓冲区准备为数据传出状态
                      buffer.flip();
                      
                      
// 将获得字节字符串(使用Charset进行解码)   
                      String receivedString=Charset.forName(LocalCharsetName).newDecoder().decode(buffer).toString();
                      
                      
// 控制台打印出来
                      System.out.println("接收到信息:"+receivedString);
                      
                      
// 准备发送的文本
                      String sendString="你好,客户端. 已经收到你的信息"+receivedString;
                      
                      
// 将要发送的字符串编码(使用Charset进行编码)后再进行包装
                      buffer=ByteBuffer.wrap(sendString.getBytes(LocalCharsetName));
                      
                      
// 发送回去
                      clientChannel.write(buffer);
                      
                      
// 设置为下一次读取或是写入做准备
                      key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
                    }
                }
                
                keyIter.remove();
            }
        }
        
    }
}

与之配合的客户端代码,没有采用NIO方式。
package com.heyang.biz.server.test.nio;

import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;



public class TestClient{
    
public static void main(String[] args) throws Exception{
        Socket s
=new Socket("127.0.0.1",8888);
        
        InputStream  inStram
=s.getInputStream();
        OutputStream outStream
=s.getOutputStream();
        
        
// 输出
        PrintWriter out=new PrintWriter(outStream,true);
        
        out.print(
"getPublicKey你好!");
        out.flush();

        s.shutdownOutput();
// 输出结束
        
        
// 输入
        Scanner in=new Scanner(inStram);
        StringBuilder sb
=new StringBuilder();
        
while(in.hasNextLine()){
            String line
=in.nextLine();
            sb.append(line);
        }
        String response
=sb.toString();
        System.out.println(
"response="+response);
    }
}

服务器端的输出:
.
接收到信息:getPublicKey你好!
.

客户端的输出:
response=你好,客户端. 已经收到你的信息getPublicKey你好!
posted @ 2011-01-03 14:36 何杨 阅读(4699) | 评论 (0)编辑 收藏

一.目录互斥方案:这种互斥要求同一目录下不能运行两个应用程序的实例,通常用文件解决之,即程序启动时看看所在目录下有没有一个特定文件的存在,在的话就退出,不在的话就自己创建一个,退出时再删除之。这种情况下,这个临时文件便成了同一目录下程序已经启动实例的标志。为了避免误删除导致此功能失常,可以给文件加上隐藏属性。

二.本机互斥方案:这种互斥要求在一台机器上只能运行一个实例。一种解决办法是在注册表的特定位置进行记录,原理和文件互斥类似;另一个解决办法是利用端口,即程序启动后在某一端口进行监听,这样第二个实例启动时再去监听就不可行了,这种方式很巧妙,无须顾忌程序异常退出问题,只是会无端占用一个端口,有时客观环境不允许这么干。

三.单用户互斥方案:这种互斥要求系统只允许同名用户登录一次,因此服务器端必须存放用户对应的数据,当用户登上时检查这部分数据就可以了,这种方式最棘手的是客户端程序的异常退出导致再也无法登录,但让客户端发心跳包或是服务器端反连客户端来解决。

综上,互斥功能要成功,就要找到互斥对象共同的环境或是上下文。如果找到了,互斥就变成了一个技术问题。

posted @ 2010-12-31 13:47 何杨 阅读(256) | 评论 (0)编辑 收藏

按:以下是一个老旧的IM服务器客户机例子,网络间传递的信息采用混合加密方式以保护客户隐私,熟知这两细节者就不用往下看了,以免浪费宝贵时间。

下载地址
http://www.box.net/shared/jcrbps2hk3

下载包说明:
从上述地址下载完毕后是一个Eclispe工程,载入即可使用,可以执行RunServer运行服务器端,执行RunClient运行客户端。

使用说明:
先启动服务器端,让其开始监听;再由各客户端连到服务器上,注册好用户后各个客户机之间即可通信,此时服务器起一个消息转发器的作用。

截图:


几句啰嗦的话:
1.本程序是为了配合说明混合加密方式在IM程序中的使用,目标不是做一个商用IM软件。
2.其中的混合加密方式的使用可以参考(http://www.blogjava.net/heyang/archive/2010/12/25/341518.html)和(http://www.blogjava.net/heyang/archive/2010/12/26/341556.html)这两篇文章。

posted @ 2010-12-31 13:06 何杨 阅读(1858) | 评论 (6)编辑 收藏

一般来说,“.”在正则表达式中是匹配任意字符,但是,实际上,它是不能匹配换行符的,因为它的真实定义是“ 匹配除换行符号之外的任意字符”,这点要特别注意一下。
要匹配任意字符,可用"(.|\\n)+"或是"(.|\\s)+".

posted @ 2010-12-27 18:04 何杨 阅读(589) | 评论 (0)编辑 收藏

     摘要: 按:前面(http://www.blogjava.net/heyang/archive/2010/12/25/341518.html)已经提到过混合加密方式,今天这里再来赘述一下完成的代码和流程,熟悉者就不用往下看了。完整的IM程序在(http://www.box.net/shared/jcrbps2hk3)可以下载。 整个流程的UML Sequence图(Amaterus UML用得还不熟...  阅读全文
posted @ 2010-12-26 11:41 何杨 阅读(7330) | 评论 (4)编辑 收藏

     摘要: 按:下面的文字涉及早已在工程中广泛采用的混合加密方式,对此熟知者就不用往下看了,以免浪费时间。 我们知道,现代加密方式有两大类:一类是对称加密方式,其优点是加密速度快,缺点是密钥不便于传递,其中典型代表是AES;一类是非对称加密方式,优点是交换钥匙方便,缺点是加密时间长,代表是RSA。在实际应用,我们可以取其所长,弃其所短,这就是混合加密方式,有的场合也成为Hybrid方式。 具体来说...  阅读全文
posted @ 2010-12-25 16:02 何杨 阅读(4294) | 评论 (6)编辑 收藏

仅列出标题
共28页: First 上一页 12 13 14 15 16 17 18 19 20 下一页 Last