无窗体多人聊天室
思路:
1.首先创建服务器和客户端,实现服务器和客户端的数据交互正常
服务器
1 public class ServerSocketDome { 2 public static void main(String[] args) throws IOException { 3 ServerSocket ss = new ServerSocket(8800); 4 5 Socket socket = ss.accept(); 6 7 InputStream is = socket.getInputStream(); 8 //接受处理消息 9 byte[] b = new byte[100]; 10 is.read(b); 11 String str = new String(b); 12 System.out.println("客户端发来的消息:\t"+str.trim()); 13 14 //返回消息 15 OutputStream os = socket.getOutputStream(); 16 String str2 ="--->发送成功!"; 17 os.write(str2.getBytes()); 18 19 os.close(); 20 is.close(); 21 ss.close(); 22 23 } 24 }
客户端
1 public class Client { 2 public static void main(String[] args) throws UnknownHostException, IOException { 3 Scanner input = new Scanner(System.in); 4 Socket s = new Socket("127.0.0.1",8800); 5 6 //发送请求 7 OutputStream os = s.getOutputStream(); 8 System.out.println("请输入请求内容:"); 9 String str =input.nextLine(); 10 os.write(str.getBytes()); 11 12 //接收回馈 13 InputStream is = s.getInputStream(); 14 byte[] b = new byte[100]; 15 is.read(b); 16 String str2 = new String(b); 17 System.out.println(str2.trim()); 18 19 is.close(); 20 os.close(); 21 s.close(); 22 } 23 }
2.实现了客户端和服务器的数据交互后,将客户端和服务器线程化
服务器线程
1 public class ServerThread extends Thread { 2 private Socket socket; 3 4 public ServerThread(Socket socket) { 5 super(); 6 this.socket = socket; 7 } 8 9 @Override 10 public void run() { 11 InputStream is; 12 try { 13 14 is = socket.getInputStream(); 15 16 // 接受处理消息 17 byte[] b = new byte[100]; 18 is.read(b); 19 String str = new String(b); 20 System.out.println("客户端发来的消息:\t" + str.trim()); 21 22 // 返回消息 23 OutputStream os = socket.getOutputStream(); 24 String str2 = "--->发送成功!"; 25 os.write(str2.getBytes()); 26 27 os.close(); 28 is.close(); 29 30 31 } catch (IOException e) { 32 // TODO Auto-generated catch block 33 e.printStackTrace(); 34 } 35 36 } 37 38 }
然后是服务器线程的创建
1 public class Server { 2 public static void main(String[] args) throws IOException { 3 ServerSocket ss = new ServerSocket(9000); 4 5 while(true){ 6 Socket socket = ss.accept(); 7 8 new ServerThread(socket).start(); 9 10 } 11 } 12 }
再来创建客户端的线程,客户端线程宜分为输出端和接收端
输出端为
1 public class ClientThreadOut extends Thread { 2 private Scanner input; 3 private Socket socket; 4 5 public ClientThreadOut(Scanner input, Socket socket) { 6 super(); 7 this.input = input; 8 this.socket = socket; 9 } 10 11 public ClientThreadOut() { 12 super(); 13 } 14 15 @Override 16 public void run() { 17 try { 18 19 OutputStream os = socket.getOutputStream(); 20 System.out.println("请输入要说的话:"); 21 String str = input.nextLine(); 22 os.write(str.getBytes()); 23 24 } catch (IOException e) { 25 e.printStackTrace(); 26 } 27 28 } 29 30 }
接受端为
1 public class ClientThreadIn extends Thread{ 2 private Socket socket; 3 4 5 public ClientThreadIn(Socket socket) { 6 super(); 7 this.socket = socket; 8 } 9 10 11 public ClientThreadIn() { 12 super(); 13 } 14 15 16 @Override 17 public void run() { 18 19 try { 20 21 InputStream is = socket.getInputStream(); 22 byte[] b = new byte[100]; 23 is.read(b); 24 String str = new String(b); 25 System.out.println(str.trim()); 26 27 } catch (IOException e) { 28 e.printStackTrace(); 29 } 30 31 } 32 33 }
客户端的线程启动代码
1 public class ClientThreadTest { 2 public static void main(String[] args) throws UnknownHostException, IOException { 3 Socket socket = new Socket("127.0.0.1",9000); 4 Scanner input = new Scanner(System.in); 5 6 Thread th1 = new ClientThreadOut(input, socket); 7 Thread th2 = new ClientThreadIn(socket); 8 th1.start(); 9 th2.start(); 10 11 } 12 }
3.之前的都是与服务器的单次数据交互,要实现客户端与服务器的多次数据交互,服务器则需要循环的今天,客户端也需要可以循环输入
服务器线程代码
1 public class ServerThread extends Thread { 2 private Socket socket; 3 4 public ServerThread(Socket socket) { 5 super(); 6 this.socket = socket; 7 } 8 9 @Override 10 public void run() { 11 InputStream is; 12 try { 13 14 is = socket.getInputStream(); 15 while(true){ 16 // 接受处理消息 17 byte[] b = new byte[100]; 18 is.read(b); 19 String str = new String(b); 20 System.out.println("客户端发来的消息:\t" + str.trim()); 21 22 // 返回消息 23 String str2 = "--->发送成功!"; 24 OutputStream os = socket.getOutputStream(); 25 os.write(str2.getBytes()); 26 27 if(str.trim().equals("bye")){ 28 break; 29 } 30 //os.close(); 31 //is.close(); 32 } 33 34 } catch (IOException e) { 35 System.err.println("客户端下线"); 36 } 37 38 } 39 40 }
服务器线程启动代码(死循环循环监听)
1 public class Server { 2 public static void main(String[] args) throws IOException { 3 ServerSocket ss = new ServerSocket(9000); 4 5 while(true){ 6 Socket socket = ss.accept(); 7 8 new ServerThread(socket).start(); 9 10 } 11 } 12 }
客户端输出线程
1 public class ClientThreadOut extends Thread { 2 private Socket socket; 3 4 public ClientThreadOut(Socket socket) { 5 super(); 6 this.socket = socket; 7 } 8 9 @Override 10 public void run() { 11 try { 12 13 while (true) { 14 OutputStream os = socket.getOutputStream(); 15 Scanner input = new Scanner(System.in); 16 System.out.println("请输入:"); 17 String str = input.nextLine(); 18 os.write(str.getBytes()); 19 if(str.trim().equals("bye")){ 20 21 System.exit(0); 22 } 23 //os.close(); 24 } 25 } catch (IOException e) { 26 // TODO Auto-generated catch block 27 e.printStackTrace(); 28 } 29 } 30 31 }
客户端输入线程
1 public class ClientThreadIn extends Thread { 2 private Socket socket; 3 4 public ClientThreadIn(Socket socket) { 5 super(); 6 this.socket = socket; 7 } 8 9 @Override 10 public void run() { 11 try { 12 13 while (true) { 14 InputStream is = socket.getInputStream(); 15 byte[] b = new byte[100]; 16 is.read(b); 17 String str = new String(b); 18 System.out.println(str); 19 //is.close(); 20 21 } 22 23 } catch (IOException e) { 24 // TODO Auto-generated catch block 25 e.printStackTrace(); 26 } 27 } 28 29 }
客户端线程启动代码
1 public class ClientTest { 2 public static void main(String[] args) throws UnknownHostException, IOException { 3 Socket socket = new Socket("127.0.0.1",9000); 4 5 new ClientThreadOut(socket).start(); 6 new ClientThreadIn(socket).start(); 7 } 8 }
4.之前所做的都是服务器与客户端的数据交互,而聊天需要客户端与客户端实现数据的交互,因此要实现服务器将一个客户端的消息发给其他的客户端.
在此处也说明了之前将客户端线程分为输出和接受两个线程是正确的.
此时的服务器线程需要进行修改,并且可以做一些小小的改进.
1 public class ServerThread extends Thread { 2 private Socket socket; 3 public static List<Socket> list = new ArrayList<Socket>(); 4 5 public ServerThread() { 6 super(); 7 } 8 9 public ServerThread(Socket socket) { 10 super(); 11 this.socket = socket; 12 } 13 14 @Override 15 public void run() { 16 try { 17 //打开输入流 18 InputStream is = socket.getInputStream(); 19 while (true) { 20 //判断该客户端是否在服务器注册,若没有,则注册 21 if (!list.contains(socket)) { 22 list.add(socket); 23 System.err.println("有客户端上线"); 24 } 25 //创建数组 26 byte[] b = new byte[100]; 27 //接受消息 28 is.read(b); 29 for (int i = 0; i < list.size(); i++) { 30 //遍历所有的客户端 31 Socket socketLS = list.get(i); 32 //输出给每个客户端的接受线程 33 OutputStream os = socketLS.getOutputStream(); 34 os.write(b); 35 } 36 37 // os.close(); 38 // is.close(); 39 } 40 41 } catch (IOException e) { 42 System.err.println("有客户端下线"); 43 } 44 45 } 46 47 }
客户端输入输出也要做相应的改动
1 public class ClientThreadOut extends Thread { 2 private Socket socket; 3 private String name; 4 5 public ClientThreadOut() { 6 super(); 7 } 8 9 public ClientThreadOut(Socket socket, String name) { 10 super(); 11 this.socket = socket; 12 this.name = name; 13 } 14 15 @Override 16 public void run() { 17 try { 18 //开启文本扫描 19 Scanner input = new Scanner(System.in); 20 //打开输出流 21 OutputStream os = socket.getOutputStream(); 22 //向服务器发送一条空信息以此在服务器注册 23 os.write((name+"上线").getBytes()); 24 while (true) { 25 //输入要发送的话 26 String str = input.nextLine(); 27 //字节化姓名及话并发送 28 os.write((name+":"+str).getBytes()); 29 //如果输入bye就关闭虚拟机 30 if(str.trim().equals("bye")){ 31 System.exit(0); 32 } 33 //os.close(); 34 } 35 } catch (IOException e) { 36 e.printStackTrace(); 37 } 38 } 39 40 }
1 public class ClientThreadIn extends Thread { 2 private Socket socket; 3 private String name; 4 5 public ClientThreadIn(Socket socket, String name) { 6 super(); 7 this.socket = socket; 8 this.name = name; 9 } 10 11 public ClientThreadIn() { 12 super(); 13 } 14 15 @Override 16 public void run() { 17 try { 18 //开启输入流 19 InputStream is = socket.getInputStream(); 20 while (true) { 21 //创建数组 22 byte[] b = new byte[100]; 23 //接受数据并放入数组 24 is.read(b); 25 //数组转化为字符串 26 String str = new String(b); 27 //判断是否为自己说的话 28 if(str.contains(name)){ 29 System.out.println("发送成功√"); 30 }else{ 31 System.out.println(str); 32 } 33 //is.close(); 34 } 35 } catch (IOException e) { 36 e.printStackTrace(); 37 } 38 } 39 40 }
改动后,客户端线程启动代码也要相应的改进
1 public class ClientTest { 2 public static void main(String[] args) throws UnknownHostException, IOException { 3 Socket socket = new Socket("127.0.0.1",9000); 4 Scanner input = new Scanner(System.in); 5 System.out.println("请输入昵称:"); 6 String name = input.next(); 7 8 Thread th1 = new ClientThreadOut(socket,name); 9 Thread th2 = new ClientThreadIn(socket,name); 10 th1.start(); 11 th2.start(); 12 } 13 }
5.以上已经实现了客户端和客户端的交互,但是出现了一个BUG,即在判断是否为自己说的话时出现困难
可以发现是因为集合中没有保存姓名信息,因此,改用可以保存映射关系的Map类集合
于是对服务器进程做了改进
1 public class ServerThread extends Thread { 2 private Socket socket; 3 public static Map<Socket,String> map = new HashMap<Socket,String>(); 4 5 public ServerThread() { 6 super(); 7 } 8 9 public ServerThread(Socket socket) { 10 super(); 11 this.socket = socket; 12 } 13 14 @Override 15 public void run() { 16 try { 17 //打开输入流 18 InputStream is = socket.getInputStream(); 19 while (true) { 20 //创建数组 21 byte[] b = new byte[100]; 22 //判断该客户端是否在服务器注册,若没有,则注册 23 if (!map.containsKey(socket)) { 24 is.read(b); 25 String name = new String(b); 26 map.put(socket,name); 27 System.err.println(name+"注册"); 28 29 }else{ 30 //接受消息 31 is.read(b); 32 //返回key值即返回Socket 33 Set set = map.keySet(); 34 //声明迭代器 35 Iterator it = set.iterator(); 36 //迭代输出 37 while(it.hasNext()){ 38 Socket socket1 = (Socket) it.next(); 39 String str = map.get(socket1); 40 //判断是否是自己说的话 41 if(!str.equals(map.get(socket))){ 42 OutputStream os = socket1.getOutputStream(); 43 os.write(b); 44 }else{ 45 String str1="-----发送成功√-----"; 46 OutputStream os = socket1.getOutputStream(); 47 os.write(str1.getBytes()); 48 } 49 } 50 } 51 52 53 54 // os.close(); 55 // is.close(); 56 } 57 58 } catch (IOException e) { 59 System.err.println("有客户端下线"); 60 } 61 62 } 63 64 }
这样就实现了映射关系,可以准确的判断一句话是否为自己发出的
同时,又对客户端的进程做了修改,加入了发送时间和敏感词过滤等小玩意儿.
1 public class ClientThreadOut extends Thread { 2 private Socket socket; 3 private String name; 4 5 public ClientThreadOut() { 6 super(); 7 } 8 9 public ClientThreadOut(Socket socket, String name) { 10 super(); 11 this.socket = socket; 12 this.name = name; 13 } 14 15 @Override 16 public void run() { 17 try { 18 //开启文本扫描 19 Scanner input = new Scanner(System.in); 20 //打开输出流 21 OutputStream os = socket.getOutputStream(); 22 //向服务器发送一条注册信息以此在服务器注册 23 os.write(name.getBytes()); 24 while (true) { 25 //输入要发送的话 26 String str = input.nextLine(); 27 //过滤敏感词 28 String str1 = new MinGanCi().Buffer(str); 29 Date date = new Date(); 30 //Calendar cal = Calendar.getInstance(); 31 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 32 String str2 = sdf.format(date); 33 //字节化姓名及话并发送 34 os.write((name+": "+str2+"\n"+str1).getBytes()); 35 //如果输入bye就关闭虚拟机 36 if(str.trim().equals("bye")){ 37 System.exit(0); 38 } 39 //os.close(); 40 } 41 } catch (IOException e) { 42 e.printStackTrace(); 43 } 44 } 45 46 }
1 public class ClientThreadIn extends Thread { 2 private Socket socket; 3 private String name; 4 5 public ClientThreadIn(Socket socket, String name) { 6 super(); 7 this.socket = socket; 8 this.name = name; 9 } 10 11 public ClientThreadIn() { 12 super(); 13 } 14 15 @Override 16 public void run() { 17 try { 18 //开启输入流 19 InputStream is = socket.getInputStream(); 20 while (true) { 21 //创建数组 22 byte[] b = new byte[100]; 23 //接受数据并放入数组 24 is.read(b); 25 //数组转化为字符串 26 String str = new String(b); 27 28 System.out.println(str); 29 30 //is.close(); 31 } 32 } catch (IOException e) { 33 e.printStackTrace(); 34 } 35 } 36 37 }
接下来只要补上自己想要的Swing或者页面就可以了.