socket編程是以IO為理論基礎的,理論學得差不多也很難實現編程,畢竟里面的類和方法平時都不怎么用,難得嘗試編了個程,記錄一下。
1.幾個概念
Channel:管道,連通客戶端和服務端傳輸數據;
Buffer:緩沖區,通過管道傳輸數據必須經過的地方;
Selector:選擇器,單線程可以通過選擇器處理多個管道;本文沒有使用;
2.案例功能
啟動服務端,再啟動客戶端與服務端連接,在客戶端的控制台輸入命令獲取服務端的效果。本樣例只處理了“time”命令,獲取服務端的當前時間,其他命令都返回“非指定命令,無返回值”。詳看代碼注釋。
3.服務端代碼
1 import java.net.InetSocketAddress; 2 import java.nio.ByteBuffer; 3 import java.nio.channels.ServerSocketChannel; 4 import java.nio.channels.SocketChannel; 5 import java.nio.charset.StandardCharsets; 6 import java.util.Date; 7 import java.util.LinkedList; 8 9 public class NIOService { 10 static int PORT = 9011; 11 public static void main(String[] args) throws Exception{ 12 //存儲客戶端連接 13 LinkedList<SocketChannel> clients = new LinkedList<>(); 14 //1.服務端開啟監聽:接受客戶端 15 ServerSocketChannel ss = ServerSocketChannel.open(); 16 ss.bind(new InetSocketAddress(PORT)); 17 //2.只接受客戶端,不阻塞 18 ss.configureBlocking(false); 19 20 while (true) { 21 // 接受客戶端的連接 22 // client在Java層面是一個對象,在內核層面是一個fd 23 SocketChannel client = ss.accept(); 24 if (client == null) { 25 //while循環進來沒有 連到客戶端就不管 26 } else { 27 //和client傳輸數據使用的socket->fd 28 client.configureBlocking(false); 29 //獲取客戶端的端口號 30 int port = client.socket().getPort(); 31 System.out.println("接收到客戶端的連接,client port: " + port); 32 //將客戶端添加到列表里 33 clients.add(client); 34 } 35 //可以在堆里,堆外,相關內容,可以看看JVM直接內存 36 ByteBuffer buffer = ByteBuffer.allocateDirect(4096); 37 38 //遍歷已經鏈接進來的客戶端 的管道channel里有沒有數據 39 for (SocketChannel c : clients) { 40 //每循環一次都是一次系統調用,都是一次用戶內核態的切換 41 int num = c.read(buffer); 42 if (num > 0) { 43 buffer.flip(); 44 byte[] bytes = new byte[buffer.limit()]; 45 buffer.get(bytes); 46 String s = new String(bytes); 47 System.out.println("端口為"+c.socket().getPort() + "的客戶端發來命令:" +s); 48 String res = ""; 49 ByteBuffer byteBuffer = ByteBuffer.allocateDirect(4096); 50 if("time".equals(s)){ 51 //獲取時間 52 long l = System.currentTimeMillis(); 53 res = "time: "+new Date(l).toString(); 54 System.out.println(res); 55 }else{ 56 res = "非指定命令,無返回值"; 57 } 58 byteBuffer.put(res.getBytes(StandardCharsets.UTF_8)); 59 byteBuffer.flip(); 60 c.write(byteBuffer); 61 } 62 buffer.clear(); 63 } 64 } 65 } 66 }
4.客戶端代碼
1 import java.io.*; 2 import java.net.Socket; 3 import java.nio.channels.ServerSocketChannel; 4 import java.nio.channels.SocketChannel; 5 import java.util.Scanner; 6 7 public class Client { 8 public static void main(String[] args) { 9 try { 10 //建立一個客戶端連到9010的服務端 11 Socket client = new Socket("127.0.0.1",9011); 12 //設置發送命令的長度 13 client.setSendBufferSize(20); 14 /** 15 * 關閉Nagle算法:該算法是將多個命令打包一起發送給服務端,避免網絡擁擠。 16 * 但是現在網絡寬松,隨便發也沒事。所以關閉。 17 */ 18 client.setTcpNoDelay(false); 19 20 OutputStream out = client.getOutputStream(); 21 InputStream input = client.getInputStream(); 22 InputStream in = System.in; 23 BufferedReader reader = new BufferedReader(new InputStreamReader(in)); 24 Scanner scan = new Scanner(System.in); 25 while(true){ 26 String line = scan.nextLine(); 27 //String line = reader.readLine(); 28 if(line != null ){ 29 //將輸入的命令用字節數組存起來,通過客戶端client的輸出流發送給服務端 30 byte[] bb = line.getBytes(); 31 out.write(bb); 32 } 33 byte b[] = new byte[1024]; 34 /** 35 從輸入流里讀 東西 到 b數組,如果沒有東西,就會一直讀,阻塞。 36 len獲取當前輸入流里的內容長度,英文占1個長度,中文占3個長度 37 */ 38 int len = input.read(b); 39 System.out.println(new String(b)); 40 } 41 } catch (IOException e) { 42 e.printStackTrace(); 43 } 44 } 45 46 }