使用说明
这个代码是模拟一个简单的QQ,微信群聊,首先运行服务端代码,在运行客户端,每运行一次客户端,都会创建一个用户在聊天室中模拟发言。(注:服务端要一直处于开启状态,不能关闭)
客户端代码
import java.io.BufferedReader;
import java.io.BufferedWriter;
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.UnsupportedEncodingException;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Scanner;
public class Client {
/*
* 在整个客户端类中都会使用到Socket对象,所以生命成属性。
* Socket内部封装了TCP协议,可以完成安全可靠的数据传输。
*
* 客户端向服务端发送数据时,需要使用输出流进行操作。
*/
private Socket socket;
/**
* 客户端无参构造器
* 目的:用于创建客户端对象时,对socket属性进行初始化
*
*/
public Client() {
try {
System.out.println("开始连接");
/*
* Socket实质是用于描述IP地址和端口号
* 当创建Socket对象时需要调用两个参数的构造器方法
* 参数一:需要指定IP(服务端的IP地址)
* 参数二:需要指定端口号(服务端的端口号)
*/
socket = new Socket("localhost", 8899);
System.out.println("连接服务端成功");
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 向服务端写出数据的方法
*/
public void start() {
/*
* 如果需要发送消息,要使用socket对象获取输出流
* getOutputStream()方法的返回值要求返回一个OutputStream
*/
try {
//创建接受服务端的消息线程,并添加任务序列
ServerHandler serverHandler = new ServerHandler();
Thread thread = new Thread(serverHandler);
thread.start();
OutputStream os = socket.getOutputStream();
//创建字符转换流对象
OutputStreamWriter osw = new OutputStreamWriter(os, "utf-8");
//创建缓冲字符输出流对象
BufferedWriter bw = new BufferedWriter(osw);
//创建具备自动行数新功能的字符输出流
PrintWriter pw = new PrintWriter(bw, true);
//通过控制台接受客户端消息
Scanner scanner = new Scanner(System.in);
while (true) {
String message = scanner.nextLine();
pw.println(message);
}
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
//创建客户端对象
Client client = new Client();
client.start();
}
/**
* 用于处理服务端发送过来的数据的读取操作
*/
class ServerHandler implements Runnable {
@Override
public void run() {
try {
InputStream is = socket.getInputStream();
//创建字符转换流对象
InputStreamReader isr = new InputStreamReader(is,"utf-8");
//创建缓冲字符输入流对象
BufferedReader br = new BufferedReader(isr);
//读取数据,按行进行读取
String message;
while ((message = br.readLine()) != null) {
System.err.println(message);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
服务端代码
import java.io.BufferedReader;
import java.io.BufferedWriter;
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.util.Arrays;
public class Server {
/*
* 声明服务端ServerSocket对象
*/
private ServerSocket serverSocket;
/*
* 定义一个输出流数组,保存的是指向各个客户端的输出流对象
*/
private PrintWriter[] allout = {};
/**
* 服务端无参构造器
* 目的:用于创建服务端对象时,对serverSocket属性进行初始化
*
*/
public Server() {
try {
System.out.println("服务端正在启动");
serverSocket = new ServerSocket(8899);
System.out.println("服务端启动完毕");
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 服务端接收客户端消息的方法
*/
public void start() {
/*
* Socket accept()方法是一个阻塞方法,
* 循环等待客户端连接,如果没有客户端连接就一直在这等着。
*/
try {
while (true) {
System.out.println("等待接收客户端连接");
Socket socket = serverSocket.accept();
System.out.println("一个客户端已经连接");
/*
* 创建任务序列对象
* 创建线程对象并添加任务序列
* 开启线程
* 目的:等待客户端连接以后,可以开启一条线程,
* 在新的线程中用于处理客户端连接后的一些列操作
*/
ClientHandler clientHandler = new ClientHandler(socket);
Thread thread = new Thread(clientHandler);
thread.start();
}
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
//创建服务端对象
Server server = new Server();
server.start();
}
/**
* 用于处理客户端请求的内部类,
* 主要的职责是充当线程的任务序列,
* 在任务序列中,主要做获取输入流对象,循环判断,
* 及其信息输出的操作。
* @author Administrator
*
*/
class ClientHandler implements Runnable{
//客户端发送过来的Socket对象
Socket socket;
//客户端的地址信息
String host;
public ClientHandler(Socket socket) {
this.socket = socket;
this.host = socket.getInetAddress().getHostAddress();
}
@Override
public void run() {
//字符输出流局部变量
PrintWriter pw = null;
try {
/*
* 如果需要从服务端打印客户端传输的信息,
* 需要从Socket中获取输入流
*/
InputStream is = socket.getInputStream();
//创建字符转换流对象
InputStreamReader isr = new InputStreamReader(is,"utf-8");
//创建缓冲字符输入流对象
BufferedReader br = new BufferedReader(isr);
/*
* 获取Socket中的输出流对象,用于给客户端写出数据
*/
OutputStream os = socket.getOutputStream();
OutputStreamWriter osw = new OutputStreamWriter(os,"utf-8");
BufferedWriter bw = new BufferedWriter(osw);
pw = new PrintWriter(bw,true);
//将输出流对象添加到allout输出流数组中
synchronized (allout) {
//对原数进行扩容
allout = Arrays.copyOf(allout, allout.length+1);
//对数组中的最后一个元素进行赋值
allout[allout.length-1] = pw;
}
//在控制台中打印当前在线人数
System.out.println(host+"上线了,当前在线人数为:"+allout.length);
//读取数据,按行进行读取
String message;
while ((message = br.readLine()) != null) {
System.err.println(host+"说:"+message);
//读取消息以后就向客户端写出
synchronized (allout) {
//遍历输出流数组,向所有的客户端输出数据
for (int i = 0; i < allout.length; i++) {
allout[i].println(host+"说了:"+message);
}
}
}
} catch (Exception e) {
/*
* 如果停掉客户端会发生异常,可以在当前的catch块中
* 进行处理删除发送给当前已经停止掉的客户端的输出流对象
*/
synchronized (allout) {
for (int i = 0; i < allout.length; i++) {
if (allout[i] == pw) {
/*
* 删除的原则是找到需要移除的输出流对象,
* 然后将输出流数组中最后一个输出流对象
* 赋值给当前删除的输出流对象的下标位置。
* 最后缩容数组
*/
allout[i] = allout[allout.length-1];
allout = Arrays.copyOf(allout, allout.length-1);
//结束循环
break;
}
}
}
} finally {
System.out.println(host+"下线了,当前在线人数:"+allout.length);
try {
//关闭Socket资源
socket.close();
} catch (IOException e) {
}
}
}
}
}