0
点赞
收藏
分享

微信扫一扫

简易聊天室


全文思维导图

简易聊天室_开发语言

一、基本思路

使用C/S架构进行设计,设置一个服务器,服务器中使用多线程处理每一个客户端,分别对客户端发送的消息进行读取,然后根据广播思想,把消息分别转发给每一个客户端。

此处较为重要的知识点,我认为在于理解**客户端和服务器端之间是如何交互的; 其实本质上就是获取输入输出流 **,然后再对信息进行处理的一个过程。所以在客户端要有输入流输出流,在服务器端要获取客户套接字的输入输出流。网络连接其实都已经封装的很好了,直接用套接字即可,但要注意多线程的运用(因为需要不断监听,while循环太多,需要用多线程处理)。

实现十分简单,代码结构如下:

简易聊天室_java_02

二、具体实现

1.复用界面的实现

其实服务器端完全没必要有界面,但是老师布置的实验里将服务器和客户端复用了同一个界面,并且服务器本身还能发送消息(类似于将一个客户端兼做服务器)。此处只是单纯令服务器和客户端复用同一个界面,而没有实现服务器发送消息的功能(因为服务器采用的是ServerSocket 不能获取其输入流)。
需要注意的是,此处我是形参id来区分是服务器还是客户端的,所以把服务器的线程也写在UI类里。

package Schoolwork.ChatHouse;

import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class MyWindow extends JFrame {
//显示聊天界面
private int id;
private String name;

private Client client = null;
private Server server = null;

private JTextField inputField = new JTextField(20);
private JTextArea messageArea = new JTextArea(50, 30);
private JButton send = new JButton("发送");

public JTextArea getMessageArea() {
return messageArea;
}

MyWindow(int id, String name) {
this.id = id;
this.name = name;

this.setTitle("QQ聊天室-" + this.name);
this.setLayout(null);

//消息显示区禁止编辑
messageArea.setEditable(false);
//给文本框添加滚动条,注意!此时设置矩形大小是设置jScrollPane大小
JScrollPane jScrollPane = new JScrollPane(messageArea);
jScrollPane.setBounds(10, 10, 400, 300);

inputField.setBounds(10, 340, 400, 35);
send.setBounds(10, 380, 100, 35);
send.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (client!=null){
client.getWriter().println(name + ":" + inputField.getText());
client.getWriter().flush(); //刷新客户端输入流
inputField.setText(""); //将输入框清空
inputField.requestFocus(); //获取鼠标焦点
}else if (id==0){ //id为0表示是服务器
inputField.setText(""); //将输入框清空
inputField.requestFocus(); //获取鼠标焦点
messageArea.append(inputField.getText()); //这是把自己的消息添加到消息框
System.out.println("服务器不能发消息!");
}
}
});

this.add(jScrollPane);
this.add(inputField);
this.add(send);

this.setSize(440, 590);
this.setLocation(300 + id * 10, 100); //设置初始出现位置

if (id == 0) { //id为0即为服务器
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
ServerThread serverThread = new ServerThread(this.server);
serverThread.start();
}
this.setResizable(true);
this.setVisible(true);
}

//调用setNet方法,则创建客户端
public void setNet(String ip, int port) { //如果是客户端,则添加线程
client = new Client(ip, port,this.messageArea);
}

public class ServerThread extends Thread {
Server server;
ServerThread(Server s){
this.server = s;
}
@Override
public void run() {
server = new Server(8888,messageArea);
System.out.println("服务器已开启");
}
}

}

2.客户端实现

客户端会使用到两个流,如下:

private BufferedReader reader; //缓冲字符输入流,用于读取服务器转发的消息
private PrintWriter writer; //输出字符流,用于给服务器发消息

并且重写了一个线程类,用来不断读取缓冲流中的数据(其实就是服务器转发的消息),并在读取后将消息(包括自己发的消息)显示到自己的文本框中。
代码如下:

package Schoolwork.ChatHouse;

import javax.swing.*;
import java.io.*;
import java.net.Socket;

public class Client {
private Socket s; //创建和服务器连接的套接字
private BufferedReader reader; //缓冲字符输入流,用于读取服务器转发的消息
private PrintWriter writer; //输出字符流,用于给服务器发消息
private JTextArea messageArea;

Client(String ip, int port, JTextArea messageArea){
this.messageArea = messageArea;
try {
// 创建客户端对象
this.s = new Socket(ip, port);

//与socket建立字符缓冲输入流,用于接收发送框中的消息
InputStreamReader inputStreamReader = new InputStreamReader(s.getInputStream());
reader = new BufferedReader(inputStreamReader);
//与socket建立字符缓冲输出流,用于发送消息
writer = new PrintWriter(this.s.getOutputStream());
System.out.println("客户端成功连接到服务器...");
messageArea.append("客户端成功连接服务器"+"\n");
//开启一个线程,不断读取服务器发送的信息
RevThread thread = new RevThread();
thread.start();

}catch (Exception e){
e.printStackTrace();
}
}

//类中类
public class RevThread extends Thread{
private String message; //用于接受从服务器与客户端1建立的输入流中读出的信息

//不断接收服务器端发来的消息
@Override
public void run() {
try {
while ((this.message = reader.readLine()) != null) { //不断读取服务器转发的消息(包括自己发的)
messageArea.append(message+"\n"); //在聊天消息框中显示消息
}

} catch (Exception e) {
e.printStackTrace();
}
}

public String getMessage() {
return message;
}
}

public BufferedReader getReader() {
return reader;
}

public PrintWriter getWriter() {
return writer;
}

}

3.服务器端实现

服务器端会设置一个集合,其元素是客户端的输出流,后面只需要遍历该集合,然后就可以向各个客户端的缓冲流中 转发服务器收到的消息即可实现广播。
服务器在初始化时就会进入while(){ }循环,不断监听客户端的连接,每获取一个客户端,都为其单独开一个线程来不断接收其发送的消息(即获取其套接字的缓冲输入流,然后读取信息)
代码如下:

package Schoolwork.ChatHouse;

import javax.swing.*;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;

public class Server {
int port;
JTextArea messageArea;

//创建一个数组用来存储客户端的输出流
ArrayList clientOutPutStreams = new ArrayList();

Server(int port, JTextArea messageArea) {
this.port = port;
this.messageArea = messageArea;
try {
//server端创建出与client端通信的socket
ServerSocket serverSocket = new ServerSocket(port); //用传入的端口参数创建服务器套接字
System.out.println("服务器启动成功!");

while (true) {
//服务器创建出与客户端通信的socket
Socket clientSocket = serverSocket.accept(); //这是一个阻塞方法,会等待客户端来连接,有则返回 来连接的客户端的套接字
String ip = clientSocket.getInetAddress().getHostAddress();
System.out.println(ip + "加入了聊天室!");//控制台打印进入到聊天室的用户地址并提示

//获取客户端套接字的输出流,为以后给客户端写数据做准备
PrintWriter printWriter = new PrintWriter(clientSocket.getOutputStream());
clientOutPutStreams.add(printWriter);

//开启一个线程用于与一个客户端进行通信
Thread thread = new Thread(new clientHandler(clientSocket));

//开启线程,使服务器一直处于接受消息并转发给所有人
thread.start();
System.out.println("有新连接加入");

}

} catch (IOException e) {
e.printStackTrace();
}
}

//广播转发消息
public void sendEveryone(String message) {
for (Object OPStream:clientOutPutStreams) {
try {
PrintWriter writer = (PrintWriter) OPStream;
writer.println(message);
writer.flush();
}catch (Exception e){
e.printStackTrace();
}
}
}

//类中类重写线程类接口
public class clientHandler implements Runnable {
BufferedReader reader;
Socket socket;

/**
* clientHandler的构造方法
*/
public clientHandler(Socket socket) {
//与客户端建立通信 并 定义接受数据的输入流
try {
this.socket = socket;
//获取客户端套接字的字节输入流(方便读数据)
InputStreamReader inputStreamReader = new InputStreamReader(socket.getInputStream());
//获取缓冲输入流
reader = new BufferedReader(inputStreamReader);
} catch (IOException e) {
e.printStackTrace();
}
}

/**
* 开启线程之后要执行的run()方法
*/
@Override
public void run() {
//定义准备接受消息变量
String message = null;

try {
//使线程不断从socket上读取数据的状态
while ((message = reader.readLine()) != null) {
System.out.println("MESSAGE_Receive: " + message);
messageArea.append(message+"\n"); //并显示在自己的窗体中
//将数据发送给每一个客户端
sendEveryone(message);
}
} catch (IOException e) {
e.printStackTrace();
}

}
}
}

4.测试类

下面是用本机连接的测试类,还没有多机实验过。大家可以尝试联机呀 ^ _ ^

package Schoolwork.ChatHouse;

public class JavaMain {
public static void main(String[] args) {
MyWindow myWindow = new MyWindow(0,"服务器");
MyWindow myWindow1 = new MyWindow(1,"xlk");
myWindow1.setNet("127.0.0.1",8888);
MyWindow myWindow2 = new MyWindow(2,"windx");
myWindow2.setNet("127.0.0.1",8888);
}
}

三、最终效果

简易聊天室_java_03


举报

相关推荐

0 条评论