微信、QQ早已经融入我们生活的点点滴滴。在学习了Java后,内心不禁想学习做一个简易聊天室,哪怕只是在命令行运行,只要实现聊天功能即可。心动不如行动 :)
我用的系统是win10系统,所用的开发工具是IDEA,创建的是一个maven项目
说到QQ聊天,首先应该想到它是一款C/S架构的APP,而要实现聊天这个功能,最基础应该想到它是基于Socket编程,客户端与用户端连接。
知道聊天室是基于Socket来实现的后,首先考虑客户端与服务端分别负责的功能。客户端用于发送数据,而服务端用于与客户建立连接、接收数据、发送数据。
1.对于服务端Socket来说,它的工作流程应该是:(想象你打电话,首先电话要有信号,即要有基站来接收信号)
a.建立服务器端Socket,等待客户连接 ------建立基站
ServerSocket server = new ServerSockrt(port)
b.等待客户端的连接 ---基站建好后要有客户端来使用,则必须先与其进行连接,否则服务端一直等待。。。
accpet()
c.取得输入输出流 ---服务端与客户端连接成功,客户端想要收发消息服务端必须要取得输入输出流
getInputStream() 、 getOutputStream()
d.关闭流 ---将基站关闭
server.close()
PS: 建立服务端,有几个重要的方法要注意:
public ServerSocket(int port) :在本机根据指定端口号创建服务器
public Socket accept() : 侦听并接收连接到本服务器的客户端连接,此方法会一直阻塞,直到有一个客户端成功连接返回此连接
package com.lmd.Single;
import java.io.IOException;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
public class SingleServer {
public static void main(String[] args) throws IOException {
ServerSocket server = null;
PrintStream printStream = null;
Scanner scanner = null;
try {
//1.建立基站
server = new ServerSocket(8888);
System.out.println("等待客户连接。。。");
//2.等待客户连接
Socket client = server.accept();
System.out.println("终于等到你还好我没放弃~,你的端口号为:"+client.getPort());
//3.取得输入输出流
printStream = new PrintStream(client.getOutputStream(),true,"UTF-8");
printStream.println("风里雨里一直等你,hello我是服务端");
scanner = new Scanner(client.getInputStream());
if(scanner.hasNext()){
System.out.println("客户端发来的消息~:"+scanner.nextLine());
}
} catch (IOException e) {
e.printStackTrace();
}finally {
printStream.close();
scanner.close();
server.close();
}
}
}
2.对于客户端Socket来说,它的工作流程应该是:(想象你打电话,首先要有信号,即你要与基站建立连接,否则不能使用)
a.连接到指定服务器
Socket socket = new Socket(ip,port);
b.取得输入输出流
getInputStream() 、getOutputStream()
c.关闭流
socket.close()
PS: 建立客户端,有几个重要的方法要注意:
public Socket(String host, int port) :根据指定IP和端口号创建套接字并连接到远程服务器端
public InputStream getInputStream():返回此套接字的输入流
public OutputStream getOutputStream():返回此套接字的输入流
package com.lmd.Single;
import java.io.IOException;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;
public class SingleClient {
public static void main(String[] args) throws IOException {
Socket client = null;
PrintStream sendmsgtoServer = null;
Scanner getServerMsg = null;
try {
//1.连接到指定服务器
client = new Socket("127.0.0.1",8888);
System.out.println("连接服务器ing。。。");
//2.获得输入输出流
getServerMsg = new Scanner(client.getInputStream());
sendmsgtoServer = new PrintStream(client.getOutputStream());
sendmsgtoServer.println("hello i am Client");
if(getServerMsg.hasNext()){
System.out.println("服务端发来的消息为:"+getServerMsg.nextLine());
}
} catch (IOException e) {
e.printStackTrace();
}finally {
//3.关闭流
client.close();
getServerMsg.close();
sendmsgtoServer.close();
}
}
}
3.运行程序
注意:1. 一定要先启动服务器端然后启动客户端,如果相反,则会一直运行不成功,服务端接收不到客户端 2. 客户端与服务端不能都先收消息再发或者先发再收,类比打电话占线
结果:
1.总结
单线程版本的聊天室,现在已经实现了客户端与服务器端的连接,可以成功的进行收发消息,对连接成功与否可以进行有效的判断。但是,QQ聊天不是只有一个人聊天,需要有多个用户同时在线进行聊天,而且不会发送一次消息就再不能发送了。
单线程通信的问题:
a.造成双方卡死(类比打电话占线)
b.发送一次数据后,服务器与客户端均退出,不能持久通信
c.不能同时进行数据的读取与写入(顺序操作)
d.服务器只能处理一个客户端的连接
2.改进
不能同时进行数据的读取与写入 --> 读写分离,两个不相关的线程读写分离,读作为一个线程,写作为一个线程。二者同时进行
服务器只能处理一个客户端的连接 --> 多线程接收多个客户端的链接请求,使用Map存储所有连接的客户端信息
1.操作规定:
操作 | 规定 | 举例 |
用户注册 | 以 ‘userName:’ 开头 | userName: lisi |
私聊 | 以‘P:’开头,后加用户名-消息 | P: lisi - hi |
群聊 | 以‘G:’开头,后跟要发送的信息 | G:hello |
用户退出 | 内容包含‘byebye’ | byebye |
其他输入消息 | 除上述关键字以外的消息 | g:hi |
2.客户端:
为了更好的实现聊天功能,使用读写消息线程分离来实现
//读线程:读取服务器发来信息
class ReadFromServer implements Runnable{
private Socket client;
public ReadFromServer(Socket client) {
this.client = client;
}
@Override
public void run() {
Scanner readFromSer = null;
try {
while (true) {
readFromSer = new Scanner(client.getInputStream());
readFromSer.useDelimiter("\n");
if (readFromSer.hasNext()) {
System.out.println(readFromSer.nextLine());
}
if(client.isClosed()){
System.out.println("客户端关闭");
break;
}
}
} catch (IOException e) {
e.printStackTrace();
}finally {
readFromSer.close();
}
}
}
//写线程:写信息,将信息传输给服务器
class WriteMagToServer implements Runnable{
private Socket client;
public WriteMagToServer(Socket client) {
this.client = client;
}
@Override
public void run() {
PrintStream writeMsg = null;
Scanner in = null;
try {
while (true) {
writeMsg = new PrintStream(client.getOutputStream());
System.out.println("请输入信息:");
in = new Scanner(System.in);
in.useDelimiter("\n");
if (in.hasNextLine()){
writeMsg.println(in.nextLine());
if(in.nextLine().equals("byebye")){
System.out.println("客户端退出");
in.close();
writeMsg.close();
client.close();
break;
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
3.服务端:
要能接收多个客户端的链接请求 --> 多线程
如何保存多个客户端的连接 --> Map
public class MultiServer {
static Map map = new HashMap<>();
static class ExecuteClient implements Runnable{
private Socket client;
public ExecuteClient(Socket client) {
this.client = client;
}
@Override
public void run() {
Scanner msgFromClient = null;
try {
msgFromClient = new Scanner(client.getInputStream());
while (true){
if(msgFromClient.hasNextLine()){
String msg = msgFromClient.nextLine();
// windows下将默认换行/r/n中的/r替换为空字符串
Pattern pattern = Pattern.compile("\r");
Matcher matcher = pattern.matcher(msg);
msg = matcher.replaceAll("");
if(msg.startsWith("userName:")){
//注册
continue;
}
if(msg.startsWith("G:")){
//群聊
continue;
}
if(msg.startsWith("P:")){
//私聊
continue;
}
if(msg.contains("byebye")){
//退出
continue;
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
ExecutorService execute = Executors.newFixedThreadPool(20);
try {
ServerSocket server = new ServerSocket(8888);
while (true){
System.out.println("等待客户端连接。。。");
Socket client = server.accept();
System.out.println("有新的客户端连接,端口号为:"+client.getPort());
execute.submit(new ExecuteClient(client));
}
} catch (IOException e) {
e.printStackTrace();
}finally {
execute.shutdown();
}
}
}
完整代码:https://github.com/lmdlll/helloworld/tree/master/chat_space
1.Java接口、类、多线程、I/O操作
2.Socket编程
对代码编写完成后,首先应该对其进行测试
I)边界值分析
a.最大消息长度:无限制
b.最短消息长度:1
c.最多在线人数:19
d.最多群聊人数:19
II)划分等价类
消息类型 | 内容 | 测试结果 |
用户注册 | userName:1 | 注册成功 |
userName:2 | ||
userName:3 | ||
群聊消息 | G:hello | 群发成功 |
私聊消息 | 1发送消息: P:3-hi | 私聊成功 |
2发送消息: P:1-wa | ||
退出 | 1发送消息: byebye | 退出成功 |
a. 发送空消息,服务器不做任何处理
b. 私聊对象不存在,服务器不做任何处理
c. 发送消息中不包含关键字,服务器不做任何处理
III)异常点测试
a. 连接突然断开:客户端中断无法使用
b.客户端非正常退出:服务端正常工作
c.服务端异常关闭:所有客户端都随之关闭
上述只是一个最基础的聊天室,可以根据我的代码继续进行一些扩展。
扩展点:
1.使用I/O对文件进行传输与接收
2.使用数据库进行用户的信息保存
3.使用多线程建多个聊天群组
4.用户注册登录后显示好友及群组
5.不同局域网用户的划分
6.已注册用户再次登录时的历史记录
7.信息传输大小的限制
8.对于错误命令的判断与提示
9.创建一个聊天界面而非在命令行进行输入输出