就目前我们做的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),将指定值存入。(代码略)