網絡編程html 網絡的優點 java
•所謂計算機網絡,就是把分佈在不一樣地理區域的計算機與專門的外部設備用通訊線路互連成一個規模大、功能強的網絡系統,從而使衆多的計算機能夠方便地互相傳遞信息、共享硬件、軟件、數據信息等資源。ios •計算機網絡是現代通訊技術與計算機技術相結合的產物,計算機網絡能夠提供如下一些主要功能.web –資源共享。數據庫 –信息傳輸與集中處理。編程 –均衡負荷與分佈處理。數組 –綜合信息服務。瀏覽器
按規模的三種分類安全
•局域網(LAN):指在一個較小地理範圍內的各類計算機網絡設備互連在一塊兒的通訊網絡,能夠包含一個或多個子網,一般侷限在幾公里的範圍以內。服務器 •城域網(MAN):主要是由城域範圍內的各局域網之間互連而構成的,如今不多提起這個概念。 •廣域網(WAN):是由相距較遠的局域網或城域網互連而成,一般是除了計算機設備之外,還要涉及一些電信通信方式。
TCP/IP分層和OSI分層
TCP/IP協議集
IP地址
•IP地址用於標識網絡中的一個通訊實體,這個通訊實體能夠是一臺主機,也能夠是一臺打印機,或者 是路由器的某一個端口。而在基於IP協議網絡中傳輸的數據包,都必須使用IP地址來進行標識。
•IP地址是數字型的,IP地址是一個32位(32bit)整數,但一般爲了更加便於記憶,一般也把它分 成4個8位的二進制數組成,每8位之間用圓點隔開,每一個8位整數能夠轉換成一個0~255的十進制整 數,所以咱們看到的IP地址經常是以下形式:202.9.128.88。
IP與DNS
•IP 地址 –鏈接至網絡的每臺計算機都是惟一的 –32 位數字,四個用點號分隔的數字 –包括網絡 ID 和主機 ID –網絡的類包括 A、B、C和 D 類E –0~126 –128~191 –192~223 –127.0.0.1 –192.168.10.179 –10.11.0.1 •域名系統 –將特定 IP 地址映射至字符串 –映射由域名服務器系統維護
端口
•端口是一個16位的整數,用於表示數據交給哪一個通訊程序處理。所以,端口是應用程序與外界交流的 出入口,它是一種抽象的軟件結構,包括一些數據結構和I/O(基本輸入/輸出)緩衝區。
•不一樣的應用程序處理不一樣端口上的數據,同一臺機器上不能有兩個程序使用同一個端口,端口號能夠 從0到65535,一般將它分爲三類: –公認端口(Well Known Ports):從0到1023,它們緊密綁定(Binding)一些服務。 –註冊端口(Registered Ports):從1024到49151。它們鬆散地綁定一些服務。 –動態和/或私有端口(Dynamic and/or Private Ports):從49152到65535,這些端口是應用程序使用的動態端口,應用程序通常不會主動使用這些端口。
•用於實現程序間的通訊
•經常使用的端口
InetAddress
•Java提供了InetAddress類來表明IP地址,InetAddress下還有2個子類:Inet4Address、 Inet6Address,它們分別表明Internet Protocol version 4(IPv4)地址和Internet Protocol version 6(IPv6)地址。
•InetAddress類沒有提供構造器,而是提供了以下兩個靜態方法來獲取InetAddress實例: –getByName(String host):根據主機獲取對應的InetAddress對象。 –getByAddress(byte[] addr):根據原始IP地址來獲取對應的InetAddress對象。
•InetAddress還提供了以下三個方法來獲取InetAddress實例對應的IP地址和主機名: –String getCanonicalHostName():獲取此 IP 地址的全限定域名。 –String getHostAddress():返回該InetAddress實例對應的IP地址字符串(以字符串形式)。 –String getHostName():獲取此 IP 地址的主機名。
URLDecoder和URLEncoder
•URLDecoder類包含一個decode(String s,String enc)靜態方法,它能夠將看上去是亂碼的特殊字符串轉轉成普通字符串。 •URLEncoder類包含一個encode(String s,String enc)靜態方法,它能夠將普通字符串轉換成application/x-www-form-urlencoded MIME字符串。 URL
•URL(Uniform Resource Locator)對象表明統一資源定位器,它是指向互聯網「資源」的指針。 資源能夠是簡單的文件或目錄,也能夠是對更復雜的對象引用,例如對數據庫或搜索引擎的查詢。一般 狀況而言,URL能夠由協議名、主機、端口和資源組成。即知足以下格式: •protocol://host:port/resourceName
URLConnection
•程序能夠經過URLConnection實例向該URL發送請求、讀取URL引用的資源。 •一般建立一個和 URL 的鏈接,併發送請求、讀取此 URL 引用的資源須要以下幾個步驟: –(1)經過調用URL對象openConnection()方法來建立URLConnection對象。 –(2)設置URLConnection的參數和普通請求屬性。 –(3)若是隻是發送GET方式請求,使用connect方法創建和遠程資源之間的實際鏈接便可;若是須要發送POST方式的請求,須要獲取URLConnection實例對應的輸出流來發送請求參數。 –(4)遠程資源變爲可用,程序能夠訪問遠程資源的頭字段、或經過輸入流讀取遠程資源的數據。
IP協議
•IP協議是Internet上使用的一個關鍵協議,它的全稱是Internet Protocol,即Internet協議,通 常簡稱IP協議。經過使用IP協議,從而使Internet成爲一個容許鏈接不一樣類型的計算機和不一樣操做系 統的網絡。 •IP協議只保證計算機能發送和接收分組數據。IP協議負責將消息從一個主機傳送到另外一個主機,消 息在傳送的過程當中被分割成一個個的小包。
Java對TCP/IP協議的支持
•TCP/IP通訊協議是一種可靠的網絡協議,它在通訊的兩端各創建一個Socket,從而在通訊的兩端 之間造成網絡虛擬鏈路。一旦創建了虛擬的網絡鏈路,兩端的程序就能夠經過虛擬鏈路進行通訊。 Java對基於TCP協議的網絡通訊提供了良好的封裝,Java使用Socket對象來表明兩端的通訊接口, 並經過Socket產生IO流來進行網絡通訊。
TCP協議
•TCP協議被稱做一種端對端協議。這是由於它爲兩臺計算機之間的鏈接起了重要做用:當一臺計算機 須要與另外一臺遠程計算機鏈接時,TCP協議會讓它們創建一個鏈接:用於發送和接收數據的虛擬鏈路。
•TCP協議負責收集這些信息包,並將其按適當的次序放好傳送,在接收端收到後再將其正確地還原。 TCP協議保證了數據包在傳送中準確無誤。TCP協議使用重發機制:當一個通訊實體發送一個消息給另 一個通訊實體後,須要收到另外一個通訊實體確認信息,若是沒有收到另外一個通訊實體的確認信息,則會 再次重發剛纔發送的信息。
•經過這種重發機制,TCP協議嚮應用程序提供可靠的通訊鏈接,使它可以自動適應網上的各類變化。 即便在 Internet 暫時出現堵塞的狀況下,TCP也可以保證通訊的可靠。
ServerSocket
•ServerSocket對象用於監聽來自客戶端的Socket鏈接,若是沒有鏈接,它將一直處於等待狀態。ServerSocket包含一個監聽來自客戶端鏈接請求的方法: •Socket accept():若是接收到一個客戶端Socket的鏈接請求,該方法將返回一個與連客戶端Socket對應的Socket(如圖17.4所示每一個TCP鏈接有兩個Socket);不然該方法將一直處於等待狀態,線程也被阻塞。 •爲了建立ServerSocket對象,ServerSocket類提供了以下幾個構造器: –ServerSocket(int port):用指定的端口port來建立一個ServerSocket。該端口應該是有一個有效的端口整數值:0~65535。 –ServerSocket(int port,int backlog):增長一個用來改變鏈接隊列長度的參數backlog。 –ServerSocket(int port,int backlog,InetAddress localAddr):在機器存在多個 IP地址的狀況下,容許經過localAddr這個參數來指定將ServerSocket綁定到指定的IP地址。
Socket
•客戶端一般可以使用Socket的構造器來鏈接到指定服務器,Socket一般可以使用以下兩個構造器: –Socket(InetAddress/String remoteAddress, int port):建立鏈接到指定遠程主機、遠 程端口的Socket,該構造器沒有指定本地地址、本地端口,默認使用本地主機的默認IP地址,默 認使用系統動態指定的IP地址。
–Socket(InetAddress/String remoteAddress, int port, InetAddress localAddr, int localPort)::建立鏈接到指定遠程主機、遠程端口的Socket,並指定本地IP地址和本地端口 號,適用於本地主機有多個IP地址的情形。
網絡通訊
•當客戶端、服務器端產生了對應的Socket以後,此時就到了如圖17.4所示的通訊示意圖,程序無需再區分服務器、客戶端,而是經過各自的Socket進行通訊,Socket提供以下兩個方法來獲取輸入流和輸出流: –InputStream getInputStream():返回該Socket對象對應的輸入流,讓程序經過該輸入流從Socket中取出數據。 –OutputStream getOutputStream():返回該Socket對象對應的輸出流,讓程序經過該輸出流向Socket中輸出數據。
客戶端
•經過Socket創建對象並指定要鏈接的服務端主機以及端口。 Socket s = new Socket(「192.168.1.1」,9999); OutputStream out = s.getOutputStream(); out.write(「hello」.getBytes()); s.close(); 服務端
•創建服務端須要監聽一個端口 ServerSocket ss = new ServerSocket(9999); Socket s = ss.accept (); InputStream in = s.getInputStream(); byte[] buf = new byte[1024]; int num = in.read(buf); String str = new String(buf,0,num); System.out.println(s.getInetAddress().toString()+」:」+str); s.close(); ss.close(); 加入多線程支持
•實際應用中的客戶端則可能須要和服務器端保持長時間通訊,即服務器須要不斷地讀取客戶端數據, 並向客戶端寫入數據;客戶端也須要不斷地讀取服務器數據,並向服務器寫入數據。
•使用傳統BufferedReader的readLine()方法讀取數據時,當該方法成功返回以前,線程被阻塞, 程序沒法繼續執行。考慮到這個緣由,所以服務器應該每一個Socket單獨啓動一條線程,每條線程負責 與一個客戶端進行通訊。
•客戶端讀取服務器數據的線程一樣會被阻塞,因此係統應該單獨啓動一條線程,該線程專門負責讀取 服務器數據。
NIO實現非阻塞通訊
•Java的NIO爲非阻塞式的Socket通訊提供了以下幾個特殊類: –Selector:它是SelectableChannel對象的多路複用器,全部但願採用非阻塞方式進行通訊的 Channel都應該註冊到Selector對象。可經過調用此類的靜態open()方法來建立Selector實 例,該方法將使用系統默認的Selector來返回新的Selector。
–SelectableChannel:它表明能夠支持非阻塞IO操做的Channel對象,能夠將其註冊到 Selector上,這種註冊的關係由SelectionKey實例表示。Selector對象提供了一個select()方 法,該方法容許應用程序同時監控多個IO Channel。
–SelectionKey:該對象表明SelectableChannel和Selector之間的註冊關係。
–ServerSocketChannel:支持非阻塞操做,對應於java.net.ServerSocket這個類,提供了 TCP協議IO接口,只支持OP_ACCEPT操做。該類也提供了accept()方法,功能至關於 ServerSocket提供的accept()方法。
–SocketChannel:支持非阻塞操做,對應於java.net.Socket這個類,提供了TCP協議IO接 口,支持OP_CONNECT,OP_READ和OP_WRITE操做。這個類還實現了ByteChannel接 口、ScatteringByteChannel接口和GatheringByteChannel接口,因此能夠直接經過 SocketChannel來讀寫ByteBuffer對象。
NIO的非阻塞通訊
•服務器上全部Channel(包括ServerSocketChannel和SocketChannel)都須要向Selector注 冊,而該Selector則負責監視這些Socket的IO狀態,當其中任意一個或多個Channel具備可用的IO 操做時,該Selector的select()方法將會返回大於0的整數,該整數值就表示該Selector上有多少個 Channel具備可用的IO操做,並提供了selectedKeys()方法來返回這些Channel對應的 SelectionKey集合。正是經過Selector,使得服務器端只須要不斷地調用Selector實例的select() 方法便可知道當前全部Channel是否有須要處理的IO操做。
UDP協議
•UDP協議是一種不可靠的網絡協議,它在通訊實例的兩端各創建一個Socket,但這兩個Socket之 間並無虛擬鏈路,這兩個Socket只是發送、接收數據報的對象,Java提供了DatagramSocket對 象做爲基於UDP協議的Socket,使用DatagramPacket表明DatagramSocket發送、接收的數據 報。
UDP和TCP的對比
•TCP協議:可靠,傳輸大小無限制,可是須要鏈接創建時間,差錯控制開銷大。 •UDP協議:不可靠,差錯控制開銷較小,傳輸大小限制在64K如下,不須要創建鏈接。 發送數據報
•DatagramSocket自己只是碼頭,不維護狀態,不能產生IO流,它的惟一做用就是接受和發送數據 報,Java使用DatagramPacket來表明數據報,DatagramSocket接收和發送的數據都是經過 DatagramPacket對象完成的。
•DatagramSocket的構造器: –DatagramSocket():建立一個DatagramSocket實例,並將該對象綁定到本機默認IP地址、本機全部可用端口中隨機選擇的某個端口。 –DatagramSocket(int prot):建立一個DatagramSocket實例,並將該對象綁定到本機默認IP地址、指定端口。 –DatagramSocket(int port, InetAddress laddr):建立一個DatagramSocket實例,並將該對象綁定到指定IP地址、指定端口。
DatagramSocket
•DatagramSocket實例,一般在建立服務器時,咱們建立指定端口的DatagramSocket實例—— 這樣保證其餘客戶端能夠將數據發送到該服務器。一旦獲得了DatagramSocket實例以後,就能夠通 過以下兩個方法來接收和發送數據: –receive(DatagramPacket p):從該DatagramSocket中接收數據報。 –send(DatagramPacket p):以該DatagramSocket對象向外發送數據報。
DatagramPacket
•DatagramPacket自身決定數據報的目的。 •DatagramPacket的構造器: –DatagramPacket(byte buf[],int length):以一個空數組來建立DatagramPacket對象,該對象的做用是接收DatagramSocket中的數據。 –DatagramPacket(byte buf[], int length, InetAddress addr, int port):以一個包含數據的數組來建立DatagramPacket對象,建立該DatagramPacket時還指定了IP地址和端口——這就決定了該數據報的目的。 –DatagramPacket(byte[] buf, int offset, int length):以一個空數組來建立DatagramPacket對象,並指定接收到的數據放入buf數組中時從offset開始,最多放length個字節。 –DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port):建立一個用於發送的DatagramPacket對象,也多指定了一個offset參數。
經過DatagramPacket反饋
•獲取DatagramPacket對象後,若是想向該數據報的發送者「反饋」一些信息,但因爲UDP是面向非 鏈接的,因此接受者並不知道每一個數據報由誰發送過來,但程序能夠調用DatagramPacket的以下三 個方法來獲取發送者的IP和端口:
–InetAddress getAddress():返回某臺機器的 IP 地址。當程序準備發送此數據報時,該方 法返回此數據報的目標機器的IP地址;當程序剛剛接收到一個數據報時,該方法返回該數據報的發 送主機的IP地址。
–int getPort():返回某臺機器的端口,當程序準備發送此數據報時,該方法返回此數據報的目標 機器的端口;當程序剛剛接收到一個數據報時,該方法返回該數據報的發送主機的端口。
–SocketAddress getSocketAddress():返回完整SocketAddress,一般由IP地址和端口 組成。當程序準備發送此數據報時,該方法返回此數據報的目標SocketAddress;當程序剛剛接 收到一個數據報時,該方法返回該數據報的源SocketAddress。
發送端
•在發送端,要在數據包對象中明確目的地IP及端口。 DatagramSocket ds = new DatagramSocket(); byte[] by = 「hello,udp」.getBytes(); DatagramPacket dp = new DatagramPacket(by,0,by.length, InetAddress.getByName(「127.0.0.1」),10000); ds.send(dp); ds.close();
接收端
•在接收端,要指定監聽的端口。 DatagramSocket ds = new DatagramSocket(10000); byte[] by = new byte[1024]; DatagramPacket dp = new DatagramPacket(by,by.length); ds.receive(dp); String str = new String(dp.getData() ); System.out.println(str+"--"+dp.getAddress()); ds.close(); MulticastSocket與多點廣播
•DatagramSocket只容許數據報發送給指定的目標地址,而MulticastSocket能夠將數據報以廣播 方式發送到數量不等的多個客戶端。
•若要使用多點廣播時,則須要讓一個數據報標有一組目標主機地址,當數據報發出後,整個組的全部 主機都能收到該數據報。IP多點廣播(或多點發送)實現了將單一信息發送到多個接收者的廣播,其 思想是設置一組特殊網絡地址做爲多點廣播地址,每個多點廣播地址都被看做一個組,當客戶端須要 發送、接收廣播信息時,加入到該組便可。
MulticastSocket
•MulticastSocket把一個DatagramPacket發送到多點廣播IP地址後,該數據報將被自動廣播給 加入該地址的全部MulticastSocket。MulticastSocket類既能夠將數據報發送到多點廣播地址,也 能夠接收其餘主機的廣播信息。
•MulticastSocket有點像DatagramSocket,事實上MulticastSocket是DatagramSocket的 一個子類,也就是說MulticastSocket是特殊的DatagramSocket。若要發送一個數據報時,可以使 用隨機端口建立MulticastSocket,也能夠在指定端口來建立MulticastSocket。 MulticastSocket提供了以下三個構造器:
–public MulticastSocket():使用本機默認地址、隨機端口來建立一個MulticastSocket對 象。
–public MulticastSocket(int portNumber):使用本機默認地址、指定端口來建立一個 MulticastSocket對象。
–public MulticastSocket(SocketAddress bindaddr):使用本機指定IP地址、指定端口來 建立一個MulticastSocket對象。
加入多點廣播
•建立一個MulticastSocket對象後,還須要將該MulticastSocket加入到指定的多點廣播地址, MulticastSocket使用joinGroup()方法來加入指定組;使用leaveGroup()方法脫離一個組。
–joinGroup(InetAddress multicastAddr):將該MulticastSocket加入指定的多點廣播地址。
–leaveGroup(InetAddress multicastAddr):讓該MulticastSocket離開指定的多點廣播地 址。
代理服務器
•代理服務器的功能就是代理網絡用戶去取得網絡信息。咱們使用網絡瀏覽器直接鏈接其餘Internet 站點取得網絡信息時,一般須要發送Request請求來等待響應。代理服務器是介於瀏覽器和Web服務 器之間的一臺服務器,有了它以後,瀏覽器不是直接到Web服務器去取得網頁數據,而是向代理服務 器發出請求,Request請求會先送到代理服務器,由代理服務器來取回瀏覽器所須要的信息並送回給 網絡瀏覽器。
使用Proxy
•Proxy有以下一個構造器:Proxy(Proxy.Type type, SocketAddress sa):建立表示代理服務 器的Proxy對象。而sa參數指定代理服務器的地址,其中type是該代理服務器的類型,該服務器類型 有以下三種: –Proxy.Type.DIRECT:表示直接鏈接或代理不存在。 –Proxy.Type.HTTP:表示高級協議的代理,如 HTTP 或 FTP。 –Proxy.Type.SOCKS:表示 SOCKS(V4 或 V5)代理。
•一旦建立了Proxy對象以後,程序就能夠在使用URLConnection打開鏈接時,或建立Socket鏈接 時傳入一個Proxy對象,做爲本次鏈接所使用的代理服務器。
•其中URL包含了一個URLConnection openConnection(Proxy proxy)方法,該方法使用指定 的代理服務器來打開鏈接;而Socket則提供了一個Socket(Proxy proxy)構造器,該構造器使用指 定的代理服務器建立一個沒有鏈接的Socket對象。
使用ProxySelector
•ProxySelector能夠它根據不一樣的鏈接使用不一樣的代理服務器。
•系統默認的ProxySelector會檢測各類系統屬性和URL協議,而後決定怎樣鏈接不一樣的主機。當 然,程序也能夠調用ProxySelector類的setDefault()靜態方法來設置默認代理服務器,也能夠調用 getDefault()方法得到系統當前默認的代理服務器。
•程序能夠經過System類來設置系統的代理服務器屬性,關於代理服務器經常使用的屬性名有以下三個 : –http.proxyHost:設置HTTP訪問所使用的代理服務器地址。該屬性名的前綴能夠改成https、ftp 等,分別用於設置HTTP訪問、安全HTTP訪問和FTP訪問所用的代理服務器地址。
–http.proxyPort:設置HTTP訪問所使用的代理服務器端口。該屬性名的前綴能夠改成https、ftp 等,分別用於設置HTTP訪問、安全HTTP訪問和FTP訪問所用的代理服務器端口。
–http.nonProxyHosts:設置HTTP訪問中不須要使用代理服務器的遠程主機,可使用*通配符, 若是有多個地址,多個地址用豎線(|)分隔。
自定義ProxySelector
•系統提供了默認的ProxySelector子類做爲代理選擇器,開發者能夠實現本身的代理選擇器,程序 能夠經過繼承ProxySelector來實現本身的代理選擇器。繼承ProxySelector須要重寫兩個方法:
–List<Proxy> select(URI uri):實現該方法讓代理選擇器根據不一樣的URI來使用不一樣的代理服務 器,該方法就是代理選擇器管理網絡鏈接使用代理服務器的關鍵。
–connectFailed(URI uri, SocketAddress sa, IOException ioe):當系統經過默認的代理服 務器創建鏈接失敗後,代理選擇器將會自動調用該方法。經過重寫該方法能夠對鏈接代理服務器失敗的 情形進行處理。
<?xml version="1.0" encoding="ISO-8859-1"?> <web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0" metadata-complete="true"> </web-app>
public class DownUtil { // 定義下載資源的路徑 private String path; // 指定所下載的文件的保存位置 private String targetFile; // 定義須要使用多少線程下載資源 private int threadNum; // 定義下載的線程對象 private DownThread[] threads; // 定義下載的文件的總大小 private int fileSize; public DownUtil(String path, String targetFile, int threadNum) { this.path = path; this.threadNum = threadNum; // 初始化threads數組 threads = new DownThread[threadNum]; this.targetFile = targetFile; } public void download() throws Exception { URL url = new URL(path); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setConnectTimeout(5 * 1000); conn.setRequestMethod("GET"); conn.setRequestProperty( "Accept", "image/gif, image/jpeg, image/pjpeg, image/pjpeg, " + "application/x-shockwave-flash, application/xaml+xml, " + "application/vnd.ms-xpsdocument, application/x-ms-xbap, " + "application/x-ms-application, application/vnd.ms-excel, " + "application/vnd.ms-powerpoint, application/msword, */*"); conn.setRequestProperty("Accept-Language", "zh-CN"); conn.setRequestProperty("Charset", "UTF-8"); conn.setRequestProperty("Connection", "Keep-Alive"); // 獲得文件大小 fileSize = conn.getContentLength(); conn.disconnect(); int currentPartSize = fileSize / threadNum + 1; RandomAccessFile file = new RandomAccessFile(targetFile, "rw"); // 設置本地文件的大小 file.setLength(fileSize); file.close(); for (int i = 0; i < threadNum; i++) { // 計算每條線程的下載的開始位置 int startPos = i * currentPartSize; // 每一個線程使用一個RandomAccessFile進行下載 RandomAccessFile currentPart = new RandomAccessFile(targetFile, "rw"); // 定位該線程的下載位置 currentPart.seek(startPos); // 建立下載線程 threads[i] = new DownThread(startPos, currentPartSize, currentPart); // 啓動下載線程 threads[i].start(); } } // 獲取下載的完成百分比 public double getCompleteRate() { // 統計多條線程已經下載的總大小 int sumSize = 0; for (int i = 0; i < threadNum; i++) { sumSize += threads[i].length; } // 返回已經完成的百分比 return sumSize * 1.0 / fileSize; } private class DownThread extends Thread { // 當前線程的下載位置 private int startPos; // 定義當前線程負責下載的文件大小 private int currentPartSize; // 當前線程須要下載的文件塊 private RandomAccessFile currentPart; // 定義已經該線程已下載的字節數 public int length; public DownThread(int startPos, int currentPartSize, RandomAccessFile currentPart) { this.startPos = startPos; this.currentPartSize = currentPartSize; this.currentPart = currentPart; } @Override public void run() { try { URL url = new URL(path); HttpURLConnection conn = (HttpURLConnection) url .openConnection(); conn.setConnectTimeout(5 * 1000); conn.setRequestMethod("GET"); conn.setRequestProperty( "Accept", "image/gif, image/jpeg, image/pjpeg, image/pjpeg, " + "application/x-shockwave-flash, application/xaml+xml, " + "application/vnd.ms-xpsdocument, application/x-ms-xbap, " + "application/x-ms-application, application/vnd.ms-excel, " + "application/vnd.ms-powerpoint, application/msword, */*"); conn.setRequestProperty("Accept-Language", "zh-CN"); conn.setRequestProperty("Charset", "UTF-8"); InputStream inStream = conn.getInputStream(); // 跳過startPos個字節,代表該線程只下載本身負責哪部分文件。 inStream.skip(this.startPos); byte[] buffer = new byte[1024]; int hasRead = 0; // 讀取網絡數據,並寫入本地文件 while (length < currentPartSize && (hasRead = inStream.read(buffer)) != -1) { currentPart.write(buffer, 0, hasRead); // 累計該線程下載的總大小 length += hasRead; } currentPart.close(); inStream.close(); } catch (Exception e) { e.printStackTrace(); } } } } public class GetPostTest { /** * 向指定URL發送GET方法的請求 * * @param url * 發送請求的URL * @param param * 請求參數,格式知足name1=value1&name2=value2的形式。 * @return URL所表明遠程資源的響應 */ public static String sendGet(String url, String param) { String result = ""; String urlName = url + "?" + param; try { URL realUrl = new URL(urlName); // 打開和URL之間的鏈接 URLConnection conn = realUrl.openConnection(); // 設置通用的請求屬性 conn.setRequestProperty("accept", "*/*"); conn.setRequestProperty("connection", "Keep-Alive"); conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)"); // 創建實際的鏈接 conn.connect(); // 獲取全部響應頭字段 Map<String, List<String>> map = conn.getHeaderFields(); // 遍歷全部的響應頭字段 for (String key : map.keySet()) { System.out.println(key + "--->" + map.get(key)); } try ( // 定義BufferedReader輸入流來讀取URL的響應 BufferedReader in = new BufferedReader(new InputStreamReader( conn.getInputStream(), "utf-8"))) { String line; while ((line = in.readLine()) != null) { result += "\n" + line; } } } catch (Exception e) { System.out.println("發送GET請求出現異常!" + e); e.printStackTrace(); } return result; } /** * 向指定URL發送POST方法的請求 * * @param url * 發送請求的URL * @param param * 請求參數,格式應該知足name1=value1&name2=value2的形式。 * @return URL所表明遠程資源的響應 */ public static String sendPost(String url, String param) { String result = ""; try { URL realUrl = new URL(url); // 打開和URL之間的鏈接 URLConnection conn = realUrl.openConnection(); // 設置通用的請求屬性 conn.setRequestProperty("accept", "*/*"); conn.setRequestProperty("connection", "Keep-Alive"); conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)"); // 發送POST請求必須設置以下兩行 conn.setDoOutput(true); conn.setDoInput(true); try ( // 獲取URLConnection對象對應的輸出流 PrintWriter out = new PrintWriter(conn.getOutputStream())) { // 發送請求參數 out.print(param); // flush輸出流的緩衝 out.flush(); } try ( // 定義BufferedReader輸入流來讀取URL的響應 BufferedReader in = new BufferedReader(new InputStreamReader( conn.getInputStream(), "utf-8"))) { String line; while ((line = in.readLine()) != null) { result += "\n" + line; } } } catch (Exception e) { System.out.println("發送POST請求出現異常!" + e); e.printStackTrace(); } return result; } // 提供主方法,測試發送GET請求和POST請求 public static void main(String args[]) { // 發送GET請求 String s = GetPostTest.sendGet("http://localhost:8888/abc/a.jsp", null); System.out.println(s); // 發送POST請求 String s1 = GetPostTest.sendPost("http://localhost:8888/abc/login.jsp", "name=crazyit.org&pass=leegang"); System.out.println(s1); } } public class InetAddressTest { public static void main(String[] args) throws Exception { // 根據主機名來獲取對應的InetAddress實例 InetAddress ip = InetAddress.getByName("www.crazyit.org"); // 判斷是否可達 System.out.println("crazyit是否可達:" + ip.isReachable(2000)); // 獲取該InetAddress實例的IP字符串 System.out.println(ip.getHostAddress()); // 根據原始IP地址來獲取對應的InetAddress實例 InetAddress local = InetAddress .getByAddress(new byte[] { 127, 0, 0, 1 }); System.out.println("本機是否可達:" + local.isReachable(5000)); // 獲取該InetAddress實例對應的全限定域名 System.out.println(local.getCanonicalHostName()); } } public class MultiThreadDown { public static void main(String[] args) throws Exception { // 初始化DownUtil對象 final DownUtil downUtil = new DownUtil("http://www.crazyit.org/" + "attachments/month_1403/1403202355ff6cc9a4fbf6f14a.png" , "ios.png", 4); // 開始下載 downUtil.download(); new Thread(() -> { while(downUtil.getCompleteRate() < 1) { // 每隔0.1秒查詢一次任務的完成進度, // GUI程序中可根據該進度來繪製進度條 System.out.println("已完成:" + downUtil.getCompleteRate()); try { Thread.sleep(1000); } catch (Exception ex){} } }).start(); } } public class URLDecoderTest { public static void main(String[] args) throws Exception { // 將application/x-www-form-urlencoded字符串 // 轉換成普通字符串 // 其中的字符串直接從圖17.3所示窗口複製過來 String keyWord = URLDecoder.decode("%E7%96%AF%E7%8B%82java", "utf-8"); System.out.println(keyWord); // 將普通字符串轉換成 // application/x-www-form-urlencoded字符串 String urlStr = URLEncoder.encode("瘋狂Android講義", "GBK"); System.out.println(urlStr); } } <%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title> 測試頁面 </title> <meta name="website" content="http://www.crazyit.org"/> </head> <body> 服務器時間爲:<%=new java.util.Date()%> </body> </html> <%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="" %> <% request.setCharacterEncoding("UTF-8"); String name = request.getParameter("name"); String pass = request.getParameter("pass"); if(name.equals("crazyit.org") && pass.equals("leegang")) { out.println("登陸成功!"); } else { out.println("登陸失敗!"); } %> public class SimpleAIOClient { static final int PORT = 30000; public static void main(String[] args) throws Exception { // 用於讀取數據的ByteBuffer。 ByteBuffer buff = ByteBuffer.allocate(1024); Charset utf = Charset.forName("utf-8"); try ( // ①建立AsynchronousSocketChannel對象 AsynchronousSocketChannel clientChannel = AsynchronousSocketChannel .open()) { // ②鏈接遠程服務器 clientChannel.connect(new InetSocketAddress("127.0.0.1", PORT)) .get(); // ④ buff.clear(); // ③從clientChannel中讀取數據 clientChannel.read(buff).get(); // ⑤ buff.flip(); // 將buff中內容轉換爲字符串 String content = utf.decode(buff).toString(); System.out.println("服務器信息:" + content); } } } public class SimpleAIOServer { static final int PORT = 30000; public static void main(String[] args) throws Exception { try ( // ①建立AsynchronousServerSocketChannel對象。 AsynchronousServerSocketChannel serverChannel = AsynchronousServerSocketChannel .open()) { // ②指定在指定地址、端口監聽。 serverChannel.bind(new InetSocketAddress(PORT)); while (true) { // ③採用循環接受來自客戶端的鏈接 Future<AsynchronousSocketChannel> future = serverChannel .accept(); // 獲取鏈接完成後返回的AsynchronousSocketChannel AsynchronousSocketChannel socketChannel = future.get(); // 執行輸出。 socketChannel.write( ByteBuffer.wrap("歡迎你來自AIO的世界!".getBytes("UTF-8"))) .get(); } } } }
client
public class Client { private static final int SERVER_PORT = 30000; private Socket socket; private PrintStream ps; private BufferedReader brServer; private BufferedReader keyIn; public void init() { try { // 初始化表明鍵盤的輸入流 keyIn = new BufferedReader(new InputStreamReader(System.in)); // 鏈接到服務器 socket = new Socket("127.0.0.1", SERVER_PORT); // 獲取該Socket對應的輸入流和輸出流 ps = new PrintStream(socket.getOutputStream()); brServer = new BufferedReader(new InputStreamReader( socket.getInputStream())); String tip = ""; // 採用循環不斷地彈出對話框要求輸入用戶名 while (true) { String userName = JOptionPane.showInputDialog(tip + "輸入用戶名"); // ① // 將用戶輸入的用戶名的先後增長協議字符串後發送 ps.println(CrazyitProtocol.USER_ROUND + userName + CrazyitProtocol.USER_ROUND); // 讀取服務器的響應 String result = brServer.readLine(); // 若是用戶重複,開始下次循環 if (result.equals(CrazyitProtocol.NAME_REP)) { tip = "用戶名重複!請從新"; continue; } // 若是服務器返回登陸成功,結束循環 if (result.equals(CrazyitProtocol.LOGIN_SUCCESS)) { break; } } } // 捕捉到異常,關閉網絡資源,並退出該程序 catch (UnknownHostException ex) { System.out.println("找不到遠程服務器,請肯定服務器已經啓動!"); closeRs(); System.exit(1); } catch (IOException ex) { System.out.println("網絡異常!請從新登陸!"); closeRs(); System.exit(1); } // 以該Socket對應的輸入流啓動ClientThread線程 new ClientThread(brServer).start(); } // 定義一個讀取鍵盤輸出,並向網絡發送的方法 private void readAndSend() { try { // 不斷讀取鍵盤輸入 String line = null; while ((line = keyIn.readLine()) != null) { // 若是發送的信息中有冒號,且以//開頭,則認爲想發送私聊信息 if (line.indexOf(":") > 0 && line.startsWith("//")) { line = line.substring(2); ps.println(CrazyitProtocol.PRIVATE_ROUND + line.split(":")[0] + CrazyitProtocol.SPLIT_SIGN + line.split(":")[1] + CrazyitProtocol.PRIVATE_ROUND); } else { ps.println(CrazyitProtocol.MSG_ROUND + line + CrazyitProtocol.MSG_ROUND); } } } // 捕捉到異常,關閉網絡資源,並退出該程序 catch (IOException ex) { System.out.println("網絡通訊異常!請從新登陸!"); closeRs(); System.exit(1); } } // 關閉Socket、輸入流、輸出流的方法 private void closeRs() { try { if (keyIn != null) { ps.close(); } if (brServer != null) { ps.close(); } if (ps != null) { ps.close(); } if (socket != null) { keyIn.close(); } } catch (IOException ex) { ex.printStackTrace(); } } public static void main(String[] args) { Client client = new Client(); client.init(); client.readAndSend(); } } public class ClientThread extends Thread { // 該客戶端線程負責處理的輸入流 BufferedReader br = null; // 使用一個網絡輸入流來建立客戶端線程 public ClientThread(BufferedReader br) { this.br = br; } public void run() { try { String line = null; // 不斷從輸入流中讀取數據,並將這些數據打印輸出 while ((line = br.readLine()) != null) { System.out.println(line); /* * 本例僅打印了從服務器端讀到的內容。實際上,此處的狀況能夠更復雜: 若是但願客戶端能看到聊天室的用戶列表,則可讓服務器在 * 每次有用戶登陸、用戶退出時,將全部用戶列表信息都向客戶端發送一遍。 * 爲了區分服務器發送的是聊天信息,仍是用戶列表,服務器也應該 * 在要發送的信息前、後都添加必定的協議字符串,客戶端此處則根據協議 字符串的不一樣而進行不一樣的處理! 更復雜的狀況: * 若是兩端進行遊戲,則還有可能發送遊戲信息,例如兩端進行五子棋遊戲, * 則還須要發送下棋座標信息等,服務器一樣在這些下棋座標信息前、後 * 添加協議字符串後再發送,客戶端就能夠根據該信息知道對手的下棋座標。 */ } } catch (IOException ex) { ex.printStackTrace(); } // 使用finally塊來關閉該線程對應的輸入流 finally { try { if (br != null) { br.close(); } } catch (IOException ex) { ex.printStackTrace(); } } } } public interface CrazyitProtocol { // 定義協議字符串的長度 int PROTOCOL_LEN = 2; // 下面是一些協議字符串,服務器和客戶端交換的信息 // 都應該在前、後添加這種特殊字符串。 String MSG_ROUND = "§γ"; String USER_ROUND = "∏∑"; String LOGIN_SUCCESS = "1"; String NAME_REP = "-1"; String PRIVATE_ROUND = "★【"; String SPLIT_SIGN = "※"; }
server
// 經過組合HashMap對象來實現CrazyitMap,CrazyitMap要求value也不可重複 public class CrazyitMap<K, V> { // 建立一個線程安全的HashMap public Map<K, V> map = Collections.synchronizedMap(new HashMap<K, V>()); // 根據value來刪除指定項 public synchronized void removeByValue(Object value) { for (Object key : map.keySet()) { if (map.get(key) == value) { map.remove(key); break; } } } // 獲取全部value組成的Set集合 public synchronized Set<V> valueSet() { Set<V> result = new HashSet<V>(); // 將map中全部value添加到result集合中 map.forEach((key , value) -> result.add(value)); return result; } // 根據value查找key。 public synchronized K getKeyByValue(V val) { // 遍歷全部key組成的集合 for (K key : map.keySet()) { // 若是指定key對應的value與被搜索的value相同,則返回對應的key if (map.get(key) == val || map.get(key).equals(val)) { return key; } } return null; } // 實現put()方法,該方法不容許value重複 public synchronized V put(K key, V value) { // 遍歷全部value組成的集合 for (V val : valueSet()) { // 若是某個value與試圖放入集合的value相同 // 則拋出一個RuntimeException異常 if (val.equals(value) && val.hashCode() == value.hashCode()) { throw new RuntimeException("MyMap實例中不容許有重複value!"); } } return map.put(key, value); } } public interface CrazyitProtocol { // 定義協議字符串的長度 int PROTOCOL_LEN = 2; // 下面是一些協議字符串,服務器和客戶端交換的信息都應該在前、後添加這種特殊字符串。 String MSG_ROUND = "§γ"; String USER_ROUND = "∏∑"; String LOGIN_SUCCESS = "1"; String NAME_REP = "-1"; String PRIVATE_ROUND = "★【"; String SPLIT_SIGN = "※"; } public class Server { private static final int SERVER_PORT = 30000; // 使用CrazyitMap對象來保存每一個客戶名字和對應輸出流之間的對應關係。 public static CrazyitMap<String, PrintStream> clients = new CrazyitMap<>(); public void init() { try ( // 創建監聽的ServerSocket ServerSocket ss = new ServerSocket(SERVER_PORT)) { // 採用死循環來不斷接受來自客戶端的請求 while (true) { Socket socket = ss.accept(); new ServerThread(socket).start(); } } // 若是拋出異常 catch (IOException ex) { System.out.println("服務器啓動失敗,是否端口" + SERVER_PORT + "已被佔用?"); } } public static void main(String[] args) { Server server = new Server(); server.init(); } } public class ServerThread extends Thread { private Socket socket; BufferedReader br = null; PrintStream ps = null; // 定義一個構造器,用於接收一個Socket來建立ServerThread線程 public ServerThread(Socket socket) { this.socket = socket; } public void run() { try { // 獲取該Socket對應的輸入流 br = new BufferedReader(new InputStreamReader( socket.getInputStream())); // 獲取該Socket對應的輸出流 ps = new PrintStream(socket.getOutputStream()); String line = null; while ((line = br.readLine()) != null) { // 若是讀到的行以CrazyitProtocol.USER_ROUND開始,並以其結束, // 能夠肯定讀到的是用戶登陸的用戶名 if (line.startsWith(CrazyitProtocol.USER_ROUND) && line.endsWith(CrazyitProtocol.USER_ROUND)) { // 獲得真實消息 String userName = getRealMsg(line); // 若是用戶名重複 if (Server.clients.map.containsKey(userName)) { System.out.println("重複"); ps.println(CrazyitProtocol.NAME_REP); } else { System.out.println("成功"); ps.println(CrazyitProtocol.LOGIN_SUCCESS); Server.clients.put(userName, ps); } } // 若是讀到的行以CrazyitProtocol.PRIVATE_ROUND開始,並以其結束, // 能夠肯定是私聊信息,私聊信息只向特定的輸出流發送 else if (line.startsWith(CrazyitProtocol.PRIVATE_ROUND) && line.endsWith(CrazyitProtocol.PRIVATE_ROUND)) { // 獲得真實消息 String userAndMsg = getRealMsg(line); // 以SPLIT_SIGN分割字符串,前半是私聊用戶,後半是聊天信息 String user = userAndMsg.split(CrazyitProtocol.SPLIT_SIGN)[0]; String msg = userAndMsg.split(CrazyitProtocol.SPLIT_SIGN)[1]; // 獲取私聊用戶對應的輸出流,併發送私聊信息 Server.clients.map.get(user).println( Server.clients.getKeyByValue(ps) + "悄悄地對你說:" + msg); } // 公聊要向每一個Socket發送 else { // 獲得真實消息 String msg = getRealMsg(line); // 遍歷clients中的每一個輸出流 for (PrintStream clientPs : Server.clients.valueSet()) { clientPs.println(Server.clients.getKeyByValue(ps) + "說:" + msg); } } } } // 捕捉到異常後,代表該Socket對應的客戶端已經出現了問題 // 因此程序將其對應的輸出流從Map中刪除 catch (IOException e) { Server.clients.removeByValue(ps); System.out.println(Server.clients.map.size()); // 關閉網絡、IO資源 try { if (br != null) { br.close(); } if (ps != null) { ps.close(); } if (socket != null) { socket.close(); } } catch (IOException ex) { ex.printStackTrace(); } } } // 將讀到的內容去掉先後的協議字符,恢復成真實數據 private String getRealMsg(String line) { return line.substring(CrazyitProtocol.PROTOCOL_LEN, line.length() - CrazyitProtocol.PROTOCOL_LEN); } } public class NClient { // 定義檢測SocketChannel的Selector對象 private Selector selector = null; static final int PORT = 30000; // 定義處理編碼和解碼的字符集 private Charset charset = Charset.forName("UTF-8"); // 客戶端SocketChannel private SocketChannel sc = null; public void init() throws IOException { selector = Selector.open(); InetSocketAddress isa = new InetSocketAddress("127.0.0.1", PORT); // 調用open靜態方法建立鏈接到指定主機的SocketChannel sc = SocketChannel.open(isa); // 設置該sc以非阻塞方式工做 sc.configureBlocking(false); // 將SocketChannel對象註冊到指定Selector sc.register(selector, SelectionKey.OP_READ); // 啓動讀取服務器端數據的線程 new ClientThread().start(); // 建立鍵盤輸入流 Scanner scan = new Scanner(System.in); while (scan.hasNextLine()) { // 讀取鍵盤輸入 String line = scan.nextLine(); // 將鍵盤輸入的內容輸出到SocketChannel中 sc.write(charset.encode(line)); } } // 定義讀取服務器數據的線程 private class ClientThread extends Thread { public void run() { try { while (selector.select() > 0) // ① { // 遍歷每一個有可用IO操做Channel對應的SelectionKey for (SelectionKey sk : selector.selectedKeys()) { // 刪除正在處理的SelectionKey selector.selectedKeys().remove(sk); // 若是該SelectionKey對應的Channel中有可讀的數據 if (sk.isReadable()) { // 使用NIO讀取Channel中的數據 SocketChannel sc = (SocketChannel) sk.channel(); ByteBuffer buff = ByteBuffer.allocate(1024); String content = ""; while (sc.read(buff) > 0) { sc.read(buff); buff.flip(); content += charset.decode(buff); } // 打印輸出讀取的內容 System.out.println("聊天信息:" + content); // 爲下一次讀取做準備 sk.interestOps(SelectionKey.OP_READ); } } } } catch (IOException ex) { ex.printStackTrace(); } } } public static void main(String[] args) throws IOException { new NClient().init(); } } public class NServer { // 用於檢測全部Channel狀態的Selector private Selector selector = null; static final int PORT = 30000; // 定義實現編碼、解碼的字符集對象 private Charset charset = Charset.forName("UTF-8"); public void init() throws IOException { selector = Selector.open(); // 經過open方法來打開一個未綁定的ServerSocketChannel實例 ServerSocketChannel server = ServerSocketChannel.open(); InetSocketAddress isa = new InetSocketAddress("127.0.0.1", PORT); // 將該ServerSocketChannel綁定到指定IP地址 server.bind(isa); // 設置ServerSocket以非阻塞方式工做 server.configureBlocking(false); // 將server註冊到指定Selector對象 server.register(selector, SelectionKey.OP_ACCEPT); while (selector.select() > 0) { // 依次處理selector上的每一個已選擇的SelectionKey for (SelectionKey sk : selector.selectedKeys()) { // 從selector上的已選擇Key集中刪除正在處理的SelectionKey selector.selectedKeys().remove(sk); // ① // 若是sk對應的Channel包含客戶端的鏈接請求 if (sk.isAcceptable()) // ② { // 調用accept方法接受鏈接,產生服務器端的SocketChannel SocketChannel sc = server.accept(); // 設置採用非阻塞模式 sc.configureBlocking(false); // 將該SocketChannel也註冊到selector sc.register(selector, SelectionKey.OP_READ); // 將sk對應的Channel設置成準備接受其餘請求 sk.interestOps(SelectionKey.OP_ACCEPT); } // 若是sk對應的Channel有數據須要讀取 if (sk.isReadable()) // ③ { // 獲取該SelectionKey對應的Channel,該Channel中有可讀的數據 SocketChannel sc = (SocketChannel) sk.channel(); // 定義準備執行讀取數據的ByteBuffer ByteBuffer buff = ByteBuffer.allocate(1024); String content = ""; // 開始讀取數據 try { while (sc.read(buff) > 0) { buff.flip(); content += charset.decode(buff); } // 打印從該sk對應的Channel裏讀取到的數據 System.out.println("讀取的數據:" + content); // 將sk對應的Channel設置成準備下一次讀取 sk.interestOps(SelectionKey.OP_READ); } // 若是捕捉到該sk對應的Channel出現了異常,即代表該Channel // 對應的Client出現了問題,因此從Selector中取消sk的註冊 catch (IOException ex) { // 從Selector中刪除指定的SelectionKey sk.cancel(); if (sk.channel() != null) { sk.channel().close(); } } // 若是content的長度大於0,即聊天信息不爲空 if (content.length() > 0) { // 遍歷該selector裏註冊的全部SelectionKey for (SelectionKey key : selector.keys()) { // 獲取該key對應的Channel Channel targetChannel = key.channel(); // 若是該channel是SocketChannel對象 if (targetChannel instanceof SocketChannel) { // 將讀到的內容寫入該Channel中 SocketChannel dest = (SocketChannel) targetChannel; dest.write(charset.encode(content)); } } } } } } } public static void main(String[] args) throws IOException { new NServer().init(); } }
client
public class ClientThread implements Runnable { // 該線程負責處理的Socket private Socket s; // 該線程所處理的Socket所對應的輸入流 BufferedReader br = null; public ClientThread(Socket s) throws IOException { this.s = s; br = new BufferedReader(new InputStreamReader(s.getInputStream())); } public void run() { try { String content = null; // 不斷讀取Socket輸入流中的內容,並將這些內容打印輸出 while ((content = br.readLine()) != null) { System.out.println(content); } } catch (Exception e) { e.printStackTrace(); } } } public class MyClient { public static void main(String[] args) throws Exception { Socket s = new Socket("127.0.0.1", 30000); // 客戶端啓動ClientThread線程不斷讀取來自服務器的數據 new Thread(new ClientThread(s)).start(); // ① // 獲取該Socket對應的輸出流 PrintStream ps = new PrintStream(s.getOutputStream()); String line = null; // 不斷讀取鍵盤輸入 BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); while ((line = br.readLine()) != null) { // 將用戶的鍵盤輸入內容寫入Socket對應的輸出流 ps.println(line); } } }
server
public class MyServer { // 定義保存全部Socket的ArrayList,並將其包裝爲線程安全的 public static List<Socket> socketList = Collections .synchronizedList(new ArrayList<>()); public static void main(String[] args) throws IOException { ServerSocket ss = new ServerSocket(30000); while (true) { // 此行代碼會阻塞,將一直等待別人的鏈接 Socket s = ss.accept(); socketList.add(s); // 每當客戶端鏈接後啓動一條ServerThread線程爲該客戶端服務 new Thread(new ServerThread(s)).start(); } } } // 負責處理每一個線程通訊的線程類 public class ServerThread implements Runnable { // 定義當前線程所處理的Socket Socket s = null; // 該線程所處理的Socket所對應的輸入流 BufferedReader br = null; public ServerThread(Socket s) throws IOException { this.s = s; // 初始化該Socket對應的輸入流 br = new BufferedReader(new InputStreamReader(s.getInputStream())); } public void run() { try { String content = null; // 採用循環不斷從Socket中讀取客戶端發送過來的數據 while ((content = readFromClient()) != null) { // 遍歷socketList中的每一個Socket, // 將讀到的內容向每一個Socket發送一次 for (Socket s : MyServer.socketList) { PrintStream ps = new PrintStream(s.getOutputStream()); ps.println(content); } } } catch (IOException e) { e.printStackTrace(); } } // 定義讀取客戶端數據的方法 private String readFromClient() { try { return br.readLine(); } // 若是捕捉到異常,代表該Socket對應的客戶端已經關閉 catch (IOException e) { // 刪除該Socket。 MyServer.socketList.remove(s); // ① } return null; } } public class Client { public static void main(String[] args) throws Exception { Socket s = new Socket("localhost", 30000); Scanner scan = new Scanner(s.getInputStream()); while (scan.hasNextLine()) { System.out.println(scan.nextLine()); } PrintStream ps = new PrintStream(s.getOutputStream()); ps.println("客戶端的第一行數據"); ps.println("客戶端的第二行數據"); ps.close(); scan.close(); s.close(); } } public class Server { public static void main(String[] args) throws Exception { ServerSocket ss = new ServerSocket(30000); Socket socket = ss.accept(); PrintStream ps = new PrintStream(socket.getOutputStream()); ps.println("服務器的第一行數據"); ps.println("服務器的第二行數據"); // 關閉socket的輸出流,代表輸出數據已經結束 socket.shutdownOutput(); // 下面語句將輸出false,代表socket還未關閉。 System.out.println(socket.isClosed()); Scanner scan = new Scanner(socket.getInputStream()); while (scan.hasNextLine()) { System.out.println(scan.nextLine()); } scan.close(); socket.close(); ss.close(); } } public class AIOClient { final static String UTF_8 = "utf-8"; final static int PORT = 30000; // 與服務器端通訊的異步Channel AsynchronousSocketChannel clientChannel; JFrame mainWin = new JFrame("多人聊天"); JTextArea jta = new JTextArea(16, 48); JTextField jtf = new JTextField(40); JButton sendBn = new JButton("發送"); public void init() { mainWin.setLayout(new BorderLayout()); jta.setEditable(false); mainWin.add(new JScrollPane(jta), BorderLayout.CENTER); JPanel jp = new JPanel(); jp.add(jtf); jp.add(sendBn); // 發送消息的Action,Action是ActionListener的子接口 Action sendAction = new AbstractAction() { public void actionPerformed(ActionEvent e) { String content = jtf.getText(); if (content.trim().length() > 0) { try { // 將content內容寫入Channel中 clientChannel .write(ByteBuffer.wrap(content.trim().getBytes( UTF_8))).get(); // ① } catch (Exception ex) { ex.printStackTrace(); } } // 清空輸入框 jtf.setText(""); } }; sendBn.addActionListener(sendAction); // 將Ctrl+Enter鍵和"send"關聯 jtf.getInputMap().put( KeyStroke.getKeyStroke('\n', java.awt.event.InputEvent.CTRL_MASK), "send"); // 將"send"和sendAction關聯 jtf.getActionMap().put("send", sendAction); mainWin.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); mainWin.add(jp, BorderLayout.SOUTH); mainWin.pack(); mainWin.setVisible(true); } public void connect() throws Exception { // 定義一個ByteBuffer準備讀取數據 final ByteBuffer buff = ByteBuffer.allocate(1024); // 建立一個線程池 ExecutorService executor = Executors.newFixedThreadPool(80); // 以指定線程池來建立一個AsynchronousChannelGroup AsynchronousChannelGroup channelGroup = AsynchronousChannelGroup .withThreadPool(executor); // 以channelGroup做爲組管理器來建立AsynchronousSocketChannel clientChannel = AsynchronousSocketChannel.open(channelGroup); // 讓AsynchronousSocketChannel鏈接到指定IP、指定端口 clientChannel.connect(new InetSocketAddress("127.0.0.1", PORT)).get(); jta.append("---與服務器鏈接成功---\n"); buff.clear(); clientChannel.read(buff, null, new CompletionHandler<Integer, Object>() // ② { @Override public void completed(Integer result, Object attachment) { buff.flip(); // 將buff中內容轉換爲字符串 String content = StandardCharsets.UTF_8.decode(buff) .toString(); // 顯示從服務器端讀取的數據 jta.append("某人說:" + content + "\n"); buff.clear(); clientChannel.read(buff, null, this); } @Override public void failed(Throwable ex, Object attachment) { System.out.println("讀取數據失敗: " + ex); } }); } public static void main(String[] args) throws Exception { AIOClient client = new AIOClient(); client.init(); client.connect(); } } public class AIOServer { static final int PORT = 30000; final static String UTF_8 = "utf-8"; static List<AsynchronousSocketChannel> channelList = new ArrayList<>(); public void startListen() throws InterruptedException, Exception { // 建立一個線程池 ExecutorService executor = Executors.newFixedThreadPool(20); // 以指定線程池來建立一個AsynchronousChannelGroup AsynchronousChannelGroup channelGroup = AsynchronousChannelGroup .withThreadPool(executor); // 以指定線程池來建立一個AsynchronousServerSocketChannel AsynchronousServerSocketChannel serverChannel = AsynchronousServerSocketChannel .open(channelGroup) // 指定監聽本機的PORT端口 .bind(new InetSocketAddress(PORT)); // 使用CompletionHandler接受來自客戶端的鏈接請求 serverChannel.accept(null, new AcceptHandler(serverChannel)); // ① Thread.sleep(5000); } public static void main(String[] args) throws Exception { AIOServer server = new AIOServer(); server.startListen(); } } // 實現本身的CompletionHandler類 class AcceptHandler implements CompletionHandler<AsynchronousSocketChannel, Object> { private AsynchronousServerSocketChannel serverChannel; public AcceptHandler(AsynchronousServerSocketChannel sc) { this.serverChannel = sc; } // 定義一個ByteBuffer準備讀取數據 ByteBuffer buff = ByteBuffer.allocate(1024); // 當實際IO操做完成時候觸發該方法 @Override public void completed(final AsynchronousSocketChannel sc, Object attachment) { // 記錄新鏈接的進來的Channel AIOServer.channelList.add(sc); // 準備接受客戶端的下一次鏈接 serverChannel.accept(null, this); sc.read(buff, null, new CompletionHandler<Integer, Object>() // ② { @Override public void completed(Integer result, Object attachment) { buff.flip(); // 將buff中內容轉換爲字符串 String content = StandardCharsets.UTF_8.decode(buff) .toString(); // 遍歷每一個Channel,將收到的信息寫入各Channel中 for (AsynchronousSocketChannel c : AIOServer.channelList) { try { c.write(ByteBuffer.wrap(content .getBytes(AIOServer.UTF_8))).get(); } catch (Exception ex) { ex.printStackTrace(); } } buff.clear(); // 讀取下一次數據 sc.read(buff, null, this); } @Override public void failed(Throwable ex, Object attachment) { System.out.println("讀取數據失敗: " + ex); // 從該Channel讀取數據失敗,就將該Channel刪除 AIOServer.channelList.remove(sc); } }); } @Override public void failed(Throwable ex, Object attachment) { System.out.println("鏈接失敗: " + ex); } } public class Client { public static void main(String[] args) throws IOException { Socket socket = new Socket("127.0.0.1", 30000); // ① // 將Socket對應的輸入流包裝成BufferedReader BufferedReader br = new BufferedReader(new InputStreamReader( socket.getInputStream())); // 進行普通IO操做 String line = br.readLine(); System.out.println("來自服務器的數據:" + line); // 關閉輸入流、socket br.close(); socket.close(); } } public class Server { public static void main(String[] args) throws IOException { // 建立一個ServerSocket,用於監聽客戶端Socket的鏈接請求 ServerSocket ss = new ServerSocket(30000); // 採用循環不斷接受來自客戶端的請求 while (true) { // 每當接受到客戶端Socket的請求,服務器端也對應產生一個Socket Socket s = ss.accept(); // 將Socket對應的輸出流包裝成PrintStream PrintStream ps = new PrintStream(s.getOutputStream()); // 進行普通IO操做 ps.println("您好,您收到了服務器的新年祝福!"); // 關閉輸出流,關閉Socket ps.close(); s.close(); } } } // 定義交談的對話框 public class ChatFrame extends JDialog { // 聊天信息區 JTextArea msgArea = new JTextArea(12, 45); // 聊天輸入區 JTextField chatField = new JTextField(30); // 發送聊天信息的按鈕 JButton sendBn = new JButton("發送"); // 該交談窗口對應的用戶 UserInfo user; // 構造器,用於初始化交談對話框的界面 public ChatFrame(LanTalk parent, final UserInfo user) { super(parent, "和" + user.getName() + "聊天中", false); this.user = user; msgArea.setEditable(false); add(new JScrollPane(msgArea)); JPanel buttom = new JPanel(); buttom.add(new JLabel("輸入信息:")); buttom.add(chatField); buttom.add(sendBn); add(buttom, BorderLayout.SOUTH); // 發送消息的Action,Action是ActionListener的子接口 Action sendAction = new AbstractAction() { @Override public void actionPerformed(ActionEvent evt) { InetSocketAddress dest = (InetSocketAddress) user.getAddress(); // 在聊友列表中,全部人項的SocketAddress是null // 這代表是向全部人發送消息 if (dest == null) { LoginFrame.comUtil.broadCast(chatField.getText()); msgArea.setText("您對你們說:" + chatField.getText() + "\n" + msgArea.getText()); } // 向私人發送信息 else { // 獲取發送消息的目的 dest = new InetSocketAddress(dest.getHostName(), dest.getPort() + 1); LoginFrame.comUtil.sendSingle(chatField.getText(), dest); msgArea.setText("您對" + user.getName() + "說:" + chatField.getText() + "\n" + msgArea.getText()); } chatField.setText(""); } }; sendBn.addActionListener(sendAction); // 將Ctrl+Enter鍵和"send"關聯 chatField.getInputMap().put( KeyStroke.getKeyStroke('\n', java.awt.event.InputEvent.CTRL_MASK), "send"); // 將"send"與sendAction關聯 chatField.getActionMap().put("send", sendAction); pack(); } // 定義向聊天區域添加消息的方法 public void addString(String msg) { msgArea.setText(msg + "\n" + msgArea.getText()); } } // 聊天交換信息的工具類 public class ComUtil { // 定義本程序通訊所使用的字符集 public static final String CHARSET = "utf-8"; // 使用常量做爲本程序的多點廣播IP地址 private static final String BROADCAST_IP = "230.0.0.1"; // 使用常量做爲本程序的多點廣播目的的端口 // DatagramSocket所用的的端口爲該端口+1。 public static final int BROADCAST_PORT = 30000; // 定義每一個數據報的最大大小爲4K private static final int DATA_LEN = 4096; // 定義本程序的MulticastSocket實例 private MulticastSocket socket = null; // 定義本程序私聊的Socket實例 private DatagramSocket singleSocket = null; // 定義廣播的IP地址 private InetAddress broadcastAddress = null; // 定義接收網絡數據的字節數組 byte[] inBuff = new byte[DATA_LEN]; // 以指定字節數組建立準備接受數據的DatagramPacket對象 private DatagramPacket inPacket = new DatagramPacket(inBuff, inBuff.length); // 定義一個用於發送的DatagramPacket對象 private DatagramPacket outPacket = null; // 聊天的主界面程序 private LanTalk lanTalk; // 構造器,初始化資源 public ComUtil(LanTalk lanTalk) throws Exception { this.lanTalk = lanTalk; // 建立用於發送、接收數據的MulticastSocket對象 // 由於該MulticastSocket對象須要接收,因此有指定端口 socket = new MulticastSocket(BROADCAST_PORT); // 建立私聊用的DatagramSocket對象 singleSocket = new DatagramSocket(BROADCAST_PORT + 1); broadcastAddress = InetAddress.getByName(BROADCAST_IP); // 將該socket加入指定的多點廣播地址 socket.joinGroup(broadcastAddress); // 設置本MulticastSocket發送的數據報被回送到自身 socket.setLoopbackMode(false); // 初始化發送用的DatagramSocket,它包含一個長度爲0的字節數組 outPacket = new DatagramPacket(new byte[0], 0, broadcastAddress, BROADCAST_PORT); // 啓動兩個讀取網絡數據的線程 new ReadBroad().start(); Thread.sleep(1); new ReadSingle().start(); } // 廣播消息的工具方法 public void broadCast(String msg) { try { // 將msg字符串轉換字節數組 byte[] buff = msg.getBytes(CHARSET); // 設置發送用的DatagramPacket裏的字節數據 outPacket.setData(buff); // 發送數據報 socket.send(outPacket); } // 捕捉異常 catch (IOException ex) { ex.printStackTrace(); if (socket != null) { // 關閉該Socket對象 socket.close(); } JOptionPane.showMessageDialog(null, "發送信息異常,請確認30000端口空閒,且網絡鏈接正常!", "網絡異常", JOptionPane.ERROR_MESSAGE); System.exit(1); } } // 定義向單獨用戶發送消息的方法 public void sendSingle(String msg, SocketAddress dest) { try { // 將msg字符串轉換字節數組 byte[] buff = msg.getBytes(CHARSET); DatagramPacket packet = new DatagramPacket(buff, buff.length, dest); singleSocket.send(packet); } // 捕捉異常 catch (IOException ex) { ex.printStackTrace(); if (singleSocket != null) { // 關閉該Socket對象 singleSocket.close(); } JOptionPane.showMessageDialog(null, "發送信息異常,請確認30001端口空閒,且網絡鏈接正常!", "網絡異常", JOptionPane.ERROR_MESSAGE); System.exit(1); } } // 不斷從DatagramSocket中讀取數據的線程 class ReadSingle extends Thread { // 定義接收網絡數據的字節數組 byte[] singleBuff = new byte[DATA_LEN]; private DatagramPacket singlePacket = new DatagramPacket(singleBuff, singleBuff.length); public void run() { while (true) { try { // 讀取Socket中的數據。 singleSocket.receive(singlePacket); // 處理讀到的信息 lanTalk.processMsg(singlePacket, true); } // 捕捉異常 catch (IOException ex) { ex.printStackTrace(); if (singleSocket != null) { // 關閉該Socket對象 singleSocket.close(); } JOptionPane.showMessageDialog(null, "接收信息異常,請確認30001端口空閒,且網絡鏈接正常!", "網絡異常", JOptionPane.ERROR_MESSAGE); System.exit(1); } } } } // 持續讀取MulticastSocket的線程 class ReadBroad extends Thread { public void run() { while (true) { try { // 讀取Socket中的數據。 socket.receive(inPacket); // 打印輸出從socket中讀取的內容 String msg = new String(inBuff, 0, inPacket.getLength(), CHARSET); // 讀到的內容是在線信息 if (msg.startsWith(YeekuProtocol.PRESENCE) && msg.endsWith(YeekuProtocol.PRESENCE)) { String userMsg = msg.substring(2, msg.length() - 2); String[] userInfo = userMsg .split(YeekuProtocol.SPLITTER); UserInfo user = new UserInfo(userInfo[1], userInfo[0], inPacket.getSocketAddress(), 0); // 控制是否須要添加該用戶的旗標 boolean addFlag = true; ArrayList<Integer> delList = new ArrayList<>(); // 遍歷系統中已有的全部用戶,該循環必須循環完成 for (int i = 1; i < lanTalk.getUserNum(); i++) { UserInfo current = lanTalk.getUser(i); // 將全部用戶失去聯繫的次數加1 current.setLost(current.getLost() + 1); // 若是該信息由指定用戶發送過來 if (current.equals(user)) { current.setLost(0); // 設置該用戶無須添加 addFlag = false; } if (current.getLost() > 2) { delList.add(i); } } // 刪除delList中的全部索引對應的用戶 for (int i = 0; i < delList.size(); i++) { lanTalk.removeUser(delList.get(i)); } if (addFlag) { // 添加新用戶 lanTalk.addUser(user); } } // 讀到的內容是公聊信息 else { // 處理讀到的信息 lanTalk.processMsg(inPacket, false); } } // 捕捉異常 catch (IOException ex) { ex.printStackTrace(); if (socket != null) { // 關閉該Socket對象 socket.close(); } JOptionPane.showMessageDialog(null, "接收信息異常,請確認30000端口空閒,且網絡鏈接正常!", "網絡異常", JOptionPane.ERROR_MESSAGE); System.exit(1); } } } } } public class LanTalk extends JFrame { private DefaultListModel<UserInfo> listModel = new DefaultListModel<>(); // 定義一個JList對象 private JList<UserInfo> friendsList = new JList<>(listModel); // 定義一個用於格式化日期的格式器 private DateFormat formatter = DateFormat.getDateTimeInstance(); public LanTalk() { super("局域網聊天"); // 設置該JList使用ImageCellRenderer做爲單元格繪製器 friendsList.setCellRenderer(new ImageCellRenderer()); listModel.addElement(new UserInfo("all", "全部人", null, -2000)); friendsList.addMouseListener(new ChangeMusicListener()); add(new JScrollPane(friendsList)); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setBounds(2, 2, 160, 600); } // 根據地址來查詢用戶 public UserInfo getUserBySocketAddress(SocketAddress address) { for (int i = 1; i < getUserNum(); i++) { UserInfo user = getUser(i); if (user.getAddress() != null && user.getAddress().equals(address)) { return user; } } return null; } // ------下面四個方法是對ListModel的包裝------ // 向用戶列表中添加用戶 public void addUser(UserInfo user) { listModel.addElement(user); } // 從用戶列表中刪除用戶 public void removeUser(int pos) { listModel.removeElementAt(pos); } // 獲取該聊天窗口的用戶數量 public int getUserNum() { return listModel.size(); } // 獲取指定位置的用戶 public UserInfo getUser(int pos) { return listModel.elementAt(pos); } // 實現JList上的鼠標雙擊事件的監聽器 class ChangeMusicListener extends MouseAdapter { public void mouseClicked(MouseEvent e) { // 若是鼠標的擊鍵次數大於2 if (e.getClickCount() >= 2) { // 取出鼠標雙擊時選中的列表項 UserInfo user = (UserInfo) friendsList.getSelectedValue(); // 若是該列表項對應用戶的交談窗口爲null if (user.getChatFrame() == null) { // 爲該用戶建立一個交談窗口,並讓該用戶引用該窗口 user.setChatFrame(new ChatFrame(null, user)); } // 若是該用戶的窗口沒有顯示,則讓該用戶的窗口顯示出來 if (!user.getChatFrame().isShowing()) { user.getChatFrame().setVisible(true); } } } } /** * 處理網絡數據報,該方法將根據聊天信息獲得聊天者, 並將信息顯示在聊天對話框中。 * * @param packet * 須要處理的數據報 * @param single * 該信息是否爲私聊信息 */ public void processMsg(DatagramPacket packet, boolean single) { // 獲取該發送該數據報的SocketAddress InetSocketAddress srcAddress = (InetSocketAddress) packet .getSocketAddress(); // 若是是私聊信息,則該Packet獲取的是DatagramSocket的地址, // 將端口減1纔是對應的MulticastSocket的地址 if (single) { srcAddress = new InetSocketAddress(srcAddress.getHostName(), srcAddress.getPort() - 1); } UserInfo srcUser = getUserBySocketAddress(srcAddress); if (srcUser != null) { // 肯定消息將要顯示到哪一個用戶對應窗口上。 UserInfo alertUser = single ? srcUser : getUser(0); // 若是該用戶對應的窗口爲空,顯示該窗口 if (alertUser.getChatFrame() == null) { alertUser.setChatFrame(new ChatFrame(null, alertUser)); } // 定義添加的提示信息 String tipMsg = single ? "對您說:" : "對你們說:"; try { // 顯示提示信息 alertUser.getChatFrame().addString( srcUser.getName() + tipMsg + "......................(" + formatter.format(new Date()) + ")\n" + new String(packet.getData(), 0, packet .getLength(), ComUtil.CHARSET) + "\n"); } catch (Exception ex) { ex.printStackTrace(); } if (!alertUser.getChatFrame().isShowing()) { alertUser.getChatFrame().setVisible(true); } } } // 主方法,程序的入口 public static void main(String[] args) { LanTalk lanTalk = new LanTalk(); new LoginFrame(lanTalk, "請輸入用戶名、頭像後登陸"); } } // 定義用於改變JList列表項外觀的類 class ImageCellRenderer extends JPanel implements ListCellRenderer<UserInfo> { private ImageIcon icon; private String name; // 定義繪製單元格時的背景色 private Color background; // 定義繪製單元格時的前景色 private Color foreground; @Override public Component getListCellRendererComponent(JList list, UserInfo userInfo, int index, boolean isSelected, boolean cellHasFocus) { // 設置圖標 icon = new ImageIcon("ico/" + userInfo.getIcon() + ".gif"); name = userInfo.getName(); // 設置背景色、前景色 background = isSelected ? list.getSelectionBackground() : list .getBackground(); foreground = isSelected ? list.getSelectionForeground() : list .getForeground(); // 返回該JPanel對象做爲單元格繪製器 return this; } // 重寫paintComponent方法,改變JPanel的外觀 public void paintComponent(Graphics g) { int imageWidth = icon.getImage().getWidth(null); int imageHeight = icon.getImage().getHeight(null); g.setColor(background); g.fillRect(0, 0, getWidth(), getHeight()); g.setColor(foreground); // 繪製好友圖標 g.drawImage(icon.getImage(), getWidth() / 2 - imageWidth / 2, 10, null); g.setFont(new Font("SansSerif", Font.BOLD, 18)); // 繪製好友用戶名 g.drawString(name, getWidth() / 2 - name.length() * 10, imageHeight + 30); } // 經過該方法來設置該ImageCellRenderer的最佳大小 public Dimension getPreferredSize() { return new Dimension(60, 80); } } // 登陸用的對話框 public class LoginFrame extends JDialog { public JLabel tip; public JTextField userField = new JTextField("李剛", 20); public JComboBox<Integer> iconList = new JComboBox<>(new Integer[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }); private JButton loginBn = new JButton("登陸"); // 聊天的主界面 private LanTalk chatFrame; // 聊天通訊的工具實例 public static ComUtil comUtil; // 構造器,用於初始化的登陸對話框 public LoginFrame(LanTalk parent, String msg) { super(parent, "輸入名字後登陸", true); this.chatFrame = parent; setLayout(new GridLayout(5, 1)); JPanel jp = new JPanel(); tip = new JLabel(msg); tip.setFont(new Font("Serif", Font.BOLD, 16)); jp.add(tip); add(jp); add(getPanel("用戶名", userField)); iconList.setPreferredSize(new Dimension(224, 20)); add(getPanel("圖 標", iconList)); JPanel bp = new JPanel(); loginBn.addActionListener(new MyActionListener(this)); bp.add(loginBn); add(bp); pack(); setVisible(true); } // 工具方法,該方法將一個字符串和組件組合成JPanel對象 private JPanel getPanel(String name, JComponent jf) { JPanel jp = new JPanel(); jp.add(new JLabel(name + ":")); jp.add(jf); return jp; } // 該方法用於改變登陸窗口最上面的提示信息 public void setTipMsg(String tip) { this.tip.setText(tip); } // 定義一個事件監聽器 class MyActionListener implements ActionListener { private LoginFrame loginFrame; public MyActionListener(LoginFrame loginFrame) { this.loginFrame = loginFrame; } // 當鼠標單擊事件發生時 public void actionPerformed(ActionEvent evt) { try { // 初始化聊天通訊類 comUtil = new ComUtil(chatFrame); final String loginMsg = YeekuProtocol.PRESENCE + userField.getText() + YeekuProtocol.SPLITTER + iconList.getSelectedObjects()[0] + YeekuProtocol.PRESENCE; comUtil.broadCast(loginMsg); // 啓動定時器每20秒廣播一次在線信息 javax.swing.Timer timer = new javax.swing.Timer(1000 * 10 , event-> comUtil.broadCast(loginMsg)); timer.start(); loginFrame.setVisible(false); chatFrame.setVisible(true); } catch (Exception ex) { loginFrame.setTipMsg("確認30001端口空閒,且網絡正常!"); } } } } public class UserInfo { // 該用戶的圖標 private String icon; // 該用戶的名字 private String name; // 該用戶的MulitcastSocket所在的IP和端口 private SocketAddress address; // 該用戶失去聯繫的次數 private int lost; // 該用戶對應的交談窗口 private ChatFrame chatFrame; public UserInfo() { } // 有參數的構造器 public UserInfo(String icon, String name, SocketAddress address, int lost) { this.icon = icon; this.name = name; this.address = address; this.lost = lost; } // 省略全部成員變量的setter和getter方法 // icon的setter和getter方法 public void setIcon(String icon) { this.icon = icon; } public String getIcon() { return this.icon; } // name的setter和getter方法 public void setName(String name) { this.name = name; } public String getName() { return this.name; } // address的setter和getter方法 public void setAddress(SocketAddress address) { this.address = address; } public SocketAddress getAddress() { return this.address; } // lost的setter和getter方法 public void setLost(int lost) { this.lost = lost; } public int getLost() { return this.lost; } // chatFrame的setter和getter方法 public void setChatFrame(ChatFrame chatFrame) { this.chatFrame = chatFrame; } public ChatFrame getChatFrame() { return this.chatFrame; } // 使用address做爲該用戶的標識,因此根據address做爲 // 重寫hashCode()和equals方法的標準 public int hashCode() { return address.hashCode(); } public boolean equals(Object obj) { if (obj != null && obj.getClass() == UserInfo.class) { UserInfo target = (UserInfo) obj; if (address != null) { return address.equals(target.getAddress()); } } return false; } } public interface YeekuProtocol { String PRESENCE = "⊿⊿"; String SPLITTER = "▓"; } // 讓該類實現Runnable接口,該類的實例可做爲線程的target public class MulticastSocketTest implements Runnable { // 使用常量做爲本程序的多點廣播IP地址 private static final String BROADCAST_IP = "230.0.0.1"; // 使用常量做爲本程序的多點廣播目的的端口 public static final int BROADCAST_PORT = 30000; // 定義每一個數據報的最大大小爲4K private static final int DATA_LEN = 4096; // 定義本程序的MulticastSocket實例 private MulticastSocket socket = null; private InetAddress broadcastAddress = null; private Scanner scan = null; // 定義接收網絡數據的字節數組 byte[] inBuff = new byte[DATA_LEN]; // 以指定字節數組建立準備接受數據的DatagramPacket對象 private DatagramPacket inPacket = new DatagramPacket(inBuff, inBuff.length); // 定義一個用於發送的DatagramPacket對象 private DatagramPacket outPacket = null; public void init() throws IOException { try ( // 建立鍵盤輸入流 Scanner scan = new Scanner(System.in)) { // 建立用於發送、接收數據的MulticastSocket對象 // 因爲該MulticastSocket對象須要接收數據,因此有指定端口 socket = new MulticastSocket(BROADCAST_PORT); broadcastAddress = InetAddress.getByName(BROADCAST_IP); // 將該socket加入指定的多點廣播地址 socket.joinGroup(broadcastAddress); // 設置本MulticastSocket發送的數據報會被回送到自身 socket.setLoopbackMode(false); // 初始化發送用的DatagramSocket,它包含一個長度爲0的字節數組 outPacket = new DatagramPacket(new byte[0], 0, broadcastAddress, BROADCAST_PORT); // 啓動以本實例的run()方法做爲線程體的線程 new Thread(this).start(); // 不斷讀取鍵盤輸入 while (scan.hasNextLine()) { // 將鍵盤輸入的一行字符串轉換字節數組 byte[] buff = scan.nextLine().getBytes(); // 設置發送用的DatagramPacket裏的字節數據 outPacket.setData(buff); // 發送數據報 socket.send(outPacket); } } finally { socket.close(); } } public void run() { try { while (true) { // 讀取Socket中的數據,讀到的數據放在inPacket所封裝的字節數組裏。 socket.receive(inPacket); // 打印輸出從socket中讀取的內容 System.out.println("聊天信息:" + new String(inBuff, 0, inPacket.getLength())); } } // 捕捉異常 catch (IOException ex) { ex.printStackTrace(); try { if (socket != null) { // 讓該Socket離開該多點IP廣播地址 socket.leaveGroup(broadcastAddress); // 關閉該Socket對象 socket.close(); } System.exit(1); } catch (IOException e) { e.printStackTrace(); } } } public static void main(String[] args) throws IOException { new MulticastSocketTest().init(); } } public class UdpClient { // 定義發送數據報的目的地 public static final int DEST_PORT = 30000; public static final String DEST_IP = "127.0.0.1"; // 定義每一個數據報的最大大小爲4K private static final int DATA_LEN = 4096; // 定義接收網絡數據的字節數組 byte[] inBuff = new byte[DATA_LEN]; // 以指定字節數組建立準備接受數據的DatagramPacket對象 private DatagramPacket inPacket = new DatagramPacket(inBuff, inBuff.length); // 定義一個用於發送的DatagramPacket對象 private DatagramPacket outPacket = null; public void init() throws IOException { try ( // 建立一個客戶端DatagramSocket,使用隨機端口 DatagramSocket socket = new DatagramSocket()) { // 初始化發送用的DatagramSocket,它包含一個長度爲0的字節數組 outPacket = new DatagramPacket(new byte[0], 0, InetAddress.getByName(DEST_IP), DEST_PORT); // 建立鍵盤輸入流 Scanner scan = new Scanner(System.in); // 不斷讀取鍵盤輸入 while (scan.hasNextLine()) { // 將鍵盤輸入的一行字符串轉換字節數組 byte[] buff = scan.nextLine().getBytes(); // 設置發送用的DatagramPacket裏的字節數據 outPacket.setData(buff); // 發送數據報 socket.send(outPacket); // 讀取Socket中的數據,讀到的數據放在inPacket所封裝的字節數組裏。 socket.receive(inPacket); System.out.println(new String(inBuff, 0, inPacket.getLength())); } } } public static void main(String[] args) throws IOException { new UdpClient().init(); } } public class UdpServer { public static final int PORT = 30000; // 定義每一個數據報的最大大小爲4K private static final int DATA_LEN = 4096; // 定義接收網絡數據的字節數組 byte[] inBuff = new byte[DATA_LEN]; // 以指定字節數組建立準備接受數據的DatagramPacket對象 private DatagramPacket inPacket = new DatagramPacket(inBuff, inBuff.length); // 定義一個用於發送的DatagramPacket對象 private DatagramPacket outPacket; // 定義一個字符串數組,服務器發送該數組的的元素 String[] books = new String[] { "瘋狂Java講義", "輕量級Java EE企業應用實戰", "瘋狂Android講義", "瘋狂Ajax講義" }; public void init() throws IOException { try ( // 建立DatagramSocket對象 DatagramSocket socket = new DatagramSocket(PORT)) { // 採用循環接受數據 for (int i = 0; i < 1000; i++) { // 讀取Socket中的數據,讀到的數據放入inPacket封裝的數組裏。 socket.receive(inPacket); // 判斷inPacket.getData()和inBuff是不是同一個數組 System.out.println(inBuff == inPacket.getData()); // 將接收到的內容轉成字符串後輸出 System.out.println(new String(inBuff, 0, inPacket.getLength())); // 從字符串數組中取出一個元素做爲發送的數據 byte[] sendData = books[i % 4].getBytes(); // 以指定字節數組做爲發送數據、以剛接受到的DatagramPacket的 // 源SocketAddress做爲目標SocketAddress建立DatagramPacket。 outPacket = new DatagramPacket(sendData, sendData.length, inPacket.getSocketAddress()); // 發送數據 socket.send(outPacket); } } } public static void main(String[] args) throws IOException { new UdpServer().init(); } } public class DefaultProxySelectorTest { // 定義須要訪問的網站地址 static String urlStr = "http://www.crazyit.org"; public static void main(String[] args) throws Exception { // 獲取系統的默認屬性 Properties props = System.getProperties(); // 經過系統屬性設置HTTP訪問所用的代理服務器的主機地址、端口 props.setProperty("http.proxyHost", "192.168.10.96"); props.setProperty("http.proxyPort", "8080"); // 經過系統屬性設置HTTP訪問無需使用代理服務器的主機 // 可使用*通配符,多個地址用|分隔 props.setProperty("http.nonProxyHosts", "localhost|192.168.10.*"); // 經過系統屬性設置HTTPS訪問所用的代理服務器的主機地址、端口 props.setProperty("https.proxyHost", "192.168.10.96"); props.setProperty("https.proxyPort", "443"); /* * DefaultProxySelector不支持https.nonProxyHosts屬性, * DefaultProxySelector直接按http.nonProxyHosts的設置規則處理 */ // 經過系統屬性設置FTP訪問所用的代理服務器的主機地址、端口 props.setProperty("ftp.proxyHost", "192.168.10.96"); props.setProperty("ftp.proxyPort", "2121"); // 經過系統屬性設置FTP訪問無需使用代理服務器的主機 props.setProperty("ftp.nonProxyHosts", "localhost|192.168.10.*"); // 經過系統屬性設置設置SOCKS代理服務器的主機地址、端口 props.setProperty("socks.ProxyHost", "192.168.10.96"); props.setProperty("socks.ProxyPort", "1080"); // 獲取系統默認的代理選擇器 ProxySelector selector = ProxySelector.getDefault(); // ① System.out.println("系統默認的代理選擇器:" + selector); // 根據URI動態決定所使用的代理服務器 System.out.println("系統爲ftp://www.crazyit.org選擇的代理服務器爲:" + ProxySelector.getDefault().select( new URI("ftp://www.crazyit.org"))); // ② URL url = new URL(urlStr); // 直接打開鏈接,默認的代理選擇器會使用http.proxyHost、 // http.proxyPort系統屬性設置的代理服務器, // 若是沒法鏈接代理服務器,默認的代理選擇器會嘗試直接鏈接 URLConnection conn = url.openConnection(); // ③ // 設置超時時長。 conn.setConnectTimeout(3000); try (Scanner scan = new Scanner(conn.getInputStream(), "utf-8")) { // 讀取遠程主機的內容 while (scan.hasNextLine()) { System.out.println(scan.nextLine()); } } } } public class ProxySelectorTest { // 下面是代理服務器的地址和端口, // 隨便一個代理服務器的地址和端口 final String PROXY_ADDR = "139.82.12.188"; final int PROXY_PORT = 3124; // 定義須要訪問的網站地址 String urlStr = "http://www.crazyit.org"; public void init() throws IOException, MalformedURLException { // 註冊默認的代理選擇器 ProxySelector.setDefault(new ProxySelector() { @Override public void connectFailed(URI uri, SocketAddress sa, IOException ioe) { System.out.println("沒法鏈接到指定代理服務器!"); } // 根據"業務須要"返回特定的對應的代理服務器 @Override public List<Proxy> select(URI uri) { // 本程序老是返回某個固定的代理服務器。 List<Proxy> result = new ArrayList<>(); result.add(new Proxy(Proxy.Type.HTTP, new InetSocketAddress( PROXY_ADDR, PROXY_PORT))); return result; } }); URL url = new URL(urlStr); // 沒有指定代理服務器、直接打開鏈接 URLConnection conn = url.openConnection(); // ① // 設置超時時長。 conn.setConnectTimeout(3000); try ( // 經過代理服務器讀取數據的Scanner Scanner scan = new Scanner(conn.getInputStream()); PrintStream ps = new PrintStream("index.htm")) { while (scan.hasNextLine()) { String line = scan.nextLine(); // 在控制檯輸出網頁資源內容 System.out.println(line); // 將網頁資源內容輸出到指定輸出流 ps.println(line); } } } public static void main(String[] args) throws IOException, MalformedURLException { new ProxySelectorTest().init(); } } public class ProxyTest { // 下面是代理服務器的地址和端口, // 換成實際有效的代理服務器的地址和端口 final String PROXY_ADDR = "129.82.12.188"; final int PROXY_PORT = 3124; // 定義須要訪問的網站地址 String urlStr = "http://www.crazyit.org"; public void init() throws IOException, MalformedURLException { URL url = new URL(urlStr); // 建立一個代理服務器對象 Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress( PROXY_ADDR, PROXY_PORT)); // 使用指定的代理服務器打開鏈接 URLConnection conn = url.openConnection(proxy); // 設置超時時長。 conn.setConnectTimeout(5000); try ( // 經過代理服務器讀取數據的Scanner Scanner scan = new Scanner(conn.getInputStream(), "utf-8"); PrintStream ps = new PrintStream("index.htm")) { while (scan.hasNextLine()) { String line = scan.nextLine(); // 在控制檯輸出網頁資源內容 System.out.println(line); // 將網頁資源內容輸出到指定輸出流 ps.println(line); } } } public static void main(String[] args) throws IOException, MalformedURLException { new ProxyTest().init(); } } |