就目前我们做的c/s的简单交互而言主要用到了三个类:客户端线程类ClientThread 、 服务端监听类ChatServer 、 服务端线程类ChatThread。
简单理解:通过socket建立客户端与服务器的连接,在客户端和服务器分别用输入输出流来获得和写出消息,通过消息的传递达到交互的目的。
(ps: socket 实现客户端套接字 套接字是两台机器间通信的端点)
打开输入输出流(用字符流封装) InputStream in = socket.getInputStream(); OutputStream out = socket.getOutputStream(); //将字节流转成字符流 reader = new BufferedReader(new InputStreamReader(in, "GB2312")); writer = new BufferedWriter(new OutputStreamWriter(out, "GB2312")); //读消息(线程循环读取) while(line != null){ line = reader.readLine(); } //发送消息 writer.write(msg); writer.flush();
客户端: 登陆界面类LoginUI 、 客户消息处理线程类ClientThread 、 聊天界面类ClientUI。
(1)登陆界面:主要是用来创建一个登陆的窗口,获得用户名以及密码
package 1; /** * (1) */ import java.awt.FlowLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPasswordField; import javax.swing.JTextField; public class LoginUI extends JFrame{ private ClientThread ct; //入口函数 public static void main(String args[]){ LoginUI loginUI = new LoginUI(); loginUI.init(); } public void init(){ //创建一个登陆界面 this.setTitle("登陆界面"); this.setSize(200,200); this.setResizable(false); this.setDefaultCloseOperation(3); this.setLayout(new FlowLayout()); //创建标签 JLabel jb1 = new JLabel("用户名:"); this.add(jb1); //创建文本输入框 final JTextField username = new JTextField(10); this.add(username); JLabel jb2 = new JLabel("密 码:"); this.add(jb2); final JPasswordField passwd = new JPasswordField(10); this.add(passwd); //创建一个按钮对象 JButton jb3 = new JButton("登陆"); this.add(jb3); jb3.setActionCommand("login"); //给按钮添加监听器(内部类) jb3.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent e) { //创建一个用户线程,在构造函数中传入界面对象(LoginUI.this)!!!!!!!!!!!!! 、ip、端口号、用户名、密码 ct = new ClientThread(LoginUI.this,"127.0.0.1",8008,username.getText(),passwd.getText()); //启动线程 Thread th = new Thread(ct); th.start(); } }); this.setVisible(true); } public void LoginResult(boolean result){ if(result == false){ //JOptionPane 有助于方便地弹出要求用户提供值或向其发出通知的标准对话框 (静态方法直接用类名调用) JOptionPane.showMessageDialog(this,"登陆失败!!哈哈哈哈。。。"); }else{ //登陆界面消失 this.dispose(); //创建一个聊天窗口 ClientUI ui = new ClientUI(ct); ui.init(); //将窗口作为参数传给用户处理线程 ct.setClientUI(ui); } } }
(2)线程类:将登陆结果返回给登陆界面、将服务器传送来的消息发给聊天界面对象
package 1; /** * 用户登陆处理线程 (2) */ import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.net.Socket; import java.net.UnknownHostException; import javax.swing.JFrame; public class ClientThread extends JFrame implements Runnable{ private LoginUI loginUI; private String ip; private int port; private String username; private String passwd; private ClientUI ui; private Socket socket; private BufferedWriter writer; private BufferedReader reader; private boolean runFlag = true; private boolean isLogin = false; //重载构造函数 public ClientThread(LoginUI loginUI,String ip,int port,String username,String passwd){ this.loginUI = loginUI; this.ip = ip; this.port = port; this.username = username; this.passwd = passwd; } //得到用户聊天界面对象 public void setClientUI(ClientUI ui){ this.ui = ui; } public boolean connectServer(){ try { //创建一个套接字 socket = new Socket(ip,port); //得到输入输出流对象 InputStream in = socket.getInputStream(); OutputStream out = socket.getOutputStream(); //将字节流转成字符流 reader = new BufferedReader(new InputStreamReader(in, "GB2312")); writer = new BufferedWriter(new OutputStreamWriter(out, "GB2312")); return true; } catch (UnknownHostException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return false; } public void run(){ if(connectServer() == false){ System.out.println("登陆服务器失败!"); return; } login(); if(isLogin == false){ return; } String msg; try { //通过读入流获得消息 msg = reader.readLine(); while(runFlag && msg != null){ //将消息发送给聊天界面 ui.onMsg(msg); msg = reader.readLine(); } } catch (IOException e) { e.printStackTrace(); } System.out.println("ClientThread退出来了"); } public void sendMsg(String msg){ try { writer.write(msg+"\r\n"); writer.flush(); } catch (IOException e) { e.printStackTrace(); } } public void login(){ try { writer.write(username+"\r\n"); writer.write(passwd+"\r\n"); writer.flush(); String result = reader.readLine(); if("LoginOK".equals(result)){ System.out.println("登陆成功!"); isLogin = true; //创建一个新的聊天界面 loginUI.LoginResult(true); }else{ System.out.println(result); //弹出一个窗口显示登陆失败了 loginUI.LoginResult(false); } } catch (IOException e) { e.printStackTrace(); } } }
(3)聊天界面类:将消息显示在指定区域。
package 1; import java.awt.FlowLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JTextArea; public class ClientUI extends JFrame{ //声明一个线程 private ClientThread ct; public ClientUI(ClientThread ct){ this.ct = ct; } //创建一个指定行列的空文本域 “聊区” JTextArea logArea = new JTextArea(13,40); public void init(){ //创建聊天窗口 this.setTitle("聊吧"); this.setSize(500,400); this.setResizable(false); this.setDefaultCloseOperation(3); this.setLayout(new FlowLayout()); this.add(logArea); //创建一个指定行列的文本域 “发送消息区” final JTextArea sendMsg = new JTextArea(4,40); this.add(sendMsg); //创建一个发送按钮 JButton jb1 = new JButton("发送"); this.add(jb1); jb1.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent e) { //发送文本区的内容 String msg = sendMsg.getText().toString(); //线程将消息发送过来 ct.sendMsg(msg); //将发送区清空 sendMsg.setText(""); } }); this.setVisible(true); } //接受服务器发送过来的消息 public void onMsg(String msg ){ //将消息显示在“聊吧”界面的聊天区 logArea.setText(logArea.getText()+msg+"\r\n"); } }
服务端: 服务端监听类ChatServer 、 服务端线程类ChatThread 、用户存储队列ChatTools、用户数据类DateTools。
(1)监听类的作用:监听端口 (监听一个端口号,等待用户的访和 创建一个服务端线程对象 。
package 1; /** * (1) */ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket; public class ChatServer { public static void main(String[] args) { new ChatServer().setupServer(8008); } public void setupServer(int port) { try { //监听一个端口号码,等待用户的访问 ServerSocket ss = new ServerSocket(port); //循环等待客户端的访问 while(true){ //得到socket Socket socket = ss.accept(); //将socket传给服务端线程进行处理 ChatThread ct = new ChatThread(socket); //启动线程 ct.start(); } } catch (IOException e) { e.printStackTrace(); } } }
(2)线程类的作用:打开输入输出流(用字符流封装) 读消息(线程循环读取)、 处理消息、 发送消息给登录界面(不需要线程)、创建一个队列保存所有用户信息 。
package 1; /** * (2) */ import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.net.Socket; public class ChatThread extends Thread { private Socket socket; private String username; private boolean isLogin = false; private BufferedWriter writer; private BufferedReader reader; public ChatThread(Socket socket){ this.socket = socket; } public void run() { try { InputStream in = socket.getInputStream(); OutputStream out = socket.getOutputStream(); //将字节流转成字符流 reader = new BufferedReader(new InputStreamReader(in, "GB2312")); writer = new BufferedWriter(new OutputStreamWriter(out, "GB2312")); login(); //将登录成功的用户添加到在线用户队列 ChatTools.addClient(this); String line = reader.readLine(); while(line != null){ ChatTools.castMsg(username +"说:"+line); line = reader.readLine(); } close(); //ChatTools.castMsg(username +"走了"); } catch (Exception e) { close(); e.printStackTrace(); } } private void login() throws IOException { String msg = null; while(isLogin == false){ String username = reader.readLine(); String passwd = reader.readLine(); //从数据库中取密码 String passwd2 = DateTools.getPasswd(username); //如果为null,说明数据库里面没有该用户 if(passwd2 == null){ msg = "用户不存在\r\n"; writer.write(msg); writer.flush(); } else { if(passwd2.equals(passwd)){ isLogin = true; this.username = username; } } if(isLogin == false){ msg = "密码错误!\r\n"; writer.write(msg); writer.flush(); } } //给用户登陆界面一个信号,决定其下一步是弹出“登录失败”还是一个新的聊天窗口 msg = "LoginOK\r\n"; writer.write(msg); writer.flush(); } public void sendMsg(String msg) { try { writer.write(msg); writer.flush(); } catch (IOException e) { e.printStackTrace(); } } public String getUsername() { return username; } public void close() { try { ChatTools.remove(this); socket.close(); } catch (IOException e) { e.printStackTrace(); } } }
(4)用户存储队列ChatTools :将登录成功的用户加入到队列中(代码略)
(5)用户数据类DateTools : 创建一个哈希表(key,value),将指定值存入。(代码略)