项目名称
利用TCP协议,做一个带有登录,注册的无界面,控制版的多人聊天室。
使用到的知识点
循环,判断,集合,IO,多线程,网络编程等
准备工作
在当前模块下新建txt文件,文件中保存正确的用户名和密码
文件内容如下:
//左边是用户名 //右边是密码 zhangsan=123 lisi=1234 wangwu=12345
需求描述
① 客户端启动之后,需要连接服务端,并出现以下提示:
服务器已经连接成功 ==============欢迎来到黑马聊天室================ 1登录 2注册 请输入您的选择:
②选择登录之后,出现以下提示:
服务器已经连接成功 ==============欢迎来到黑马聊天室================ 1登录 2注册 请输入您的选择: 1 请输入用户名
③需要输入用户名和密码,输入完毕,没有按回车时,效果如下:
服务器已经连接成功 ==============欢迎来到黑马聊天室================ 1登录 2注册 请输入您的选择: 1 请输入用户名 zhangsan 请输入密码 123
④按下回车,提交给服务器验证
服务器会结合txt文件中的用户名和密码进行判断
根据不同情况,服务器回写三种判断提示:
服务器回写第一种提示:登录成功 服务器回写第二种提示:密码有误 服务器回写第三种提示:用户名不存在
⑤客户端接收服务端回写的数据,根据三种情况进行不同的处理方案
登录成功的情况, 可以开始聊天,出现以下提示:
服务器已经连接成功 ==============欢迎来到黑马聊天室================ 1登录 2注册 请输入您的选择: 1 请输入用户名 zhangsan 请输入密码 123 1 登录成功,开始聊天 请输入您要说的话
密码错误的情况,需要重新输入,出现以下提示:
服务器已经连接成功 ==============欢迎来到黑马聊天室================ 1登录 2注册 请输入您的选择: 1 请输入用户名 zhangsan 请输入密码 aaa 密码输入错误 ==============欢迎来到黑马聊天室================ 1登录 2注册 请输入您的选择:
用户名不存在的情况,需要重新输入,出现以下提示:
服务器已经连接成功 ==============欢迎来到黑马聊天室================ 1登录 2注册 请输入您的选择: 1 请输入用户名 zhaoliu 请输入密码 123456 用户名不存在 ==============欢迎来到黑马聊天室================ 1登录 2注册 请输入您的选择:
⑥如果成功登录,就可以开始聊天,此时的聊天是群聊,一个人发消息给服务端,服务端接收到之后需要群发给所有人
提示:
此时不能用广播地址,因为广播是UDP独有的
服务端可以将所有用户的Socket对象存储到一个集合中
当需要群发消息时,可以遍历集合发给所有的用户
此时的服务端,相当于做了一个消息的转发
转发核心思想如下图所示:
其他要求:
用户名和密码要求:
要求1:用户名要唯一,长度:6~18位,纯字母,不能有数字或其他符号。
要求2:密码长度3~8位。第一位必须是小写或者大小的字母,后面必须是纯数字。
客户端:
拥有登录、注册、聊天功能。
① 当客户端启动之后,要求让用户选择是登录操作还是注册操作,需要循环。
-
如果是登录操作,就输入用户名和密码,以下面的格式发送给服务端
username=zhangsan&password=123
-
如果是注册操作,就输入用户名和密码,以下面的格式发送给服务端
username=zhangsan&password=123
② 登录成功之后,直接开始聊天。
服务端:
① 先读取本地文件中所有的正确用户信息。
② 当有客户端来链接的时候,就开启一条线程。
③ 在线程里面判断当前用户是登录操作还是注册操作。
④ 登录,校验用户名和密码是否正确
⑤ 注册,校验用户名是否唯一,校验用户名和密码的格式是否正确
⑥ 如果登录成功,开始聊天
⑦ 如果注册成功,将用户信息写到本地,开始聊天
代码
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
public class Client {
public static void main(String[] args) throws IOException {
//创建客服端对象,连接服务器
Socket socket = new Socket("127.0.0.1", 10000);
System.out.println("服务器已经连接成功");
while (true) {
System.out.println("==============欢迎来到黑马聊天室================");
System.out.println("1登录");
System.out.println("2注册");
System.out.println("请输入您的选择:");
Scanner sc = new Scanner(System.in);
String choose = sc.nextLine();
switch (choose) {
case "1" -> login(socket);
case "2" -> System.out.println("用户选择了注册");
default -> System.out.println("没有这个选项");
}
}
}
private static void login(Socket socket) throws IOException {
//向服务器写出数据
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
//键盘录入
Scanner sc = new Scanner(System.in);
System.out.println("请输入用户名");
String username = sc.nextLine();
System.out.println("请输入密码");
String password = sc.nextLine();
//拼接
StringBuilder sb = new StringBuilder();
//username=zhangsan&password=123
sb.append("username=").append(username).append("&password=").append(password);
//第一次写的是执行登录操作
method(bw, "login");
//第二次写的是用户名和密码的信息
//往服务器写出用户名和密码
method(bw, sb.toString());
//接受服务器反馈的信息
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String message = br.readLine();
//状态码 1:登录成功 2:用户名不存在 3:密码错误
if ("1".equals(message)) {
System.out.println("登录成功,开始聊天");
//开始聊天的时候,服务器可能会向客服端发信息
//开一条单独的线程,专门负责接受服务器发来的信息
ClientThread t = new ClientThread(socket);
t.start();
//开始聊天
talkAll2(bw);
} else if ("3".equals(message)) {
System.out.println("用户名不存在");
} else if ("2".equals(message)) {
System.out.println("密码错误");
}
}
//向服务器写出消息
private static void talkAll2(BufferedWriter bw) throws IOException {
Scanner sc = new Scanner(System.in);
while (true) {
System.out.println("请输入消息:");
String str = sc.nextLine();
;
method(bw, str);
}
}
private static void method(BufferedWriter bw, String str) throws IOException {
bw.write(str);
bw.newLine();
bw.flush();
}
}
//接受服务器发来的群发消息
class ClientThread extends Thread {
Socket socket;
ClientThread(Socket socket) {
this.socket = socket;
}
//接受服务器发来的消息
@Override
public void run() {
// 循环重复的接受
while (true) {
try {
//接受服务器发来的聊天记录
InputStream is = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String message = br.readLine();
System.out.println(message);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
import java.io.*;
import java.net.Socket;
import java.util.Properties;
public class Mythread extends Thread {
Socket socket;
Properties p;
public Mythread(Socket socket, Properties p) {
this.socket = socket;
this.p = p;
}
@Override
public void run() {
//读取客户端发送过来的数据(用户名和密码)
try {
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
while (true) {
String choose = br.readLine();
switch (choose) {
case "login" -> login(br);
case "register" -> System.out.println("用户选择了注册操作");
}
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
//获取用户登录时,传递过来的信息。
//并进行判断
//读取客户端发送过来的数据(用户名和密码)
public void login(BufferedReader br) throws IOException {
System.out.println("用户选择了登录操作");
String userinfo = br.readLine();
//username=zhangsan&password=123
//将用户名和密码进行拆分
String[] arr = userinfo.split("&");
String usernameInput = arr[0].split("=")[1];
String passwordInput = arr[1].split("=")[1];
System.out.println("用户输入的用户名为:" + usernameInput);
System.out.println("用户输入的密码为:" + passwordInput);
if (p.containsKey(usernameInput)) {
//如果用户名存在,继续判断密码 //通过用户名获取密码(键获取值)
String rightPassword = p.get(usernameInput) + "";
if (rightPassword.equals(passwordInput)) {
//提示用户登录成功,可以开始聊天
writeMessage2Client("1");
//群发
//登录成功的时候就需要把客服端的Socket对象保存起来,方便后面给其他用户发送消息
Server.list.add(socket);//类名直接调用集合,保存所有的用户对象,方便对每一个用户客服端群发
//接受客户端发送过来的消息,并打印到控制台
talkAll1(br, usernameInput);
} else {
//密码输入有误
writeMessage2Client("2");
}
} else {
//如果用户名不存在,直接回写
writeMessage2Client("3");
}
}
//读取客户端发送过来的消息,并打印到控制台
private void talkAll1(BufferedReader br, String username) throws IOException {
while (true) {
String message = br.readLine();
System.out.println(username + "发送的消息:" + message);
//群发
for (Socket s : Server.list) {
//s代表每一个客服端的Socket连接对象
writeMessage2Client(s, username + "发送的消息:" + message);
}
}
}
//作用:服务端将消息发送给所有用户的客服端(群发)
public void writeMessage2Client(Socket s, String message) throws IOException {
// 写出数据
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
bw.write(message);
bw.newLine();
bw.flush();
}
/*
作用: 将服务器端的消息回写客户端
*/
public void writeMessage2Client(String message) throws IOException {
// 写出数据
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
bw.write(message);
bw.newLine();
bw.flush();
}
}
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Properties;
public class Server {
static ArrayList<Socket> list = new ArrayList<>();
public static void main(String[] args) throws IOException {
//创建服务的对象,绑定端口号
ServerSocket ss = new ServerSocket(10000);
//1.读取本地文件里正确的用户名和密码
Properties p = new Properties();
FileInputStream fis = new FileInputStream("..\\WX\\.idea\\Server\\userinfo.txt");
//把文件里的数据加载到p集合当中
p.load(fis);
fis.close();
while (true) {
//2.只要来了一个客户端,就开一条线程处理
//等待客户端连接
Socket socket = ss.accept();
//读取客服端发送的数据
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
System.out.println("有客户端来链接");
Mythread t = new Mythread(socket, p);
t.start();
}
}
}