Java 網絡編程相關知識
- 2020 年 4 月 11 日
- 筆記
網絡的一些基礎知識
IP地址分類
IP地址根據網絡ID的不同分為5種類型,A類地址、B類地址、C類地址、D類地址和E類地址。A類保留給政府機構,B類分配給中等規模的公司,C類分配給任何需要的人,D類用於組播,E類用於實驗,各類可容納的地址數目不同。(IP地址由網絡號和主機號組成)。
-
A類地址
一個A類IP地址由1位元組的網絡地址和3位元組主機地址組成,網絡地址的最高位必須是「0」, 地址範圍從1.0.0.0 到126.0.0.0。可用的A類網絡有126個,每個網絡能容納1億多個主機。A類地址一般分配給大型網絡。
- A類地址第1位元組為網絡地址,其它3個位元組為主機地址,默認的子網掩碼255.0.0.0。
- A類地址範圍:0.0.0.0—126.255.255.255(0.0.0.0和126.255.255.255這種IP一般不使用)。
- A類地址中的私有地址和保留地址。
- 10.X.X.X是私有地址(所謂的私有地址就是在互聯網上不使用,而被用在局域網絡中的地址)。
- 127.X.X.X是保留地址,用做循環測試用的。
-
B類地址
一個B類IP地址由2個位元組的網絡地址和2個位元組的主機地址組成,網絡地址的最高位必須是「10」,地址範圍從128.0.0.0到191.255.255.255。可用的B類網絡有16382個,每個網絡能容納6萬多個主機,一般分配給中型網絡 。
- B類地址第1位元組和第2位元組為網絡地址,其它2個位元組為主機地址,默認子網掩碼是255.255.0.0。
- B類地址範圍:128.0.0.0—191.255.255.255(128.0.0.0和191.255.255.255這種地址一般不用)。
- B類地址的私有地址和保留地址。
- 172.16.0.0—172.31.255.255是私有地址。
- 169.254.X.X是保留地址。如果你的IP地址是自動獲取IP地址,而你在網絡上又沒有找到可用的DHCP服務器。就會得到其中一個IP。
-
C類地址
一個C類IP地址由3位元組的網絡地址和1位元組的主機地址組成,網絡地址的最高位必須是「110」。範圍從192.0.0.0到223.255.255.255。C類網絡可達209萬餘個,每個網絡能容納254個主機。
- C類地址前3個位元組為網絡地址,第4個個位元組為主機地址。另外第1個位元組的前三位固定為110,默認子網掩碼是255.255.255.0。
- C類地址範圍:192.0.0.0—223.255.255.255(192.0.0.0和223.255.255.255一般不用)。
- C類地址中的私有地址。
- 192.168.X.X是私有地址。
-
D類地址
D類IP地址第一個位元組以「1110」開始,它是一個專門保留的地址。它並不指向特定的網絡,目前這一類地址被用在多點廣播(Multicast)中。多點廣播地址用來一次尋址一組計算機,它標識共享同一協議的一組計算機。
- D類地址不分網絡地址和主機地址,它的第1個位元組的前四位固定為1110。
- D類地址範圍:224.0.0.1—239.255.255.254。
-
E類地址
以「11110」開始,為將來使用保留。全零(「0.0.0.0」)地址對應於當前主機。全「1」的IP地址(「255.255.255.255」)是當前子網的廣播地址。
- E類地址也不分網絡地址和主機地址,它的第1個位元組的前五位固定為11110。
- E類地址範圍:240.0.0.1—255.255.255.254
端口分類
- 公認端口(well know port):從0到1023,他們緊密綁定一些特定服務。比如80端口綁定http服務,443端口綁定https服務,22端口綁定ssh服務等;
- 主粗端口(Registed port):1024到49151,他們鬆散的綁定一些服務,我們自己寫的應用程序應該使用這個範圍內的端口;
- 動態或私有端口:49152到65535,這些端口應用程序會動態使用。
Java的基本網絡支持
Java中對網絡支持的類大都在java.net這個包下面,常用的類有URL、URLConnection、URLDecoder、URLENcoder和InetAddress等。
InetAddress的使用
InetAddress代表IP地址,它有兩個子類Inet4Address和Inet6Address。
public class InetAddressDemo { public static void main(String[] args) throws Exception { //獲取本機的IP地址 InetAddress localHost = InetAddress.getLocalHost(); System.out.println("host address:"+localHost.getHostAddress()); System.out.println("host name:"+localHost.getHostName()); //根據百度的域名,隨機獲取百度的一個IP地址 InetAddress baidu = InetAddress.getByName("www.baidu.com"); System.out.println("host address:"+baidu.getHostAddress()); System.out.println("host name:"+baidu.getHostName()); //根據IP地址,貨期InetAddress InetAddress loopAddress = InetAddress.getByAddress(new byte[]{127,0,0,1}); System.out.println("host address:"+loopAddress.getHostAddress()); System.out.println("host name:"+loopAddress.getHostName()); //根據域名,獲取域名對應的所有IP地址 InetAddress[] baidus = InetAddress.getAllByName("www.baidu.com"); for (InetAddress inetAddress : baidus) { System.out.println("host address:"+inetAddress.getHostAddress()); System.out.println("host name:"+inetAddress.getHostName()); } } }
URLEncoder和URLDecoder的使用
當URL地址中包含非西歐字符的字符串時,系統會將這些非西歐字符自動編碼。在我們編程過程中就會涉及到將這些普通的字符串和特殊字符串之間的轉換,這時就需要使用URLEncoder和URLDecoder。
String encodedUrl = "https://www.baidu.com/s?wd=ip%E5%9C%B0%E5%9D%80%E5%88%86%E7%B1%BB&rsv_spt=1&rsv_iqid=0xe6273c5300071242&issp=1&f=8&rsv_bp=1&rsv_idx=2&ie=utf-8&rqlang=cn&tn=baiduhome_pg&rsv_enter=1&oq=IP%25E5%259C%25B0%25E5%259D%2580%25E5%2588%2586%25E7%25B1%25BB&rsv_t=8798Pr4JiDoBuHX8kSW6i384TlOk5p8vEQ4c4tWrc0suF31CjvBh6stq0gyq0PtETa9x&inputT=9569&rsv_sug3=24&rsv_sug1=14&rsv_sug7=100&rsv_pq=b3c08c4200035639&bs=IP%E5%9C%B0%E5%9D%80%E5%88%86%E7%B1%BB"; String decodeURL = URLDecoder.decode(encodedUrl, Charset.forName("UTF8").name()); System.out.println("decodeURL:"+decodeURL);
URLEncoder和URLDecoder這兩個類只提供了encode和decode方法供我們使用。
URL、URLConnection和URLPermission的使用
這邊先講下URL和URI的區別:
URI:是uniform resource identifier,統一資源標識符,用來唯一的標識一個資源。Web上可用的每種資源如HTML文檔、圖像、視頻片段、程序等都是一個來URI來定位的URI一般由三部組成:①訪問資源的命名機制②存放資源的主機名③資源自身的名稱,由路徑表示,着重強調於資源。
URL是uniform resource locator,統一資源定位器,它是一種具體的URI,即URL可以用來標識一個資源,而且還指明了如何locate這個資源。URL一般由三部組成:①協議(或稱為服務方式)②存有該資源的主機IP地址(有時也包括端口號)③主機資源的具體地址。
簡單的說,URI是一種互聯網上資源的唯一標識符(我們可以將它看成一個資源的id),URL是一種特殊的URI,URL不僅能標識一個互聯網資源,而且通過URL能夠獲取到這個資源。Java中的兩個類URI和URL就分別對應這兩個概念。URL類可以打開一個流來獲取具體的資源。
String[] urls = {"https://img0.pconline.com.cn/pconline/1707/21/9625301_20150814_6d20f056ee9803d9419buyemaASeB0KJ_thumb.jpg"}; for (String url : urls) { URL url1 = new URL(url); int port = url1.getPort(); System.out.println("port:"+port); String host = url1.getHost(); System.out.println("host:"+host); String protocol = url1.getProtocol(); System.out.println("protocol:"+protocol); String file = url1.getFile(); System.out.println("fileName:"+file); //這段是否要設置權限,為什麼open總是失敗? InputStream inputStream = url1.openStream(); FileOutputStream fos = new FileOutputStream("D:\"+new Date()+".jpeg"); FileCopyUtils.copy(inputStream,fos); inputStream.close(); fos.close(); }
基於TCP協議的網絡編程
使用ServerSocket建立TCP服務端
Java中能夠接收其他通信實體請求的類是ServerSocket。這個對象可以監聽來自客戶端的Socket連接(每個TCP連接兩個Socket,一個IP加一個端口組成一個Socket)。如果沒有連接,它將一直處於等待狀態。
- Socket accept():該方法返回客戶端Socket,沒有連接將一直處於等待狀態(同步),線程也被阻塞(阻塞);
ServerSocket存在如下的構造函數:
- public ServerSocket(int port):指定端口,backlog默認50;
- public ServerSocket(int port, int backlog) :backlog用於指定連接隊列的長度,這個值和操作系統也有關,嘗試了下Windows下最多設置200個,如果我們設置的值超過200,就取200。
- public ServerSocket(int port, int backlog, InetAddress bindAddr):如果機器有多個網卡還可以指定具體監聽哪個網卡。
(netstat 命令詳解)
使用Socket進行通信
下面是一個很加單的clientSocket和serverSocket的列子:客戶端每隔一秒鐘給服務端發一個消息,服務端給出響應:
Socket clientSocket = new Socket("127.0.0.1",30000); //inputStream用來接受服務端返回的消息 InputStream inputStream = clientSocket.getInputStream(); //outputStream用來給服務端發消息 OutputStream outputStream = clientSocket.getOutputStream(); while (true){ outputStream.write(("hi, l am clinetSocket").getBytes()); byte[] bytes = new byte[1024]; inputStream.read(bytes); System.out.println("get message from server:"+new String(bytes)); Thread.sleep(1000); }
服務端程序
public class ServerSocketDemo { private static ExecutorService executorService = Executors.newFixedThreadPool(10); public static void main(String[] args) throws Exception { ServerSocket serverSocket = new ServerSocket(30000, 5); while (true) { Socket socket = serverSocket.accept(); System.out.println("get socket:" + socket); executorService.execute(new Printer(socket)); } } private static class Printer implements Runnable { private Socket socket; public Printer(Socket socket) { this.socket = socket; } @Override public void run() { OutputStream outputStream; InputStream inputStream; try { //對於服務端來說,inputStream用來接收客戶端的報文 inputStream = socket.getInputStream(); //對於服務端來說,outputStream用來給客戶端響應報文 outputStream = socket.getOutputStream(); while (true) { byte[] bytes = new byte[1024]; inputStream.read(bytes); System.out.println("wa.. l got you " + new String(bytes)); outputStream.write("l am serverSocket".getBytes()); } } catch (IOException e) { e.printStackTrace(); } } } }
半關閉的Socket
半關閉的Socket是指只關閉Socket的輸入輸出流,但是不關閉整個Socket連接。
使用NIO實現非阻塞的Socket通信
Java NIO下幾個用於Socket通信的類的簡單說明:
- Selector:它是SelectableChannel對象的多路復用器器,所有希望採用非阻塞方式進行通信的Channel都應該註冊到Selector對象上。可以調用selector = Selector.open()來構造Selector對象。
- SelectionKey:一個Selector實例有三種SelectionKey集合。第一種是通過selector的keys()方法返回的所有SelectionKey集合,代表所有註冊在這個Selector實例上的Channel;第二種是通過selectedKeys()方法返回的SelectionKey集合,代表需要進行IO處理的channel;第三種是已經取消註冊的Channel,一般不用。
public class NioServerSocketDemo { private Selector selector; public static final int port = 30000; private Charset charset = Charset.forName("UTF-8"); public void init() throws Exception{ selector = Selector.open(); ServerSocketChannel server = ServerSocketChannel.open(); InetSocketAddress address = new InetSocketAddress("127.0.0.1",port); server.bind(address,5); server.configureBlocking(false); //serverSocketChannel也要註冊到selector上面 server.register(selector, SelectionKey.OP_ACCEPT); //selector.select()會阻塞當前線程 //selector.select(long timeout),設置超時時間 //selector.selectNow()不會阻塞線程 while (selector.select()>0){ for(SelectionKey key : selector.selectedKeys()){ //已經處理過了,將其刪除 selector.selectedKeys().remove(key); if(key.isConnectable()){ SocketChannel channel = (SocketChannel)key.channel(); System.out.println(channel+" has connected..."); } if(key.isAcceptable()){ SocketChannel acceptChannel = server.accept(); acceptChannel.configureBlocking(false); acceptChannel.register(selector,SelectionKey.OP_READ); key.interestOps(SelectionKey.OP_ACCEPT); } if(key.isReadable()){ SocketChannel channel = (SocketChannel)key.channel(); ByteBuffer buffer = ByteBuffer.allocate(1024); String content = ""; try{ while (channel.read(buffer)>0){ buffer.flip(); content+=charset.decode(buffer); } System.out.println("get content:"+content); channel.write(buffer); key.interestOps(SelectionKey.OP_READ); }catch (IOException ex){ key.cancel(); if(key.channel()!=null){ key.channel().close(); } } } } } } public static void main(String[] args) throws Exception { new NioServerSocketDemo().init(); } }
使用AIO實現非阻塞的Socket通信
public class AIOServerSocket { private static Charset charset = Charset.forName("UTF-8"); public static void main(String[] args) throws Exception { AsynchronousServerSocketChannel serverSocketChannel = AsynchronousServerSocketChannel.open(); serverSocketChannel.bind(new InetSocketAddress(30000),1000); while (true){ Future<AsynchronousSocketChannel> future = serverSocketChannel.accept(); AsynchronousSocketChannel socketChannel = future.get(); ByteBuffer buffer = ByteBuffer.allocate(1024); socketChannel.read(buffer); System.out.println("get from client:"+charset.decode(buffer)); } } }