项目要求
设计一个简单的聊天室程序。采用C/S模式,分为客户端程序和服务器端程序。
客户端程序和服务器程序通过网络交换聊天字符串内容。同一端可实现多语句收发。
服务端
- 创建ServerSocket并在本机8888窗口监听
- 调用accept方法建立连接
- 启动发送和接收线程
发送线程
- BufferedWriter对象writer:用于向客户端写入内容
- BufferedReader对象kb:用于获取键盘输入内容
- running:标记是否关闭线程
重载run()方法
- 首先while循环条件检查running,running为false则关闭。
- 检查键盘输入kb是否可用。
可用则调用readLine方法获取输入并向客户端发送。同时检查输入是否为quit
。如果是,则设置running为false以便关闭线程,同时调用receive线程的setRunning方法将标志设置为关闭。
不可用则线程休眠100ms,再从头执行。
- 发送
调用writer对象的write方法向客户端写入信息,调用newLine方法进行换行。最重要的:调用flush方法刷新缓冲区。
- 关闭
关闭writer、kb。
接收线程
- BufferedReader对象reader:用于获取客户端发送的内容
- running:标记是否关闭线程
run
- 检查running
- 调用reader的readLine方法获取客户端发送的信息
- 判断信息内容是否为空:不为空则在控制台打印输出
- 判断是否为
quit
:是则设置running和send线程的running为false。 - 关闭
代码
package pro2.multi;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @author zayn
* @date 2023/4/7/19:00
* TCP 聊天程序服务器端
*/
public class Server {
static ServerSocket serverSocket;
static Socket socket;
static int port = 8888;
public static void main(String[] args) throws IOException, InterruptedException {
serverSocket = new ServerSocket(port);
System.out.println("服务端在8888端口监听");
socket = serverSocket.accept();
System.out.println("客户端连接成功");
//创建线程并启动
ServerSend send = new ServerSend();
// send.setDaemon(true);//设置为守护线程
send.start();
ServerReceive receive = new ServerReceive();
// receive.setDaemon(true);//设置为守护线程
receive.start();
//线程插队执行
send.join();
receive.join();
//关闭流
socket.close();
serverSocket.close();
System.out.println("服务端退出成功");
}
}
class ServerSend extends Thread {
//运行标记
private static boolean running = true;
//输出流
static BufferedWriter writer;
//键盘输入流
static BufferedReader kb;
//接收输入流
static final int TIMEOUT = 100;
static {
try {
writer = new BufferedWriter(new OutputStreamWriter(Server.socket.getOutputStream()));
kb = new BufferedReader(new InputStreamReader(System.in));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void run() {
while (running) {
String str;
try {
if (kb != null && kb.ready()) {//检查键盘输入是否可用
str = kb.readLine();//获取键盘输入
if (!str.equals("")) {//判断是否为空
send(str);//发送信息至客户端
if (str.equals("quit")) {//判断是否退出
running = false;
close();
ServerReceive.setRunning();
break;
}
}
} else {
//等待指定的超时时间
Thread.sleep(TIMEOUT);
}
} catch (IOException | InterruptedException e) {
throw new RuntimeException(e);
}
}
}
/**
* 发送信息
*
* @param str 要发送的信息
* @throws IOException IO异常
*/
public static void send(String str) throws IOException {
writer.write(str);
writer.newLine();
writer.flush();
}
/**
* 关闭流
*/
public static void close() {
try {
writer.close();
kb.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static void setRunning() {
running = false;
}
}
class ServerReceive extends Thread {
static BufferedReader reader;
//运行标记
private static boolean running = true;
static {
try {
reader = new BufferedReader(new InputStreamReader(Server.socket.getInputStream()));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* 接收信息
* 判断quit退出
*
*/
@Override
public void run() {
while (running) {
String str = "";
try {
str = reader.readLine();//接收信息
} catch (IOException e) {
// throw new RuntimeException(e);
}
if (!str.equals("")) {//判断是否为空
System.out.println("客户端:" + str);
}
if (str.equals("quit")) {//判断是否退出
running = false;
ServerSend.setRunning();
break;
}
}
close();
}
/**
* 关闭流和socket
*/
public static void close() {
try {
reader.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* 设置运行标记
*/
public static void setRunning() {
running = false;
}
}
客户端
- 创建连接本机8888窗口的套接字
- 启动发送和接收线程
客户端功能和代码实现都与服务端有很高的相似性,这里直接放代码。
package pro2.multi;
import java.io.*;
import java.net.InetAddress;
import java.net.Socket;
/**
* @author zayn
* @date 2023/4/7/18:58
* TCP 聊天程序客户端
*/
public class Client {
static Socket ClientSocket;
static int port = 8888;
public static void main(String[] args) throws IOException, InterruptedException {
//创建客户端套接字
ClientSocket = new Socket(InetAddress.getLocalHost(), port);
System.out.println("客户端启动成功");
System.out.println("连接服务器成功");
//创建线程并启动
ClientSend send = new ClientSend();
// send.setDaemon(true);//设置为守护线程
send.start();
ClientReceive receive = new ClientReceive();
// receive.setDaemon(true);//设置为守护线程
receive.start();
//线程插队
send.join();
receive.join();
//关闭
ClientSocket.close();
System.out.println("客户端退出成功");
}
}
class ClientSend extends Thread {
//输出流
static BufferedWriter writer;
//键盘输入流
static BufferedReader kb;
//接收输入流
//运行标记
private static boolean running = true;
static final int TIMEOUT = 100;
static {
try {
writer = new BufferedWriter(new OutputStreamWriter(Client.ClientSocket.getOutputStream()));
kb = new BufferedReader(new InputStreamReader(System.in));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* 获取键盘输入
* 发送信息
* 判断quit退出
*/
@Override
public void run() {
while (running) {
String str;
try {
if (kb != null && kb.ready()) {//检查键盘输入是否可用
str = kb.readLine();//获取键盘输入
if (!str.equals("")) {//判断是否为空
send(str);//发送信息至客户端
if (str.equals("quit")) {//判断是否退出
running = false;
close();
ClientReceive.setRunning();
break;
}
}
} else {
//等待指定的超时时间
Thread.sleep(TIMEOUT);
}
} catch (IOException | InterruptedException e) {
throw new RuntimeException(e);
}
}
}
/**
* 发送信息
*
* @param str 要发送的信息
* @throws IOException IO异常
*/
public static void send(String str) throws IOException {
writer.write(str);
writer.newLine();
writer.flush();
}
/**
* 关闭流和套接字
*/
public static void close() {
try {
writer.close();
kb.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* 设置运行标记
*
*/
public static void setRunning() {
running = false;
}
}
class ClientReceive extends Thread {
//输入流
static BufferedReader reader;
static Socket clientSocket;
//运行标记
private static boolean running = true;
static {
try {
reader = new BufferedReader(new InputStreamReader(Client.ClientSocket.getInputStream()));
clientSocket = Client.ClientSocket;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* 接收信息
* 判断quit退出
*
*/
@Override
public void run() {
while (running) {
String str = "";
try {
str = reader.readLine();//接收信息
} catch (IOException e) {
// throw new RuntimeException(e);
}
if (str != null) {
if (!str.equals("")) {//判断是否为空
System.out.println("服务端:" + str);
}
if (str.equals("quit")) {//判断是否退出
running = false;
ClientSend.setRunning();//关闭发送线程
break;
}
}
}
close();
}
/**
* 关闭流
*/
public static void close() {
try {
reader.close();
clientSocket.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static void setRunning() {
running = false;
}
}
结果展示
总结
两端发送和接收信息的处理都比较好做。就是某一端发起退出然后两端都退出的操作不是很好实现。