![cover](/upload/Java%20高级程序设计.jpg)
Java 高级程序设计:作业 12
Java 高级程序设计:实验十二 网络编程
代码地址:Github
实验目的和要求
-
理解网络编程技术;
-
能够用网络编程技术实现聊天程序。
实验内容
实现一个多线程并发服务器(服务器功能自选),要求该服务器可以体现出同时为多个客户端服务的特性。
实验需要实现了一个基于多线程并发服务器的网络聊天室原型,用控制台的方式实现即可,不需要用窗口应用程序的方式。
实验过程与步骤
需求分析
工具采用了Client/Server结构,将聊天室划分为两个子程序:客户端子程序、服务器端子程序。
![image-20221206153659798](https://owen-resource.oss-cn-hangzhou.aliyuncs.com/images/image-20221206153659798.png)
- 客户端程序:实现客户端与服务器端进行连接,并可与服务器端进行实时通讯。
- 服务器端:实现建立与多个客户端程序之间的连接,管理连接并能够通过多线程技术同时与多个客户端进行实时通讯。
系统设计
系统的设计中,客户端与服务器端的连接,通过TCP/IP网络建立,并依托Socket进行实时通讯。系统中,各个模块之间的关系如下图所示:
![image-20221206153803537](https://owen-resource.oss-cn-hangzhou.aliyuncs.com/images/image-20221206153803537.png)
上图中,显示同一服务器,通过网络可以同时与多个客户端程序建立连接并实现实时通讯功能,通讯流程如下图所示:
![image-20221206153827032](https://owen-resource.oss-cn-hangzhou.aliyuncs.com/images/image-20221206153827032.png)
根据图示,客户端与服务器之间通过Socket()进行实时通讯,通讯步骤是:服务器监听、客户端发出请求、服务器接受、建立连接、交互通讯、关闭连接。另外,由于聊天室工具必须实现多客户端同时连接通讯的要求,因此,在服务器端设计时考虑采用多线程技术,每个服务器拥有多个服务线程,每个线程负责与一个客户端进行连接通讯,从而达到一个服务器同时与多个客户端并发通讯的效果。
实验结果
代码
Server.java
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @author Owem
* @date 2022/12/6 15:55
* @description TODO
**/
public class Server {
public static void main(String[] args) {
try {
int port = 6666;
ServerSocket serverSocket = new ServerSocket(port);
System.out.println("服务器启动..." + serverSocket.getLocalSocketAddress()); //服务器启动,打印本地地址
//线程池
ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2);
while (true) { //死循环
Socket client = serverSocket.accept();
System.out.println("有客户端连接到服务器:" + client.getRemoteSocketAddress());
executorService.execute(new HandlerClient(client));
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
HandlerClient.java
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.util.Map;
import java.util.Scanner;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author Owem
* @date 2022/12/6 15:56
* @description TODO
**/
public class HandlerClient implements Runnable {
/**
* 维护所有的连接到服务端的客户端对象
*/
private static final Map<String, Socket> ONLINE_CLIENT_MAP = new ConcurrentHashMap<>(); //静态是为了不让对象变化,final不让对象被修改,ConcurrentHashMap是线程安全的类
private final Socket client;
public HandlerClient(Socket client) { //HandlerClient在多线程环境下调用,所以会产生资源竞争,用一个并发的HashMap
this.client = client; //为了防止变量被修改,用final修饰
}
public void run() {
try {
InputStream clientInput = client.getInputStream(); //获取客户端的数据流
Scanner scanner = new Scanner(clientInput); //字节流转字符流
/*
消息是按行读取
1.注册: register:<username> 例如: register:张三
2.群聊: groupChat:<message> 例如: groupChat:大家好
3.查询用户: users list
4.私聊: <user>:<message> 例如: 张三:你好,还钱
5.退出: bye
*/
while (true) {
// 读数据,按行读
String data = scanner.nextLine();
if (data.startsWith("register:")) {
//注册
String userName = data.split(":")[1];
register(userName);
continue;
}
if (data.startsWith("groupChat:")) {
String message = data.split(":")[1];
groupChat(message);
continue;
}
if (data.equals("users list")) {
usersList();
continue;
}
if (data.contains(":")) {
String[] segments = data.split(":");
String targetUserName = segments[0]; //取目标用户名
String message = segments[1]; //取发送的消息内容
privateChat(targetUserName, message);
}
if (data.equals("bye")) {
bye();
break;
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 当前客户端退出
*/
private void bye() {
for (Map.Entry<String, Socket> entry : ONLINE_CLIENT_MAP.entrySet()) {
Socket target = entry.getValue();
if (target.equals(this.client)) { //在在线用户中找到自己并且移除
this.sendMessage(this.client, "您已下线...", false);
System.out.println("{ " + getCurrentUserName() + " }退出聊天室");
ONLINE_CLIENT_MAP.remove(entry.getKey());
break;
}
}
printOnlineClient();//打印当前用户
}
private void usersList() {
StringBuilder s = new StringBuilder();
for (Map.Entry<String, Socket> entry : ONLINE_CLIENT_MAP.entrySet()) {
if (entry.getValue().equals(this.client)) {
continue;
}
s.append("{ ").append(entry.getKey()).append(" } ");
}
if (!s.isEmpty()) {
this.sendMessage(this.client, "当前在线用户: " + s, false);
} else {
this.sendMessage(this.client, "当前没有其他用户", false);
}
}
private String getCurrentUserName() {
for (Map.Entry<String, Socket> entry : ONLINE_CLIENT_MAP.entrySet()) {
Socket target = entry.getValue(); //getvalue得到Socket对象
if (target.equals(this.client)) { //排除群聊的时候自己给自己发消息的情况
return entry.getKey();
}
}
return "";
}
/**
* 私聊,给targetUserName发送message消息
*
* @param targetUserName
* @param message
*/
private void privateChat(String targetUserName, String message) {
Socket target = ONLINE_CLIENT_MAP.get(targetUserName);//获取目标用户名
if (target == null) {
this.sendMessage(this.client, "没有这个人" + targetUserName, false);
} else {
this.sendMessage(target, message, true);
}
}
/**
* 群聊,发送message
*
* @param message
*/
private void groupChat(String message) {
for (Map.Entry<String, Socket> entery : ONLINE_CLIENT_MAP.entrySet()) {
Socket target = entery.getValue(); //getvalue得到Socket对象
if (target.equals(this.client)) {
continue; //排除群聊的时候自己给自己发消息的情况
}
this.sendMessage(target, message, true);
}
}
/**
* 以userName为key注册当前用户(Socket client)
*
* @param userName
*/
private void register(String userName) {
if (ONLINE_CLIENT_MAP.containsKey(userName)) {
this.sendMessage(this.client, "您已经注册过了,无需重复注册", false);
} else {
ONLINE_CLIENT_MAP.put(userName, this.client);
printOnlineClient();
this.sendMessage(this.client, "恭喜{ " + userName + " }注册成功\n", false);
}
}
private void sendMessage(Socket target, String message, boolean prefix) {
OutputStream clientOutput; //value是每一个客户端
try {
clientOutput = target.getOutputStream();
OutputStreamWriter writer = new OutputStreamWriter(clientOutput);
if (prefix) {
String currentUserName = this.getCurrentUserName();
writer.write("<" + currentUserName + ">" + message + "\n");
} else {
writer.write(message + "\n");
}
writer.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 打印在线客户端
*/
private void printOnlineClient() {
System.out.println("当前在线人数:" + ONLINE_CLIENT_MAP.size() + "," + "用户名如下列表:");
System.out.println("--------------------");
for (String userName : ONLINE_CLIENT_MAP.keySet()) { //Map的key为用户名
System.out.println("{ " + userName + " }");
}
System.out.println("--------------------");
}
}
Client.java
import java.io.IOException;
import java.net.Socket;
/**
* @author Owem
* @date 2022/12/6 15:56
* @description TODO
**/
public class Client {
public static void main(String[] args) {
try {
//读取地址
String host = "127.0.0.1";
//读取端口号
int port = 6666;
Socket client = new Socket(host, port); //先写数据再读数据,读写线程分离
new ReadDataFromServerThread(client).start();//启动读线程
new WriteDataToServerThread(client).start();//启动写线程
} catch (IOException e) {
e.printStackTrace();
}
}
}
WriteDataToServerThread.java
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.util.Scanner;
/**
* @author Owem
* @date 2022/12/6 15:57
* @description TODO
**/
public class WriteDataToServerThread extends Thread{
private final Socket client;
public WriteDataToServerThread(Socket client){
this.client = client;
}
@Override
public void run(){
try {
OutputStream clientOutput = this.client.getOutputStream();
OutputStreamWriter writer = new OutputStreamWriter(clientOutput);
Scanner scanner = new Scanner(System.in); //有客户端输入数据
while(true){
System.out.print("请输入>>");
String data = scanner.nextLine(); //读数据
writer.write(data+"\n");
writer.flush();
if(data.equals("bye")){
break;
}
}
Thread.sleep(1000);
this.client.close();
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
}
ReadDataFromServerThread.java
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.util.Scanner;
/**
* @author Owem
* @date 2022/12/6 15:58
* @description TODO
**/
public class ReadDataFromServerThread extends Thread {
private final Socket client;
public ReadDataFromServerThread(Socket client) {
this.client = client;
}
@Override
public void run() {
try {
InputStream clientInput = this.client.getInputStream();
Scanner scanner = new Scanner(clientInput);
while (true) {
String data = scanner.nextLine();//按行读数据
if (data.startsWith("<")) {
System.out.println(data);
} else {
System.out.println("<服务端>" + data);
}
if (data.equals("您已下线...")) {
break;
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
运行结果
启动服务器
![image-20221206165441866](https://owen-resource.oss-cn-hangzhou.aliyuncs.com/images/image-20221206165441866.png)
启动用户端(这里以两个为例)
![image-20221206165514496](https://owen-resource.oss-cn-hangzhou.aliyuncs.com/images/image-20221206165514496.png)
用户端注册
![image-20221206165558473](https://owen-resource.oss-cn-hangzhou.aliyuncs.com/images/image-20221206165558473.png)
![image-20221206165611475](https://owen-resource.oss-cn-hangzhou.aliyuncs.com/images/image-20221206165611475.png)
群聊
![image-20221206165648160](https://owen-resource.oss-cn-hangzhou.aliyuncs.com/images/image-20221206165648160.png)
查询用户
![image-20221206165709457](https://owen-resource.oss-cn-hangzhou.aliyuncs.com/images/image-20221206165709457.png)
私聊
![image-20221206165733391](https://owen-resource.oss-cn-hangzhou.aliyuncs.com/images/image-20221206165733391.png)
退出
![image-20221206165818982](https://owen-resource.oss-cn-hangzhou.aliyuncs.com/images/image-20221206165818982.png)
本文是原创文章,采用 CC BY-NC-ND 4.0 协议,完整转载请注明来自 Owen
评论
匿名评论
隐私政策
你无需删除空行,直接评论以获取最佳展示效果