前面一片學習了TCP/IP的基礎網絡編程,並給出了簡單的服務端與客戶端通信交互的例子。還介紹了UPC的通信例子。
這次學習TCP/IP的多線程編程。因為涉及到TCP/IP一般都是多線程,服務端會一直監聽端口,多個客戶端發來信息,收到某個客戶端發來的數據后,如果所有處理都放在服務端,這樣程序就會顯得很臃腫。就需要單獨啟動一個線程去執行,來一個客戶端就啟動一個對應的程序。
下面給出具體例子:Java 多線程實現Socket通信
1 package lesson1220Socket; 2 3 import java.net.ServerSocket; 4 import java.net.Socket; 5 6 public class SeverTCPDemo { 7 8 private static ServerSocket ss; //定義成static類型是有原因的,因為ss不能進行close. 9 10 public static void main(String[] args) throws Exception { 11 ss = new ServerSocket(9999); 12 Socket socket = null; 13 int num = 0; 14 System.out.println("******服務器已啟動,等待客戶端連接*****"); 15 16 while(true){ 17 socket = ss.accept(); 18 num++; 19 new SocketThread(socket,num).start(); 20 } 21 } 22 } 23 24 package lesson1220Socket; 25 26 import java.io.BufferedReader; 27 import java.io.IOException; 28 import java.io.InputStream; 29 import java.io.InputStreamReader; 30 import java.io.OutputStream; 31 import java.io.PrintWriter; 32 import java.net.Socket; 33 34 public class SocketThread extends Thread { 35 36 Socket socket; 37 private int num; 38 39 public SocketThread(Socket socket, int num) { 40 super(); 41 this.socket = socket; 42 this.num = num; 43 } 44 45 /* (non-Javadoc) 46 * @see java.lang.Thread#run() 47 */ 48 @Override 49 public void run() { 50 51 InputStream is = null; 52 BufferedReader br = null; 53 OutputStream os = null; 54 PrintWriter pw = null; 55 try { 56 is = socket.getInputStream(); 57 br = new BufferedReader(new InputStreamReader(is)); 58 String info = null; 59 while((info=br.readLine())!=null){ 60 System.out.println("客戶端發來消息:" + info); 61 } 62 socket.shutdownInput(); 63 64 os = socket.getOutputStream(); 65 pw= new PrintWriter(os); 66 pw.write("hello, I am Server! you are " + num + " Client!"); 67 pw.flush(); 68 socket.shutdownOutput(); 69 70 } catch (IOException e) { 71 e.printStackTrace(); 72 }finally{ 73 try { 74 is.close(); 75 } catch (IOException e) { 76 e.printStackTrace(); 77 } 78 try { 79 br.close(); 80 } catch (IOException e) { 81 e.printStackTrace(); 82 } 83 try { 84 os.close(); 85 } catch (IOException e) { 86 e.printStackTrace(); 87 } 88 pw.close(); 89 } 90 } 91 } 92 93 package lesson1220Socket; 94 95 import java.io.BufferedReader; 96 import java.io.IOException; 97 import java.io.InputStream; 98 import java.io.InputStreamReader; 99 import java.io.OutputStream; 100 import java.io.PrintWriter; 101 import java.net.Socket; 102 import java.net.UnknownHostException; 103 104 public class ClientTCPDemo { 105 106 public static void main(String[] args){ 107 108 Socket socket = null; 109 OutputStream os = null; 110 PrintWriter pw = null; 111 InputStream is = null; 112 BufferedReader br = null; 113 114 try { 115 socket = new Socket("127.0.0.1", 9999); 116 os = socket.getOutputStream(); 117 pw = new PrintWriter(os); 118 pw.write("hello, server! I am client!"); 119 pw.flush(); 120 121 socket.shutdownOutput(); 122 123 is = socket.getInputStream(); 124 br = new BufferedReader(new InputStreamReader(is)); 125 String info = null; 126 while((info=br.readLine())!=null){ 127 System.out.println("服務端返回信息:" + info); 128 } 129 130 socket.shutdownInput(); 131 132 } catch (UnknownHostException e) { 133 e.printStackTrace(); 134 } catch (IOException e) { 135 e.printStackTrace(); 136 }finally{ 137 try { 138 os.close(); 139 } catch (IOException e1) { 140 e1.printStackTrace(); 141 } 142 pw.close(); 143 try { 144 is.close(); 145 } catch (IOException e) { 146 e.printStackTrace(); 147 } 148 try { 149 br.close(); 150 } catch (IOException e) { 151 e.printStackTrace(); 152 } 153 try { 154 socket.close(); 155 } catch (IOException e) { 156 e.printStackTrace(); 157 } 158 } 159 } 160 161 }
運行結果:分別啟動三個客戶端:
******服務器已啟動,等待客戶端連接*****
客戶端發來消息:hello, server! I am client!
客戶端發來消息:hello, server! I am client!
客戶端發來消息:hello, server! I am client!
客戶端:
服務端返回信息:hello, I am Server! you are 1 Client!
服務端返回信息:hello, I am Server! you are 2 Client!
服務端返回信息:hello, I am Server! you are 3 Client!
Note:一定要等程序都運行完后,才可以關閉相關資源例如:os,is,pw等。
上面的例子通信一次就關閉了,下面再給出幾個TCP/IP通信例子,兩邊可以交互通信:
例子1:兩邊通信,按順序一發一收。客戶端和服務器都有收發功能,並且收發有相應的順序,都對應相應的阻塞:緩沖區br.readLine(), 鍵盤輸入sc.nextLine()。
1 package lesson1220Socket; 2 3 import java.io.BufferedReader; 4 import java.io.IOException; 5 import java.io.InputStream; 6 import java.io.InputStreamReader; 7 import java.io.OutputStream; 8 import java.io.OutputStreamWriter; 9 import java.io.PrintWriter; 10 import java.net.ServerSocket; 11 import java.net.Socket; 12 import java.util.Scanner; 13 14 public class ChatServer { 15 public static ServerSocket ss; 16 17 public ChatServer() { 18 try { 19 ss = new ServerSocket(8888); 20 } catch (IOException e) { 21 e.printStackTrace(); 22 } 23 } 24 25 public void start(){ 26 Socket socket = null; 27 InputStream is = null; 28 InputStreamReader isr = null; 29 BufferedReader br = null; 30 OutputStream os = null; 31 PrintWriter pw = null; 32 33 try { 34 System.out.println("服務端啟動。。。。。。"); 35 socket = ss.accept(); 36 37 is = socket.getInputStream(); 38 isr = new InputStreamReader(is); 39 br = new BufferedReader(isr); 40 41 os = socket.getOutputStream(); 42 //pw = new PrintWriter(os); //很奇怪,這個地方要這么寫,要不然數據發不出去? 43 OutputStreamWriter osw = new OutputStreamWriter(os); 44 pw = new PrintWriter(osw, true);//解釋上一行,要輸入參數true,會自動刷新flush() 45 46 Scanner sc = new Scanner(System.in); 47 //也可以通過下面code來獲取系統輸入 48 //BufferedReader br2 = new BufferedReader(new InputStreamReader(System.in)); 49 //br2.readLine(); 50 51 String info; 52 53 while(true){ 54 55 System.out.println("收到客戶端消息: " + br.readLine()); 56 pw.println(info = sc.nextLine()); 57 //pw.println(info = br2.readLine()); 58 if("bye".equals(info)){ 59 System.out.println("byebye!"); 60 break; 61 } 62 } 63 64 } catch (IOException e) { 65 e.printStackTrace(); 66 } 67 } 68 69 public static void main(String[] args) { 70 new ChatServer().start(); 71 } 72 } 73 74 package lesson1220Socket; 75 76 import java.io.BufferedReader; 77 import java.io.IOException; 78 import java.io.InputStream; 79 import java.io.InputStreamReader; 80 import java.io.OutputStream; 81 import java.io.PrintWriter; 82 import java.net.Socket; 83 import java.util.Scanner; 84 85 public class ChatClient { 86 87 Socket socket ; 88 89 public ChatClient() { 90 try { 91 this.socket = new Socket("127.0.0.1", 8888); 92 } catch (IOException e) { 93 e.printStackTrace(); 94 } 95 } 96 97 public void start(){ 98 99 InputStream is = null; 100 InputStreamReader isr = null; 101 BufferedReader br = null; 102 OutputStream os = null; 103 PrintWriter pw = null; 104 try { 105 is = socket.getInputStream(); 106 isr = new InputStreamReader(is); 107 br = new BufferedReader(isr); 108 os = socket.getOutputStream(); 109 pw = new PrintWriter(os,true); //true代表 每寫一行自動刷新 110 111 Scanner sc = new Scanner(System.in); 112 //BufferedReader br2 = new BufferedReader(new InputStreamReader(System.in)); 113 //br2.readLine(); 114 115 String info; 116 while(true){ 117 pw.println(info = sc.nextLine()); 118 //pw.write(info = sc.nextLine()+"\r\n"); 119 //pw.flush(); 120 //詳細講解可以看帖子:https://www.cnblogs.com/wxgblogs/p/5347996.html 121 System.out.println("收到服務端回應消息:" + br.readLine()); 122 123 if("bye".equals(info)){ 124 System.out.println("byebye!"); 125 break; 126 } 127 } 128 129 } catch (IOException e) { 130 e.printStackTrace(); 131 } 132 133 } 134 135 public static void main(String[] args) { 136 new ChatClient().start(); 137 } 138 139 }
必須按順序進行收發,不能同時進行。
要想收發同時進行,收發就不能再同一個線程內,就應該有兩個線程,一個負責發,一個負責收。
例子2:通過將收發單獨分開,用線程實現。兩邊可以任意發送。
1 package lesson1220Socket; 2 3 import java.io.IOException; 4 import java.net.ServerSocket; 5 import java.net.Socket; 6 7 public class ChatServer2 { 8 9 10 public static ServerSocket ss; 11 private Socket socket; 12 13 public ChatServer2() { 14 try { 15 ss = new ServerSocket(8888); 16 socket = ss.accept(); 17 } catch (IOException e) { 18 e.printStackTrace(); 19 } 20 } 21 22 public void start(){ 23 24 Thread st = new Thread(new SendThread(socket)); 25 st.start(); 26 Thread rv = new Thread(new ReceiveThread(socket)); 27 rv.start(); 28 29 } 30 31 public static void main(String[] args) { 32 new ChatServer2().start(); 33 } 34 35 } 36 37 package lesson1220Socket; 38 39 import java.io.IOException; 40 import java.net.Socket; 41 42 43 public class ChatClient2 { 44 Socket socket ; 45 46 public ChatClient2() { 47 try { 48 this.socket = new Socket("127.0.0.1", 8888); 49 } catch (IOException e) { 50 e.printStackTrace(); 51 } 52 } 53 54 public void start(){ 55 56 Thread st = new Thread(new SendThread(socket)); 57 st.start(); 58 Thread rv = new Thread(new ReceiveThread(socket)); 59 rv.start(); 60 61 } 62 63 public static void main(String[] args) { 64 new ChatClient2().start(); 65 } 66 67 } 68 69 package lesson1220Socket; 70 71 import java.io.IOException; 72 import java.io.OutputStream; 73 import java.io.PrintWriter; 74 import java.net.Socket; 75 import java.util.Scanner; 76 77 public class SendThread implements Runnable { 78 79 private Socket socket; 80 public SendThread(Socket socket) { 81 this.socket = socket; 82 } 83 84 @Override 85 public void run() { 86 87 OutputStream os = null; 88 PrintWriter pw = null; 89 90 try { 91 os = socket.getOutputStream(); 92 pw = new PrintWriter(os, true); 93 Scanner sc = new Scanner(System.in); 94 95 while(true){ 96 pw.println(sc.nextLine()); 97 } 98 } catch (IOException e) { 99 e.printStackTrace(); 100 } 101 } 102 } 103 104 package lesson1220Socket; 105 106 import java.io.BufferedReader; 107 import java.io.IOException; 108 import java.io.InputStream; 109 import java.io.InputStreamReader; 110 import java.net.Socket; 111 112 public class ReceiveThread implements Runnable { 113 114 Socket socket; 115 public ReceiveThread(Socket socket) { 116 this.socket = socket; 117 } 118 119 @Override 120 public void run() { 121 122 InputStream is = null; 123 InputStreamReader isr = null; 124 BufferedReader br = null; 125 126 try { 127 is = socket.getInputStream(); 128 isr = new InputStreamReader(is); 129 br = new BufferedReader(isr); 130 131 while(true){ 132 System.out.println("收到消息:" + br.readLine()); 133 } 134 135 } catch (IOException e) { 136 e.printStackTrace(); 137 } 138 } 139 }
服務端和客戶端共用收發線程,只要兩邊Socket連接建立完成,所有的工作都是在收發線程完成。
下面考慮多發通信問題。
例子3:一對多通信。 多個客戶端同時給服務端發消息,然后服務端回消息,這個回的是群發消息。
1 package lesson1220Socket; 2 3 import java.io.IOException; 4 import java.io.OutputStream; 5 import java.io.PrintWriter; 6 import java.net.ServerSocket; 7 import java.net.Socket; 8 import java.util.ArrayList; 9 import java.util.Scanner; 10 11 public class ChatServer3 { 12 13 public static ServerSocket ss; 14 Socket cs; 15 ArrayList<Socket> list = new ArrayList<Socket>(); 16 private boolean isGroup = true; 17 private Scanner input = new Scanner(System.in); 18 19 public ChatServer3() { 20 21 try { 22 ss = new ServerSocket(7777); 23 } catch (IOException e) { 24 e.printStackTrace(); 25 } 26 } 27 28 public void start(){ 29 System.out.println("服務端啟動。。。"); 30 while(true){ 31 try { 32 cs = ss.accept(); 33 list.add(cs); 34 System.out.println("新的連接" + cs.getPort()); 35 36 Thread receiveThread = new Thread(new ReceiveThread(cs)); 37 receiveThread.start(); 38 39 if(isGroup){ 40 isGroup = false; 41 new SendFunc().start(); 42 } 43 44 } catch (IOException e) { 45 e.printStackTrace(); 46 } 47 } 48 } 49 50 public class SendFunc extends Thread { 51 @Override 52 public void run() { 53 OutputStream os = null; 54 PrintWriter pw = null; 55 while(true){ 56 try { 57 String msg =input.nextLine(); 58 for(Socket sc:list){ 59 os = sc.getOutputStream(); 60 pw = new PrintWriter(os, true); 61 pw.println(msg); 62 } 63 } catch (IOException e) { 64 e.printStackTrace(); 65 } 66 } 67 } 68 } 69 70 public static void main(String[] args) { 71 new ChatServer3().start(); 72 } 73 74 } 75 76 package lesson1220Socket; 77 78 import java.io.IOException; 79 import java.net.Socket; 80 81 public class ChatClient3 { 82 Socket socket; 83 84 public ChatClient3() { 85 try { 86 System.out.println("客戶端啟動。。。。。"); 87 socket = new Socket("127.0.0.1", 7777); 88 } catch (IOException e) { 89 e.printStackTrace(); 90 } 91 } 92 93 public void start(){ 94 Thread sendThread = new Thread(new SendThread(socket)); 95 sendThread.start(); 96 97 Thread receiveThread = new Thread(new ReceiveThread(socket)); 98 receiveThread.start(); 99 } 100 101 public static void main(String[] args) { 102 new ChatClient3().start(); 103 } 104 105 } 106 107 package lesson1220Socket; 108 109 import java.io.IOException; 110 import java.io.OutputStream; 111 import java.io.PrintWriter; 112 import java.net.Socket; 113 import java.util.Scanner; 114 115 public class SendThread implements Runnable { 116 117 private Socket socket; 118 public SendThread(Socket socket) { 119 this.socket = socket; 120 } 121 122 @Override 123 public void run() { 124 125 OutputStream os = null; 126 PrintWriter pw = null; 127 128 try { 129 os = socket.getOutputStream(); 130 pw = new PrintWriter(os, true); 131 Scanner sc = new Scanner(System.in); 132 133 while(true){ 134 pw.println(sc.nextLine()); 135 } 136 } catch (IOException e) { 137 e.printStackTrace(); 138 } 139 } 140 } 141 142 package lesson1220Socket; 143 144 import java.io.BufferedReader; 145 import java.io.IOException; 146 import java.io.InputStream; 147 import java.io.InputStreamReader; 148 import java.net.Socket; 149 150 public class ReceiveThread implements Runnable { 151 152 Socket socket; 153 public ReceiveThread(Socket socket) { 154 this.socket = socket; 155 } 156 157 @Override 158 public void run() { 159 160 InputStream is = null; 161 InputStreamReader isr = null; 162 BufferedReader br = null; 163 164 try { 165 is = socket.getInputStream(); 166 isr = new InputStreamReader(is); 167 br = new BufferedReader(isr); 168 169 while(true){ 170 System.out.println("收到消息:" + br.readLine()); 171 } 172 173 } catch (IOException e) { 174 e.printStackTrace(); 175 } 176 } 177 }
上面code可以實現多個客戶端向服務端發送消息,服務端可以群發消息。客戶端沒有做任何改變。
只需修改服務端,就是如何讓消息群發出去,由一個線程來控制,循環遍歷所有socket,然后將消息發出去。
網上很多帖子都是服務端自動回一個固定消息,實際上沒有實現一對多通信。
但是上面的功能不能實現服務端向指定客戶端回消息。若是收到一個客戶端,服務端就啟動一個收發線程,收的線程沒有問題,可是發的線程有問題。 沒法進行維護?也不知道該數據發向哪個Socket????
下面這兩個也是很好的通過線程來調用Socket的例子:
https://www.cnblogs.com/sddychj/p/6102192.html
https://www.cnblogs.com/qqzy168/p/3772215.html
https://blog.csdn.net/sl_40/article/details/53232423------這個博客解決了我群發消息的問題。
https://blog.csdn.net/aiynmimi/article/details/47323165------這個例子也實現多個客戶端向服務端發消息,服務端群發消息。這個里面有界面,通過ActionListener來實現群發!
