網絡編程分爲BIO(傳統IO)、NIO、AIO。Socket編程屬於BIO這種傳統IO。java
java.net.InetAddress是JAVA中管理IP地址的類,經常使用編程
public static void main(String[] args) throws UnknownHostException { InetAdressDemo.getLocalHost(); System.out.println("---------------------------"); getHostByName("Lenovo-Autumn"); } /** * 獲取主機ip和主機名 * @throws UnknownHostException */ public static void getLocalHost() throws UnknownHostException { //根據InetAddress獲取主機名和主機ip InetAddress localHost = InetAddress.getLocalHost(); System.out.println(localHost); //打印:Lenovo-Autumn/192.168.56.1 //根據getLocalHost()返回的值獲取ip和主機名 String hostName = localHost.getHostName(); String hostAddress = localHost.getHostAddress(); System.out.println(hostName); //打印 Lenovo-Autumn System.out.println(hostAddress); //打印 192.168.56.1 //根據切割獲取主機名和ip String[] str = localHost.toString().split("/"); System.out.println(str[0]); //打印 Lenovo-Autumn System.out.println(str[1]); //打印 192.168.56.1 } /** * 根據主機名稱獲取ip地址 * @param otherName 主機名(能夠是局域網中的機器名或者是域名或者是ip) * @throws UnknownHostException */ public static void getHostByName(String otherName) throws UnknownHostException { InetAddress otherHost = InetAddress.getByName(otherName); String hostName = otherHost.getHostName(); String hostAddress = otherHost.getHostAddress(); System.out.println(hostName); //打印 Lenovo-Autumn System.out.println(hostAddress); //打印 192.168.56.1 System.out.println(otherHost); //打印:Lenovo-Autumn/192.168.56.1 }
code數組
發送數據時必須指定接收端的IP地址和端口號,就好像發送貨物的集裝箱上面必須標明接收人的地址同樣。服務器
接收端不須要明確知道數據的來源,只須要接收到數據便可。網絡
構造函數:併發
第一種是用來接受的數據包,不須要指定IP和端口,第二種是用來發送的數據包須要指定ip和端口異步
方法:socket
獲取ip地址,獲取服務器ip或者客戶端ipide
返回端口號,獲取發送方或者函數
返回數據緩衝區
返回要發送或接受的數據包大小
DatagramPacket數據包的做用就如同是「集裝箱」,能夠將發送端或者接收端的數據封裝起來。然而運輸貨物只有「集裝箱」是不夠的,還須要有碼頭。在程序中須要實現通訊只有DatagramPacket數據包也一樣不行,爲此JDK中提供的一個DatagramSocket類。DatagramSocket類的做用就相似於碼頭,使用這個類的實例對象就能夠發送和接收DatagramPacket數據包,發送數據的過程以下圖所示。
構造函數:
用來建立發送端的DatagramSocket對象
用來建立接收端的DatagramSocket對象
方法:
發送數據報包
接收數據報包
1, 建立DatagramSocket對象
DatagramSocket()
2,建立DatagramPacket對象,封裝數據,並指定ip和端口。
DatagramPacket(byte[] buf, int length, InetAddress address, int port)
3,發送數據
socket.send(DatagramPacket dp)
4,釋放流資源
ds.close();
1,建立DatagramSocket對象,只須要指定端口
DatagramSocket(port)
2,建立DatagramPacket對象
DatagramPacket(byte[] data, int length)
3,接收數據存儲到DatagramPacket對象中
receive(DatagramPackage dp)
4,獲取DatagramPacket對象的內容
new String(data,0,dp.getLength());
5,釋放流資源
ds.close();
/** * 實現udp發送端 * 用java.net.DatagramPackage封裝數據 * 用java.net.DatagramSocket發送數據 * * 實現步驟 * 1.用DatagramPackage對象,封裝數據,接受的地址和端口 * 2.建立DatagramSocket * 3.調用DatagramSocket對象send方法,發送數據 * 4.關閉資源 * * DatagramPackage構造函數 * DatagramPacket(byte[] buf, int length, InetAddress address, int port) * DatagramSocket構造函數 * DatagramSocket() * 方法:send(DatagramPacket d) * Created by Autumn on 2018/2/5. */public class UdpSend { public static void main(String[] args) throws Exception { Scanner scanner = new Scanner(System.in); //獲取地址 InetAddress inet = InetAddress.getByName("127.0.0.1"); //建立DatagramSocket,負責接受和發送數據 DatagramSocket ds = new DatagramSocket(); while(true){ String msg = scanner.nextLine(); //建立數據包對象對象 byte[] data = msg.getBytes(); //封裝數據,接受的地址和端口 DatagramPacket dp = new DatagramPacket(data,data.length,inet,6000); //發送數據包 ds.send(dp); if(msg.equals("exit")){ break; } } //關閉 ds.close(); } }/** * 實現udp接收端 * 用java.net.DatagramPackage 接受數據 * 用java.net.DatagramSocket 接受數據包 * * 步驟 * 1.建立DatagramSocket對象,綁定端口號(要和發送端端口一致) * 2.建立字節數組用來接受數據 * 3.建立數據對象包DatagramPackage * 4.建立DatagramSocket * receive(DatagramPackage dp)接受數據,將數據封裝如dp中 * 5.拆包 * 發送端的ip地址(DatagramPackage.get) * 接受到的字節數組 * 發送的端口號 * 6.關閉資源 * Created by Autumn on 2018/2/5. */public class UdpReceive { public static void main(String[] args) throws IOException { //建立數據包傳輸的對象,並綁定端口號 DatagramSocket ds = new DatagramSocket(6000); //建立字節數組 byte[] data = new byte[1024]; while(true){ //建立數據包對象,傳遞字節數組 DatagramPacket dp = new DatagramPacket(data,data.length); //調用ds對象的receive接受數據包,receive()有線程阻塞效果會一直等待接受數據 ds.receive(dp); //獲取數據包大小 int len = dp.getLength(); //獲取發送端的ip地址 InetAddress sendAddress = dp.getAddress(); String sendHostAddress = sendAddress.getHostAddress(); //System.out.println(sendHostAddress); //獲取發送端端口號 int port = dp.getPort(); //System.out.println(port); //System.out.println(new String(data)); //直接打印1024個字節的字符串,有不少空格 System.out.println(sendHostAddress+":"+port+" "+new String(data,0,len)); //這樣打印沒有多餘的空格 if(new String(data,0,len).equals("exit")){ break; } } //關閉 ds.close(); } }
code
ServerSocket類,用於表示服務器端,Socket類,用於表示客戶端。創建鏈接後用流進行輸入和輸出
實例化一個ServerSocket類,指定端口號
監聽並接受此套接字的鏈接
返回此服務器套接字的本地地址
實例化一個Socket並指定ip和端口
返回一個輸出流,用於客戶端發送數據
返回一個輸入流,用於服務器端接受數據
返回ip地址(服務器端的地址)
返回端口號
1,建立客戶端的Socket對象,指定服務器IP和端口號
Socket(String host, int port)
2,獲取Socket的輸出流對象
getOutputStream();
3,寫數據給服務器
out.write("服務器數據".getBytes());
4,關閉流資源
socket.close();
1,建立服務器端ServerSocket對象,指定服務器端端口號
ServerSocket(int port)
2,開啓服務器,等待着客戶端Socket對象的鏈接,若有客戶端鏈接,返回客戶端的Socket對象
accept()
3,經過客戶端的Socket對象,獲取客戶端的輸入流,爲了實現獲取客戶端發來的數據
socket.getInputStream();
4,經過客戶端的輸入流,獲取流中的數據
byte[] data = new byte[1024];
int len = inputStream.read(data);
System.out.println(new String(data,0,len));
5,經過客戶端的Socket對象,獲取客戶端的輸出流,爲了實現給客戶端反饋信息
6,經過客戶端的輸出流,寫數據到流中
7,關閉流資源
socket.close();
serverSocket.close();
/** * 實現TCP服務器程序 * 表示服務器程序的類java.net.ServerSocket * 構造方法: * ServerSocket(int port); 傳遞端口號 * * Important:必須得到客戶端的套接字(Socket) * 方法:Socket accept() * 服務器能夠獲取到客戶端的套接字 * Created by Autumn on 2018/2/5. */public class TCPServer { public static void main(String[] args) throws IOException { ServerSocket serverSocket = new ServerSocket(8888); //調用服務器套接字對象accept()獲取客戶端套接字,具備線程等待效果 Socket socket = serverSocket.accept(); //這裏會阻塞等待鏈接接入 cmd中telnet 127.0.0.1 8888便可鏈接 //根據得到的客戶端的socket獲取輸入流 InputStream inputStream = socket.getInputStream(); //根據輸入流將數據讀入到data中 byte[] data = new byte[1024]; int len = inputStream.read(data); //這裏會阻塞,等待數據 cmd中ctrl+]進入到telnet操做模式send value發送數據 System.out.println(new String(data,0,len)); socket.close(); serverSocket.close(); } }/** * 實現TCP客戶端,鏈接到服務器 * 和服務器實現數據交換 * 實現TCP客戶端程序的類 java.net.Socket * * 構造方法: * Socket(String host, int port)傳遞服務器IP和端口號 * 注意:構造方法只要運行,就會和服務器進行鏈接,鏈接時報,拋出異常 * * OutputStream getOutputStream() 返回套接字的輸出流 * 做用:將數據輸出,輸出到服務器 * InputStream getInputStream() 返回套接字的輸入流 * 做用:從服務器端讀取數據 * * 客戶端服務器數據交換,必須使用套接字對象Socket中的獲取的IO劉,本身new的流不行 * * Created by Autumn on 2018/2/5. */public class TCPClient { public static void main(String[] args) throws IOException { //建立Socket對象,鏈接服務器 Socket socket = new Socket("127.0.0.1",8888); //經過客戶端的套接字對象Socket方法,獲取字節輸出流,將數據寫向服務器 OutputStream out = socket.getOutputStream(); out.write("這是一條來客戶端的數據".getBytes()); socket.close(); } }
import java.io.IOException;import java.io.InputStream;import java.net.ServerSocket;import java.net.Socket;public class TCPServer { /*同步阻塞*/ public static void main(String[] args) throws IOException { ServerSocket serverSocket = new ServerSocket(8888); System.out.println("服務端啓動成功..."); while(true){ /*一次只能處理一個鏈接,在一個鏈接沒關閉前沒法接收第二個鏈接,在這裏開一個框發送數無問題,開放兩個框時會出現第二個無反應*/ Socket socket = serverSocket.accept(); //這裏會阻塞等待鏈接接入 cmd中telnet 127.0.0.1 8888即表明鏈接 System.out.println("新客戶端鏈接成功...."); InputStream inputStream = socket.getInputStream(); while(true) { byte[] data = new byte[1024]; System.out.println("正在等待數據..."); int len = inputStream.read(data); //這裏會阻塞,等待數據,若是直接關閉cmd窗口會由於關閉socket通道致使len返回-1 cmd中ctrl+]進入到telnet操做模式send value發送數據 if (len != -1){ System.out.println(new String(data, 0,len, "GBK")); //用GBK是由於CMD窗口命令發送的數據是GBK編碼 }else{ break; } } } //socket.close(); //serverSocket.close(); //System.out.println("服務器端關閉...."); } }
鏈接服務端
向服務端發送數據
再開一個cmd而後telnet發送數據,發現無反應。必須關閉第一個cmd才能鏈接成功。
用ExecutorService線程池實現每個鏈接建立一個新的線程
import java.io.IOException;import java.io.InputStream;import java.io.UnsupportedEncodingException;import java.net.ServerSocket;import java.net.Socket;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;public class TCPServer { /*異步阻塞*/ public static void main(String[] args) throws IOException { ExecutorService threadPool = Executors.newCachedThreadPool(); //線程池 ServerSocket serverSocket = new ServerSocket(8888); System.out.println("服務端啓動成功..."); while(true){ /*一次只能處理一個鏈接,在一個鏈接沒關閉前沒法接收第二個鏈接,在這裏開一個框發送數無問題,開放兩個框時會出現第二個無反應*/ System.out.println("等待客戶端鏈接..."); final Socket socket = serverSocket.accept(); //這裏會阻塞等待鏈接接入 cmd中telnet 127.0.0.1 8888即表明鏈接 threadPool.execute(new Runnable() { //啓動一個線程 public void run() { try { System.out.println("新客戶端鏈接成功...."); InputStream inputStream = socket.getInputStream(); while(true) { byte[] data = new byte[1024]; System.out.println("正在等待數據..."); int len = inputStream.read(data); //這裏會阻塞,等待數據,若是直接關閉cmd窗口會由於關閉socket通道致使len返回-1 cmd中ctrl+]進入到telnet操做模式send value發送數據 if (len != -1){ System.out.println(Thread.currentThread()+new String(data, 0,len, "GBK")); //用GBK是由於CMD窗口命令發送的數據是GBK編碼 }else{ System.out.println("break循環"); break; } } System.out.println("一次Socket鏈接關閉"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }); } //socket.close(); //serverSocket.close(); //System.out.println("服務器端關閉...."); } }
優勢:傳輸質量好(因此BIO適合傳輸鏈接少可是數據量大的請求)
缺點:每個鏈接都佔用一個線程,很佔用系統資源。
tip:全部的調優都關聯到系統資源(IO、存儲、內存、CPU)
code