IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 网络协议 -> 基于TCP网络通信的聊天室 -> 正文阅读

[网络协议]基于TCP网络通信的聊天室

  1. 在实训中,利用简单的ETCP网络通信模式来扩展的聊天室,包括服务器启动关闭、登录、注册、聊天群发以及下线(带音效)等功能,还有连接了数据库JDBC的功能,同时对所有的界面进行美化,在登录,注册界面背景图片是动态的,可以实现多人聊天,私聊。

具体效果看下图:
?

系统整体流程图:

登录:

注册:

聊天界面:

创建JDBC数据库(在dos窗口下):

create database 数据库名 character set utf8;

use 数据库名;

create table 表名(

? ? ?id int primary key auto_increment,

??? ?stuName varchar(50),

? ?password(varchar)

? ?);

注:本项目需要传输在lib下导入sql包,所以有一下的配置文件。

配置文件jdbc.properties:

url=jdbc:mysql://localhost:3306/java_test?useUnicode=true&characterEncoding=utf-8
username=root

  • password=123456

注:此外还有个文件夹需要创建 自动生成ip地址的 config.properties文件夹,这个包与jdbc。properties 放在file文件夹中

1.服务端:server

package cn.tedu.server;

import java.awt.Color;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Properties;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;

import cn.tedu.bean.ChatBean;
import cn.tedu.bean.Client;
import cn.tedu.util.ImageUtil;
import cn.tedu.util.WhisperUtil;

public class Server extends JFrame implements ActionListener{
	/**
	 * 当JAVA 需要远程调用时,传输的数据都是binary 的类型,所以需要把数据序列化,
	 * 当另一台机器接受以后,需要反序列化然后读出数据。
	 * 所以就有了把所在类加个serial ID, 这样在反序列化的时候再次验证这个ID 
	 * 以确定是同一个OBJECT (class)。
	 * JAVA 5 之后,可以加为默认的,所以可以Add default serial version ID,
	 * 也可以自动获取一个 Add generated serail version ID。
	 * 所谓的远程调用有RMI ,读取数据库MYSQL 
	 */
	private static final long serialVersionUID = 1L;
	//服务器页面相关属性
	private JPanel panel;
	private JLabel label1,label2;
	private JTextField textfield;
	private JButton start,end;
	
	
	//定义了一个相关变量
	private ServerSocket serverSocket;
	//构建一个键值对为 <String,PECORD>的hashMap对象
	private HashMap<String,Client> onlines=new HashMap<>();
	
	//服务器属性初始化
	public Server() {
		init();
	}
	//初始化属性
	private void init() {
		 //设置标题
		this.setTitle("聊天室服务器");  
		//设置宽高
		this.setSize(500, 335);		
		 //设置关闭方式
		this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);  
		
		//设置大小不可变
		this.setResizable(false);    
		
		//设置居中
		this.setLocationRelativeTo(null);   
		
		//背景设置
		ImageUtil	imageUtil=new ImageUtil("images/server.gif");
		panel= imageUtil.bg(this);
		//构造panel对象
		//panel=new JPanel();

		
		//绝对布局
		panel.setLayout(null);
		
		label1=new JLabel("端口");
		//设置字体的属性
		label1.setFont(new Font("微软 雅黑",Font.BOLD,16));
		
		//设置文字的颜色
		label1.setForeground(Color.ORANGE);
		//设置字体定位
		label1.setBounds(170, 100, 50, 30);
		
		textfield=new JTextField();
		/*
		 * 端口号
		 * 设置端口号
		 */
		textfield.setText("8088");
		/**
		 * 设置文本框字体	
		 */
		//设置文字的颜色
		textfield.setForeground(Color.ORANGE);
		//设置字体居中
		textfield.setHorizontalAlignment(JTextField.CENTER);  
		textfield.setBounds(220,100,80,30);
		//按钮初始化
		start=new JButton("启动服务器");
		start.setBounds(170, 220, 140, 35);
		//按钮绑定事件
		start.addActionListener(this);
		
		//添加组件到画板
		panel.add(label1);
		panel.add(textfield);
		panel.add(start);
	
		//this.add(panel);
		this.setVisible(true);
	}
	
	//start.addActionListener(this);start点击执行下面的代码
	@Override
	public void actionPerformed(ActionEvent e) {
		//e.getActionCommand()获取按钮的值
		if("启动服务器".equals(e.getActionCommand())) {
			String text=textfield.getText();
			//将端口号保存到配置文件去
			Properties pro=new Properties();
			pro.setProperty("port", text);
			pro.setProperty("ip", "127.0.0.1");
			try {
				//字节输出流
				FileOutputStream fos=new FileOutputStream("file/config.properties");
				pro.store(fos,"服务器的端口号配置文件");
			} catch (FileNotFoundException e1) {
				e1.printStackTrace();
			} catch (IOException e1) {
				e1.printStackTrace();
			}
			
			int port=Integer.parseInt(text);
			try {
				//创建服务器端的socket
				serverSocket=new ServerSocket(port);
				onlines=new HashMap<String,Client>();
				//更新页面
				panel.removeAll();
				//panel.setBackground(Color.cyan);
				label2=new JLabel("端口:"+port+",服务器正在运行,请勿关闭");
				//字体属性
				label2.setFont(new Font("宋体",Font.BOLD, 16));
				//设置文字的颜色
				label2.setForeground(Color.RED);
				//定位
				label2.setBounds(100, 100, 300, 50);
				
				end=new JButton("关闭服务器");
				end.setBounds(170, 220, 140, 35);
				end.addActionListener(this);

				panel.add(label2);
				panel.add(end);
				//调用方法更新
				panel.updateUI();
				
			} catch (IOException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			}
			//因为服务器要不断监听客户端,所以需要创建一个线程,不断监听客户端连接
			new ServerThread().start();
		}else if("关闭服务器".equals(e.getActionCommand())) {
			System.exit(-1);	
		}
	}
	
	/**
	 * 服务器线程
	 * @author Administrator
	 *
	 */
	class ServerThread extends Thread{
		public void run() {
			try {
				while(true) {
					//监听客户端连接,获取到连接服务器的客户端的socket
					Socket socket = serverSocket.accept();
					System.out.println(socket+":连上了服务器");
					//因为监听到了客户端连接要对该客户端不断进行读写,所以创建了一个对服务器进行读写的线程
					new ClientThread(socket).start();
				}
			} catch (IOException e) {
		    	e.printStackTrace();
			}
		}
	}
	/**
	 * 客户端线程
	 * 服务器专门对客户端进行读写的过程
	 * @author Administrator
	 *
	 */
	class ClientThread extends Thread{
		private Socket socket;  //客户端的socket
		private ChatBean bean;
		private ObjectOutputStream oos;
		private ObjectInputStream ois;
		
		public ClientThread(Socket socket) {
			this.socket=socket;
		}
			
		public void run() {
			try {
				while(true) {
					ois=new ObjectInputStream(socket.getInputStream());
					bean=(ChatBean)ois.readObject();
					switch(bean.getType()) {
						
					case 0:{
						//0:表示上下线更新列表
						//创建客户端相关信息
						Client client=new Client();
						client.setName(bean.getName());
						client.setSocket(socket);
						onlines.put(bean.getName(), client);
							
						//告诉其他客户端XXX上线的消息
						ChatBean chatbean=new ChatBean();
						//存放在线用户名
						HashSet<String>set=new HashSet<>();
						set.addAll(onlines.keySet());
						chatbean.setClients(set);
						chatbean.setInfo(WhisperUtil.getTimer()+" "+bean.getName()+"上线了\n");
						bean.setType(0);
							
						sendAll(chatbean);
						break;
					}   
					case -1: {
						 //下线
						ChatBean chatbean=new ChatBean();
						chatbean.setType(-1);
						//返回结果运行客户端下线
						oos=new ObjectOutputStream(socket.getOutputStream());
						oos.writeObject(chatbean);
						oos.flush();
							
						//在线用户列表移除该下线用户
						onlines.remove(bean.getName());
						//通知剩下的用户有人下线了并刷新其列表
						ChatBean chatbean2=new ChatBean();
						chatbean2.setType(0);
						chatbean2.setInfo(bean.getTimer()+" "+bean.getName()+" 下线了\n");
							
						HashSet<String> set=new HashSet<>();
						set.addAll(onlines.keySet());
						chatbean2.setClients(set);
						
						sendAll(chatbean2);
						return;
					} 
					case 1:{
						//聊天
						ChatBean chatbean=new ChatBean();
						chatbean.setType(1);
						chatbean.setTimer(bean.getTimer());
						chatbean.setInfo(bean.getInfo());
						chatbean.setClients(bean.getClients());
						chatbean.setName(bean.getName());
							
						sendMessage(chatbean);
						break;
					}
					default:
						break;
					}
				}
			} catch (ClassNotFoundException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
			} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
			}finally {
				close();
			}
		}

		/**
		 * 发送信息
		 * @param chatbean
		 */
		private void sendMessage(ChatBean chatbean) {
			Collection<Client> allOnlines=onlines.values();
			Iterator<Client>it=allOnlines.iterator();
			HashSet<String>clients=chatbean.getClients();
			while(it.hasNext()) {
				Socket socket=null;
				Client client=it.next();
				if(clients.contains(client.getName())) {
					socket=client.getSocket();
					
					try {
						oos=new ObjectOutputStream(socket.getOutputStream());
						oos.writeObject(chatbean);
						oos.flush();
					} catch (IOException e) {
						e.printStackTrace();
					}
				}
			}
		}
			
		/**
		 * 负责向每个在线用户刷新信息
	     * @param cb
		 */
		private void sendAll(ChatBean cb) {
			//1.获取所有的在线客户端Socket
			Collection<Client>clients=onlines.values();
			//2.遍历集合,取到每一个客户端
			Iterator<Client>it=clients.iterator();
			ObjectOutputStream oos=null;
			//3.把消息类用输出流发送给每一个客户端
			while(it.hasNext()) {
			    Socket socket=it.next().getSocket();
				try{
					oos=new ObjectOutputStream(socket.getOutputStream());
					oos.writeObject(cb);
					oos.flush();
				}catch (IOException e) {
					e.printStackTrace();
				} 
			}
		}
		/**
		 * 关闭流
		 * 关闭此输入流并释放与该流关联的所有系统资源
	     */
		public void close() {
			if(oos!=null) {
		
				try {
						oos.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			if(ois!=null) {
				try {
						ois.close();
				} catch (IOException e) {
						e.printStackTrace();
				}
			}
			if(socket!=null) {
				try {
					socket.close();
				} catch (IOException e) {
				    e.printStackTrace();
				}
			}
		}
	}
	public static void main(String[] args) {
		Server server=new Server();
	}
}
	

2.Bean包下的实体类:

? ①客户端:client

package cn.tedu.bean;

import java.net.Socket;

/**
 * 封装的客户端实体类
 * @author lenovo
 *
 */
public class Client {
	 //客户端名称
	private String name;   
	//客户端的socket
	private Socket socket;     
	
	public Client() {
		super();
	}

	public Client(String name, Socket socket) {
		super();
		this.name = name;
		this.socket = socket;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public Socket getSocket() {
		return socket;
	}

	public void setSocket(Socket socket) {
		this.socket = socket;
	}
}

②封装类信息:ChatBean

package cn.tedu.bean;

import java.io.Serializable;
import java.util.HashMap;
import java.util.HashSet;

/**
 * 聊天信息的封装类
 * @author lenovo
 *
 */
public class ChatBean implements Serializable{
	private static final long serialVersionUID=1L;
	private int type;   //聊天类型  1.上线更新   -1下线更新
	private HashSet<String> clients;         //存放先选中的用户
	private HashMap<String,Client> onlines;  //存放在线用户
	private String name;  //姓名
	private String info;  //信息
	private String timer; //时间
	public ChatBean() {
		super();
	}
	public ChatBean(int type, HashSet<String> clients, HashMap<String, Client> onlines, String name, String info,
			String timer) {
		super();
		this.type = type;
		this.clients = clients;
		this.onlines = onlines;
		this.name = name;
		this.info = info;
		this.timer = timer;
	}
	public int getType() {
		return type;
	}
	public void setType(int type) {
		this.type = type;
	}
	public HashSet<String> getClients() {
		return clients;
	}
	public void setClients(HashSet<String> clients) {
		this.clients = clients;
	}
	public HashMap<String, Client> getOnlines() {
		return onlines;
	}
	public void setOnlines(HashMap<String, Client> onlines) {
		this.onlines = onlines;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getInfo() {
		return info;
	}
	public void setInfo(String info) {
		this.info = info;
	}
	public String getTimer() {
		return timer;
	}
	public void setTimer(String timer) {
		this.timer = timer;
	}
	public static long getSerialversionuid() {
		return serialVersionUID;
	}
}

③封装用户:User

package cn.tedu.bean;
/**
 * 封装用户类
 * @author lenovo
 *
 */
public class User {
	private int id;
	private String userName;
	private String passWord;
	
	public User() {
		super();
	}
	public User(String userName, String passWord) {
		super();
		this.userName = userName;
		this.passWord = passWord;
	}
	public User(int id, String userName, String passWord) {
		super();
		this.id = id;
		this.userName = userName;
		this.passWord = passWord;
	}
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	public String getUserName() {
		return userName;
	}
	public void setUserName(String userName) {
		this.userName = userName;
	}
	public String getPassWord() {
		return passWord;
	}
	public void setPassWord(String passWord) {
		this.passWord = passWord;
	}
	@Override
	public String toString() {
		return "User [id=" + id + ", userName=" + userName + ", passWord=" + passWord + "]";
	}
}

3.登录:login

package cn.tedu.login;

import java.awt.EventQueue;
import java.awt.FlowLayout;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JPasswordField;
import java.awt.BorderLayout;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Properties;
import java.util.Vector;

import javax.swing.JTextField;

import cn.tedu.bean.ChatBean;
import cn.tedu.bean.User;
import cn.tedu.client.ChatRoom;
import cn.tedu.dao.UserDao;
import cn.tedu.dao.impl.UserDaoImpl;
import cn.tedu.util.ImageUtil;

import javax.swing.JButton;
import java.awt.Color;

public class Login implements ActionListener{
	/**
	 * 当JAVA 需要远程调用时,传输的数据都是binary 的类型,所以需要把数据序列化,
	 * 当另一台机器接受以后,需要反序列化然后读出数据。
	 * 所以就有了把所在类加个serial ID, 这样在反序列化的时候再次验证这个ID 
	 * 以确定是同一个OBJECT (class)。
	 * JAVA 5 之后,可以加为默认的,所以可以Add default serial version ID,
	 * 也可以自动获取一个 Add generated serail version ID。
	 * 所谓的远程调用有RMI ,读取数据库MYSQL 
	 */
	private static final long serialVersionUID = 1L;
	/**
	 * 创建一个标签类的私有成员变量,取名为jLabel1。
	 */
	private JLabel user;
	private JLabel pass;
	private JTextField userName;
	private JPasswordField password;
	private JLabel welcome;
	private JButton login;
	private JButton register;
	 //用来设置背景样式
	private ImageUtil imageUtil;  
	private JFrame JFrame;
	
	private Socket socket;
	private UserDao userdao=new UserDaoImpl();
	/**
	 * Launch the application.
	 */
	public static void main(String[] args) {
		new Login();
	}

	/**
	 * Create the application.初始化属性
	 */
	public Login() {
		init();
	}

	/**
	 * 进行属性初始化
	 */
	private void init() {
		
		JFrame=new JFrame("登陆界面");
		JFrame.setTitle("轻聊聊天室");
		JFrame.setSize(500,335);
		JFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		//定位
		JFrame.setResizable(false);
		JFrame.setLocationRelativeTo(null);   

		
		//背景设置
		imageUtil=new ImageUtil("images/loginbg.gif");
		JPanel pan= imageUtil.bg(JFrame);
	
		
		
		 //设置绝对布局
		pan.setLayout(null); 
		
		welcome=new JLabel("欢迎登录");
		//设置字体的属性
		welcome.setFont(new Font("楷体", Font.BOLD, 38));
		//设置文字的颜色
		welcome.setForeground(Color.ORANGE);
		//字体定位
		welcome.setBounds(170, 0, 200, 100);
		pan.add(welcome);
		//用户名定位
		user=new JLabel("用户名");
		//设置文字的颜色
		user.setForeground(Color.CYAN);
		//定位
		user.setBounds(130,110,50,30);
		pan.add(user);
		//用户名框定位
		userName=new JTextField(10);
		userName.setBounds(180,110,140,30);
		pan.add(userName);
		
		//密码定位
		pass=new JLabel("密    码");
		//设置文字的颜色
		pass.setForeground(Color.CYAN);
		//定位
		pass.setBounds(130,150,50,30);
		pan.add(pass);
		//密码框定位
		password=new JPasswordField(10);
		password.setBounds(180,150,140,30);
		pan.add(password);
		
		//按钮的初始化
		login=new JButton("登录");
		login.setBackground(Color.GREEN);
		login.setBounds(280,230,80,30);
		//登录按钮的点击事件
		login.addActionListener(this);
		pan.add(login);
		
		register=new JButton("注册");
		register.setBackground(Color.RED);
		register.setBounds(380, 230,80,30);
		//注册按钮的点击事件
		register.addActionListener(this);
		pan.add(register);
		JFrame.setVisible(true);
	}

	//给按钮绑定点击事件
	@Override
	public void actionPerformed(ActionEvent e) {
		String name=userName.getText();
		String pass=password.getText();
		if("登录".equals(e.getActionCommand())) {
			if(name.equals("")||pass.equals("")) {
				JOptionPane.showMessageDialog(null, "用户名或密码为空");
			}else {
				//传入用户密码,判断是否存在
				User user=userdao.login(name,pass);
				if(user==null) {
					JOptionPane.showMessageDialog(null, "用户名或者密码错误,请重新输入!");
				}else {
					//表示登录成功
					JOptionPane.showMessageDialog(null, "登录成功!");
					String ip=getIpOrPort("ip");
					int port=Integer.parseInt(getIpOrPort("port"));
					try {
						//创建客户端的socket
						socket=new Socket(ip,port);
						//创建聊天界面
						ChatRoom cr=new ChatRoom(name,socket);
						//关闭当前页面
						JFrame.setVisible(false);
					} catch (Exception e1) {
						// TODO Auto-generated catch block
						e1.printStackTrace();
					}
				}
			}
			
		}else if("注册".equals(e.getActionCommand())) {
			//关闭当前登录页面
			JFrame.setVisible(false);
			//打开注册页面
			Register register=new Register();	
			}
	}
	
	private String getIpOrPort(String key) {
		String result=null;
		Properties pro=new Properties();
		try {
			FileInputStream in=new FileInputStream("file/config.properties");
			//需要加载的配置文件
			pro.load(in);
			result=pro.getProperty(key);
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return result;
	}
}

4.注册:register

package cn.tedu.login;

import java.awt.Color;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.net.Socket;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPasswordField;
import javax.swing.JTextField;

import cn.tedu.bean.User;
import cn.tedu.dao.UserDao;
import cn.tedu.dao.impl.UserDaoImpl;
import cn.tedu.util.ImageUtil;

public class Register extends JPanel implements ActionListener{

	/**
	 * 当JAVA 需要远程调用时,传输的数据都是binary 的类型,所以需要把数据序列化,
	 * 当另一台机器接受以后,需要反序列化然后读出数据。
	 * 所以就有了把所在类加个serial ID, 这样在反序列化的时候再次验证这个ID 
	 * 以确定是同一个OBJECT (class)。
	 * JAVA 5 之后,可以加为默认的,所以可以Add default serial version ID,
	 * 也可以自动获取一个 Add generated serail version ID。
	 * 所谓的远程调用有RMI ,读取数据库MYSQL 
	 */
	private static final long serialVersionUID = 1L;
	
	
	/**
	 * 页面布局属性
	 * 创建一个标签类的私有成员变量,取名为jLabel
	 */
	private JLabel user;
	private JLabel pass,rePass;
	private JTextField userName;
	private JPasswordField password,rePassword;
	private JLabel welcome;
	private JButton cancel;
	private JButton register;
	//用来设置背景样式
	private ImageUtil imageUtil;   
	private JFrame JFrame;
	private Socket socket;
	private UserDao userdao=new UserDaoImpl();
	private User user2;
	
	//初始化属性
		public Register() {
			init();
		}

	private void init() {
		JFrame=new JFrame("注册界面");
		JFrame.setTitle("轻聊聊天室");
		JFrame.setSize(500,335);
		JFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		//定位
		JFrame.setResizable(false);
		JFrame.setLocationRelativeTo(null);
		
		
		//背景设置
		imageUtil=new ImageUtil("images/registerbg.gif");
		JPanel pan= imageUtil.bg(JFrame);
		
		 //设置绝对布局
		pan.setLayout(null); 
		
		welcome=new JLabel("欢迎注册");
		welcome.setFont(new Font("楷体",Font.BOLD,38));
		//设置文字的颜色
		welcome.setForeground(Color.ORANGE);
		//字体定位
		welcome.setBounds(170, 0, 200, 100);
		//用户名定位
		user=new JLabel("用  户 名");
		
		user.setForeground(Color.CYAN);
		
		/*
		 *public void setBounds(int x,int y,int width,int height)
		 * 移动组件并调整其大小。由 x 和 y 指定左上角的新位置,由 width 和 height 指定新的大小。
		 */
		
		user.setBounds(125,100,50,30);
		
		//用户名框定位
		userName=new JTextField(10);
		userName.setBounds(180,100,140,30);
		
		//密码定位
		pass=new JLabel("密      码");
		pass.setForeground(Color.CYAN);
		pass.setBounds(130,140,50,30);	
		//密码框定位
		password=new JPasswordField(10);
		password.setBounds(180,140,140,30);
		
		//确定密码定位
		rePass=new JLabel("确认密码");
		rePass.setForeground(Color.CYAN);
		rePass.setBounds(122,180,80,30);	
		//确定密码框定位
		rePassword=new JPasswordField(10);
		rePassword.setBounds(180,180,140,30);
		
		//按钮的初始化
		register=new JButton("注册");
		register.setBackground(Color.green);
		register.setBounds(280,255,80,30);
		//登录按钮的点击事件
		register.addActionListener(this);
		
		cancel=new JButton("返回");
		cancel.setBackground(Color.red);
		cancel.setBounds(380,255,80,30);
		//取消按钮的点击事件
		cancel.addActionListener(this);
		
		
	
		
		/**
		 * 整体调用pan方法
		 */
		pan.add(welcome);
		pan.add(user);
		pan.add(pass);
		pan.add(rePass);
		pan.add(userName);
		pan.add(password);
		pan.add(rePassword);
		pan.add(cancel);
		pan.add(register);
		pan.add(imageUtil);

		
		JFrame.add(this);
		JFrame.setVisible(true);
	}

	@Override
	public void actionPerformed(ActionEvent e) {
		String name=userName.getText();
		String passWord=password.getText();
		String repassWord=rePassword.getText();
		if("注册".equals(e.getActionCommand())) {
			if(name.equals("")||passWord.equals("")||repassWord.equals("")) {
				JOptionPane.showMessageDialog(null, "用户名或密码不能为空");
			}else {
				if(!passWord.equals(repassWord)) {
					JOptionPane.showMessageDialog(null, "两次密码不一致");
				}else {
					//去数据库查询用户名是否重复
					boolean b=userdao.selectByName(name);
					if(b) {
						//提示用户该用户名已存在
						JOptionPane.showMessageDialog(null, "该用户名已存在,请重新输入!");
					}else {
						//输入数据成功,注册成功;向数据库插入数据
						user2=new User(name, repassWord);
						//判断向数据库插入信息是否成功
						if(userdao.insertUser(user2)>0) {
							JOptionPane.showMessageDialog(null, "注册成功!");
							//JFrame.setVisible(false);
							new Login();
						}else {
							JOptionPane.showMessageDialog(null, "注册失败,请稍后再试!");
						}
					}
				}
			}
		}else if("返回".equals(e.getActionCommand())) {
			//JFrame.setVisible(false);
			new Login();
		}
	}
	public static void main(String[] args) {
		new Register();
	}
}

5.Util包下的3个类:

①图片管理类:image

package cn.tedu.util;

import java.awt.FlowLayout;
import java.awt.Graphics;

import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
/**
 * 创建一个图片文件管理工具类来继承Jpanel
 * @author lenovo
 *
 */
public class ImageUtil extends JPanel{
	/**
	 * 当JAVA 需要远程调用时,传输的数据都是binary 的类型,所以需要把数据序列化,
	 * 当另一台机器接受以后,需要反序列化然后读出数据。
	 * 所以就有了把所在类加个serial ID, 这样在反序列化的时候再次验证这个ID 
	 * 以确定是同一个OBJECT (class)。
	 * JAVA 5 之后,可以加为默认的,所以可以Add default serial version ID,
	 * 也可以自动获取一个 Add generated serail version ID。
	 * 所谓的远程调用有RMI ,读取数据库MYSQL 
	 */
	private static final long serialVersionUID = 1L;
	private String imgPath;

	public ImageUtil(String imgPath) {
		this.imgPath = imgPath;
	
	}
	
	/*
	 * paintComponent()方法是系统自动调用的,
	 * 一般在组件构造函数自动调用了以后就调用它来显示组件,
	 *  还有就是在窗体改变大小或者被遮挡的时候(组件的外形被破坏),
	 * 所以这个时候系统也自动调用paintComponent()方法来从新画组件。
	 *
	 */
	protected void paintComponent(Graphics g) {
		ImageIcon icon=new ImageIcon(imgPath);
		g.drawImage(icon.getImage(), 0, 0, getWidth(), getHeight(), null);
	}
	
	
	
	
	/**
	 * 设置动态图片定义类
	 * @param frame
	 * @return
	 */
	public JPanel bg(JFrame frame) {
		ImageIcon icon=new ImageIcon(imgPath);
		JLabel label =new JLabel(icon);
		label.setSize(500, 300);
		frame.getLayeredPane().add(label, new Integer(Integer.MIN_VALUE));
		// 2.把窗口面板设为内容面板并设为透明、流动布局。
		JPanel pan = (JPanel) frame.getContentPane();
		pan.setOpaque(false);
		pan.setLayout(new FlowLayout());
		return pan;
	}
	
}
  


②连接数据库:JDBCutil

package cn.tedu.util;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;

public class JDBCUtil {
	//工具类的特点:方法都是静态的
	static String url=null;
	static String username=null;
	static String password=null;
	
	//读取配置文件
	static {
		try {
			Properties pro=new Properties();
			//采用文件字节输入流,对文件数据以字节的形式进行读取操作如读取图片视频
			FileInputStream in=new FileInputStream("file/jdbc.properties");
			//InputStream in=JDBCUtil.class.getClassLoader().getResourceAsStream("jdbc.properties");
			pro.load(in);
			//Uniform Resource Locator,中文名:统一资源定位符  地址
			url=pro.getProperty("url");
			username=pro.getProperty("username");
			password=pro.getProperty("password");
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			}
		}
		
	//获取连接的方法
	public static Connection getConn() {
		Connection conn=null;
		try {
			//1.注册驱动java.sql.DriverManager.registerDriver(new Driver())
			//DriverManager.registerDriver(new Driver());
			//Class.forName("com.mysql.jdbc.Driver");
			//2.创建链接

			conn=DriverManager.getConnection(url,username,password);
			} catch (SQLException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				}
		return conn;
		}
		
	//释放资源的方法
	public static void close(Connection conn,Statement sta,ResultSet rs) {
		closeConn(conn);
		closeSta(sta);
		closeRs(rs);
		}
		
		
	//释放资源
	public static void close(Connection conn,Statement sta) {
		closeConn(conn);
		closeSta(sta);
		}
		
	//关闭连接的方法
	private static void closeConn(Connection conn) {
		try {
			if (conn!=null) {
				conn.close();
				}
			} catch (SQLException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
	//关闭通道的方法
	private static void closeSta(Statement sta) {
		try {
			if (sta!=null) {
				sta.close();
				}	
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
		
	//关闭结果集的方法
	private static void closeRs(ResultSet rs) {
		try {
			if (rs!=null) {
				rs.close();
				}	
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
}

③加载文件:?WhisperUtil

package cn.tedu.util;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Properties;

public class WhisperUtil {

	// Properties加载配置文件信息
	public static void loadPro(Properties pro, File file) {
		if (!file.exists()) {
			try {
				file.createNewFile();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		try {
			pro.load(new FileInputStream(file));
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	@SuppressWarnings("unused")
	/*
	 * @SuppressWarnings("unused")
	 * 表示该属性在方法或类中没有使用。
	 */
	public static String getTimer() {
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		return sdf.format(new Date());
	}
	
}

6.聊天室界面

package cn.tedu.client;

import java.applet.Applet;
import java.applet.AudioClip;
import java.awt.Color;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.MalformedURLException;
import java.net.Socket;
import java.net.URL;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;

import javax.swing.AbstractListModel;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.ListSelectionModel;
import javax.swing.border.Border;
import javax.swing.border.TitledBorder;

import cn.tedu.bean.ChatBean;
import cn.tedu.util.ImageUtil;
import cn.tedu.util.WhisperUtil;

/**
 * 聊天室界面
 * @author lenovo
 *
 */
public class ChatRoom extends JPanel implements ActionListener{
	//聊天室界面属性
	private static final long serialVersionUID=1L;
	private JFrame JFrame;
	private ImageUtil imageUtil;
	//发送框、接收框、好友列表的滚动条
	private JScrollPane sendMsg,receiveMsg,friendList;
	//信息接收框、消息发送框
	private JTextArea receiveInfo,sendInfo;
	// 显示指定数组中的元素(列表容器)
	private JList<String> list;
	// 用于显示当前用户
	private JLabel currentUser;
	// 发送和关闭按钮
	private JButton send, close;
	
	//聊天室其他属性
	private String name;
	private Socket socket;
	private ObjectOutputStream oos;
	private ObjectInputStream ois;
	private Vector onlines;
	//列表模型
	private static AbstractListModel listmodel;
	
	//构造私有类
	private File file1, file2;
	// 消息提示音
	private URL sound1;
	// 上线提示音
	private URL sound2;
	private AudioClip audioClip1, audioClip2;
	
	//聊天室构造方法
	public ChatRoom(String name,Socket socket) {
		this.name=name;
		this.socket=socket;
		onlines=new Vector<>();
		init();
		
		try {
			oos=new ObjectOutputStream(socket.getOutputStream());
			//记录上线用户的信息在chatbean中,并发送给服务器
			ChatBean chatbean=new ChatBean();
			chatbean.setType(0);
			chatbean.setName(name);
			chatbean.setTimer(WhisperUtil.getTimer());
			oos.writeObject(chatbean);
			oos.flush();
			
			//加载音频:消息提示音
			file1=new File("sounds/叮.wav");
			file2=new File("sounds/呃欧.wav");
			
			//转化音频文件为URL格式
			sound1=file1.toURL();
			sound2=file2.toURL();
			
			//包装音频URL
			audioClip1=Applet.newAudioClip(sound1);
			audioClip2=Applet.newAudioClip(sound2);
		} catch (MalformedURLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		try {
			//通知服务器,客户端上线了
			ChatBean cb=new ChatBean();
			cb.setType(0);
			cb.setName(name);
			//发送给服务器
			oos=new ObjectOutputStream(socket.getOutputStream());
			oos.writeObject(cb);
			oos.flush();
		} catch (IOException e) {
			e.printStackTrace();
		}

		//读取服务器发送过来的消息(用一个线程,因为不知道什么时候要接收服务器发送的消息)
		new ClientInputThread().start();
		
		JFrame.addWindowListener(new WindowAdapter() {
			@Override
			public void windowClosing(WindowEvent e) {
				int result = JOptionPane.showConfirmDialog(JFrame.getContentPane(), "您确定要离开聊天室嘛?");
				if (result == 0) {
					ChatBean chatBean = new ChatBean();
					chatBean.setType(-1);
					chatBean.setName(name);
					chatBean.setTimer(WhisperUtil.getTimer());
					sendMessage(chatBean);
//					jFrame.setVisible(false);
				}
			}
		});
	}
	
	//聊天室界面属性初始化
	private void init() {
		JFrame = new JFrame("轻聊聊天室");
		JFrame.setSize(800, 600);
		JFrame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
		JFrame.setResizable(false);
		JFrame.setLocationRelativeTo(null);
		this.setLayout(null);

		imageUtil = new ImageUtil("images/chatbg.jpg");
		imageUtil.setBounds(0, 0, 800, 600);

		// 页面布局
		// 接收框的布局
		receiveInfo = new JTextArea();
		receiveInfo.setEditable(false);  //关闭编辑
		receiveInfo.setLineWrap(true);   //自动换行
		receiveInfo.setFont(new Font("宋体", Font.BOLD, 15));
		
		//添加文本域到滚动条里
		receiveMsg = new JScrollPane();
		receiveMsg.setBounds(30, 10, 520, 380);
		receiveMsg.setViewportView(receiveInfo);

		//发送框布局
		sendInfo = new JTextArea();
		sendInfo.setLineWrap(false);
		sendInfo.setFont(new Font("宋体", Font.BOLD, 15));
	
		//添加到滚动条里
		sendMsg = new JScrollPane(sendInfo);
		sendMsg.setBounds(30, 400, 520, 120);
		//发送按钮
		send = new JButton("发送");
		send.setBackground(Color.green);
		send.setBounds(450, 520, 100, 30);
		send.addActionListener(this);//绑定事件
		//关闭按钮
		close = new JButton("关闭");
		close.setBackground(Color.red);
		close.setBounds(345, 520, 100, 30);
		close.addActionListener(this);//绑定事件

		//显示当前用户的名字
		currentUser = new JLabel("你好," + name + "!");
		//设置字体水平居中
		currentUser.setHorizontalAlignment(JLabel.CENTER);
		currentUser.setFont(new Font("楷体", Font.BOLD, 20));
		currentUser.setBounds(560, 10, 230, 50);

		//好友列表
		list = new JList<>();
		//设置选择模式——表示可以选择不相邻的几页
		list.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);// 表示可以选择不相邻的几项
		list.setFont(new Font("华文行楷", Font.BOLD, 18));
		list.setBackground(Color.white);
		
		// 设置虚边框样式
		Border etch = BorderFactory.createEtchedBorder();
		list.setBorder(BorderFactory.createTitledBorder(etch, "在线用户:", TitledBorder.LEADING, TitledBorder.TOP,new Font("楷体", Font.BOLD, 20), Color.green));

		//初始化滚动条
		friendList = new JScrollPane();
		friendList.setViewportView(list);      //不直接将子集添加到滚动窗口
		friendList.setBounds(560, 60, 230, 330);

		this.add(send);
		this.add(close);
		this.add(currentUser);
		this.add(receiveMsg);
		this.add(sendMsg);
		this.add(friendList);
		this.add(imageUtil);

		JFrame.add(this);
		JFrame.setVisible(true);
	}
	
	/**
	 * 传入Vector,创建ListModel
	 * 为了更新页面的list集合,封装了一个model类
	 */
	class ListModel extends AbstractListModel{
		private Vector vs;
		
		public ListModel(Vector vs) {
			this.vs=vs;
		}
		
		@Override
		public int getSize() {
			return vs.size();
		}

		@Override
		public Object getElementAt(int index) {
			return vs.get(index);
		}
	}
	
	@Override
	public void actionPerformed(ActionEvent e) {
		if(e.getSource()==close) {
			int status = JOptionPane.showConfirmDialog(null, "您要离开聊天室吗?");
			System.out.println(status);
			if (status == 0) {
				close.setEnabled(false);
				ChatBean chatBean = new ChatBean();
				chatBean.setType(-1);
				chatBean.setName(name);
				chatBean.setTimer(WhisperUtil.getTimer());
				sendMessage(chatBean);
                //JFrame.setVisible(false);
			}
		}else if(send==e.getSource()) {
			//发送信息
			String string = sendInfo.getText();
			List<String> to = list.getSelectedValuesList();
			if (to.size() < 1) {
				JOptionPane.showMessageDialog(null, "请选择聊天对象!");
				return;
			} else if (to.toString().contains(name + "(我)")) {
				JOptionPane.showMessageDialog(null, "不能向自己发送信息!");
				return;
			} else if (string.equals("")) {
				JOptionPane.showMessageDialog(null, "不能发送空信息!");
				return;
			}

			ChatBean chatBean = new ChatBean();
			HashSet<String> set = new HashSet<>();
			set.addAll(to);
			chatBean.setClients(set);
			chatBean.setName(name);
			chatBean.setInfo(string);
			chatBean.setType(1);
			String time = WhisperUtil.getTimer();
			chatBean.setTimer(time);
			sendMessage(chatBean);

			receiveInfo.append(time + "我对" + to + "说:\r\n" + string + "\r\n\n");
			sendInfo.setText(null);
		}
	}
	public static void main(String[] args) {
		
	}
	
	
	/**
	 * 用户读取服务器发送的信息
	 * 客户端接收服务器端传送过来的消息
	 * @author Administrator
	 *
	 */
	class ClientInputThread extends Thread{
		public void run() {
			try {
				while(true) {//表示一直可以接收消息
					//读取服务器的对象流
					ois=new ObjectInputStream(socket.getInputStream());
					ChatBean cb=(ChatBean)ois.readObject();
					switch(cb.getType()) {
					//更新上线
					case 0:{
						onlines.clear();//清空在线用户列表
						HashSet<String>clients=cb.getClients();
						Iterator<String>it=clients.iterator();
						while(it.hasNext()) {
							String clientName=it.next();
						if(clientName.equals(name)) {
								onlines.add(clientName+"(我)");
							}else {
								onlines.add(clientName);
							}
						}
						listmodel=new ListModel(onlines);
						list.setModel(listmodel);
						//在消息接收文本域添加消息
						audioClip1.play();
						receiveInfo.append(cb.getInfo());
						receiveInfo.selectAll();
						break;
					}	
					case 1:     //聊天
					{
						String info = cb.getTimer() + "  " + cb.getName() + " 对 " + cb.getClients() + "说:\r\n";
						if (info.contains(name)) {
							info = info.replace(name, "我");
						}
						audioClip1.play();
						receiveInfo.append(info + cb.getInfo() + "\r\n\n");
						receiveInfo.selectAll();
						break;
					}
					case -1:    //下线
					{
						return;
					}
					default:
						break;
					}
				}
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (ClassNotFoundException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}finally {
				close();
				System.exit(0);
			}
		}
	}
	
	/**
	 * 关闭流
	 */
	public void close() {
		if (oos != null) {
			try {
				oos.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		if (ois != null) {
			try {
				ois.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		if (socket != null) {
			try {
				socket.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	
	/**
	 * 发送信息
	 */
	private void sendMessage(ChatBean chatBean) {
		try {
			oos = new ObjectOutputStream(socket.getOutputStream());
			oos.writeObject(chatBean);
			oos.flush();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

7.dao包和dao.imp包下的具体实现类

package cn.tedu.dao;
import cn.tedu.bean.User;

public interface UserDao {
	//根据用户名查询用户的方法
	boolean selectByName(String username);
	
	//插入数据的方法(注册)
	int insertUser(User user);
	
	//登录的方法
	User login(String userName,String passWord);
	
}
//interface java中这个称之为接口,如果你不好理解,我通俗点打个比方就行了!
//火车,很多车厢,每一节车厢都一个挂钩,可以相互挂在一起,你可以把这个挂钩的大小理解成 interface;
//不管哪个火车箱的生产厂家,只要符合这个挂钩的规则,就可以挂在这个火车一起了!
//不管什么对象,只要实现过这个interface几口以后就可以相互联系在一起了!  这就是规则
package cn.tedu.dao.impl;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import cn.tedu.bean.User;
import cn.tedu.dao.UserDao;
import cn.tedu.util.JDBCUtil;
/**
 *     impl 实现类、接口实现;
 * @author lenovo
 *
 */
public class UserDaoImpl implements UserDao{

	@Override
	public boolean selectByName(String username) {
		Connection conn=null;
		PreparedStatement pre=null;
		ResultSet rs=null;
		try {
			//1.创建连接
			conn=JDBCUtil.getConn();
			String sql="select * from User where username=?";
			//2.创建通道
			pre=conn.prepareStatement(sql);
			//3.给参数赋值
			pre.setString(1, username);
			//4.执行查询
			rs=pre.executeQuery();
			//5.遍历结果集
			if(rs.next()) {
				return true;
			}
		} catch (SQLException e) {
			e.printStackTrace();
		}finally{
			//6.释放资源
			JDBCUtil.close(conn, pre, rs);
		}
		return false;
	}

	@Override
	public int insertUser(User user) {
		Connection conn=null;
		PreparedStatement pre=null;
		int i=0;
		try {
			//1.创建连接
			conn=JDBCUtil.getConn();
			String sql="insert into user(username,password) values (?,?)";
			//2.创建通道
			pre=conn.prepareStatement(sql);
			//3.给参数赋值
			pre.setString(1, user.getUserName());
			pre.setString(2, user.getPassWord());
			//4.执行更新
			i=pre.executeUpdate();
			//5.遍历结果集
			return i;
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}finally{
			//6.释放资源
			JDBCUtil.close(conn, pre);
		}
		return i;
	}

	@Override
	public User login(String userName, String passWord) {
		Connection conn=null;
		PreparedStatement pre=null;
		ResultSet rs=null;
		User user=null;
		
		try {
			//1.创建链接
			conn=JDBCUtil.getConn();
			String sql="select * from User where username=? and password=?";
			//2.创建通道
			pre=conn.prepareStatement(sql);
			//3.给参数赋值
			pre.setString(1, userName);
			pre.setString(2, passWord);
			//4.执行查询
			rs=pre.executeQuery();
			//5.遍历结果集
			while (rs.next()) {
				String name=rs.getString("username");
				String pass=rs.getString("password");
				user=new User(name, pass);
			}
			return user;
		} catch (SQLException e) {
			e.printStackTrace();
		}finally {
			//6.释放资源
			JDBCUtil.close(conn, pre, rs);
		}
		return user;
	}
}

以上是全部代码,我在下面放一张具体项目表

注:图中的code是加载验证码的图片,暂时验证码还没加好,是注册界面的验证码,可以不写!

以上是全部内容,如有侵权,可删!?

欢迎大家转载!!!

  网络协议 最新文章
使用Easyswoole 搭建简单的Websoket服务
常见的数据通信方式有哪些?
Openssl 1024bit RSA算法---公私钥获取和处
HTTPS协议的密钥交换流程
《小白WEB安全入门》03. 漏洞篇
HttpRunner4.x 安装与使用
2021-07-04
手写RPC学习笔记
K8S高可用版本部署
mySQL计算IP地址范围
上一篇文章      下一篇文章      查看所有文章
加:2022-01-03 16:27:45  更:2022-01-03 16:29:53 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年10日历 -2024/10/5 9:22:34-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码