目录
一:项目实现前提
1:掌握Java基本语法,如基本选择,循环,顺序语句结构,基本字符串相关语法,了解类和对象的运用。
2:能正确使用Java高级语法如线程,异常,IO,集合等。
3:能很好的使用Java的Swing下的各种方法,创建聊天室界面
4:了解网络三要素,并正确的使用
二:项目需求
一:客户端
1:实现登录,注册功能,并包括其界面
2:实现聊天功能,并包括其界面
3:创建一个客户端对象,并且能与服务器产生连接
二:服务器
1:创建一个服务器对象,并能与客户端产生连接
2:能接收客户端发送来的用户名和密码,并存入一个txt文件中
3:能接收客户端发来的消息并实现群发功能
三:项目的具体实现
一:客户端
1:设置IP和端口的输入
首先要建立一个让用户输入IP和端口的界面(因为在这个项目中是本机做的服务器,所以服务器IP会经常变化,需让用户自行输入),此时需注意提醒用户输入Ip和端口的格式是否正确(此时只需调用JTextField 下的addFocusListener方法即可),在判断格式是否正确时可以用正则表达式简化代码,如果匹配成功则直接进入注册界面,若失败则跳出一个界面来提醒用户。PS:在这个类下我们需要创建一个静态对象Socket用于与服务器进行连接。以下为该类的完整代码,界面布局方面不过多赘述。
package com.TWT.Chat.Client;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.io.IOException;
import java.net.Socket;
//设立端口和Ip输入界面
public class SettingIp {
public SettingIp(){
InitWindow();
InitSetting();
ClickButton();
AddWord();
jfm.setVisible(true);
}
//各种静态变量
private static JFrame jfm=new JFrame("设置IP,port");
private static final int FRAME_LEN=300;
private static final int FRAME_HIGH=300;
private static JLabel jbl1=new JLabel("IPv4:");
private static JLabel jbl2=new JLabel("Port:");
private static JTextField jtf1=new JTextField();
private static JTextField jtf2=new JTextField();
private static JButton jbt=new JButton("确定");
public static String str;
public static String str1;
public static Socket socket;
//初始化窗口
private void InitWindow(){
jfm.setSize(FRAME_LEN,FRAME_HIGH);
jfm.setLayout(null);
jfm.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jfm.setResizable(false);
jfm.setLocationRelativeTo(null);
}
//初始化各种组件
private void InitSetting(){
Font f=new Font("宋体",Font.BOLD,+15);
jbl1.setBounds(25,50,50,30);
jbl1.setFont(f);
jtf1.setBounds(80,55,125,20);
jbl2.setBounds(25,125,50,30);
jbl2.setFont(f);
jtf2.setBounds(80,130,125,20);
jbt.setBounds(115,200,70,30);
jbt.setFont(new Font("宋体",Font.BOLD,17));
jbt.setFocusPainted(false);
jtf1.setText("请输入正确ip地址");
jtf2.setText("请输入正确端口");
jtf1.setForeground(new Color(204,204,204));
jtf2.setForeground(new Color(204,204,204));
jfm.getContentPane().add(jtf1);
jfm.getContentPane().add(jbl1);
jfm.getContentPane().add(jtf2);
jfm.getContentPane().add(jbl2);
jfm.getContentPane().add(jbt);
}
//添加按钮的监视,运用匿名内部类
private void AddWord(){
//设置文本框提示输入,下同
jtf1.addFocusListener(new FocusListener() {
@Override
public void focusGained(FocusEvent e) {
if(jtf1.getText().equals("请输入正确ip地址")){
jtf1.setText("");
jtf1.setForeground(Color.BLACK);
}
}
@Override
public void focusLost(FocusEvent e) {
if(jtf1.getText().equals("")){
jtf1.setText("请输入正确ip地址");
jtf1.setForeground(new Color(204,204,204));
}
}
});
jtf2.addFocusListener(new FocusListener() {
@Override
public void focusGained(FocusEvent e) {
if(jtf2.getText().equals("请输入正确端口")){
jtf2.setText("");
jtf2.setForeground(Color.BLACK);
}
}
@Override
public void focusLost(FocusEvent e) {
if(jtf2.getText().equals("")){
jtf2.setText("请输入正确端口");
jtf2.setForeground(new Color(204,204,204));
}
}
});
}
//功能按钮键的各种方法
private void ClickButton(){
jbt.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
str=jtf1.getText();
//用正则表达式辨别IP和端口输入是否有误
boolean RightIP=str.matches("^(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)" +
"(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}$");
System.out.println(RightIP);
jtf1.setText("");
str1=jtf2.getText();
boolean RightPort=str1.matches("([0-9]|[1-9]\\d{1,3}|[1-5]\\d{4}|6[0-4]\\d{4}|65[0-4]\\d" +
"{2}|655[0-2]\\d|6553[0-5])");
System.out.println(RightPort);
jtf2.setText("");
if(RightIP&&RightPort){
try {
//获取文本框中的IP和端口并创建客户端对象
socket=new Socket(str, Integer.valueOf(str1));
} catch (IOException ex) {
}
//若端口IP均正确则关闭当前界面,切换注册界面
jfm.setVisible(false);
GuiEnroll.jfm.setVisible(true);
}
else {
//有误则跳出提示界面提醒
InputError.jLabel.setText("请输入正确的ip和端口");
InputError.ErrorInput.setVisible(true);
}
}
});
}
}
2:注册
当用户完成IP和端口输入后转到了登录界面,界面设置如下
如界面所示,设置了用户名,密码,验证码输入界面所示提醒输入与上一个相同。当用户输入玩用户名,密码和验证码后,先进行格式判断,若格式错误则弹出一个界面提醒用户重新输入,若正确则将用户名和密码打包成一个字符串发送给服务器,再由服务器判断是否注册成功(在服务器模块详解),若成功则,转为登入界面(具体详解看代码注释)。
package com.TWT.Chat.Client;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.io.*;
//注册界面
public class GuiEnroll extends JFrame {
public GuiEnroll() {
Init_Window();
InitPanel();
ClickJbt();
InitJMenu();
AddWord();
}
//窗口变量和各种组件变量
public static JFrame jfm=new JFrame();
private static final int FRAME_LEN=400;
private static final int FRAME_HIGH=500;
private static JTextField jtf1=new JTextField();
private static JPasswordField jtf2=new JPasswordField();
private static JTextField jtf3=new JTextField();
private static JPasswordField jtf4=new JPasswordField();
private static JLabel jbl1=new JLabel();
private static JLabel jbl2=new JLabel();
private static JLabel jbl3=new JLabel();
private static JLabel jbl4=new JLabel();
private static JLabel jbl5=new JLabel();
private static JButton jbt1=new JButton();
private static JButton jbt2=new JButton();
private JMenuBar jMenuBar=new JMenuBar();
private JMenu jMenu=new JMenu("切换登录注册");
private JMenuItem jMenuItem1=new JMenuItem("登录");
private JMenuItem jMenuItem2=new JMenuItem("注册");
private static int EnrollRunnable=0;
//初始化窗口的方法
private void Init_Window(){
jfm.setTitle("注册");
jfm.setLayout(null);
jfm.setResizable(false);
jfm.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jfm.setSize(FRAME_LEN,FRAME_HIGH);
jfm.setLocationRelativeTo(null);
jfm.setVisible(false);
}
//窗口中组件的初始化
private void InitPanel(){
Font f=new Font("宋体",Font.BOLD,25);
Font f2=new Font("楷体",Font.BOLD,17);
jbl1.setBounds(170,15,60,60);
jbl1.setText("注册");
jbl1.setFont(f);
jbl2.setText("用户名:");
jbl2.setBounds(15,90,85,60);
jbl2.setFont(f2);
jtf1.setBounds(105,105,250,30);
jbl3.setBounds(15,150,85,60);
jbl3.setText("密码:");
jbl3.setFont(f2);
jtf2.setBounds(105,165,250,30);
jtf2.setEchoChar('\0');
jbl4.setText("验证码:");
jbl4.setBounds(15,275,85,60);
jbl4.setFont(f2);
jtf3.setBounds(105,290,135,30);
jbl5.setFont(f2);
jbl5.setText("再次确认:");
jbl5.setBounds(15,210,90,60);
jtf4.setBounds(105,225,250,30);
jtf4.setEchoChar('\0');
jtf4.setText("请再次输入密码");
jtf3.setText("请输入验证码");
jfm.getContentPane().add(jbl5);
jfm.getContentPane().add(jtf4);
jfm.getContentPane().add(jbl1);
jfm.getContentPane().add(jbl2);
jfm.getContentPane().add(jtf1);
jfm.getContentPane().add(jbl3);
jfm.getContentPane().add(jtf3);
jfm.getContentPane().add(jtf2);
jfm.getContentPane().add(jbl4);
jfm.getContentPane().add(jbt2);
jbt1.setBounds(160,350,80,35);
jbt1.setText("注册");
jbt1.setFont(new Font("宋体",Font.PLAIN,22));
jbt1.setFocusPainted(false);
jfm.getContentPane().add(jbt1);
jbt2.setFocusPainted(false);
jbt2.setBounds(270,290,80,30);
jbt2.setFont(new Font("微软雅黑",Font.TYPE1_FONT,12));
jbt2.setText(GuiLogin.yzword());
jtf1.setText("请输入用户名,要求只含8到12为字母或数字");
jtf2.setText("请输入密码,要求只含4到10为字母或数字");
jtf1.setForeground(new Color(204,204,204));
jtf2.setForeground(new Color(204,204,204));
jtf3.setForeground(new Color(204,204,204));
jtf4.setForeground(new Color(204,204,204));
}
//初始化菜单栏实现登录与注册界面的切换
private void InitJMenu(){
jMenuBar.add(jMenu);
jMenu.add(jMenuItem1);
jMenu.add(jMenuItem2);
jfm.setJMenuBar(jMenuBar);
jMenuItem1.addActionListener(new ActionListener() {
//当前为注册界面,点击后,关闭注册界面显示登录界面
@Override
public void actionPerformed(ActionEvent e) {
GuiEnroll.jfm.setVisible(false);
GuiLogin.jfm.setVisible(true);
}
});
}
//设置按钮监听事件
private void ClickJbt(){
jbt2.addActionListener(new ActionListener() {
//点击切换验证码
@Override
public void actionPerformed(ActionEvent e) {
jbt2.setText(GuiLogin.yzword());
}
});
jbt1.addActionListener(new ActionListener() {
//注册按钮
@Override
public void actionPerformed(ActionEvent e) {
//用户名
String str1 = jtf1.getText();
//密码
String str2 = new String(jtf2.getPassword());
//二次确认密码
String str3 = new String(jtf4.getPassword());
//验证码
String str4 = jtf3.getText();//yzm
jtf1.setText("");
jtf2.setText("");
jtf3.setText("");
jtf4.setText("");
//判断这些输入是否符合要求
boolean p1 = str1.matches("^[a-zA-Z0-9]{8,16}$");
boolean p2 = str2.matches("^[a-zA-Z0-9]{4,10}$");
boolean p3 = str2.equals(str3);
boolean p4 = str4.equalsIgnoreCase(jbt2.getText());
if (p1 && p2 && p3 && p4) {
try {
//创建输出流
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(SettingIp.socket.getOutputStream()));
//发送消息给服务器表示客户端已经开始注册
write(bw);
//传入用户名密码给服务器
String str = "username=" + str1 + "&" + "password=" + str2;
OutWrite(bw, str);
} catch (IOException ex) {
throw new RuntimeException(ex);
}
//判断是否注册成功
run1();
if (EnrollRunnable == 2) {
System.out.println("注册");
//若注册成功则关闭注册界面,打开登录界面
GuiEnroll.jfm.setVisible(false);
GuiLogin.jfm.setVisible(true);
} else if (EnrollRunnable == 1) {
System.out.println("用户名重复,请重新输入");
InputError.jLabel.setText("用户名重复");
InputError.ErrorInput.setVisible(true);
}
//对相应的输入错误做出相应的反应
}else if(!p1&!p2){
InputError.jLabel.setText("用户名或密码格式错误");
InputError.ErrorInput.setVisible(true);
}
else if(!p3){
InputError.jLabel.setText("两次密码不同");
InputError.ErrorInput.setVisible(true);
}
else if(!p4){
InputError.jLabel.setText("验证码有误");
InputError.ErrorInput.setVisible(true);
}
}
});
}
//设置文本框提示文字
private void AddWord(){
jtf1.addFocusListener(new FocusListener() {
@Override
public void focusGained(FocusEvent e) {
if(jtf1.getText().equals("请输入用户名,要求只含8到12为字母或数字")){
jtf1.setText("");
jtf1.setForeground(Color.BLACK);
}
}
@Override
public void focusLost(FocusEvent e) {
if(jtf1.getText().equals("")){
jtf1.setText("请输入用户名,要求只含8到12为字母或数字");
jtf1.setForeground(new Color(204,204,204));
}
}
});
jtf2.addFocusListener(new FocusListener() {
@Override
public void focusGained(FocusEvent e) {
if(jtf2.getText().equals("请输入密码,要求只含4到10为字母或数字")){
jtf2.setText("");
jtf2.setEchoChar('*');
jtf2.setForeground(Color.BLACK);
}
}
@Override
public void focusLost(FocusEvent e) {
if(jtf2.getText().equals("")){
jtf2.setText("请输入密码,要求只含4到10为字母或数字");
jtf2.setEchoChar('\0');
jtf2.setForeground(new Color(204,204,204));
}
}
});
jtf4.addFocusListener(new FocusListener() {
@Override
public void focusGained(FocusEvent e) {
if(jtf4.getText().equals("请再次输入密码")){
jtf4.setText("");
jtf4.setEchoChar('*');
jtf4.setForeground(Color.BLACK);
}
}
@Override
public void focusLost(FocusEvent e) {
if(jtf4.getText().equals("")){
jtf4.setText("请再次输入密码");
jtf4.setEchoChar('\0');
jtf4.setForeground(new Color(204,204,204));
}
}
});
jtf3.addFocusListener(new FocusListener() {
@Override
public void focusGained(FocusEvent e) {
if(jtf3.getText().equals("请输入验证码")){
jtf3.setText("");
jtf3.setForeground(Color.BLACK);
}
}
@Override
public void focusLost(FocusEvent e) {
if(jtf3.getText().equals("")){
jtf3.setText("请输入验证码");
jtf3.setForeground(new Color(204,204,204));
}
}
});
}
public static void write(BufferedWriter bw) throws IOException {
OutWrite(bw,"1");
}
public static void OutWrite(BufferedWriter bw,String str) throws IOException {
bw.write(str);
bw.newLine();
bw.flush();
}
private void run1(){
try {
BufferedReader br=new BufferedReader(new InputStreamReader(SettingIp.socket.getInputStream()));
String str = null;
while(true){
//接收服务器传来的消息,并返回相应的数字表示是否注册成功
if((str=br.readLine())!=null) {
if (str.equals("1")) {
EnrollRunnable = 1;
System.out.println("用户名重复");
break;
} else if (str.equals("2")) {
EnrollRunnable = 2;
System.out.println("注册成功");
break;
}
}
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
3:登入
该模块与注册相暂不详解,若有疑问请参考注册模板,界面图如下
package com.TWT.Chat.Client;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.io.*;
import java.util.ArrayList;
import java.util.Random;
public class GuiLogin extends JFrame {
public GuiLogin() {
Init_Window();
InitPanel();
ClickJbt();
InitJMenu();
AddWord();
jfm.setVisible(false);
}
//与注册类似
public static JFrame jfm = new JFrame();
private static final int FRAME_LEN = 400;
private static final int FRAME_HIGH = 400;
private static JTextField jtf1 = new JTextField();
private static JPasswordField jtf2 = new JPasswordField();
private static JTextField jtf3 = new JTextField();
private static JLabel jbl1 = new JLabel();
private static JLabel jbl2 = new JLabel();
private static JLabel jbl3 = new JLabel();
private static JLabel jbl4 = new JLabel();
private static JButton jbt1 = new JButton();
private static JButton jbt2 = new JButton();
private JMenuBar jMenuBar = new JMenuBar();
private JMenu jMenu = new JMenu("切换登陆注册");
private JMenuItem jMenuItem1 = new JMenuItem("登录");
private JMenuItem jMenuItem2 = new JMenuItem("注册");
public static String username = new String();
private static int EnrollRunnable=0;
//初始化窗口
private void Init_Window() {
jfm.setTitle("登录");
jfm.setLayout(null);
jfm.setResizable(false);
jfm.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jfm.setSize(FRAME_LEN, FRAME_HIGH);
jfm.setLocationRelativeTo(null);
}
//初始化各种组件
private void InitPanel() {
Font f = new Font("宋体", Font.BOLD, 25);
Font f2 = new Font("楷体", Font.BOLD, 17);
jbl1.setBounds(170, 15, 60, 60);
jbl1.setText("登录");
jbl1.setFont(f);
jbl2.setText("用户名:");
jbl2.setBounds(15, 90, 85, 60);
jbl2.setFont(f2);
jtf1.setBounds(105, 105, 250, 30);
jtf1.setText("请输入用户名,要求只含8到12为字母或数字");
jtf2.setText("请输入密码,要求只含4到10为字母或数字");
jtf3.setText("请输入验证码");
jtf3.setForeground(new Color(204,204,204));
jtf1.setForeground(new Color(204,204,204));
jtf2.setForeground(new Color(204,204,204));
jtf2.setEchoChar('\0');
jbl3.setBounds(15, 150, 85, 60);
jbl3.setText("密码:");
jbl3.setFont(f2);
jtf2.setBounds(105, 165, 250, 30);
jbl4.setText("验证码:");
jbl4.setBounds(15, 210, 85, 60);
jbl4.setFont(f2);
jtf3.setBounds(105, 225, 135, 30);
jfm.getContentPane().add(jbl1);
jfm.getContentPane().add(jbl2);
jfm.getContentPane().add(jtf1);
jfm.getContentPane().add(jbl3);
jfm.getContentPane().add(jtf3);
jfm.getContentPane().add(jtf2);
jfm.getContentPane().add(jbl4);
jfm.getContentPane().add(jbt2);
jbt1.setBounds(160, 285, 80, 35);
jbt1.setText("登录");
jbt1.setFont(new Font("宋体", Font.PLAIN, 22));
jbt1.setFocusPainted(false);
jfm.getContentPane().add(jbt1);
jbt2.setFocusPainted(false);
jbt2.setBounds(270, 225, 80, 30);
jbt2.setFont(new Font("微软雅黑", Font.TYPE1_FONT, 12));
jbt2.setText(yzword());
}
//设置文本框提示文字
private void AddWord(){
jtf1.addFocusListener(new FocusListener() {
@Override
public void focusGained(FocusEvent e) {
if(jtf1.getText().equals("请输入用户名,要求只含8到12为字母或数字")){
jtf1.setText("");
jtf1.setForeground(Color.BLACK);
}
}
@Override
public void focusLost(FocusEvent e) {
if(jtf1.getText().equals("")){
jtf1.setText("请输入用户名,要求只含8到12为字母或数字");
jtf1.setForeground(new Color(204,204,204));
}
}
});
jtf2.addFocusListener(new FocusListener() {
@Override
public void focusGained(FocusEvent e) {
if(jtf2.getText().equals("请输入密码,要求只含4到10为字母或数字")){
jtf2.setText("");
jtf2.setEchoChar('*');
jtf2.setForeground(Color.BLACK);
}
}
@Override
public void focusLost(FocusEvent e) {
if(jtf2.getText().equals("")){
jtf2.setText("请输入密码,要求只含4到10为字母或数字");
jtf2.setEchoChar('\0');
jtf2.setForeground(new Color(204,204,204));
}
}
});
jtf3.addFocusListener(new FocusListener() {
@Override
public void focusGained(FocusEvent e) {
if(jtf3.getText().equals("请输入验证码")){
jtf3.setText("");
jtf3.setForeground(Color.BLACK);
}
}
@Override
public void focusLost(FocusEvent e) {
if(jtf3.getText().equals("")){
jtf3.setText("请输入验证码");
jtf3.setForeground(new Color(204,204,204));
}
}
});
}
//验证码方法,要求四个字母和一个舒字
public static String yzword() {
Random ran = new Random();
//用集合来存储52个字母
ArrayList<Character> list = new ArrayList<>();
//字符串拼接
StringBuilder sb = new StringBuilder();
//将字母添加到集合中
for (int i = 0; i < 26; i++) {
list.add((char) ('a' + i));
list.add((char) ('A' + i));
}
//随机添加四个字母
for (int i = 0; i < 4; i++) {
int num = ran.nextInt(list.size());
sb.append(list.get(num));
}
//添加数字
int c = ran.nextInt(10);
sb.append(c);
char[] ch = sb.toString().toCharArray();
int num = ran.nextInt(4);
char r;
r = ch[num];
//数字位序的随机替换实现打乱验证码
ch[num] = ch[ch.length - 1];
ch[ch.length - 1] = r;
//返回验证码
return new String(ch);
}
//添加按钮监听事件,大致与注册方法想同
private void ClickJbt() {
jbt2.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
jbt2.setText(yzword());
}
});
jbt1.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("djl");
jbt2.setText(yzword());
String str1 = jtf1.getText();
String str2 = new String(jtf2.getPassword());
boolean p1 = str1.matches("^[a-zA-Z0-9]{8,12}$");
boolean p2 = str2.matches("^[a-zA-Z0-9]{4,10}$");
jtf1.setText("");
jtf2.setText("");
jtf3.setText("");
if (p1 && p2) {
try {
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(SettingIp.socket.getOutputStream()));
OutWrite(bw, "2");
String str = "username=" + str1 + "&" + "password=" + str2;
OutWrite(bw, str);
} catch (IOException ex) {
throw new RuntimeException(ex);
}
try {
run1();
} catch (IOException ex) {
throw new RuntimeException(ex);
}
if(EnrollRunnable==0){
System.out.println("error");
}
if (EnrollRunnable == 1) {
System.out.println("用户名不存在");
InputError.jLabel.setText("用户名不存在");
InputError.ErrorInput.setVisible(true);
}
if (EnrollRunnable == 2) {
System.out.println("账号或密码错误");
InputError.jLabel.setText("用户名或密码错误");
InputError.ErrorInput.setVisible(true);
}
if (EnrollRunnable == 3) {
System.out.println("登录成功");
username = str1;
GuiSend.jbl1.setText(str1);
GuiSend.UserName.setText(str1);
//开启一条线程用于接收系统消息
new Thread(new RunnableSend(SettingIp.socket)).start();
try {
GuiSend.ClickButton();
} catch (IOException ex) {
throw new RuntimeException(ex);
}
//登入成功则切换聊天界面
GuiLogin.jfm.setVisible(false);
GuiSend.SockSever.setVisible(true);
}
}
else {
InputError.jLabel.setText("用户名或密码格式有误");
InputError.ErrorInput.setVisible(true);
}
}
});
}
//切换登录注册姐界面与注册的大致相同
private void InitJMenu() {
jMenuBar.add(jMenu);
jMenu.add(jMenuItem1);
jMenu.add(jMenuItem2);
jfm.setJMenuBar(jMenuBar);
jMenuItem2.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
GuiEnroll.jfm.setVisible(true);
GuiLogin.jfm.setVisible(false);
}
});
}
//提取方法,减少代码复杂度
public static void OutWrite(BufferedWriter bw, String str) throws IOException {
bw.write(str);
bw.newLine();
bw.flush();
}
//预注册大致相同
private void run1() throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(SettingIp.socket.getInputStream()));
String str = br.readLine();
System.out.println("返回="+str);
if (str.equals("1")) {
EnrollRunnable = 1;
System.out.println("用户名不存在");
} else if (str.equals("2")) {
EnrollRunnable = 2;
System.out.println("用户名或密码错误");
} else if (str.equals("3")) {
EnrollRunnable = 3;
System.out.println("登录成功");
}
}
}
4:发送消息
与前面类似,当完成登录后即进入了消息发送的界面,界面展示如下
该界面用了一个JtexttArea的对象用于展示发送的消息,同时用一个按钮将文本框中的消息发出。当用户输入了消息并点击了发送按钮后,获取文本框中的字符串,并将其发送给服务器·,再由服务器实现群发功能,此时客户端需再开启一条线程循环接收服务器发来的消息,再将接收的字符串添加到文本域中即可实现消息的展示。代码展示如下:
package com.TWT.Chat.Client;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
//消息发送界面
public class GuiSend extends JFrame {
public GuiSend() {
ChangeName();
InitWindow();
InitSetting();
ChangeSetting();
}
//与登录注册界面设计方法,大致相同
public static JFrame SockSever=new JFrame();
private static final int FRAME_LEN=700;
private static final int FRAME_HIGH=600;
public static JTextArea jta1=new JTextArea();
private static JScrollPane jsp=new JScrollPane(jta1);
private static JTextField jtf=new JTextField();
private static JButton jbt=new JButton("发送");
private static JLabel jbl=new JLabel("当前用户:");
private static JLabel jbl2=new JLabel("当前昵称:");
public static JLabel UserName=new JLabel();
public static JLabel jbl1=new JLabel();
private static JLabel NameChange=new JLabel("昵称:");
private static JTextField AddNew=new JTextField();
private static JButton Change=new JButton("更换昵称");
private static JButton Determine=new JButton("确定");
private static JFrame Tips=new JFrame();
private void InitWindow(){
SockSever.setLayout(null);
SockSever.setSize(FRAME_LEN,FRAME_HIGH);
SockSever.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
SockSever.setResizable(false);
SockSever.setLocationRelativeTo(null);
SockSever.setTitle("客户端");
SockSever.getContentPane().setBackground(new Color(0x1EDCDC));
SockSever.setVisible(false);
}
//更换昵称界面设置
private void ChangeName(){
Tips.setSize(300,300);
Tips.setLayout(null);
Tips.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
Tips.setLocationRelativeTo(null);
Tips.setResizable(false);
Tips.setTitle("更换昵称");
Tips.getContentPane().setBackground(new Color(0xE3FEFE));
Tips.setVisible(false);
}
//更换昵称的各种组件
private void ChangeSetting(){
NameChange.setBounds(10,80,50,50);
AddNew.setBounds(70,90,177,30);
NameChange.setFont(new Font("宋体",Font.BOLD,17));
Determine.setBounds(110,165,70,30);
Determine.setFocusPainted(false);
Determine.setBackground(new Color(0x41A9A9));
Determine.setFont(new Font("楷体",Font.PLAIN,17));
Tips.getContentPane().add(Determine);
Tips.getContentPane().add(NameChange);
Tips.getContentPane().add(AddNew);
}
private void InitSetting(){
jsp.setBounds(0,0,550,450);
jta1.setBackground(Color.WHITE);
jta1.setEnabled(false);
jta1.setLineWrap(true);
jta1.setDisabledTextColor(Color.BLACK);
jta1.setFont(new Font("宋体",Font.BOLD+Font.PLAIN,20));
jtf.setBounds(20,470,510,30);
jtf.setFont(new Font("宋体",Font.ROMAN_BASELINE,15));
jbl.setBounds(550,0,150,50);
jbl.setFont(new Font("宋体",Font.BOLD,15));
jbl1.setBounds(550,50,150,50);
jbl1.setFont(new Font("宋体",Font.BOLD,20));
jbl1.setText("12345");
jbl1.setForeground(new Color(0xF0F05D));
jbl2.setBounds(550,100,150,50);
jbl2.setFont(new Font("宋体",Font.BOLD,15));
UserName.setBounds(550,150,150,50);
UserName.setFont(new Font("宋体",Font.BOLD,15));
UserName.setForeground(new Color(0xF0F05D));
UserName.setFont(new Font("宋体",Font.BOLD,20));
UserName.setText("12345");
jbt.setBounds(560,470,70,30);
jbt.setFocusPainted(false);
jbt.setBackground(new Color(0x41A9A9));
jbt.setFont(new Font("楷体",Font.PLAIN,17));
Change.setBounds(570,200,90,25);
Change.setBackground(new Color(0x41A9A9));
Change.setFocusPainted(false);
SockSever.getContentPane().add(jbl2);
SockSever.getContentPane().add(Change);
SockSever.getContentPane().add(UserName);
SockSever.getContentPane().add(jbt);
SockSever.getContentPane().add(jsp);
SockSever.getContentPane().add(jtf);
SockSever.getContentPane().add(jbl);
SockSever.getContentPane().add(jbl1);
}
//按钮监听事件
public static void ClickButton() throws IOException {
jbt.addActionListener(new ActionListener() {
//设置缓冲流用于发送消息
BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(SettingIp.socket.getOutputStream()));
@Override
public void actionPerformed(ActionEvent e) {
String str=jtf.getText();
jtf.setText("");
if(!str.equals("")) {
try {
//发送消息给服务器
System.out.println("发送了");
System.out.println(str);
String str1 ="用户【" + UserName.getText() +"】:"+str;
bw.write(str1);
bw.newLine();
bw.flush();
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
}
});
//改变昵称按钮监听
Change.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
Tips.setVisible(true);
System.out.println("djl");
}
});
Determine.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if(!AddNew.getText().equals("")) {
UserName.setText(AddNew.getText());
}
AddNew.setText("");
Tips.setVisible(false);
}
});
}
}
接收消息类:
package com.TWT.Chat.Client;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
public class RunnableSend implements Runnable {
Socket socket;
public RunnableSend(Socket socket){
this.socket=socket;
}
@Override
public void run() {
while (true){
try {
//将服务器传来的消息存入文本域中展示
BufferedReader bufferedReader=new BufferedReader(new InputStreamReader(socket.getInputStream()));
System.out.println("线程已开启");
System.out.println("进入循环");
String str=bufferedReader.readLine();
System.out.println("收到了"+str);
GuiSend.jta1.append(str+"\r\n");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
至此 ,客户端的主要功能已完全实现。
二:服务器
1:主要思路
先创建 一个服务器对象ServerSocket,和一个文件对象(用于存储用户名和密码的信息,并在的登入时与输入用户名和密码进行匹配),然后用SeverSocket对象循环接收客户端对象,每接收一个对象即开启一条线程与客户端对应。关于群发逻辑:将接收到的每一个客户端对象存入一个集合中,当收到客户端的消息时,遍历集合判断客户端是否已连接,若连接则将消息发送给客户端。
服务器主入口:
package com.TWT.Chat.Server;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
// 服务器端
public class SeverSocket1 {
//设置静态客户端集合,用于存储连接服务器的客户端用于群发
public static ArrayList<Socket> list1 =new ArrayList<>();
//设置用户的集合,用于在登入时对账号密码进行匹配
public static ArrayList<user> list=new ArrayList<>();
//服务器入口
public static void main(String[] args) throws IOException {
//设置文件写入缓冲流用于对已注册的账号进行储存
BufferedWriter bufferedWriter=new BufferedWriter(new FileWriter("MyChat\\Login.txt",true));
adduser();
//创建服务器界面的对象
new GuiServe();
//创建服务器对象,用于接收客户端
ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket(Integer.valueOf(GuiServe.Jbt2_Str));
} catch (IOException ex) {
throw new RuntimeException(ex);
}
//设置一寻环,用于重复接收客户端传来的信息
while (true) {
Socket socket ;
try {
//socket为客户端对象
socket = serverSocket.accept();
} catch (IOException ex) {
throw new RuntimeException(ex);
}
System.out.println("连接成功");
//接收一对象,就创建一线程
new Thread(new MyRunnable(socket,bufferedWriter)).start();
}
}
//此方法为减少算法的时间复杂度
private static void adduser() throws IOException {
//设置一缓冲流用于读取注册表中的信息
BufferedReader br=new BufferedReader(new FileReader("MyChat\\Login.txt"));
String str;
//将读取的用户名和密码存入集合中
while((str= br.readLine())!=null){
String str1[]= str.split("&");
String username=str1[0].split("=")[1];
String password=str1[1].split("=")[1];
user u=new user(username,password);
list.add(u);
}
}
}
2:界面设置
由于界面尚未完善所以暂不赘述
package com.TWT.Chat.Server;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
/*
*
*服务端界面,未完善,暂不详解
*/
public class GuiServe extends JFrame {
public GuiServe(){
InitWindow();
InitTextArea();
InitSend();
ClickButton();
}
public static JFrame SeSock=new JFrame();
private static final int FRAME_HIGH=500;
private static final int FRAME_LEN=600;
public static JTextArea jta1=new JTextArea(20,30);
private static JTextArea jta2=new JTextArea(20,30);
private static JScrollPane jsp=new JScrollPane(jta1);
private static JTextField jtf1=new JTextField();
private static JTextField jtf2=new JTextField();
private static JButton jbt1=new JButton("发送");
private static JButton jbt2=new JButton("设置端口");
public static JLabel jbl=new JLabel();
private static String Jbt1_Str="";
public static String Jbt2_Str="12346";
private void InitWindow(){
SeSock.setTitle("服务器");
SeSock.setSize(FRAME_LEN,FRAME_HIGH);
SeSock.setLocationRelativeTo(null);
SeSock.setResizable(false);
SeSock.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
SeSock.setLayout(null);
SeSock.setVisible(true);
}
private void InitTextArea(){
jsp.setBounds(0,0,450,350);
jta1.setBackground(new Color(1));
jta1.setLineWrap(true);
jta1.setEnabled(false);
jbl.setBounds(450,0,150,30);
jbl.setText("当前在线人数: "+String.valueOf(MyRunnable.num));
jta2.setBounds(450,40,150,310);
jta2.setLineWrap(true);
jta2.setEnabled(false);
jta2.setText("--------用户在线列表-------");
jta2.setBackground(new Color(0xCA8B48));
SeSock.getContentPane().add(jta2);
SeSock.getContentPane().add(jbl);
SeSock.getContentPane().add(jsp);
}
private void InitSend(){
jtf1.setBounds(20,370,400,30);
jbt1.setBounds(460,370,110,30);
jtf2.setBounds(20,420,400,30);
jbt2.setBounds(460,420,110,30);
SeSock.getContentPane().add(jtf2);
SeSock.getContentPane().add(jbt2);
SeSock.getContentPane().add(jbt1);
SeSock.getContentPane().add(jtf1);
}
private void ClickButton(){
jbt1.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
Jbt1_Str= jtf1.getText();
System.out.println(Jbt1_Str);
jtf1.setText("");
}
});
jbt2.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
Jbt2_Str= jtf2.getText();
System.out.println(Jbt2_Str);
jtf2.setText("");
boolean RightPort=Jbt2_Str.matches("([0-9]|[1-9]\\d{1,3}|[1-5]\\d{4}|6[0-4]\\d{4}|65[0-4]\\d" +
"{2}|655[0-2]\\d|6553[0-5])");
}
});
}
}
3:代码实现逻辑
1:当开启一条线程后,先创建一个输入流用于接收客户端发来的消息,再匹配字符串,判断客户端当前的状态看是登入还是注册,还是正在发消息。
2:注册设置,当接收到客户端发来的消息是,先将用户名与注册表中的用户名进行匹配若重复则发送给客户端注册失败,若成功则提醒用户注册成功,并将用户名与密码存入注册表中
3:登入设置,即将用户名与密码与注册表中的进行匹配,并给于客户端各种提醒,如用户名不存在等。
4:聊天设置,当客户端登入成功后进入转发消息的模块,循环接收客户端发来的消息并群发给集合中储存的用户(正在连接服务器)
5:代码如下
package com.TWT.Chat.Server;
import java.io.*;
import java.net.Socket;
public class MyRunnable implements Runnable {
//设置静态变量用于表示当前系统的在线人数
public static int num=0;
//客户端对象
Socket socket;
//文件缓冲流
BufferedWriter bufferedWriter;
//设立有参构造方法用于传入,服务器接收的对象
public MyRunnable(Socket socket, BufferedWriter bufferedWriter) {
this.socket=socket;
this.bufferedWriter=bufferedWriter;
}
@Override
public void run() {
try {
System.out.println("线程开启");
//设置读取缓冲流用于读取客户端发送的信息
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
while (true) {
String str = br.readLine();
System.out.println(str);
switch (str) {
//若为一则注册
case "1":
Enroll(br, bufferedWriter);
break;
//若为二则登录
case "2":
Login(br);
break;
}
}
//连接异常则说明改客户端以断开
} catch (IOException e) {
System.out.println("已断开");
System.out.println("线程已结束");
//在线人数减一
num--;
GuiServe.jbl.setText("当前在线人数: " + String.valueOf(MyRunnable.num));
System.out.println("当前在线人数"+num);
}
}
//注册方法
private void Enroll(BufferedReader br,BufferedWriter bufferedWriter) throws IOException {
//设置缓冲输出流发送给客户端用于判断注册后的结果
BufferedWriter bufferedWriter1=new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
System.out.println("注册");
String str=br.readLine();
System.out.println(str);
//发送消息给客户端
if(IfEnroll(str)==1){
OutWrite(bufferedWriter1,"1");
System.out.println("用户名重复");
}
if(IfEnroll(str)==2) {
OutWrite(bufferedWriter1,"2");
try {
bufferedWriter.write(str);
bufferedWriter.newLine();
bufferedWriter.flush();
}catch (IOException e){
System.out.println("传入文件错误");
}
useradd(str);
System.out.println("注册成功");
}
}
//登入方法
private void Login(BufferedReader br) throws IOException {
//与注册同
BufferedWriter bufferedWriter1=new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
System.out.println("登录");
String str=br.readLine();
System.out.println(str);
int i=IfLogin(str);
if(i==1){
OutWrite(bufferedWriter1,"1");
System.out.println("用户名不存在");
}
else if (i==2){
OutWrite(bufferedWriter1,"2");
System.out.println("用户名或密码错误");
}
else if (i==3){
OutWrite(bufferedWriter1,"3");
System.out.println("登录成功");
num++;
SeverSocket1.list1.add(socket);
GuiServe.jbl.setText("当前在线人数: "+String.valueOf(MyRunnable.num));
//登录成功,开始聊天
Talk(br);
}
}
//判断是否注册成功
private int IfEnroll(String str){
String str1[]= str.split("&");
String username=str1[0].split("=")[1];
for (int i = 0; i < SeverSocket1.list.size(); i++) {
//若用户名重复,则说明注册失败返回1
if(SeverSocket1.list.get(i).getUsername().equals(username)){
return 1;
}
}
//注册成功返回二
return 2;
}
//检测是否登录成功,与注册同
private int IfLogin(String str){
String str1[]= str.split("&");
String username=str1[0].split("=")[1];
String password=str1[1].split("=")[1];
for (int i = 0; i < SeverSocket1.list.size(); i++) {
if(SeverSocket1.list.get(i).getUsername().equals(username)){
if(password.equals(SeverSocket1.list.get(i).getPassword())){
GuiServe.jta1.append("用户【"+username+"】已连接服务器"+"\r\n");
return 3;
}
else return 2;
}
}
return 1;
}
//若注册成功则添加雄安锡到集合中
private static void useradd(String str){
String str1[]= str.split("&");
String username=str1[0].split("=")[1];
String password=str1[1].split("=")[1];
user u=new user(username,password);
SeverSocket1.list.add(u);
System.out.println("已添加注册消息");
}
//当前服务器发送客户端简化代码方法
private static void OutWrite(BufferedWriter bw,String str) throws IOException {
bw.write(str);
bw.newLine();
bw.flush();
}
//判断远程客户端是否连接
public static boolean isClientDisconnected(Socket socket) {
if (socket == null) {
return false; // 如果socket为null,则假设客户端已断开连接
}
if (socket.isConnected() && !socket.isClosed()) {
try {
socket.sendUrgentData(0);
return true; // 客户端仍然连接
} catch (IOException e) {
return false; // 发送紧急数据失败,客户端已断开连接
}
} else {
return true; // socket没有连接或已关闭,客户端已断开连接
}
}
//聊天方法
private void Talk(BufferedReader bufferedReader) throws IOException {
//循环转发
while (true){
//接收客户端发来的信息
String str=bufferedReader.readLine();
System.out.println("收到了"+str);
int i=0;
for ( Socket users:SeverSocket1.list1) {
//转发给正在连接服务器的客户端
if (isClientDisconnected(users)){
if(users.equals(socket)){
//辨别是否为本人发送
write(users,"(我)"+str);
}
else write(users,str);
System.out.println("发送了"+(i++));
}
}
}
}
//此为提取方法,用于简化代码
public void write(Socket s, String message) throws IOException {
//获取输出流
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
bw.write(message);
bw.newLine();
bw.flush();
}
}
四:项目总结
一:优点
1:实现了基本登录注册功能
2:实现了注册表的实现
3:实现了让用户的自行输入IP和端口
4:实现了基本群聊功能
5:实现了群聊时对用户是否断开服务器的判断
6:实现了对昵称的更换功能
二:缺点
1:未实现用户的私聊功能
2:未实现聊天记录的保存功能
3:无法的知用户的在线情况
4:没有丰富聊天机制,如文件传输,表情输入等
5:没用完善服务器的某些功能
6:没有对用户的某些特征进行完善,如个人信息,头像等