swing编写client端及多线程server端之client端

简介: swing编写client端及多线程server端之client端

前段时间要求做一个项目,项目比较简单,项目要求是:1、从客户端读取指定目录、格式(.xml)的文件,然后传给服务器;2、做成客户端形式。

client端写好了,server端的代码是从网上借鉴的,后来才做成窗口形式。功能不是很完美,有些bug尚未解决,希望大家多多指正。下面我会把主要代码贴出来给大家看看。

client端的编写过程:

首先是设计界面,界面也比较简洁,一个主窗口、一个功能配置窗口

     

这个说明一下,这个窗口样式是采用SynthLookAndFeel的方式。用到了开源的beautyeye样式,大家可以在网上搜索下载。

这里有个注意的地方:beautyeye默认的样式看起来相当漂亮了,但是可能不太符合我的要求,所以根据作者的文档修改了下

//隐藏“设置”按钮
UIManager.put("RootPane.setupButtonVisible", false);
//设置本属性将改变窗口边框样式定义
BeautyEyeLNFHelper.frameBorderStyle = BeautyEyeLNFHelper.FrameBorderStyle.translucencySmallShadow;
//BeautyEyeLNFHelper.frameBorderStyle = BeautyEyeLNFHelper.FrameBorderStyle.translucencyAppleLike;//默认样式
//加载Beauty Eye的外观,如果不需要其他设置的话,直接写这句话就OK了,其他都可以不用写
BeautyEyeLNFHelper.launchBeautyEyeLNF();
//关闭窗口在不活动时的半透明效果
BeautyEyeLNFHelper.translucencyAtFrameInactive = false;

上面代码加到布局之前。

还有种方法可以加载,效果跟上面差不多,区别在于上面的判断了系统兼容问题,可以处理更多的系统,如linux。

try {
  // 使用配置文件创建窗口皮肤
  SynthLookAndFeel synth = new SynthLookAndFeel();
  /**
  //加载自定义皮肤文件
  InputStream is = clazz.getResourceAsStream("window.xml");
  if (is == null) {
    System.err.println("Unable to find theme configuration");
    System.exit(0);
  }
  synth.load(is, clazz);
  */
  //UIManager.put("swing.boldMetal", Boolean.FALSE);//修改皮肤
  //UIManager.setLookAndFeel(synth);//自定义风格,需要编写皮肤文件。如上面的window.xml然后加载
  //UIManager.setLookAndFeel("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel");//自带的其他风格
  //UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel");//Windows风格 
  //UIManager.setLookAndFeel("com.sun.java.swing.plaf.motif.MotifLookAndFeel") ; //Mac风格 
  //UIManager.setLookAndFeel("javax.swing.plaf.metal.MetalLookAndFeel") ;//Java默认风格 dowsLookAndFeel");
  UIManager.setLookAndFeel("org.jb2011.lnf.beautyeye.BeautyEyeLookAndFeelWin");//BeautyEye风格
  this.init();//初始化主窗口
} catch (Exception e) {
  e.printStackTrace();
  System.exit(0);//报错程序退出
}



自定义皮肤文件window.xml代码,仅供参考,本项目没有使用到,网上的教程比较少,做的比较难看,所以使用现有的beautyeye皮肤样式,如果大家喜欢折腾可以试试:

<?xml version="1.0" encoding="UTF-8"?>
<synth>
  
  <style id="default">
    <font name="Aharoni" size="12" />   
    <!--
    <state>
      <color value="#FFFFFF" type="BACKGROUND" />
      <color value="#FFFFFF" type="TEXT_FOREGROUND" />
    </state>
    -->
  </style>
  <bind style="default" type="region" key=".*" />
  
  <style id="border">
    <opaque value="true" />
    <state>
      <color value="#0692fa" type="BACKGROUND" />
      <color value="#0692fa" type="TEXT_FOREGROUND" />
      <insets top="5" left="5" bottom="5" right="5" />
    </state>
  </style>
  <bind style="border" type="region" key="Border" />
  
  <style id="menubar">
    <opaque value="true" />
    <state>
      <color value="#eeeeee" type="BACKGROUND" />
      <color value="#000000" type="TEXT_FOREGROUND" />
    </state>
  </style>
  <bind style="menubar" type="region" key="MenuBar" />
  
  <style id="menuitem">
    <opaque value="true" />
    <state>
      <color value="#eeeeee" type="BACKGROUND" />
      <color value="#000000" type="TEXT_FOREGROUND" />
    </state>
  </style>
  <bind style="menuitem" type="region" key="MenuItem" />
  
  <style id="textfield">
    <opaque value="true" />
    <state>
      <color value="#eeeeee" type="BACKGROUND" />
      <color value="#000000" type="TEXT_FOREGROUND" />
      <insets top="5" left="5" bottom="15" right="5" />
    </state>
  </style>
  <bind style="textfield" type="region" key="Textfield" />
  <style id="label">
    <opaque value="true" />
    <state>
      <color value="#eeeeee" type="BACKGROUND" />
      <color value="#000000" type="TEXT_FOREGROUND" />
      <insets top="5" left="20" bottom="5" right="5" />
    </state>
  </style>
  <bind style="label" type="region" key="JLabel" />
  
  <style id="optionpane">
    <opaque value="true" />
    <state>
      <color value="#eeeeee" type="BACKGROUND" />
      <color value="#000000" type="TEXT_FOREGROUND" />
      <insets top="5" left="20" bottom="5" right="5" />
    </state>
  </style>
  <bind style="optionpane" type="region" key="JOptionPane" />
  
  <style id="button">
    <opaque value="true"></opaque>
    <state>
      <insets top="5" left="5" bottom="5" right="5" />
      <color type="BACKGROUND" value="#0092fe" />
      <color type="TEXT_FOREGROUND" value="#FFFFFF" />
    </state>
    <state value="MOUSE_OVER">
      <insets top="5" left="10" bottom="5" right="5" />
      <color type="TEXT_FOREGROUND" value="#c0c0c0" />
    </state>
    <state value="PRESSED">
      <insets top="5" left="5" bottom="5" right="5" />
      <color type="TEXT_FOREGROUND" value="#c0c0c0" />
    </state>
    <state value="DISABLED">
      <insets top="5" left="5" bottom="5" right="5" />
      <color type="TEXT_FOREGROUND" value="#777777" />
    </state>
    <property key="Button.margin" type="insets" value="5 5 5 5" />
  </style>
  <bind style="button" type="region" key="Button" />  
</synth>


布局类的代码如下:

ImageIcon icon=new ImageIcon(clazz.getResource("icon.jpg"));
mainFrame = new JFrame("设备状态监控配置");
mainFrame.setIconImage(icon.getImage());//设置窗口的icon
mainFrame.setSize(508, 536);
mainFrame.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);//关闭不是直接程序退出,只是界面隐藏
JComponent bar = (JComponent) ((JLayeredPane) mainFrame.getRootPane().getComponents()[1]).getComponent(1);
JButton closeBtn = (JButton) bar.getComponent(1);// 获取关闭按钮
closeBtn.setToolTipText("最小化到托盘");//修改关闭按钮默认的ToolTipText
if (SystemTray.isSupported()) {//判断系统是否支持托盘功能
  this.minTray();//最小化到托盘
}
//Toolkit toolkit = Toolkit.getDefaultToolkit();这个我写成了全局变量,这里贴出来给大家看
//屏幕中间显示
Dimension screenSize = toolkit.getScreenSize();
Dimension jfSize = mainFrame.getSize();
int x = screenSize.width / 2 - jfSize.width / 2;
int y = screenSize.height / 2 - jfSize.height / 2;
mainFrame.setLocation(x, y);
mainFrame.setLayout(null);//设置Layout为null
JPanel p1 = new JPanel(new BorderLayout());
Border border = BorderFactory.createTitledBorder(new LineBorder(Color.LIGHT_GRAY), "运行状态", 1, 0, new Font("宋体", Font.PLAIN, 12));//设置边框,可以对比图
JPanel statusPanel = new JPanel();
statusPanel.setLayout(new GridLayout(9, 2, 0, 5));//设置GridLayout布局9横2纵,上下5px的间隔
JLabel jb0 = new JLabel("服务器IP:", JLabel.LEFT);
//...(省略其他)
//初始化label,当显示文本框用
label0 = new JLabel();
//设置边框颜色
label0.setBorder(new LineBorder(Color.LIGHT_GRAY));
//...(省略其他)
//初始化文本框的值
label2.setText("未连接");
label3.setText("未获取");
label4.setText("未启动");
label6.setText("0");
//...(省略其他)
//添加到panelstatusPanel.add(jb0);statusPanel.add(label0);
//...(省略其他)
//初始化按钮
optionsButton = new JButton("配置选项");
runButton = new JButton("启 动");
stopButton = new JButton("暂 停");
aboutButton = new JButton("关 于");
//如果有需要可以修改按钮的默认颜色
//optionsButton.setUI(new BEButtonUI().setNormalColor(BEButtonUI.NormalColor.lightBlue));
//设置按钮的icon
optionsButton.setIcon(new ImageIcon(clazz.getResource("configure.png")));
//...(省略其他)
//设置按钮的大小、鼠标手势
optionsButton.setPreferredSize(new Dimension(90, 30));
optionsButton.setCursor(new Cursor(Cursor.HAND_CURSOR));
//...(省略其他)
//按钮所在的panel
JPanel panel = new JPanel();
//添加按钮
panel.add(optionsButton);
...(省略其他)
    
//用于显示错误信息的panel
JPanel errerPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0));
errerLabel = new JLabel();//用于显示错误信息的label
//设置字体颜色、长度、高度
//...(省略)
errerPanel.add(errerLabel);//主panel,把之前两个panel加进去
JPanel content=new JPanel(new BorderLayout());
content.add(statusPanel, BorderLayout.CENTER);
content.add(errerPanel, BorderLayout.SOUTH);
content.setBorder(border);
p1.add(content, BorderLayout.CENTER);
p1.add(panel, BorderLayout.SOUTH);
p1.setBounds(0, 0, 498, 500);//设置显示区域
mainFrame.add(p1);
mainFrame.setResizable(false);
mainFrame.setVisible(true);




这样基本可以实现主界面的样式。这里讲讲两个按钮(runButton、stopButton)的实现。大家自动swing有一个主线程,大家可以打印试试Thread.currentThread().getName()看看。因为本程序要使用socket程序会一直阻塞,使得其他布局或功能没有办法完成。可以通过这样解决:

runButton.addActionListener(new ActionListener() {
  public void actionPerformed(ActionEvent e) {
    //...省略其他代码
    new Thread() {//new一个线程,这样程序可以继续往下执行,及label2等设置文本能顺利执行
      public void run() {
      flag = true;sendMessage();// 执行方法,用到socket,会网络阻塞
      }
    }.start();
    label2.setText("正在连接...");
    label3.setText(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
    label4.setText("运行正常");
  }
});

程序设计的时候有一个暂停的功能,思路:通过全局变量来 flag(boolean类型) 控制,当运行的时候给flag赋值为true,暂停的时候为false;看代码:

stopButton.addActionListener(new ActionListener() {
  public void actionPerformed(ActionEvent e) {
    //...省略其他代码
    label4.setText("暂停运行");
    flag = false;
  }
});


下面是 sendMessage() 的代码:

try {   
   String string=null;
   while (flag) {
  if (socket == null) {
    socket=new Socket(LINKIP, LINKPORT);
  }   
  // 输出流,立即刷新
  PrintWriter os = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(), "UTF-8"), true);
  // 输入流
  BufferedReader is = new BufferedReader(new InputStreamReader(socket.getInputStream(),"UTF-8"));
  string = this.readSSLXML(path);//从地址为path的地方读取xml文件,使用了XMLConfiguration(commons-configuration-1.9.jar,依赖包commons-lang-2.3.jar以下),可以搜索下载,然后返回string类型的值
  Thread.sleep(TIMESPACING);//睡眠指定时间
  os.println(string);// 往Server写值
  count++;//统计
  String msg=null;
  try {
    if ((msg=is.readLine()) != null) {
      successCount++;//统计
            //其他处理(省略)
    }
  } catch (IOException e) {
    socket=new Socket(LINKIP, LINKPORT);//异常处理,重新建立连接
  }     
  System.out.println("服务器接收:"+ successCount + "条,未发送:" + errorCount + "条");
   }
      
} catch (Exception e) {
  exceptionHandler();//异常处理
}



相当于递归调用,重复20次,设置flag=false 停止运行。并在屏幕右下角弹出提示框,图标闪动,播放警示音提醒用户,跟QQ功能相似。下面是 exceptionHandler() 的代码:

private void exceptionHandler() {   
  if (trySendMessage >= TRY_COUNT) {// 重试次数
    flag = false;
    errerLabel.setIcon(new ImageIcon(clazz.getResource("error.png")));
    errerLabel.setText("连接失败,请检查服务器状态,网络连接状态,配置文件路径及格式!");
    runButton.setEnabled(true);
    optionsButton.setEnabled(true);
    stopButton.setEnabled(false);
    runButton.setCursor(new Cursor(Cursor.HAND_CURSOR));      
    // 屏幕右下角提示框
    TipWindow tw = new TipWindow();
    tw.init();
    tw.run();
    
    while (!flag) {
      try {// 系统托盘闪动
        trayIcon.setImage(toolkit.createImage(""));
        toolkit.beep();// 警示音
        Thread.sleep(400);
        Image image = toolkit.createImage(clazz.getResource("icon.jpg"));
        trayIcon.setImage(image);
        Thread.sleep(400);
      } catch (Exception e) {
        e.printStackTrace();
      }
    }
    return;
  }
  trySendMessage++;
  System.err.println("连接失败,正在重试第" + trySendMessage + "次...");
  sendMessage();//再次调用sendMessage()方法
}




下面是右下角提示框的代码:

/**
 * 屏幕右下角提示框
 * 
 * @author admin
 * 
 */
class TipWindow {
  private Dimension dim;
  private int x, y, width=300, height=180;
  private Insets screenInsets;
  private JDialog jd;
  
  private void init() {
    jd = new JDialog(mainFrame, "设备状态读取程序运行异常提示");
    jd.setSize(width, height);
    dim = toolkit.getScreenSize();
    screenInsets = toolkit.getScreenInsets(jd
        .getGraphicsConfiguration());
    x = (int) (dim.getWidth() - width);
    y = (int) (dim.getHeight() - screenInsets.bottom);
    
    // 提示内容
    JLabel info=new JLabel("设备状态读取程序运行异常,可能遇到下列问题:",JLabel.LEFT);
    info.setFont(new Font("微软雅黑", Font.PLAIN, 12));
    JTextArea textArea = new JTextArea("1、网络未连接或中断\n2、服务器未开启或挂起\n3、配置状态文件不存在或格式错误\n4、尝试重新启动程序");
    textArea.setEditable(false);
    textArea.setLineWrap(true);// 自动换行
    textArea.setPreferredSize(new Dimension(250, 120));// 分割组件的宽度     
    //设置文本颜色、字体
          textArea.setForeground(Color.RED);
          textArea.setSelectionColor(Color.WHITE);
    textArea.setSelectedTextColor(Color.RED);
    textArea.setBackground(Color.WHITE);
    textArea.setFont(new Font("微软雅黑", Font.PLAIN, 12));
    
    JPanel center=new JPanel();
    center.add(info,BorderLayout.NORTH);
    center.add(textArea,BorderLayout.CENTER);
    
    JPanel main = new JPanel(new BorderLayout(10,0));
    main.add(center, BorderLayout.CENTER);
    jd.add(main);
    jd.setAlwaysOnTop(true);
    jd.setUndecorated(true);
    jd.setResizable(false);
    jd.setVisible(true);
  }
  // 显示--渐渐滑入的效果
  private void run() {
    for (int i = 0; i <= jd.getHeight(); i += 10) {
      try {
        jd.setLocation(x, y - i);
        Thread.sleep(5);
      } catch (InterruptedException ex) {
      }
    }
  }
}



程序不希望直接关闭,所以用到了最小化到托盘的功能,这里使用了自定义的JPopupMenu ,但是它不能直接加到TrayIcon(只允许添加PopupMenu)里面,需要处理一下,具体看代码

// 最小到系统托盘
private void minTray() {
  try {
    systemTray = SystemTray.getSystemTray();//获取系统托盘
    // 右击时添加的菜单项
    Image openImage = toolkit.createImage(clazz.getResource("house.png"));//图标
    openItem = new JMenuItem("显示主窗口", new ImageIcon(openImage));//初始化,使用JMenuItem的好处是可以添加图标,样式更美观
    openItem.setCursor(new Cursor(Cursor.HAND_CURSOR));
    ...(省略其他)
    //按钮的功能实现
    openItem.addActionListener(new ActionListener() {
      @Override
      public void actionPerformed(ActionEvent event) {
        mainFrame.setVisible(true);
      }
    });
    ...(省略其他)
    //退出按钮功能
    exitItem.addActionListener(new ActionListener() {
      @Override
      public void actionPerformed(ActionEvent event) {
        if (confirm())System.exit(0);//程序退出,有个确认功能,也很简单,两个按钮,返回true或者false。true关闭,false忽略
      }
    });
    
    // 弹出式菜单,即选中右击时弹出的菜单
    final JPopupMenu popupMenu = new JPopupMenu();
    popupMenu.setPreferredSize(new Dimension(120, 165));//设置右键弹出来的菜单大小
    popupMenu.add(openItem);
    popupMenu.addSeparator();//分割线
    popupMenu.add(startItem);
    popupMenu.add(stopItem);
    popupMenu.addSeparator();
    popupMenu.add(aboutItem);
    popupMenu.addSeparator();
    popupMenu.add(exitItem);
    
    Image image = toolkit.createImage(clazz.getResource("icon.jpg"));
    trayIcon = new TrayIcon(image, "设备状态读取程序", null);// 实例化托盘图标
    trayIcon.setImageAutoSize(true);
    trayIcon.addMouseListener(new MouseAdapter() {
      public void mouseReleased(MouseEvent e) {
        if (e.getButton() == MouseEvent.BUTTON3) {//右键            
          //之前是没有这句话的,第一次点的时候因为popupMenu 还没有初始化,
          //因为不知道大小,显示的话就挨到屏幕最底部,而不是从鼠标点击的point开始,所以这样处理了一下
          int y = (int) (e.getY() - (popupMenu.getHeight() == 0 ? 165 : popupMenu.getHeight()));  
          popupMenu.setLocation(e.getX(), y);//设置显示开始的point
          popupMenu.setInvoker(popupMenu);//加载菜单
          popupMenu.setVisible(true);
        }
      }
      public void mouseClicked(MouseEvent mouse) {
        if (mouse.getButton() == MouseEvent.BUTTON1) {//左键
          mainFrame.setVisible(true);
        }
      }
    });
    systemTray.add(trayIcon);//把图标显示到系统托盘
  } catch (Exception ex) {
    ex.printStackTrace();
  }
}



基本功能就这些,大家可以参考跟指正。

本人的处女作,写的不好望大家见谅!谢谢


相关文章
|
5月前
|
安全 Java 开发者
Swing 的线程安全分析
【8月更文挑战第22天】
78 4
|
5月前
|
安全 Java API
|
5月前
|
设计模式 安全 前端开发
Swing 是线程安全的吗?
【8月更文挑战第21天】
59 0
|
8月前
|
数据处理
Swing通过后台线程实现页面更新
Swing通过后台线程实现页面更新
107 2
|
8月前
swing编写client端及多线程server端之server端
swing编写client端及多线程server端之server端
|
Java 开发工具 计算机视觉
java swing 人脸签到系统 ----- 调用 opencv 多线程
java swing 人脸签到系统 ----- 调用 opencv 多线程
104 0
|
安全 API 调度
Swing 的任务线程与 EDT 事件分发队列模型(下)
Swing 的任务线程与 EDT 事件分发队列模型(下)
213 0
Swing 的任务线程与 EDT 事件分发队列模型(下)
|
28天前
|
NoSQL Redis
单线程传奇Redis,为何引入多线程?
Redis 4.0 引入多线程支持,主要用于后台对象删除、处理阻塞命令和网络 I/O 等操作,以提高并发性和性能。尽管如此,Redis 仍保留单线程执行模型处理客户端请求,确保高效性和简单性。多线程仅用于优化后台任务,如异步删除过期对象和分担读写操作,从而提升整体性能。
62 1
|
3月前
|
存储 消息中间件 资源调度
C++ 多线程之初识多线程
这篇文章介绍了C++多线程的基本概念,包括进程和线程的定义、并发的实现方式,以及如何在C++中创建和管理线程,包括使用`std::thread`库、线程的join和detach方法,并通过示例代码展示了如何创建和使用多线程。
72 1
|
3月前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
52 3