多线程服务器:TCP协议是C/S模式,即客户端/服务器模式,一次只能处理一次C/S,但是引入多线程同一台服务器可以处理多个客户端的请求,因为服务器把接收到客户端的请求交给了其它的子线程,这样一来,服务器就总是处于等待状态。<br/>
*模拟服务器和客户端方法:分在使用eclipse创建两个独立的工作空间,使用一个空间先运行TCP服务端程序 **`ChatRoomServer.java`**,再使用另一个空间运行TCP客户端程序 **`LinkServerFrame.java`**。*
下面一共用到四个类,它们作用如下:
```
net/server/CharRoomServer.java 服务器(最先运行该程序)
net/client/LinkServerFrame.java 客户登录服务器的窗口,是客户端程序的入口,由他调用下面的ClientFrame类
net/client/ChatRoomClient.java 客户端核心程序,负责与服务器进行交互
net/client/ClientFrame.java 客户聊天窗口,由他调用上面的ChatRoomClient类
```
![](https://img.kancloud.cn/f3/3f/f33fd4aaf2f72e20d06d93de39dcf450_812x281.png)
**`net/server/ChatRoomServer.java`** (服务器)
```java
package net.server;
import java.io.*;
import java.net.*;
import java.util.*;
/**
* 服务器
*/
public class ChatRoomServer {
private ServerSocket serverSocket; // 服务器端套接字
private HashSet<Socket> allSockets; // 存储所有客户端的套接字
public ChatRoomServer() {
try {
serverSocket = new ServerSocket(4569); // 服务器端口
} catch (IOException e) {
e.printStackTrace();
}
allSockets = new HashSet<Socket>();
}
/**
* 等待来自客户端的请求,并将客户添加到allSockets中
* @throws IOException
*/
public void startService() throws IOException {
System.out.println("服务器已开启!");
while (true) {
Socket socket = serverSocket.accept(); // 等待来自客户端的请求,如果一直没有来自客户端的请求,则一直阻塞
allSockets.add(socket);
System.out.println("在线人数:" + allSockets.size());
// 服务器将每个客户的请求交给下面的线程,所以服务器总是处于等待状态
new ServerThread(socket).start();
}
}
/**
* 内部类,当有客户端进行请求时,服务器交给该线程去处理
*/
private class ServerThread extends Thread {
Socket socket;
public ServerThread(Socket socket) {
this.socket = socket;
}
public void run() {
BufferedReader br = null;
try {
// 获取客户端的发送的信息
br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
while (true) {
String str = br.readLine();
if (str.contains("%EXIT%")) {
allSockets.remove(socket);
sendMessageTOAllClient(str.split(":")[1] + " 已退出聊天室!");
socket.close();
System.out.println("在线人数:" + allSockets.size());
return;
}
sendMessageTOAllClient(str);
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 发送消息给所有客户端
* @param message String, 服务器要发送的消息
* @throws IOException
*/
public void sendMessageTOAllClient(String message) throws IOException {
for (Socket s : allSockets) {
PrintWriter pw = new PrintWriter(s.getOutputStream());
pw.println(message);
pw.flush();
}
}
}
public static void main(String[] args) {
try {
new ChatRoomServer().startService();
} catch (IOException e) {
e.printStackTrace();
}
}
}
```
**`net/client/LinkServerFrame`** (客户端——用户登录服务器窗口,客户端程序入口)
```java
package net.client;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import java.awt.Font;
import java.awt.event.*;
/**
* 服务器登录窗口,这是客户端程序的入口
*
*/
public class LinkServerFrame extends JFrame {
private static final long serialVersionUID = 1L;
private JPanel contentPane;
private JLabel lblIP;
private JLabel lblUserName;
private JTextField tfIP;
private JTextField tfUserName;
private JButton btnLink;
public LinkServerFrame() {
setTitle("登录服务器");
setResizable(false);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBounds(100, 100, 390, 150);
contentPane = new JPanel();
contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
contentPane.setLayout(null);
setContentPane(contentPane);
lblIP = new JLabel("服务器地址");
lblIP.setFont(new Font("Serif", Font.PLAIN, 14));
lblIP.setBounds(20, 15, 100, 15);
contentPane.add(lblIP);
tfIP = new JTextField("127.0.0.1");
tfIP.setBounds(121, 13, 242, 21);
contentPane.add(tfIP);
tfIP.setColumns(10);
lblUserName = new JLabel("姓名");
lblUserName.setFont(new Font("Serif", Font.PLAIN, 14));
lblUserName.setBounds(60, 40, 60, 15);
contentPane.add(lblUserName);
tfUserName = new JTextField();
tfUserName.setBounds(121, 42, 242, 21);
contentPane.add(tfUserName);
tfUserName.setColumns(10);
btnLink = new JButton("连接");
btnLink.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
do_btnLink_actionPerformed(e);
}
});
btnLink.setFont(new Font("Serif", Font.PLAIN, 14));
btnLink.setBounds(140, 80, 120, 23);
contentPane.add(btnLink);
}
public static void main(String[] args) {
LinkServerFrame linkServerFrame = new LinkServerFrame();
linkServerFrame.setVisible(true);
}
protected void do_btnLink_actionPerformed(ActionEvent e) {
if (!tfIP.getText().equals("") && !tfUserName.getText().equals("")) {
dispose();
ClientFrame clientFrame = new ClientFrame(tfIP.getText().trim(), tfUserName.getText().trim());
clientFrame.setVisible(true);
} else {
JOptionPane.showMessageDialog(null, "聊天姓名不能为空!", "提示框", JOptionPane.WARNING_MESSAGE);
}
}
}
```
**`net/client/ChatRoomClient.java`** (客户端核心程序)
```java
package net.client;
import java.io.*;
import java.net.*;
/**
* 客户端。向服务器发送消息、接收服务器返回的消息、关闭客户端与服务器的连接
*
*/
public class ChatRoomClient {
private Socket socket; // 客户端套接字
private BufferedReader bufferReader; // 用于接收服务器的消息
private PrintWriter pWriter; // 用于向服务器发送消息
/**
* @param host String, 服务器IP地址
* @param port int, 服务器端口
* @throws UnknownHostException
* @throws IOException
*/
public ChatRoomClient(String host, int port) throws UnknownHostException, IOException {
socket = new Socket(host, port);
bufferReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
pWriter = new PrintWriter(socket.getOutputStream());
}
/**
* 向服务器发送消息
*
* @param str String, 要发送的文本消息。
*/
public void sendMessage(String str) {
pWriter.println(str);
pWriter.flush();
}
/**
* 接收服务器返回的消息
*
* @return String
*/
public String reciveMessage() {
try {
return bufferReader.readLine();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
/**
* 关闭与服务器的连接
*/
public void close() {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
```
**`net/client/ClientFrame.java`** (客户端——用户聊天窗口)
```java
package net.client;
import javax.swing.*;
import java.awt.event.*;
import java.io.IOException;
import java.net.UnknownHostException;
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.swing.SwingConstants;
import javax.swing.border.EmptyBorder;
/**
* 客户端聊天窗口
*/
public class ClientFrame extends JFrame {
private static final long serialVersionUID = 1L;
private JPanel contentPane;
private JLabel lblUserName;
private JTextField tfMessage;
private JButton btnSend;
private JTextArea textArea;
private String userName;
private ChatRoomClient client;
/**
*
* @param ip String, 服务器的IP地址
* @param userName String,
*/
public ClientFrame(String ip, String userName) {
this.userName = userName;
try {
client = new ChatRoomClient(ip, 4569);
} catch (UnknownHostException e1) {
e1.printStackTrace();
} catch (IOException e1) {
e1.printStackTrace();
}
ReadMessageThread messageThread = new ReadMessageThread();
messageThread.start(); // 启动线程
init();
addListener();
}
/**
* 初始化窗口
*/
private void init() {
setTitle("客户端");
setResizable(false);
setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
setBounds(100, 100, 450, 300);
contentPane = new JPanel();
contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
contentPane.setLayout(null);
setContentPane(contentPane);
JScrollPane scrollPane = new JScrollPane();
scrollPane.setBounds(5, 5, 434, 229);
contentPane.add(scrollPane);
textArea = new JTextArea();
scrollPane.setViewportView(textArea);
textArea.setEditable(false);
JPanel panel = new JPanel();
panel.setBounds(5, 235, 434, 32);
contentPane.add(panel);
panel.setLayout(null);
lblUserName = new JLabel(userName);
lblUserName.setHorizontalAlignment(SwingConstants.TRAILING);
lblUserName.setBounds(2, 4, 55, 22);
panel.add(lblUserName);
tfMessage = new JTextField();
tfMessage.setBounds(62, 5, 274, 22);
tfMessage.setColumns(10);
panel.add(tfMessage);
btnSend = new JButton("发送");
btnSend.setBounds(336, 4, 93, 23);
panel.add(btnSend);
tfMessage.validate(); // 刷新组件
}
/**
* 为按钮添加事件
*/
private void addListener() {
btnSend.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
Date date = new Date();
SimpleDateFormat df = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
client.sendMessage("\t" + userName + " " + df.format(date) + "\n" + tfMessage.getText());
tfMessage.setText("");
}
});
this.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent atg0) {
int op = JOptionPane.showConfirmDialog(ClientFrame.this, "你确认要退出吗?", "退出", JOptionPane.YES_NO_OPTION);
if (op == JOptionPane.YES_OPTION) {
client.sendMessage("%EXIT%:" + userName);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
client.close();
System.exit(0);
}
}
});
}
/**
* 这是一个内部类,负责创建线程,使用该线程接收服务端返回的消息,并追加到文本域中
*
*/
private class ReadMessageThread extends Thread {
public void run() {
while (true) {
String str = client.reciveMessage();
textArea.append(str + "\n");
}
}
}
}
```
先运行ChatRoomServer,在运行三个或三个以上的LinkServerFrame,效果如下:
![](https://img.kancloud.cn/ac/87/ac87947685b1526417cb4867bd557682_1380x611.gif)
- 网络通信
- 网络协议
- 端口和套接字
- TCP网络程序
- UDP网络程序
- 多线程聊天室
- 多线程
- 线程相关概念
- 线程实现方式
- 中断线程
- 线程生命周期
- 线程优先级
- 优先级规则
- 案例演示
- 线程同步机制
- 线程同步机制
- synchronized关键字
- ReentrantLock类
- Condition类
- 监视器概念
- volatile关键字
- final变量
- 死锁
- 线程局部变量
- 读/写锁
- 原子类
- 阻塞队列
- 工作规则
- 案例演示
- 常用阻塞队列
- 线程安全集合
- 高效的映射/集/队列
- 并发集视图
- 写数组的拷贝
- Arrays类的并行数组算法
- 同步包装器
- Callable与Future
- 执行器
- 线程池
- 预定执行
- 控制任务组
- Fork-Join框架
- 同步器
- 同步器
- 信号量
- CountDownLatch类
- CyclicBarrier类
- Exchanger类
- SynchronousQueue类
- 线程与Swing
- Swing与线程问题
- 两个原则
- Swing工作线程
- 单一线程规则
- 文件IO
- File类
- 文件输入输出
- ZIP压缩文件
- 集合
- 集合框架
- 集合接口
- 集合实现类
- 线程安全集合
- 集合算法
- 迭代器
- 集合排序
- JDBC
- JDBC是什么
- JDBC-ODBC桥
- JDBC驱动程序类型
- JDBC常用类与接口
- 数据库操作
- 连接数据库
- 增/删/改/查/预处理
- 事务
- 批处理
- commons-dbutils工具
- 安全问题
- Jedis
- 使用Jedis操作Redis数据库
- JSON转换
- 使用连接池
- 案例
- 单例破坏
- 单例定义
- 单例实现方式
- 懒汉式实现单例
- 饿汉式实现单例
- 单例破坏
- 类的单例破坏
- 枚举的单例破坏
- 克隆
- 克隆是什么
- 浅克隆
- 深克隆
- 注解
- 注解是什么
- 三大注解
- 内置注解
- 元注解
- 自定义注解
- NIO
- 相关概念
- BIO/NIO/AIO
- 多线程编程
- 线程同步
- 线程通信
- NIO
- NIO三大核心组件
- NIO网络编程
- NIO文件读写
- AIO
- Java8新特性
- Lambda表达式
- 方法引用
- 函数式接口
- 默认方法
- 什么是默认方法
- 默认方法语法格式
- 多个同名的默认方法问题
- 静态默认方法
- 默认方法实例
- Stream
- Stream是什么
- Stream示例
- Optional容器
- 新的日期时间API
- Base64
- SPI
- SPI是什么
- SPI与API的区别
- 常见场景
- 使用SPI需遵循的约定
- SPI使用步骤