一、问题描述:
多人聊天室,实现群聊和私聊的功能。
整的项目的大框架为:服务器端和客户端两个端口。
客户端可以向服务器端发送信息,并接受服务器返回的信息。
二、代码实现
服务器S端:
package cs;
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.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Arrays;
import java.util.Collections;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 聊天室服务端
* 运行在服务端SeverSocket有两个主要作用;
* 1、向系统申请对外的服务端口,客户端就是通过这个端口与服务端建立连接
* 2、监听服务端端口,等待客户端连接,一旦一个客户端通过Socket与服务端建立连接
* 那么SeverSocket会创建一个Socket与该客户端通讯。
*
* 注意:该端口号不能和其他端口号冲突,否则会抛出端口号占用异常
*
* @author 臻冉
*
*/
public class Server {
private ServerSocket server;
private Socket socket;
private ExecutorService pool;
//所有客户端共享数据
private PrintWriter[] outAll = new PrintWriter[0];
//服务端初始化
public Server() {
try {
this.server = new ServerSocket(8088);
//创建线程池
this.pool = Executors.newFixedThreadPool(2);
//listOut = Collections.synchronizedList(list);
} catch (IOException e) {
//e.printStackTrace(); //日志跟踪
System.out.println("连接异常...");
}
}
public void start(){
/*
* ServerSocket实例化后等待客户端连接
* 提供了一个构造方法ServerSocket(int port)
*/
try {
/*
* ServerSocket提供了一个accept();
* 该方法是一个阻塞方法,调用后即等待客户端的连接,
* 一旦一个客户端通过端口连接,那么accept方法会返回一个Socket实例,
* 通过这个Socket实例就可以与连接的客户端交互了
*/
while(true) {
System.out.println("等待客户端连接...");
socket = server.accept();//阻塞作用
InetAddress address = socket.getInetAddress();
int port = socket.getPort();
System.out.println(port);
System.out.println(address+":连接上了");
//把每一个客户端连接成功后生成一个socket并纳入
ClinetHandler clinet = new ClinetHandler(socket);
pool.equals(clinet);
Thread t = new Thread(clinet);
t.start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Server s = new Server();
s.start();
}
//内部类(在这里我们要共享每个客户端pw数据):此任务主要负责接收每一个客户端输入和输出
class ClinetHandler implements Runnable {
private Socket socket;//每个用户的socket
private String host;//获取地址
private int port;//远程端口
public ClinetHandler(Socket socket){
this.socket = socket;
//获取远程的地址
InetAddress addresd = this.socket.getInetAddress();
//将远程地址转化字符串形式
this.host=addresd.getHostAddress();
this.port=this.socket.getPort();
}
public void run(){
PrintWriter pw = null;
try {
/*
* 获取远端的IP地址
* Socket提供一个获取远端的IP地址的方法getInetAddress(),
* 返回getInetAddress
*/
//获取远端IP地址
InetAddress address=socket.getInetAddress();
//获取远程的端口号
int port = socket.getPort();
System.out.println(port);
System.out.println(address+"连接上了");
//获取输出流
OutputStream out = socket.getOutputStream();
pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(out,"UTF-8")),true);
synchronized(outAll){
//对数组扩容
outAll = Arrays.copyOf(outAll, outAll.length+1);
outAll[outAll.length-1] = pw;
System.out.println("在线人数"+Arrays.toString(outAll));
}
//对数组扩容
outAll = Arrays.copyOf(outAll, outAll.length+1);
//把每个客户端pw对象存入共享数组中的最后一个元素
outAll[outAll.length-1]=pw;
System.out.println("在线人数:"+outAll.length);
//获取输入流
InputStream in = socket.getInputStream();
BufferedReader br= new BufferedReader(new InputStreamReader(in,"UTF-8"));
/*
* 客户端再断开连接时,不同的系统有不同的反应,
* linux的客户端断开后,br.readLine()会返回null,
* windows的客户端断开后,br.readLine()会直接抛出异常Connection reset
*/
String line = null;
while((line = br.readLine())!=null){
System.out.println(address+","+port+"说:"+line);
//pw.println(line);
//将当前客户端对应的输出流共享数据进行遍历
synchronized (outAll) {
for(int i=0;i<outAll.length;i++){
PrintWriter p = outAll[i];
p.println(line);
}
}
}
} catch (Exception e) {
} finally{
//1、处理客户端断开后的操作,将该客户端的输出流从共享数组中删除
synchronized (outAll) {
for(int i=0;i<outAll.length;i++){
if(outAll[i]==pw){
outAll[i] = outAll[outAll.length-1];
outAll=Arrays.copyOf(outAll, outAll.length-1);
System.out.println("远程地址:"+host+"端口号:"+port+"下线了!");
System.out.println("在线人数是:"+outAll.length);
break;
}
}
}
//2.客户端断开连接后,服务端关闭该客户Socket,释放资源
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
package cs;
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.Socket;
import java.util.Scanner;
/**
* 聊天室客户端
* @author 臻冉
*
*/
public class Clinet {
/*
*实例化Soket就是连接额服务端的过程
* Socket提供了构造方法
*Socket(InterAddress address,int prot)
*参数1:服务端地址(IP)
*参数2:服务端开启的两端
*
* 通过服务器IP地址可以找到服务器所出在通过端口找到
* 运行在服务器计算机上的服务端应用程序。
*
* localhost:本机(IP:127.0.0.1)
* 查询本机IP地址:cmd--->ipconfig 查看电脑IP地址
*/
private Socket socket;
public Clinet() {
try {
this.socket = new Socket("localhost",8088);
System.out.println("客户端连接成功!");
} catch (IOException e) {
e.printStackTrace();
}
}
//启动客户端方法
private void start(){
Scanner scanner = new Scanner(System.in);
try {
/*
* Socket提供了方法:
* getOutputStream返回OutputStream
*
* 通过返回的字节流写出的数据发送到给远端计算机,对于客户端这边而言,
* 远程计算机就是服务端了
* getInputStream 返回InputStream
*/
ServerHandler server = new ServerHandler(socket);
//该线程接收服务端发送过来的信息
Thread t = new Thread(server);
t.start();//默认调用线程中的start()
//获取输出流
OutputStream out = socket.getOutputStream(); //所有字节流的父类
PrintWriter pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(out,"UTF-8")),true);
scanner = new Scanner(System.in);
System.out.println("和服务端说话吧...");
while(true){
String message = scanner.next();
pw.println(message);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
scanner.close();
}
}
public static void main(String[] args) {
Clinet clinet = new Clinet();
clinet.start();
}
}
//该任务接收服务端发送过来的消息
class ServerHandler implements Runnable{
private Socket socket;
public ServerHandler(Socket socket){
this.socket = socket;
}
@Override
public void run() {
try {
//获取输入流
InputStream in = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(in,"UTF-8"));
String line = null;
while((line=br.readLine())!=null){
System.out.println("服务端说:"+line);
}
} catch (IOException e) {
//e.printStackTrace(); //日志跟踪
System.out.println("服务端异常,请稍等...");
//throw new RuntimeException("服务端异常,请稍等...");
}
}
}