1.群聊;
我们在原来客户端多线程的基础上在修改一下服务器端,让服务器可以是实现把一个客户端的消息转发给其他客户端的功能即可;
package TCPChatDemo03; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import java.util.ArrayList; public class Server { // 我们建立一个容器 容器的类型为我们的通道 我们放在容器里面进行同意管理 // 每一个通路都是代表与一个服务器的一个线程连接完成 private ArrayList<Channel> channels = new ArrayList<Channel>(); public static void main(String[] args) throws IOException { new Server().start();//我们调用非静态方法需要new一个对象 } public void start() throws IOException{ ServerSocket server =new ServerSocket(9999); while(true){ Socket client =server.accept(); Channel channel = new Channel(client); channels.add(channel);//没建立一个连接就把他加入到容器中 便于统一管理 new Thread(channel).start(); //一条道路 } } /** * * 我们在这里建立一个成员内部类来为每一个client建立一个通道 他的好处就是可以直接访问外部类的私有属性 这里的实现类似于client端的多线程的写法 */ private class Channel implements Runnable { // 建立一个输入流用来读取客户端发来的信息 private DataInputStream dis; // 建立一个输出流用来发送信息 private DataOutputStream dos; // 建立一个标志位 private boolean flag = true; Channel() { } Channel(Socket socket) { try { dis = new DataInputStream(socket.getInputStream()); dos = new DataOutputStream(socket.getOutputStream()); } catch (IOException e) { // TODO Auto-generated catch block // e.printStackTrace(); CloseUtil.closeAll(dis, dos); flag = false; } } /** * 接收数据 */ public String receive() { String info = null; try { info = dis.readUTF(); } catch (IOException e) { // TODO Auto-generated catch block // e.printStackTrace(); CloseUtil.closeAll(dis); flag = false; } return info; } /** * 服务器将自己接收到的数据发送给聊天室的其他成员 */ public void send(String message) { try { if (message != null) { dos.writeUTF(message); } } catch (IOException e) { // TODO Auto-generated catch block // e.printStackTrace(); CloseUtil.closeAll(dos); flag = false; channels.remove(this); // 这里就是移除我们自身正在运行的这个有问题的通道 } } /** * 把服务器接收到的数据转发给其他人 */ public void sendOthers() { String mes = this.receive(); // 我们遍历容器把信息发给容器中的其他客户端 for (Channel other : channels) { if (other == this) {// 如果是自己发的消息那就不需要发给自己了 continue;// 跳过这一次循环 } other.send("经过服务器转给你的-->" + mes); } } @Override public void run() { while (flag) { sendOthers(); } } } }
成型的简单聊天室:
1.客户端的接收信息:
package TCPChatDemo04; import java.io.DataInputStream; import java.io.IOException; import java.net.Socket; /** * * 接收数据 与发送数据的思路相同 * * @author Wang * */ public class Receive implements Runnable{ private DataInputStream dis; private boolean flag = true; Receive() { } Receive(Socket socket) { try { dis = new DataInputStream(socket.getInputStream()); } catch (IOException e) { // TODO Auto-generated catch block //e.printStackTrace(); flag =false; CloseUtil.closeAll(dis); } } /** * 我们来获取读取的信息 */ public String getReaciveData() { try { return dis.readUTF(); } catch (IOException e) { // TODO Auto-generated catch block //e.printStackTrace(); flag =false; CloseUtil.closeAll(dis); } return null; } /** * 直接用线程体 输出读取的数据 */ @Override public void run() { //线程体 while(flag){ System.out.println(getReaciveData()); } } }
2.客户端的发送信息:
package TCPChatDemo04; import java.io.BufferedReader; import java.io.DataOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.net.Socket; /** * * 消息的发送 * * @author Wang * */ public class Send implements Runnable{ //控制台输入流 private BufferedReader consoleInput; //消息的发送 private DataOutputStream dos; //设置一个消息发送的标志位 boolean flag = true; private String name; public Send() {//无参构造先把控制台的输入传进来 consoleInput = new BufferedReader(new InputStreamReader(System.in)); } public Send(Socket socket,String name) { this(); try { this.name = name; dos = new DataOutputStream(socket.getOutputStream());//我们这一句要肯定要在send的前面不然就是没有与服务器建立链接 怎么莫执行下一步的发送操作 send(this.name); } catch (IOException e) { // TODO Auto-generated catch block //e.printStackTrace(); //如果这里建立不了通道 我们关闭流和停止线程的运行 flag =false; CloseUtil.closeAll(dos,consoleInput); } } /** * 1.我们从控制台读取数据 如果读取不到就会返回一个"";(空串) */ public String getConsoleInput() { try { return consoleInput.readLine(); } catch (IOException e) { e.printStackTrace(); } return ""; } /** * 2.我们把从控制台读取的数据发送给服务器 */ public void send(String message) { if((message != null) && (message != "") ) { try { dos.writeUTF(message); dos.flush(); } catch (IOException e) { // TODO Auto-generated catch block //e.printStackTrace(); flag =false; CloseUtil.closeAll(dos,consoleInput); } } } @Override public void run() { // TODO Auto-generated method stub while(flag) { send(getConsoleInput()); //线程体里面的内容就是一直发送 } } }
3.客户端:
package TCPChatDemo04; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.Socket; import java.net.UnknownHostException; /** * * 我们来用多线程来实现 聊天的发送消息和接收消息 * 我们把他们包装成send 和 receive * * @author Wang * */ public class Client { public static void main(String[] args) throws UnknownHostException, IOException { System.out.println("请输入你的昵称:"); BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); String name = br.readLine();//我们在这里加入昵称 这样为私聊做准备 if(name.equals("")){ return; } Socket client = new Socket("localhost",9999);//创建一个客户端并于服务器建立连接 new Thread(new Send(client,name)).start(); //建立一个发送消息的线程 //我们在服务器建立链接的时候就将名字给发出去 不通过线程体 这样就不会出a现名字被当成消息的情况 //相应的 我们在服务器端也是在构造链接的时候将名字给存起来和进行其他操作 new Thread(new Receive(client)).start(); //建立一个接受消息的线程 } }
4.服务器端:
package TCPChatDemo04; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import java.util.ArrayList; public class Server { // 我们建立一个容器 容器的类型为我们的通道 我们放在容器里面进行同意管理 // 每一个通路都是代表与一个服务器的一个线程连接完成 private ArrayList<Channel> channels = new ArrayList<Channel>(); public static void main(String[] args) throws IOException { new Server().start();// 我们调用非静态方法需要new一个对象 } public void start() throws IOException { ServerSocket server = new ServerSocket(9999); while (true) { Socket client = server.accept(); Channel channel = new Channel(client); channels.add(channel);// 没建立一个连接就把他加入到容器中 便于统一管理 new Thread(channel).start(); // 一条道路 } } /** * * 我们在这里建立一个成员内部类来为每一个client建立一个通道 他的好处就是可以直接访问外部类的私有属性 这里的实现类似于client端的多线程的写法 */ private class Channel implements Runnable { // 建立一个输入流用来读取客户端发来的信息 private DataInputStream dis; // 建立一个输出流用来发送信息 private DataOutputStream dos; // 建立一个标志位 用来管理服务器线程体的运行 private boolean flag = true; // 每个通道的名字 private String name; // 设置一个标志位 用来区分是系统消息还是客户端发的消息 private boolean system = true; Channel() { } Channel(Socket socket) { try { dis = new DataInputStream(socket.getInputStream()); dos = new DataOutputStream(socket.getOutputStream()); this.name = dis.readUTF();// 我们在这个时候是刚建立好链接还没有调用线程体 所以读取到的只可能是用户名 send("欢迎您进入聊天室"); sendOthers(this.name + "进入了聊天室", true);// 发送给别人的 } catch (IOException e) { // TODO Auto-generated catch block // e.printStackTrace(); CloseUtil.closeAll(dis, dos); flag = false; } } /** * 接收数据 */ public String receive() { String info = null; try { info = dis.readUTF(); } catch (IOException e) { // TODO Auto-generated catch block // e.printStackTrace(); CloseUtil.closeAll(dis); flag = false; } return info; } /** * 服务器将自己接收到的数据发送给聊天室的其他成员 */ public void send(String message) { try { if (message != null) { dos.writeUTF(message); } } catch (IOException e) { // TODO Auto-generated catch block // e.printStackTrace(); CloseUtil.closeAll(dos); flag = false; channels.remove(this); // 这里就是移除我们自身正在运行的这个有问题的通道 } } /** * 把服务器接收到的数据转发给其他人 */ public void sendOthers(String mes, boolean system) { if (mes.startsWith("@") && mes.indexOf(":") > 0) { // 如果消息以@开头且后面的内容有冒号 即判定为私聊 // 获取name String name = mes.substring(1, mes.indexOf(":")); //包头不包尾 String content = mes.substring(mes.indexOf(":") + 1); //包头不包尾 所以加一 for (Channel other : channels) { if (other.name.equals(name)) { other.send(this.name + "私聊你说:" + content); } } } else { for (Channel other : channels) { if (other == this) {// 如果是自己发的消息那就不需要发给自己了 continue;// 跳过这一次循环 } if (system) { other.send("系统通知:" + mes); } else { other.send(this.name + "说:" + mes); } } } } @Override public void run() { while (flag) { sendOthers(this.receive(), false); } } } }5.关闭流的工具类:
package TCPChatDemo04; import java.io.Closeable; import java.io.IOException; /** * * 关闭流的工具类 * @author Wang * */ public class CloseUtil { public static void closeAll(Closeable... io) { for(Closeable temp : io) { try { temp.close(); } catch (IOException e) { // TODO Auto-generated catch block //e.printStackTrace(); System.out.println("IO流关闭失败"); } } } }