项目名称
利用TCP协议,做一个带有登录,注册的无界面,控制版的多人聊天室。
知识点
循环,判断,集合,IO,多线程,网络编程
准备内容
在当前模块下新建txt文件,在文件中保存正确的用户名和密码
java
zhangsan=123
lisi=1234
wangwu=12345
页面搭建
- 客户端连接服务器后,显示如下
java
服务器已经连接成功
==============欢迎来到小周聊天室================
1登录
2注册
请输入您的选择:
服务器
java
public class Server {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(10001);
System.out.println("有客户端来连接");
while (true) {
}
}
}
客户端
java
public class Client {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1",10001);
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);
switch(sc.nextInt()){
case 1 -> System.out.println("用户选择了登录");
case 2 -> System.out.println("用户选择了注册");
default -> System.out.println("没有这个选项");
}
}
}
}
效果展示
java
服务器已经连接成功
=========欢迎来到多人聊天室=========
1.登录
2.注册
请选择选项:
1
用户选择了登录
登录操作
- 需要输入用户名和密码
- 回车,提交给服务器,进行验证,服务器会根据txt文件中的密码
根据不同的情况,服务器会写三种判断提示
java
服务器回写第一种提示:登录成功
服务器回写第二种提示:密码有误
服务器回写第三种提示:用户名不存在
客户端会根据服务器回写的数据,根据三种情况进行不同的处理方案
- "登录成功"->开始聊天
- "密码错误"->需要重新输入
- "用户名不存在"->需要重新输入
如果登录成功,就可以开始聊天,此时的聊天,一个人发消息给服务端,服务端接收之后需要群发给所有人。
将登录操作抽成一个方法(养成一个习惯,一个方法里就实现一个功能)
首先我们要实现数据(用户名和密码)的传输
客户端
java
public 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();
//拼接
//username=zhangsan&password=123
StringBuilder sb = new StringBuilder();
sb.append("username=").append(username).append("&password=").append(password);
//写入数据
bw.write(sb.toString());
bw.newLine();
bw.flush();
}
服务器
java
public class Server {
static ArrayList<Socket> list = new ArrayList<>();
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(1000);
//获取文件中正确的用户名及密码
Properties prop = new Properties();
FileInputStream fis = new FileInputStream("D:\\java\\basic-code\\online-chat-room\\客户数据.txt");
prop.load(fis);
//只要有一个客户端,就开一条线程处理
while (true) {
Socket socket = serverSocket.accept();
System.out.println("有客户端来连接");
new Thread(new MyRunnable(socket,prop)).start();
}
}
}
//线程的作用:传输工具
class MyRunnable implements Runnable {
Socket socket;
Properties prop;
public MyRunnable(Socket socket, Properties prop) {
//连接客户端
this.socket = socket;
this.prop = prop;
}
public void run() {
try {
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line = br.readline();
System.out.println(line);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
运行结果
java
//客户端
服务器已经连接成功
=========欢迎来到小周聊天室=========
1.登录
2.注册
请选择选项:
1
请输入用户名:
zhangsan
请输入密码:
123
//服务器
有客户端来连接
username=zhangsan&password=123
要使右边的服务器知道客户端进行的是登录操作还是注册操作,从而让服务器进行不同的操作,我们可以从客户端发送一条信息,告诉服务器,是登录操作还是注册操作。
所以我们要对上面的代码进行适当的改写。
客户端发送两次数据
java
//第一次写入执行登录操作(告诉服务器)
bw.write("login");
bw.newLine();
bw.flush();
//第二次写入数据
bw.write(sb.toString());
bw.newLine();
bw.flush();
服务端根据客户端发来的第一次数据进行判断,进行选择操作
java
String choose = br.readLine();
switch (choose) {
case "login" -> System.out.println("用户选择了登录操作");
case "register" -> System.out.println("用户选择了注册操作");
}
服务器根据传来的信息(login),进行用户名和密码的校验
java
//该方法要获取输入的用户名和密码
//并进行判断
public void login(BufferedReader br) throws IOException {
System.out.println("用户选择了登录操作");
String userinfo = br.readLine();
//username=zhangsan&password=123
//对搜到的用户信息进行拆分
String[] userinfoArr = userinfo.split("&");
String usernameInput = userinfoArr[0].split("=")[1];
String passwordInput = userinfoArr[1].split("=")[1];
System.out.println("用户名为:"+usernameInput);
System.out.println("密码为:"+passwordInput);
//判断
if(prop.containsKey(usernameInput)){
//用户名存在,则继续判断密码是否正确
String rightpassword = prop.get(usernameInput)+"";
//判断密码是否正确
if(rightpassword.equals(passwordInput)){
//提示用户登录成功
writeMessage2Client("1");
}else{
//密码错误,直接回写
writeMessage2Client("2");
}
}else{
//用户名不存在,直接回写
writeMessage2Client("3");
}
}
根据客户端发来的用户名和密码,与文件中的数据进行判断是否一致,判断完之后再回写给客户端因为回写的代码是通用的,内容不一样,所以可以把它抽成一个方法。
java
public void writeMessage2Client(String message) throws IOException {
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
bw.write(message);
bw.newLine();
bw.flush();
}
当用户输错了密码,需要进行重新登录,所以要将读取客户端发来的数据的代码进行while(true)循环。
java
while (true) {
String choose = br.readLine();
switch (choose) {
case "login" -> login(br);
case "register" -> System.out.println("用户选择了注册操作");
}
}
运行结果
java
服务器已经连接成功
=========欢迎来到小周聊天室=========
1.登录
2.注册
请选择选项:
1
请输入用户名:
zhangsan
请输入密码:
12
输入完之后发现没有运行结果,聪明的你肯定发现了,是因为客户端还有写接收数据的代码。
java
//创建输入流
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String s = br.readLine();
//可以用状态码代表传输的文字,比较方便
//1:登陆成功 2:密码错误 3:用户不存在
if(s.equals("1")){
System.out.println("登录成功,可以开始聊天");
//写一个while(true)循环,表示开始聊天
while(true){}
}else if(s.equals("2")){
System.out.println("密码有误,请重新登录");
}else if(s.equals("3")){
System.out.println("用户名不存在,请先注册");
}
运行结果
java
服务器已经连接成功
=========欢迎来到小周聊天室=========
1.登录
2.注册
请选择选项:
1
请输入用户名:
zhangsan
请输入密码:
12
密码有误,请重新登录
=========欢迎来到小周聊天室=========
1.登录
2.注册
请选择选项:
1
请输入用户名:
wuyu
请输入密码:
123
用户名不存在,请先注册
=========欢迎来到小周聊天室=========
1.登录
2.注册
请选择选项:
1
请输入用户名:
zhangsan
请输入密码:
123
登录成功,可以开始聊天
接下来我们实现客户端和服务器之间的聊天连接
服务器
因为可能会有很多个客户端向服务器发送数据,所有传递的参数加一个usernameInput
java
//接收客户端发来的信息
talk2All(br,usernameInput);
java
private void talk2All(BufferedReader br, String usernameInput) throws IOException {
while (true) {
String message = br.readLine();
System.out.println(usernameInput+"发送过来消息:"+message);
}
}
}
客户端
java
//发出消息,显示在控制台
talk2All(bw);
java
private static void talk2All(BufferedWriter bw) throws IOException {
Scanner sc = new Scanner(System.in);
while(true){
System.out.println("请输入你要说的话");
String message = sc.nextLine();
bw.write(message);
bw.newLine();
bw.flush();
}
}
群发的逻辑
服务器要向每一个客户端回写数据
功能实现:
服务端可以将所有用户的Socket对象存储到一个集合中
当需要群发消息时,可以遍历集合发给所有的用户
此时的服务端,相当于做了一个消息的转发
服务器群发逻辑
在服务器的成员位置定义一个静态集合
java
static ArrayList<Socket> list = new ArrayList<>();
有客户端来连接,就把它的socket保存起来
java
Server.list.add(socket);
然后服务器的talk2All除了打印客户端发来的数据,还要进行群发的操作,遍历集合,给每一个socket对象发送数据。
java
for(Socket s : Server.list){
writeMessage2Client(s,usernameInput+"发送过来消息:"+message);
}
因为原来的writeMessage2Client方法只能传递一个message回写的消息,不能传socket,所以我们需要对writeMessage2Client方法进行重载。
方法的重载
是指在同一个类中,方法名相同但参数列表不同的多个方法。这些方法可能会有不同的参数类型、参数数量或参数顺序。方法的重载允许我们根据不同的参数来调用同名的方法。
java
public void writeMessage2Client(Socket s, String message) throws IOException {
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
bw.write(message);
bw.newLine();
bw.flush();
}
客户端群发逻辑
我们要思考我们在发送消息的时候,服务器可能会传一个消息过来,所以需要单独开一条线程,专门用来接收服务器发送过来的聊天记录,这条线程只负责接收并打印在控制台。
java
//单独开一条线程去接收服务器发送过来的聊天记录
new Thread(new ClientMyRunnable(socket)).start();
java
class ClientMyRunnable implements Runnable{
Socket socket;
public ClientMyRunnable(Socket socket){
this.socket = socket;
}
@Override
public void run() {
//循环,重复的接收
while(true) {
try {
//接收服务器发来的聊天记录
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String mag = br.readLine();
System.out.println(mag);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
结束
我们来测验一下吧
java
//服务器
有客户端来连接
有客户端来连接
用户选择了登录操作
用户名为:zhangsan
密码为:123
用户选择了登录操作
用户名为:lisi
密码为:1234
zhangsan发送过来消息:你好呀,李四
lisi发送过来消息:你好呀,张三
//客服端1
服务器已经连接成功
=========欢迎来到小周聊天室=========
1.登录
2.注册
请选择选项:
1
请输入用户名:
zhangsan
请输入密码:
123
登录成功,可以开始聊天
请输入你要说的话
你好呀,李四
请输入你要说的话
zhangsan发送过来消息:你好呀,李四
lisi发送过来消息:你好呀,张三
//客户端2
服务器已经连接成功
=========欢迎来到小周聊天室=========
1.登录
2.注册
请选择选项:
1
请输入用户名:
lisi
请输入密码:
1234
登录成功,可以开始聊天
请输入你要说的话
zhangsan发送过来消息:你好呀,李四
你好呀,张三
请输入你要说的话
lisi发送过来消息:你好呀,张三
结束了,希望能为大家提供帮助,感谢关注!