概述
使用swing搭建客户端界面;
界面源代码,绑定按钮监听响应操作,登录、聊天、文件传输。
import com.client.Client;
import com.client.SendFile;
import com.message.Message;
import javax.swing.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.File;
public class LoginFrame extends JFrame {
//登陆控件
JTextField txtUserField;
JTextField txtPwdfField;
JButton btnLogin;
JButton btnReg;
//好友区控件
JList list;
DefaultListModel model;
//消息区控件
JTextArea txtHistory = new JTextArea();
JTextArea txtSend = new JTextArea();
JButton btnSend = new JButton();
//发送文件控件
JTextField txtSendFile = new JTextField();
JButton btnSendFile = new JButton();
JProgressBar progressBar = new JProgressBar();
JLabel labSendFile = new JLabel();
private static LoginFrame frame;
public static LoginFrame getLoginFrame() {
if (frame == null) {
frame = new LoginFrame();
}
return frame;
}
private LoginFrame() {
System.out.println(Thread.currentThread().getName());
// TODO Auto-generated constructor stub
txtUserField = new JTextField();
txtPwdfField = new JTextField();
btnLogin = new JButton();
btnLogin.setText("登录");
btnReg = new JButton();
btnReg.setText("注册");
//左侧登录块
this.setLayout(null);
this.add(txtUserField);
txtUserField.setBounds(10, 10, 100, 30);
this.add(txtPwdfField);
txtPwdfField.setBounds(10, 50, 100, 30);
this.add(btnLogin);
btnLogin.setBounds(10, 90, 100, 30);
btnReg.setBounds(10, 130, 100, 30);
this.add(btnReg);
// JList是一个view,要添加数据,就是添加到model
model = new DefaultListModel();// Model
list = new JList(model);// View
this.add(list);
list.setBounds(120, 10, 100, 420);
//消息块
txtHistory.setBounds(240, 10, 300, 200);
this.add(txtHistory);
txtSend.setBounds(240, 220, 300, 200);
this.add(txtSend);
btnSend.setBounds(460, 430, 80, 30);
this.add(btnSend);
btnSend.setText("发送");
//发送文件
txtSendFile.setBounds(10, 470, 300, 30);
this.add(txtSendFile);
labSendFile.setText("发送文件");
labSendFile.setBounds(10, 440, 80, 30);
this.add(labSendFile);
btnSendFile.setBounds(10, 520, 100, 30);
btnSendFile.setText("发送文件");
this.add(btnSendFile);
//进度条
progressBar.setBounds(320, 470, 200, 30);
this.add(progressBar);
progressBar.setMaximum(100);
progressBar.setValue(0);
//窗口布局
setSize(600, 700);
setVisible(true);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
txtUserField.setText("QQ号");
txtPwdfField.setText("密码");
//登录的监听事件
btnLogin.addMouseListener(new MouseAdapter(){
@Override
public void mouseClicked(MouseEvent e) {
super.mouseClicked(e);
String msg= Message.requestLogin(txtUserField.getText(),txtPwdfField.getText());
Client.getClient().send(msg);
}
});
//发送消息监听事件
btnSend.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
super.mouseClicked(e);
String msg=Message.requestMessage(txtUserField.getText(),list.getSelectedValue().toString(),txtSend.getText());
//txtUserField.getText()拿到发送用户
//list.getSelectedValue().toString()拿到选择的好友
//txtSend.getText()聊天框信息
Client.getClient().send(msg);
}
});
//发送文件监听事件
btnSendFile.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
super.mouseClicked(e);
//文件选择器
JFileChooser chooser=new JFileChooser();
chooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);//文件名和文件
//返回一个int值,分别是 JFileChooser.APPROVE_OPTION,
// JFileChooser.CANCEL_OPTION和JFileChooser.ERROR_OPTION。
// 在获取用户选择的文件之前,通常先验证返回值是否为APPROVE_OPTION.
int num=chooser.showOpenDialog(null);
//若选择了文件,则打印选择了什么文件
if(num==JFileChooser.APPROVE_OPTION)
{
File file=chooser.getSelectedFile();//获取文件
//开启单独的文件传输线程,处理并发。使用新的端口传输文件
String msg=Message.requestSendFile(txtUserField.getText(),list.getSelectedValue().toString(),file.getName(),file.length(),"127.0.0.1",10001);
Client.getClient().send(msg);
new Thread(new SendFile(file,10001)).start();
}
}
});
}
public JTextField getTxtUserField() {
return txtUserField;
}
public void setTxtUserField(JTextField txtUserField) {
this.txtUserField = txtUserField;
}
public JTextField getTxtPwdField() {
return txtPwdfField;
}
public void setTxtPwdField(JTextField txtPwdfField) {
this.txtPwdfField = txtPwdfField;
}
public JButton getBtnLogin() {
return btnLogin;
}
public void setBtnLogin(JButton btnLogin) {
this.btnLogin = btnLogin;
}
public JButton getBtnReg() {
return btnReg;
}
public void setBtnReg(JButton btnReg) {
this.btnReg = btnReg;
}
public JTextField getTxtPwdfField() {
return txtPwdfField;
}
public void setTxtPwdfField(JTextField txtPwdfField) {
this.txtPwdfField = txtPwdfField;
}
public DefaultListModel getModel() {
return model;
}
public void setModel(DefaultListModel model) {
this.model = model;
}
public JTextArea getTxtHistory() {
return txtHistory;
}
public void setTxtHistory(JTextArea txtHistory) {
this.txtHistory = txtHistory;
}
public JTextArea getTxtSend() {
return txtSend;
}
public void setTxtSend(JTextArea txtSend) {
this.txtSend = txtSend;
}
public JTextField getTxtSendFile() {
return txtSendFile;
}
public void setTxtSendFile(JTextField txtSendFile) {
this.txtSendFile = txtSendFile;
}
public JButton getBtnSendFile() {
return btnSendFile;
}
public void setBtnSendFile(JButton btnSendFile) {
this.btnSendFile = btnSendFile;
}
public JProgressBar getProgressBar() {
return progressBar;
}
public void setProgressBar(JProgressBar progressBar) {
this.progressBar = progressBar;
}
public JLabel getLabSendFile() {
return labSendFile;
}
public void setLabSendFile(JLabel labSendFile) {
this.labSendFile = labSendFile;
}
}
核心需要两个终端(客户端Client类、服务端Server类),四个线程类(客户端线程类ClientHandler,客户端接收文件线程类RecvFile、客户端发送文件线程类SendFile、服务端线程类ServerHandler),用于处理并发情况,解决同时访问与接收数据。
客户端通过传输字符串组合成不同请求
public class Message {
public static final String LOGIN="LOGIN";//登陆消息的类型
public static final String SPLIT="&&";//分隔符
public static final String SUC="SUC";//成功
public static final String FAIL="FAIL";//失败
public static final String RELOGIN="RELOGIN";//重复登陆
public static final String FRIENDS="FRIENDS";//好友
public static final String CHAT="CHAT";//聊天
public static final String FILE="FILE";//文件
//登录消息验证
//LOGIN&&10001&&123456
public static String requestLogin(String userId, String userPassword)
{
String msg=LOGIN+SPLIT+userId+SPLIT+userPassword;
return msg;
}
//获取好友列表请求
public static String requestFriends(String userId)
{
String msg=FRIENDS+SPLIT+userId;
return msg;
}
//发送信息请求
public static String requestMessage(String userId,String friendId,String chatMessage)
{
String msg=CHAT+SPLIT+userId+SPLIT+friendId+SPLIT+chatMessage;
return msg;
}
//发送文件请求
public static String requestSendFile(String userId,String friendId,String filePath,long fileSize,String ip,int port)
{
String msg=FILE+SPLIT+userId+SPLIT+friendId+SPLIT+filePath+SPLIT+new Long(fileSize).toString()+SPLIT+ip+SPLIT+new Integer(port).toString();
return msg;
}
}
服务端同样采用字符串组合成不同的响应返回给客户端
import com.bean.User;
import java.util.ArrayList;
public class Message {
public static final String LOGIN="LOGIN";//登陆消息的类型
public static final String SPLIT="&&";//分隔符
public static final String SUC="SUC";//成功
public static final String FAIL="FAIL";//失败
public static final String FRIENDS="FRIENDS";
public static final String RELOGIN="RELOGIN";//重复登陆
public static final String CHAT="CHAT";//聊天
public static final String FILE="FILE";//文件
//响应客户端登陆
public static String resPoneLogin(String result){
String msg = LOGIN+SPLIT+result;
return msg;
}
//响应客户端好友请求
public static String resPoneFriends(ArrayList<User> friendsLIst)
{
// FRIENDS&&好友1&&好友2
String msg=FRIENDS;
for (User user:friendsLIst) {
msg+=SPLIT+user.getUserId();
}
return msg;
}
}
这个小软件使用的是本地IP:127.0.0.1,也可以使用别人的IP地址,要注意统一修改。
客户端
可以向服务端发送请求,也可以接受服务端的数据;
实现代码
import java.io.IOException;
import java.io.PrintWriter;
import java.net.Socket;
//客户端
public class Client {
private Socket socket;
private static Client client;
private static PrintWriter writer;
//单例模式
public static Client getClient() {
if (client == null)
{
client=new Client();
}
return client;
}
public Client()
{
try {
//创建socket对象,指定IP地址以及端口号
socket=new Socket("127.0.0.1",10086);
//取出socket对象的输出流:socket.getOuputStream();
writer=new PrintWriter(socket.getOutputStream());
//启动监听线程
new Thread(new ClientHandler(socket)).start();
} catch (IOException e) {
e.printStackTrace();
}
}
//发送各类请求
public static void send(String msg)
{
//使用该输出流写内容,该内容可以被服务端的输入流获取
writer.println(msg);
writer.flush();
}
}
客户端线程类处理各类响应
实现代码:
import com.message.Message;
import com.ui.LoginFrame;
import javax.swing.*;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
public class ClientHandler implements Runnable {
private Socket socket;
private BufferedReader reader;
private PrintWriter writer;
public ClientHandler(Socket socket) {
this.socket=socket;
try {
reader=new BufferedReader((new InputStreamReader(socket.getInputStream())));
writer=new PrintWriter(socket.getOutputStream());
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
boolean isOnline=true;
while (isOnline)
{
//接收服务器的信息
try {
String msg=reader.readLine();
String[] data=msg.split(Message.SPLIT);
if(data[0].equals(Message.LOGIN))
{
login(data);
}else if(data[0].equals(Message.FRIENDS))
{
getFriends(data);
}else if(data[0].equals(Message.CHAT))
{
getChatMsg(data);
}else if(data[0].equals(Message.FILE))
{
getFile(data);
}
} catch (IOException e) {
e.printStackTrace();
isOnline=false;
}
}
}
private void getFile(String[] data) {
//System.out.println(data);
//开启线程接收文件
new Thread(new RecvFile(data[5],data[6],data[3],data[4])).start();
}
//接收聊天消息
private void getChatMsg(String[] data) {
Date nowDate=new Date();
DateFormat dateFormat=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String timeStr=dateFormat.format(nowDate);
String chatMsg=timeStr+" "+data[1]+" 说:\n"+data[3]+"\n";
LoginFrame.getLoginFrame().getTxtHistory().append(chatMsg);
}
//得到好友列表
private void getFriends(String[] data) {
//事件分发线程
SwingUtilities.invokeLater((new Runnable() {
@Override
public void run() {
//清空好友列表
LoginFrame.getLoginFrame().getModel().clear();
//添加显示
LoginFrame.getLoginFrame().getModel().addElement("我的好友");
for (int i=1;i<data.length;i++)
{
LoginFrame.getLoginFrame().getModel().addElement(data[i]);//添加好友信息到界面
}
}
}));
}
//得到登录信息
private void login(String[] data) {
if(data[1].equals(Message.SUC))
{
JOptionPane.showMessageDialog(null,"登录成功");
String msg=Message.requestFriends(LoginFrame.getLoginFrame().getTxtUserField().getText());
writer.println(msg);
writer.flush();
}else if(data[1].equals(Message.RELOGIN))
{
JOptionPane.showMessageDialog(null,"重复登录");
}
else {
JOptionPane.showMessageDialog(null,"登录失败");
}
}
}
客户端处理文件传输线程类
发送文件实现代码I/O读取传输:
import com.ui.LoginFrame;
import javax.swing.*;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class SendFile implements Runnable {
private File file;
private int port;
private ServerSocket serverSocket;
private Socket socket;
private FileInputStream fileInputStream;
private BufferedOutputStream bufferedOutputStream;
public SendFile(File file, int port) {
this.file = file;
this.port = port;
}
@Override
public void run() {
LoginFrame.getLoginFrame().getProgressBar().setMaximum((int) file.length());
try {
serverSocket=new ServerSocket(port);
socket=serverSocket.accept();
fileInputStream=new FileInputStream(file);
bufferedOutputStream=new BufferedOutputStream(socket.getOutputStream());
byte[] data=new byte[1024];
int num=0;
while (true)
{
int length=fileInputStream.read();
if(length<0)
{
break;
}
num+=length;
LoginFrame.getLoginFrame().getProgressBar().setValue(num);
bufferedOutputStream.write(data,0,length);
bufferedOutputStream.flush();
}
} catch (IOException e) {
e.printStackTrace();
}finally {
JOptionPane.showMessageDialog(null,"文件传输成功");
try {
if (fileInputStream==null)
{
fileInputStream.close();
}
if (bufferedOutputStream==null)
{
bufferedOutputStream.close();
}
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
接收实现代码:
import com.ui.LoginFrame;
import javax.swing.*;
import java.io.BufferedInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.Socket;
public class RecvFile implements Runnable {
private String ip;
private int port;
private String fileName;
private String fileSize;
private Socket socket;
private FileOutputStream fileOutputStream;
private BufferedInputStream bufferedInputStream;
public RecvFile(String ip, String port, String fileName, String fileSize) {
this.ip = ip;
this.port = Integer.valueOf(port);
this.fileName = fileName;
this.fileSize = fileSize;
}
@Override
public void run() {
LoginFrame.getLoginFrame().getProgressBar().setMaximum(Integer.parseInt(fileSize));
try {
socket=new Socket(ip,port);
//存储到本地的路径加文件名
fileOutputStream=new FileOutputStream("I:/VBlog/QQ/QQClient/temp/"+fileName);
//字符流
bufferedInputStream=new BufferedInputStream(socket.getInputStream());
byte[]data=new byte[1024];
int num=0;
while (true)
{
int length=bufferedInputStream.read();
if(length<0)
{
break;
}
num+=length;
LoginFrame.getLoginFrame().getProgressBar().setValue(num);
//存入本地
fileOutputStream.write(data,0,length);
fileOutputStream.flush();
}
} catch (IOException e) {
e.printStackTrace();
}finally {
JOptionPane.showMessageDialog(null,"文件接收成功");
try {
if(fileOutputStream==null) {
fileOutputStream.close();
}
if(bufferedInputStream==null) {
bufferedInputStream.close();
}
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
服务端
Server类,实现与客户端socket绑定连接。使用多线程达成多个客户端同时连接,接受客户端请求的数据,自身也可以发送数据。
配置文件sqlite:
读取配置文件,获取对应的jdbc连接字符串:
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Properties;
public class Config {
public static String DRIVER;
public static String URL;
public static String DBTYPE;
public static void init(){
Properties properties = new Properties();
try {
//System.out.println(new FileInputStream("config.ini"));
properties.load(new FileInputStream("I:\\VBlog\\QQ\\QQService\\config.ini"));
DRIVER = properties.getProperty("DRIVER");
URL = properties.getProperty("URL");
DBTYPE = properties.getProperty("DBTYPE");
System.out.println(DBTYPE);
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
Dao层,获取对应数据库的操作对象:
import com.config.Config;
import java.util.HashMap;
import java.util.Map;
public class DaoFactory {
private static Map<String, UserDao> userDaoMap = new HashMap<String, UserDao>();
public static UserDao getUserDao(){
if (Config.DBTYPE.equals("sqlite")) {
return new UserDaoSqLite();
} else if (Config.DBTYPE.equals("oracle")) {
//return new UserDaoOracle();
} else if (Config.DBTYPE.equals("mysql")) {
//return new UserDaoMySql();
}
return null;
}
}
实体类操作:
import com.bean.User;
import java.sql.SQLException;
import java.util.ArrayList;
public interface UserDao {
public User login(String userId, String userPassword) throws SQLException;
//public User register(User user);
public ArrayList<User> friends(String user_id) throws SQLException;
}
连接数据库:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import com.config.Config;
public class DBConnection {
//private static final String DRIVER = "org.sqlite.JDBC";
//private static final String URL = "jdbc:sqlite:student.db";
private static Connection connection;//数据库连接时静态
public static Connection getConnection(){
if (connection == null) {
//数据库驱动加载
try {
Class.forName(Config.DRIVER);
connection = DriverManager.getConnection(Config.URL);
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return connection;
}
}
数据库操作:
import com.bean.User;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
class UserDaoSqLite implements UserDao {
@Override
public User login(String userId, String userPassword) throws SQLException {
Connection connection=DBConnection.getConnection();
String sql="select * from tbl_user where user_id=? and password=?";
PreparedStatement statement;
statement=connection.prepareStatement(sql);
statement.setString(1,userId);
statement.setString(2,userPassword);
ResultSet resultSet = statement.executeQuery();
User user = null;
if(resultSet.next())
{
user = new User();
user.setUserId(resultSet.getString("user_id"));
user.setUserPassword(resultSet.getString("password"));
return user;
}
statement.close();
resultSet.close();
return user;
}
@Override
public ArrayList<User> friends(String userId) throws SQLException {
ArrayList<User> friendsList=new ArrayList<User>();
Connection connection=DBConnection.getConnection();
String sql="select * from v_friends where user_id=?";
PreparedStatement statement=connection.prepareStatement(sql);
statement.setString(1,userId);
ResultSet resultSet=statement.executeQuery();
while (true){
//读取第一条数据
User user=createFriendsList(resultSet);
if(user==null)
{
//为空跳出循环
break;
}else {
friendsList.add(user);
}
}
statement.close();
resultSet.close();
return friendsList;
}
private User createFriendsList(ResultSet resultSet) throws SQLException {
User user=null;
if(resultSet.next())
{
user=new User();
user.setUserId(resultSet.getString("friend_id"));
}
return user;
}
}
服务端实现代码:
import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
//服务端
public class Server {
private ServerSocket serverSocket;
public Server()
{
try {
//初始化服务端socket并且绑定10086端口
serverSocket=new ServerSocket(10086);
Socket socket=null;
//记录客户端数量
int count=0;
//循环监听等待客户端的连接
while (true) {
System.out.println("等待客户端连接");
//创建接收接口
socket = serverSocket.accept();
System.out.println("客户端已连接");
//启动监听线程
new Thread(new ServerHandler(socket)).start();
count++;
System.out.println("客户端的数量:"+count);
InetAddress address=socket.getInetAddress();
System.out.println("当前客户端的IP:"+address.getHostAddress());
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
服务端线程类
服务端线程类:帮助服务端处理读取发送数据;
ServerHandler类:
public class ServerHandler implements Runnable {
private Socket socket;
private BufferedReader reader;//字节流
private PrintWriter writer;//字符流
private String userId;
private static Map<String,Socket> onlineMap=new HashMap<String, Socket>();//用户登录后记入在线列表
public ServerHandler(Socket socket) {
this.socket = socket;
try {
//获取输入流
reader=new BufferedReader((new InputStreamReader(socket.getInputStream())));
writer=new PrintWriter(socket.getOutputStream());
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
boolean isOnline=true;
while (isOnline) {
try {
//接收客户端发出的信息
String msg=reader.readLine();
String[] data=msg.split(Message.SPLIT);
if(data[0].equals(Message.LOGIN))
{
login(data);
}else if(data[0].equals(Message.FRIENDS))
{
getFriends(data);
}else if(data[0].equals(Message.CHAT))
{
sendChatMsg(data,msg);
}else if(data[0].equals(Message.FILE))
{
sendFile(data,msg);
}
} catch (IOException | SQLException e) {
//e.printStackTrace();
try {
//客户端断开的情况,缓存区为空的话,返回false
if(!reader.ready()){
System.out.println("用户已经退出");
synchronized (ServerHandler.class)
{
//移除用户
if(onlineMap.containsKey(userId))
onlineMap.remove(userId);
}
}
isOnline=false;
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
}
//响应发送文件请求
private void sendFile(String[] data,String msg) throws IOException {
Socket friendSocket=onlineMap.get(data[2]);
if(friendSocket!=null)
{
PrintWriter writer=new PrintWriter(friendSocket.getOutputStream());
writer.println(msg);
writer.flush();
}
}
//响应发送聊天消息请求
private void sendChatMsg(String[] data,String chatMsg) throws IOException {
Socket friendSocket=onlineMap.get(data[2]);
if(friendSocket!=null)
{
PrintWriter writer=new PrintWriter(friendSocket.getOutputStream());
writer.println(chatMsg);
writer.flush();
}
}
//获取好友列表,转发给客户端
private void getFriends(String[] data) throws SQLException {
ArrayList<User> friendsList=DaoFactory.getUserDao().friends(data[1]);
String msg=Message.resPoneFriends(friendsList);
writer.println(msg);
writer.flush();
}
//登录响应
private void login(String[] data) throws SQLException {
User user= DaoFactory.getUserDao().login(data[1],data[2]);
String reMsg=null;
if(user!=null)
{
//线程锁
synchronized (ServerHandler.class)
{
if(onlineMap.containsKey(user.getUserId()))//判断用户是否在线
{
reMsg=Message.resPoneLogin(Message.RELOGIN);
}else {
//记录第一次登陆
userId=user.getUserId();
onlineMap.put(userId,socket);
reMsg=Message.resPoneLogin(Message.SUC);
}
}
}else {
reMsg=Message.resPoneLogin(Message.FAIL);
}
writer.println(reMsg);
writer.flush();
}
}
补充工程源码:https://github.com/Kamisamakk/JavaProjects