此项目若有不懂的知识点,请各位小哥哥小姐姐移步我的其他博文学习。主要知识点网络通信 线程 输入/输出流等。
ps:只有学会知识原理,才更容易学习并掌握此程序。
一、 实现功能:
1.用户注册
2.注册成功的用户可以在控制台界面任意发送消息和接收消息没有顺序。
二、 设计思路图:
三、主要代码
1、 服务器端Server.
1) 发送消息线程:线程实现使用继承Thread类,并实现run方法。
public class SendMessageServer1 extends Thread{
//消息队列
private ConcurrentLinkedQueue<String> messageQueue = new ConcurrentLinkedQueue<String>();
//套接字集合
private ConcurrentSkipListMap<String, Socket> socketMap = new ConcurrentSkipListMap<String,Socket>();
public SendMessageServer1(ConcurrentLinkedQueue<String> messageQueue,
ConcurrentSkipListMap<String, Socket> socketMap) {
super();
this.messageQueue = messageQueue;
this.socketMap = socketMap;
}
public void run(){
try {
while(true){
//获取并移除消息队列的头,如果队列为空,则返回null。
String mesg = messageQueue.poll();
//线程协作,同步监视器
synchronized (messageQueue) {
//当拿到messageQueue时,且消息不为空,使用输出流将消息打印出来,否则等待messageQueue被释放掉。
if (mesg != null && !"".equals(mesg)) {
Collection<Socket> sockets = socketMap.values();
Iterator<Socket> iter = sockets.iterator();
while (iter.hasNext()) {
Socket socket = iter.next();
PrintWriter pw = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()));
pw.println(mesg);
pw.flush();
}
} else {
messageQueue.wait();
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
2)接收消息线程,实现Runnable接口,并实现run()方法。
public class ReceiveMessageServer1 implements Runnable {
//消息队列
private ConcurrentLinkedQueue<String> messageQueue = new ConcurrentLinkedQueue<String>();
//套接字集合
private ConcurrentSkipListMap<String, Socket> socketMap = new ConcurrentSkipListMap<String,Socket>();
//当前线程服务的客户端的套接
private Socket socket;
private BufferedReader br = null;
private PrintWriter pw = null;// PrintWriter可直接封装字节流
public ReceiveMessageServer1(ConcurrentLinkedQueue<String> messageQueue,
ConcurrentSkipListMap<String, Socket> socketMap, Socket socket) {
super();
try {
this.messageQueue = messageQueue;
this.socketMap = socketMap;
this.socket = socket;
br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
pw = new PrintWriter(socket.getOutputStream());
} catch (Exception e) {
e.printStackTrace();
}
}
//发送消息的方法
private void printMessage(String message){
pw.println(message);
pw.flush();
}
@Override
public void run() {
try {
//注册
String username = null;
//1.向客户端发送"请输入你的用户名"
while(true){
printMessage("请输入你的用户名");
username = br.readLine();
//2.等着接收客户发上来的用户名
Set<String> names = socketMap.keySet();
//3.校验用户是否已注册
boolean exist = names.contains(username);
if(exist){
printMessage("该用户已存在,请重新输入");
}else{
//4.若不存在,发错误消息,重新注册(返回ok)
printMessage("OK");
//5.否则将套接字存入套接集合
socketMap.put(username, socket);
break;
}
}
//6.开始反复接收客户消息,放入消息队列
while(true){
String message = br.readLine();
synchronized (messageQueue) {
messageQueue.offer(message);
messageQueue.notifyAll();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
3) 主函数:启动发送消息的线程,监听客户端的连接,创建接收消息的线程
public class ChatServer1 {
//消息队列
private ConcurrentLinkedQueue<String> messageQueue = new ConcurrentLinkedQueue<String>();
//套接字集合
private ConcurrentSkipListMap<String, Socket> socketMap = new ConcurrentSkipListMap<String,Socket>();
//主线程执行的任务
public void run(){
ServerSocket serverSocket = null;
Socket socket = null;
try {
//启动发送消息的线程
new SendMessageServer1(messageQueue, socketMap).start();
serverSocket = new ServerSocket(8888);
while(true){
System.out.println("准备监听客户连接...");
//监听端口和等待连接功能
socket = serverSocket.accept();
System.out.println("监听到客户连接[IP:"+socket.getInetAddress().getHostAddress()+",Port:"+socket.getPort()+"]");
//创建接收消息的线程
new Thread(new ReceiveMessageServer1(messageQueue, socketMap, socket)).start();
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
new ChatServer1().run();
}
}
2、 客户端Client
1)发送消息线程:当监听到客户键盘有输入时,将输入的消息发送的服务器。
public class SendMessageClient1 extends Thread {
private Socket socket = null;
private PrintWriter pw =null;
private String username ;
public SendMessageClient1(Socket socket, String username) {
super();
try {
this.socket = socket;
this.username = username;
pw = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()));
} catch (Exception e) {
e.printStackTrace();
}
}
public void run(){
while(true){
//接受键盘输入
Scanner scanner = new Scanner(System.in);
String str = scanner.nextLine();
//将键盘输入消息打印出来
pw.println(this.username+"说: "+str);
pw.flush();
}
}
}
2) 接收消息线程:监听服务器是否有消息,若有消息,输出到控制台。
public class ReceiveMessageClient1 implements Runnable {
private Socket socket = null;
private BufferedReader br =null;
public ReceiveMessageClient1(Socket socket) {
super();
try {
this.socket = socket;
br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void run() {
try {
while(true){
String str = br.readLine();
System.out.println(str);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
3) 主函数:创建Socket、启动发送线程和接收线程
public class ChatClient1 {
public static void main(String[] args) {
Socket socket = null;
PrintWriter pw = null;
BufferedReader br = null;
try {
socket = new Socket("127.0.1.1", 8888);
pw = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()));
br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String mesg = br.readLine();
System.out.println(mesg);
String username = null;
while(true){
//从键盘输入信息
Scanner scanner = new Scanner(System.in);
username = scanner.nextLine();
pw.println(username);
pw.flush();
mesg = br.readLine();
if(!"OK".equals(mesg)){
System.out.println(mesg);
}else{
System.out.println("注册成功,可以聊天!");
break;
}
}
new SendMessageClient1(socket,username).start();
new Thread(new ReceiveMessageClient1(socket)).start();
}catch (Exception e) {
e.printStackTrace();
}
}
三、运行测试:
1、启动服务器
2、启动一个客户端,并注册一个客户。
3、当启动客户端后,服务器监听到客户连接。
4、再启动一次客户端,并注册第二个客户。及服务器端的响应。
5、两个客户之间客户互相发送消息