思路:
一个客户端类(其中包含成员内部类:处理服务端线程,主要用来接收服务端消息)
成员属性:
Socket socket;用于连接服务端的socket
成员方法:
无参构造:初始化Socket,绑定服务端地址和端口号
start()方法:接收客户端从控制台输入的用户名和发送的消息,利用PrintWriter流进行包装,将消息发送给服务端,并开启线程类,接收服务端发来的消息。
SeverThread类:run方法中主要处理从服务端获取消息业务
一个服务器端类(其中包含成员内部类:处理客户端线程,接收客户端消息,并且将消息返回给其他几个客户端)
成员属性:
List<PrintWriter> list;存储每个客户端发送的数据流
ServerSocket ssocket:服务端Socket
成员方法:
无参构造:初始化list和socket,设置端口号
start方法:获取连接到服务器的客户端,开启线程处理客户端
线程类ClientThread:接收客户端消息并发送给其他每一个客户端
几个加锁方法:保证发送消息,连接客户端和移除客户端 的同步
代码:
package com.mafang.chat.v5;
/**
* 服务端类
* @author Administrator
*
*/
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Sever {
// 所有客户端的输出流
private List<PrintWriter> list;
// 线程池
private ExecutorService pool;
private ServerSocket severSocket;
public Sever() throws IOException {
severSocket = new ServerSocket(8880);
list = new ArrayList<PrintWriter>();
pool = Executors.newFixedThreadPool(10);// 存放10个线程
}
/**
* 服务端启动方法
*
* @throws IOException
*/
public void start() throws IOException {
while (true) {
System.out.println("服务端已开启,等待连接......");
Socket socket = severSocket.accept();// 阻塞方法
System.out.println(socket.getInetAddress() + "已连接");
ClientThread client = new ClientThread(socket);
pool.execute(client);
}
}
public static void main(String[] args) throws IOException {
Sever sever = new Sever();
sever.start();
}
/**
* 成员内部类 线程类 处理客户端请求
*
* @author Administrator
*
*/
private class ClientThread implements Runnable {
private Socket socket;
public ClientThread(Socket socket) {
this.socket = socket;
}
// 添加同步方法
private synchronized void add(PrintWriter out) {
list.add(out);
}
// 添加同步方法
private synchronized void remove(PrintWriter out) {
list.remove(out);
}
// 添加同步方法
private synchronized void send(PrintWriter pw,String mag) {
for (PrintWriter printWriter : list) {
// 不发给自己
if (printWriter != pw) {
printWriter.println(mag);
}
}
}
@Override
public void run() {
PrintWriter pw = null;
try {
// 获取客户端输出流
OutputStream out = socket.getOutputStream();
pw = new PrintWriter(new OutputStreamWriter(out), true);// 自动刷新
// 将客户端的输出流保存进集合list
add(pw);// ******
// 读取客户端输入的内容
InputStream in = socket.getInputStream();
InputStreamReader isr = new InputStreamReader(in);// 可指定编码集
BufferedReader br = new BufferedReader(isr);
String mag = null;
while ((mag = br.readLine()) != null) {
// 遍历所有输出流
send(pw,mag);
}
} catch(SocketException e){
System.out.println("用户"+socket.getInetAddress()+"已下线!");
} catch (IOException e) {
e.printStackTrace();
} finally {
remove(pw);// ******
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
}
package com.chat.v5;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Writer;
import java.net.ConnectException;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Scanner;
/**
* 客户端应用程序
*
* @author Administrator
*
*/
public class Client {
private Socket socket;
public Client() {
// socket = new Socket("192.168.0.119", 8888);
try {
socket = new Socket("localhost", 8880);
} catch (ConnectException e) {
System.out.println("服务器未启动,请稍后连接!");
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 客户端工作方法
*
* @throws IOException
*/
public void start() throws IOException {
if(socket==null){
return;
}
// 读取用户输入的内容
Scanner scan = new Scanner(System.in);
boolean flag = true;
System.out.print("输入姓名:");
String name = scan.nextLine();
// 接收服务端信息
SeverThread st = new SeverThread();
Thread t = new Thread(st);
t.setDaemon(true);// 设置为守护线程 t是main的守护线程 --> main结束,t也结束
t.start();
OutputStream os = socket.getOutputStream();// 字节输出流
Writer osw = new OutputStreamWriter(os);// 字符输出流
PrintWriter pw = new PrintWriter(osw, true);// 带格式,自动缓冲啥啥
while (flag) {
String ms = scan.nextLine();
pw.println((name + ":" + ms));
}
if (socket != null) {
socket.close();
}
}
private class SeverThread implements Runnable {
@Override
public void run() {
// 读取服务端发送的内容
try {
InputStream in = socket.getInputStream();
InputStreamReader isr = new InputStreamReader(in);// 可指定编码集
BufferedReader br = new BufferedReader(isr);
while (true) {
System.out.println(br.readLine());
}
} catch (ConnectException e) {
System.out.println("服务器未启动,请稍后连接!");
} catch (SocketException e) {
System.out.println("服务器已中断,请重试!");
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws UnknownHostException, IOException {
Client client = new Client();
client.start();
}
}
运行结果截图实例:
开启三个客户端,因为都是在本机上测试的,故address相同
关掉某个客户端时:
关掉服务器时: