输出流共享到集合中,服务端遍历集合来广播输出流给客户端,来达到聊天室的功能.

课后作业:使用Mapk来实现@xxx 时,发送私聊内容给对方

Client
package chat;
import java.io.BufferedReader;
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 Administrator
*
*/
publicclassClient {
/*
* java.net.Socket
* 封装了TCP协议,使用它就可以基于TCP协议进行网络通讯
* Socket是运行在客户端的
*/
private Socket socket;
/**
* 构造方法,用来初始化客户端
* 实例化Socket的时候需要传入两个参数:
* 1.服务端地址:通过IP地址可以找到服务端的计算机.
* 2.服务端端口:通过端口可以找到服务端计算机上的服务端应用程序
* 实例化Socket的过程就是连接的过程,若远端计算机没有响应会抛出异常.
*/
public Client() throws Exception{
System.out.println("正在连接服务端...");
socket =new Socket("localhost", 8088);
System.out.println("已与服务端建立连接!");
}
/**
* 启动客户端
*/
publicvoid start(){
try {
Scanner scan =new Scanner(System.in);
/*
* 先要求用户输入一个昵称
*/
String nickName =null;
while(true){
System.out.println("请输入昵称:");
nickName = scan.nextLine();
if(nickName.length() >0){
break;
}
System.out.println("输入有误!");
}
System.out.println("欢迎你,"+ nickName +"!开始聊天吧!");
/*
* Socket提供的方法:
* OutputStream getOutputStream()
* 获取一个字节输出流,通过该流写出的数据会被发送至远端计算机.
*/
OutputStream out = socket.getOutputStream();
OutputStreamWriter osw =new OutputStreamWriter(out, "UTF-8"); //转换流,指定字符集
PrintWriter pw =new PrintWriter(osw, true); //自动行刷新
/*
* 先将昵称发送给服务器
*/
pw.println(nickName);
/*
* 启动读取服务端发送过来消息的线程
*/
ServerHandler handler =new ServerHandler();
Thread t =new Thread(handler);
t.start();
/*
* 将字符串发送至服务端
*/
// System.out.println("输入内容:");
while(true){
pw.println(scan.nextLine());
}
} catch (Exception e) {
e.printStackTrace();
}
}
publicstaticvoid main(String[] args) {
try {
Client client =new Client();
client.start();
} catch (Exception e) {
e.printStackTrace();
System.out.println("客户端启动失败!");
}
}
/**
* 该线程用来读取服务端发送过来的消息并输出到客户端控制台
* @author Administrator
*
*/
classServerHandlerimplements Runnable{
publicvoid run(){
try {
InputStream is = socket.getInputStream();
InputStreamReader isr =new InputStreamReader(is, "UTF-8");
BufferedReader br =new BufferedReader(isr);
String message =null;
while((message = br.readLine()) !=null){
System.out.println(message);
}
} catch (Exception e) {
}
}
}
}
Server
package chat;
import java.io.BufferedReader;
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.ArrayList;
import java.util.List;
/**
* 聊天室服务端
* @author Administrator
*
*/
publicclassServer {
/*
* 运行在服务端的ServerSocket主要负责:
* 1.向系统申请服务端口
* 客户端就是通过这个端口与之连接
* 2.监听申请的服务端口,当一个客户端通过该端口尝试连接时,
* ServerSocket会在服务端创建一个Socket与客户端建立连接.
*/
private ServerSocket server;
/*
* 保存所有客户端输出流的集合
*/
private List<< span="">PrintWriter> allOut;
/**
* 用来初始化服务端
*/
public Server() throws Exception {
/*
* 初始化的同时申请服务端口
*/
server =new ServerSocket(8088);
allOut =new ArrayList<< span="">PrintWriter>();
}
/**
* 将给定的输出流存入共享集合
* @param out
*/
privatesynchronizedvoid addOut(PrintWriter out){
allOut.add(out);
}
/**
* 将给定的输出流从共享集合中删除
* @param out
*/
privatesynchronizedvoid removeOut(PrintWriter out){
allOut.remove(out);
}
/**
* 将给定消息发送给所有客户端
* @param message
*/
privatesynchronizedvoid sendMessage(String message){
for(PrintWriter out : allOut){
out.println(message);
}
}
/**
* 服务端开始工作的方法
*/
publicvoid start() {
try {
/*
* ServrSocket的accept方法是一个阻塞方法,作用是监听服务端口,
* 直到一个客户端连接并创建一个Socket,使用该Socket即可与刚连接的客户端进行交互.
*/
while(true){
System.out.println("等待客户端连接...");
Socket socket = server.accept();
System.out.println("一个客户端连接了!");
/*
* 启动一个线程,来完成与该客户端的交互
*/
ClientHandler handler =new ClientHandler(socket);
Thread t =new Thread(handler);
t.start();
}
} catch (Exception e) {
e.printStackTrace();
}
}
publicstaticvoid main(String[] args) {
try {
Server server =new Server();
server.start();
} catch (Exception e) {
e.printStackTrace();
System.out.println("服务端启动失败!");
}
}
/*
* 该线程负责处理一个客户端的交互
*/
classClientHandlerimplements Runnable{
/*
* 该线程处理的客户端的Socket
*/
private Socket socket;
/*
* 客户端该用户的昵称
*/
privateString nickName;
//客户端的地址信息
privateString host;
public ClientHandler(Socket socket){
this.socket = socket;
/*
* 通过Socket可以获取远端计算机的地址信息.
*/
InetAddress address = socket.getInetAddress();
//获取IP地址
host = address.getHostAddress();
}
publicvoid run(){
PrintWriter pw =null;
try {
/*
* Socket提供的方法
* InputStream getInputStream()
* 该方法可以获取一个输入流,从该流读取的数据就是从远端计算机发送过来的
*/
InputStream in = socket.getInputStream();
InputStreamReader isr =new InputStreamReader(in, "UTF-8");
BufferedReader br =new BufferedReader(isr);
/*
* 首先读取一行字符串为昵称
*/
nickName = br.readLine();
sendMessage(nickName +"上线了");
/*
* 通过Socket创建输出流用于将消息发送给客户端
*/
OutputStream os = socket.getOutputStream();
OutputStreamWriter osw =new OutputStreamWriter(os, "UTF-8");
pw =new PrintWriter(osw, true);
/*
* 将该客户端的输出流存入到共享集合中
*/
addOut(pw);
String message =null;
/*
* br.readLine() 在读取客户端发送过来的消息时,由于客户端断线,而其操作系统的不同,这里读取后的结果不同:
* 当windows的客户端断开时:br.readLine会抛出一场
* 当linux的客户端断开时:br.readLine会返回null
*/
while((message = br.readLine()) !=null){ //readLine() 读到文件末尾会返回null
// System.out.println(host + "说:" + message);
// pw.println(host + "说:" + message); 将客户端发送过来的消息返回给客户端,客户端去读Socket
//广播消息
sendMessage(nickName +"说:"+ message);
}
}catch (Exception e) {
e.printStackTrace();
} finally{
/*
* 处理当前客户端断开后的逻辑
*/
//将该客户端的输出流从共享集合中删除
removeOut(pw);
sendMessage(nickName +"下线了!");
try {
socket.close(); //Socket断开连接时,会自动关闭流
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}




