项目实践:
首先,做这个项目的目的主要是为了巩固java学习的整个基础,包括面向对象的思想,流的应用,线程开发,网络编程的相关基础知识。
为了完成这个项目,首先我们需要清楚他的整个框架,建立聊天程序,是实现端对端的通信,这就需要建立一个服务器,通过这个服务器把多个客户端建立连接,实现聊天。
具体框图,如下:
一,服务端
在类中添加消息队列及Socket集合
因为需要给所有客户端发送消息,所以服务器端必须持有所有客户端Socket的集合,生产和消费消息数据需要一个消息队列,所以服务器还必须定义一个消息队列。
package edu.xalead.server;
import java.net.Socket;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
public class ChatServer {
/**
* 客户端连接集合
*/
private ConcurrentHashMap<String, Socket> allCustomer = new ConcurrentHashMap<>();
/**
* 存放消息的队列
*/
private ConcurrentLinkedQueue<String> messageQueue = new ConcurrentLinkedQueue<>();
}
创建接收线程
离开ChatServer类没有利用价值,所以我这里写成内部类
package edu.xalead.server;
import java.net.Socket;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
public class ChatServer {
/**
* 所以客户端连接集合
*/
private ConcurrentHashMap<String, Socket> allCustomer = new ConcurrentHashMap<>();
/**
* 存放消息的队列
*/
private ConcurrentLinkedQueue<String> messageQueue = new ConcurrentLinkedQueue<>();
/**
* 创建接收线程
* 内部类因为已经可以访问外部类的所有属性和方法,所以没必要再创建
*/
private class ReceiveService extends Thread{
// /**
// * 持有消息队列的引用
// * 内部类因为已经可以访问外部类的所有属性和方法,所以没必要再创建引用
// */
// private ConcurrentLinkedQueue<String> messageQueue = null;
// private ReceiveService(ConcurrentLinkedQueue<String> messageQueue){
// this.messageQueue = messageQueue;
// }
public void run(){
}
}
}
接收客户消息
每个接收线程只能为一个特定客户服务,必须持有这个客户的Socket,所以在接收线程中添加客户的Socket引用
//客户端的套接字
private Socket client = null;
public ReceiveService(Socket client){
this.client = client;
}
下面我们先把接收线程的具体工作放一下,思考接收线程中的Socket怎么得到呢?
显然,需要编写监听客户端的代码吧
添加监听客户端连接的代码
/**
* 监听
*/
public void start(){
ServerSocket serverSocket = null;
Socket client = null;
try {
//申请端口
serverSocket = new ServerSocket(port);
while (true) {
//监听客户端连接
System.out.println("开始监听新的客户端连接...");
client = serverSocket.accept();
System.out.println("监听到客户端【" + client.getInetAddress()
.getHostAddress() + ":" + client.getPort() + "】");
//提供消息服务
new ReceiveService(client).start();
//把socket放进客户socket集合,以便发送线程使用
allCustomer.put(client.getInetAddress().getHostAddress(),client);
//监听下一个
}
} catch (Exception e) {
e.printStackTrace();
}
}
完成接收服务线程
public void run(){
//因为接收字符所以选择字符流,并且Buffer字符流的readLine()非常好用,所以选择它
BufferedReader br = null;
try {
//注意socket只能得到字节流,所以要把它包装成字符流得用InputStreamReadedr再包装一下
br = new BufferedReader(
new InputStreamReader(client.getInputStream()));
while (true) {
//接收消息
System.out.println("等待接收客户端【" + client.getInetAddress()
.getHostAddress() + "】消息");
String mesg = br.readLine();
System.out.println("接收到客户端【" + client.getInetAddress()
.getHostAddress() + "】消息【" + mesg + "】");
//放入消息队列
messageQueue.offer(mesg);
//接收下一条
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
定义发送线程
/**
* 创建发送线程
*/
private class SendService implements Runnable{
@Override
public void run() {
try {
PrintWriter pw = null;
while (true) {
//取消息队列中的消息
String mesg = messageQueue.poll();
if(mesg != null) {
//遍历客户连接
for (Socket socket : allCustomer.values()) {
//创建字符输出流半配网络字节流
pw = new PrintWriter(socket.getOutputStream