Java Socket 多线程聊天室
本来这次作业我是想搞个图形界面的,然而现实情况是我把题意理解错了,于是乎失去了最初的兴致,还是把程序变成了功能正确但是“UI”不友好的console了,但是不管怎么样,前期的图形界面的开发还是很有收获的,毕竟讲真,想要把Java搞得有形有色的也是很不容易的,借助可视化的插件windowsBuilder,这个过程还是既exciting 又tiring的。
好吧 ,然而图形界面已经成为了历史,现在来说说这个功能正确的console 吧
我也是刚知道的Eclipse里面是可以跑好多个程序的,只要你的一个.java文件中有public static void main,他就能给你一个窗口,让你跑起来。只不过这些窗口堆叠在一起,需要自行选择不同的窗口进行IO操作。
总的思路是采用c/s的方式,client借助socket完成向server的发送和接受两个工作,当然了,为了体现出真实情况下的双工的特点,发送和接受是需要开两个线程的,也就是说,一个用户需要自己管理两个线程。server则相对来讲比较复杂,因为这里面涉及到了调度,server需要有发送消息给在线client的线程(这个线程要做的事情就是只要有消息就要把消息发到所有的用户的窗口),以及接受client发来的消息的线程(这个线程要做的事情就是将接收到的消息全部交给发送消息的线程,于是这两个线程之间的通信问题也是实现上的一个关键~),为了使得所有的用户消息是同步的,server需要管理一个用户线程的列表,用以实现用户的行为的控制,于是乎这就要求只要有用户请求连接服务器,服务器就要为用户新建一个线程,那么client 和server 之间靠什么来进行联系呢,那就是我们的socket了。
1 import java.io.BufferedReader; 2 import java.io.InputStreamReader; 3 import java.io.PrintWriter; 4 import java.net.Socket; 5 6 public class Client extends Thread{ 7 8 private static String serverIp = "127.0.0.1"; 9 private static int serverPort = 8001; 10 11 private Socket clientsSocket; //plays key role 12 private PrintWriter pw; // for send data 13 private BufferedReader br; // for receive data 14 15 public Client(){ 16 try { 17 clientsSocket = new Socket(serverIp, serverPort); 18 pw = new PrintWriter(clientsSocket.getOutputStream(),true); 19 br = new BufferedReader(new InputStreamReader(clientsSocket.getInputStream())); 20 new readServer(); 21 22 while(true){ 23 br = new BufferedReader(new InputStreamReader(System.in)); 24 String input = br.readLine(); 25 //System.out.println("this is the input :" + input); 26 pw.println(input); 27 } 28 } catch (Exception e) { 29 e.printStackTrace(); 30 } 31 } 32 33 class readServer extends Thread{ 34 private BufferedReader reader; 35 36 public readServer(){ 37 try { 38 reader = new BufferedReader(new InputStreamReader(clientsSocket.getInputStream())); 39 start(); 40 } catch (Exception e) { 41 e.printStackTrace(); 42 } 43 } 44 45 public void run(){ 46 try { 47 while(true){ 48 String content = reader.readLine(); 49 if(content.equals("bye Client")){ 50 break; 51 } 52 else { 53 System.out.println(content); 54 } 55 } 56 } catch (Exception e) { 57 e.printStackTrace(); 58 } 59 } 60 } 61 62 public static void main(String[] args) throws Exception{ 63 new Client(); 64 } 65 }
1 import java.io.BufferedReader; 2 import java.io.InputStreamReader; 3 import java.io.PrintWriter; 4 import java.net.ServerSocket; 5 import java.net.Socket; 6 import java.util.ArrayList; 7 import java.util.LinkedList; 8 9 10 public class Server { 11 12 private static int port = 8001; 13 private static boolean prit = false; 14 private static ArrayList<String> userList = new ArrayList<>(); 15 private static LinkedList<String> messageList = new LinkedList<>(); 16 private static ArrayList<ServerThread> threadsList = new ArrayList<>(); 17 18 private ServerSocket serverSocket; 19 20 public Server(){ 21 try { 22 serverSocket = new ServerSocket(port); 23 new PrintClient(); 24 25 while(true){ 26 Socket socket = serverSocket.accept(); 27 new ServerThread(socket); 28 } 29 } catch (Exception e) { 30 e.printStackTrace(); 31 } 32 } 33 34 class PrintClient extends Thread{ 35 public PrintClient(){ 36 start(); 37 } 38 39 public void run(){ 40 while(true){ 41 try { 42 Thread.sleep(10); 43 } catch (Exception e) { 44 e.printStackTrace(); 45 } 46 if (prit == true){ 47 String msg = messageList.getFirst(); 48 //System.out.println("prepare to sent to Clent"); 49 for (ServerThread sThread : threadsList){ 50 sThread.sendMessage(msg); 51 } 52 synchronized (messageList) { 53 messageList.removeFirst(); 54 } 55 prit = messageList.size() > 0 ? true : false; 56 } 57 } 58 } 59 } 60 61 class ServerThread extends Thread{ 62 private Socket client; 63 private PrintWriter pw; 64 private BufferedReader br; 65 private String user; 66 67 public ServerThread(Socket socket){ 68 try { 69 client = socket; 70 pw = new PrintWriter(client.getOutputStream(),true); 71 br = new BufferedReader(new InputStreamReader(client.getInputStream())); 72 br.readLine(); 73 74 pw.println("connect success input your name ~"); 75 start(); 76 } catch (Exception e) { 77 e.printStackTrace(); 78 } 79 } 80 81 public void pushMessage(String msg){ 82 synchronized (messageList) { 83 messageList.add(msg); 84 } 85 prit = true; 86 } 87 88 public void sendMessage(String msg){ 89 pw.println(msg); 90 } 91 92 93 public void run(){ 94 try { 95 int first = 1; 96 String msg = br.readLine(); 97 while(!msg.equals("bye")){ 98 if (first == 1){ 99 user = msg; 100 userList.add(user); 101 threadsList.add(this); 102 pw.println(user + " hello you can chat now "); 103 this.pushMessage("Client <" + user + "> " + "join in ~"); 104 //System.out.println("the prit is " + prit); 105 first--; 106 } 107 else { 108 this.pushMessage("Client<" + user + "> "+ "say :" + msg); 109 //System.out.println("the prit is " + prit + " " + messageList.size()); 110 } 111 msg = br.readLine(); 112 //System.out.println(msg); 113 } 114 pw.println("bye Client"); 115 } catch (Exception e) { 116 e.printStackTrace(); 117 }finally{ 118 try { 119 client.close(); 120 } catch (Exception e2) { 121 e2.printStackTrace(); 122 } 123 threadsList.remove(this); 124 userList.remove(user); 125 pushMessage(user + " leave ~"); 126 } 127 } 128 } 129 130 public static void main(String[] srgs) throws Exception{ 131 new Server(); 132 } 133 }
在这里注意一个问题,server端的PrintClient线程在run的过程中一定要有sleep的过程,否则会因为一开始的prit设置为false而在一直在while(true)的死循环中得不到更新,也就是说线程的同步出了错,从而会出现客户端无法收到信息的错误。还有一点要注意的是作为良好的编程习惯需要注意多个线程公用的变量要注意互斥操作,防止出现多线程中的“magic bugs”。同步与互斥,真是线程进程调度中的老大难啊,每次编程都要小心处理这两个问题,尽量避免不必要的错误。