javaSE學習筆記(16) 網路編程 基本概念 如今,電腦已經成為人們學習、工作、生活必不可少的工具。我們利用電腦可以和親朋好友網上聊天,也可以玩網游、發郵件等等,這些功能實現都離不開電腦網路。電腦網路實現了不同電腦之間的通信,這必須依靠編寫網路程式來實現。下麵,我們將教大家如何編寫網路 ...
javaSE學習筆記(16)---網路編程
基本概念
如今,電腦已經成為人們學習、工作、生活必不可少的工具。我們利用電腦可以和親朋好友網上聊天,也可以玩網游、發郵件等等,這些功能實現都離不開電腦網路。電腦網路實現了不同電腦之間的通信,這必須依靠編寫網路程式來實現。下麵,我們將教大家如何編寫網路程式。
在學習編程之前,我們首先要瞭解關於網路通信的一些概念。
▪ 什麼是電腦網路?
電腦網路是指將地理位置不同的具有獨立功能的多台電腦及其外部設備,通過通信線路連接起來,在網路操作系統,網路管理軟體及網路通信協議的管理和協調下,實現資源共用和信息傳遞的電腦系統。
從其中我們可以提取到以下內容:
電腦網路的作用:資源共用和信息傳遞。
- 電腦網路的組成:
a) 電腦硬體:電腦(大中小型伺服器,台式機、筆記本等)、外部設備(路由器、交換機等)、通信線路(雙絞線、光纖等)。
b) 電腦軟體:網路操作系統(Windows 2000 Server/Advance Server、Unix、Linux等)、網路管理軟體(WorkWin、SugarNMS等)、網路通信協議(如TCP/IP協議棧等)。
- 電腦網路的多台電腦是具有獨立功能的,而不是脫離網路就無法存在的。
▪ 什麼是網路通信協議?
通過電腦網路可以實現不同電腦之間的連接與通信,但是電腦網路中實現通信必須有一些約定即通信協議,對速率、傳輸代碼、代碼結構、傳輸控制步驟、出錯控制等制定標準。就像兩個人想要順利溝通就必須使用同一種語言一樣,如果一個人只懂英語而另外一個人只懂中文,這樣就會造成沒有共同語言而無法溝通。
國際標準化組織(ISO,即International Organization for Standardization)定義了網路通信協議的基本框架,被稱為OSI(Open System Interconnect,即開放系統互聯)模型。要制定通訊規則,內容會很多,比如要考慮A電腦如何找到B電腦,A電腦在發送信息給B電腦時是否需要B電腦進行反饋,A電腦傳送給B電腦的數據格式又是怎樣的?內容太多太雜,所以OSI模型將這些通訊標準進行層次劃分,每一層次解決一個類別的問題,這樣就使得標準的制定沒那麼複雜。OSI模型制定的七層標準模型,分別是:應用層,表示層,會話層,傳輸層,網路層,數據鏈路層,物理層。
OSI七層協議模型如圖所示:
雖然國際標準化組織制定了這樣一個網路通信協議的模型,但是實際上互聯網通訊使用最多的網路通信協議是TCP/IP網路通信協議。
TCP/IP 是一個協議族,也是按照層次劃分,共四層:應用層,傳輸層,互連網路層,網路介面層(物理+數據鏈路層)。
那麼TCP/IP協議和OSI模型有什麼區別呢?OSI網路通信協議模型,是一個參考模型,而TCP/IP協議是事實上的標準。TCP/IP協議參考了OSI 模型,但是並沒有嚴格按照OSI規定的七層標準去劃分,而只劃分了四層,這樣會更簡單點,當劃分太多層次時,你很難區分某個協議是屬於哪個層次的。TCP/IP協議和OSI模型也並不衝突,TCP/IP協議中的應用層協議,就對應於OSI中的應用層,表示層,會話層。就像以前有工業部和信息產業部,現在實行大部制後只有工業和信息化部一個部門,但是這個部門還是要做以前兩個部門一樣多的事情,本質上沒有多大的差別。TCP/IP中有兩個重要的協議,傳輸層的TCP協議和互連網路層的IP協議,因此就拿這兩個協議做代表,來命名整個協議族了,再說TCP/IP協議時,是指整個協議族。
▪ 網路協議的分層
由於網路結點之間聯繫很複雜,在制定協議時,把複雜成份分解成一些簡單的成份,再將它們複合起來。最常用的複合方式是層次方式,即同層間可以通信、上一層可以調用下一層,而與再下一層不發生關係。
把用戶應用程式作為最高層,把物理通信線路作為最低層,將其間的協議處理分為若幹層,規定每層處理的任務,也規定每層的介面標準。
ISO模型與TCP/IP模型的對應關係如圖所示。 開放系統互連參考模型與TCP/IP參考模型對比
▪ 數據封裝與解封:
由於用戶傳輸的數據一般都比較大,有的可以達到MB位元組,一次性發送出去十分困難,於是就需要把數據分成許多片段,再按照一定的次序發送出去。這個過程就需要對數據進行封裝。
數據封裝(Data Encapsulation)是指將協議數據單元(PDU)封裝在一組協議頭和協議尾中的過程。在OSI七層參考模型中,每層主要負責與其它機器上的對等層進行通信。該過程是在協議數據單元(PDU)中實現的,其中每層的PDU一般由本層的協議頭、協議尾和數據封裝構成。
1.數據發送處理過程
(1)應用層將數據交給傳輸層,傳輸層添加上TCP的控制信息(稱為TCP頭部),這個數據單元稱為段(Segment),加入控制信息的過程稱為封裝。然後,將段交給網路層。
(2)網路層接收到段,再添加上IP頭部,這個數據單元稱為包(Packet)。然後,將包交給數據鏈路層。
(3) 數據鏈路層接收到包,再添加上MAC頭部和尾部,這個數據單元稱為幀(Frame)。然後,將幀交給物理層。
(4)物理層將接收到的數據轉化為比特流,然後在網線中傳送。
2.數據接收處理過程
(1)物理層接收到比特流,經過處理後將數據交給數據鏈路層。
(2)數據鏈路層將接收到的數據轉化為數據幀,再除去MAC頭部和尾部,這個除去控制信息的過程稱為解封,然後將包交給網路層。
(3)網路層接收到包,再除去IP頭部,然後將段交給傳輸層。
(4)傳輸層接收到段,再除去TCP頭部,然後將數據交給應用層。
從以上傳輸過程中,可以總結出以下規則:
(1)發送方數據處理的方式是從高層到底層,逐層進行數據封裝。
(2)接收方數據處理的方式是從底層到高層,逐層進行數據解封裝。
接收方的每一層只把對該層有意義的數據拿走,或者說每一層只能處理髮送方同等層的數據,然後把其餘的部分傳遞給上一層,這就是對等層通信的概念。
數據封裝與解封如圖所示:
數據封裝
數據解封
▪ IP地址:
用來標識網路中的一個通信實體的地址。通信實體可以是電腦、路由器等。 比如互聯網的每個伺服器都要有自己的IP地址,而每個區域網的電腦要通信也要配置IP地址。路由器是連接兩個或多個網路的網路設備。
目前主流使用的IP地址是IPV4,但是隨著網路規模的不斷擴大,IPV4面臨著枯竭的危險,所以推出了IPV6。
IPV4:32位地址,並以8位為一個單位,分成四部分,以點分十進位表示,如192.168.0.1。因為8位二進位的計數範圍是00000000---11111111,對應十進位的0-255,所以-4.278.4.1是錯誤的IPV4地址。
IPV6:128位(16個位元組)寫成8個16位的無符號整數,每個整數用四個十六進位位表示,每個數之間用冒號(:)分開,如:3ffe:3201:1401:1280:c8ff:fe4d:db39:1984
註意事項
127.0.0.1 本機地址
- 192.168.0.0--192.168.255.255為私有地址,屬於非註冊地址,專門為組織機構內部使用。
▪ 埠:
IP地址用來標識一臺電腦,但是一臺電腦上可能提供多種網路應用程式,如何來區分這些不同的程式呢?這就要用到埠。
埠是虛擬的概念,並不是說在主機上真的有若幹個埠。通過埠,可以在一個主機上運行多個網路應用程式。 埠的表示是一個16位的二進位整數,對應十進位的0-65535。
Oracle、MySQL、Tomcat、QQ、msn、迅雷、電驢、360等網路程式都有自己的埠。
總結
IP地址好比每個人的地址(門牌號),埠好比是房間號。必須同時指定IP地址和埠號才能夠正確的發送數據。
\2. IP地址好比為電話號碼,而埠號就好比為分機號。
▪ URL:
在www上,每一信息資源都有統一且唯一的地址,該地址就叫URL(Uniform Resource Locator),它是www的統一資源定位符。URL由4部分組成:協議 、存放資源的主機功能變數名稱、資源文件名和埠號。如果未指定該埠號,則使用協議預設的埠。例如http 協議的預設埠為 80。 在瀏覽器中訪問網頁時,地址欄顯示的地址就是URL。
在java.net包中提供了URL類,該類封裝了大量複雜的涉及從遠程站點獲取信息的細節。
▪ Socket:
我們開發的網路應用程式位於應用層,TCP和UDP屬於傳輸層協議,在應用層如何使用傳輸層的服務呢?在應用層和傳輸層之間,則是使用套接Socket來進行分離。
套接字就像是傳輸層為應用層開的一個小口,應用程式通過這個小口向遠程發送數據,或者接收遠程發來的數據;而這個小口以內,也就是數據進入這個口之後,或者數據從這個口出來之前,是不知道也不需要知道的,也不會關心它如何傳輸,這屬於網路其它層次工作。
Socket實際是傳輸層供給應用層的編程介面。Socket就是應用層與傳輸層之間的橋梁。使用Socket編程可以開發客戶機和伺服器應用程式,可以在本地網路上進行通信,也可通過Internet在全球範圍內通信。
socket的作用
TCP協議和UDP協議的聯繫和區別
TCP協議和UDP協議是傳輸層的兩種協議。Socket是傳輸層供給應用層的編程介面,所以Socket編程就分為TCP編程和UDP編程兩類。
在網路通訊中,TCP方式就類似於撥打電話,使用該種方式進行網路通訊時,需要建立專門的虛擬連接,然後進行可靠的數據傳輸,如果數據發送失敗,則客戶端會自動重發該數據。而UDP方式就類似於發送簡訊,使用這種方式進行網路通訊時,不需要建立專門的虛擬連接,傳輸也不是很可靠,如果發送失敗則客戶端無法獲得。
這兩種傳輸方式都在實際的網路編程中使用,重要的數據一般使用TCP方式進行數據傳輸,而大量的非核心數據則可以通過UDP方式進行傳遞,在一些程式中甚至結合使用這兩種方式進行數據傳遞。
由於TCP需要建立專用的虛擬連接以及確認傳輸是否正確,所以使用TCP方式的速度稍微慢一些,而且傳輸時產生的數據量要比UDP稍微大一些。
總結
TCP是面向連接的,傳輸數據安全,穩定,效率相對較低。
- UDP是面向無連接的,傳輸數據不安全,效率較高。
TCP協議
TCP(Transfer Control Protocol)是面向連接的,所謂面向連接,就是當電腦雙方通信時必需經過先建立連接,然後傳送數據,最後拆除連接三個過程。
TCP在建立連接時又分三步走:
第一步,是請求端(客戶端)發送一個包含SYN即同步(Synchronize)標誌的TCP報文,SYN同步報文會指明客戶端使用的埠以及TCP連接的初始序號。
第二步,伺服器在收到客戶端的SYN報文後,將返回一個SYN+ACK的報文,表示客戶端的請求被接受,同時TCP序號被加一,ACK即確認(Acknowledgement)。
第三步,客戶端也返回一個確認報文ACK給伺服器端,同樣TCP序列號被加一,到此一個TCP連接完成。然後才開始通信的第二步:數據處理。
這就是所說的TCP的三次握手(Three-way Handshake)。
UDP協議
基於TCP協議可以建立穩定連接的點對點的通信。這種通信方式實時、快速、安全性高,但是很占用系統的資源。
在網路傳輸方式上,還有另一種基於UDP協議的通信方式,稱為數據報通信方式。在這種方式中,每個數據發送單元被統一封裝成數據報包的方式,發送方將數據報包發送到網路中,數據報包在網路中去尋找它的目的地。
java網路編程
Java為了可移植性,不允許直接調用操作系統,而是由java.net包來提供網路功能。Java虛擬機負責提供與操作系統的實際連接。下麵我們來介紹幾個java.net包中的常用的類。
InetAddress
作用:封裝電腦的IP地址和DNS(沒有埠信息)。
註:DNS是Domain Name System,功能變數名稱系統。
特點:這個類沒有構造方法。如果要得到對象,只能通過靜態方法:getLocalHost()、getByName()、 getAllByName()、 getAddress()、getHostName()。
使用getLocalHost方法創建InetAddress對象
import java.net.InetAddress;
import java.net.UnknownHostException;
public class Test1 {
public static void main(String[] args) throws UnknownHostException {
InetAddress addr = InetAddress.getLocalHost();
//返回IP地址:192.168.1.110
System.out.println(addr.getHostAddress());
//輸出電腦名:gaoqi
System.out.println(addr.getHostName());
}
}
根據功能變數名稱得到InetAddress對象
import java.net.InetAddress;
import java.net.UnknownHostException;
public class Test2 {
public static void main(String[] args) throws UnknownHostException {
InetAddress addr = InetAddress.getByName("www.sxt.cn");
// 返回 sxt伺服器的IP:59.110.14.7
System.out.println(addr.getHostAddress());
// 輸出:www.sxt.cn
System.out.println(addr.getHostName());
}
}
根據IP得到InetAddress對象
import java.net.InetAddress;
import java.net.UnknownHostException;
public class Test3 {
public static void main(String[] args) throws UnknownHostException {
InetAddress addr = InetAddress.getByName("59.110.14.7");
// 返回sxt伺服器的IP:59.110.14.7
System.out.println(addr.getHostAddress());
/*
* 輸出ip而不是功能變數名稱。如果這個IP地址不存在或DNS伺服器不允許進行IP地址
* 和功能變數名稱的映射,getHostName方法就直接返回這個IP地址。
*/
System.out.println(addr.getHostName());
}
}
InetSocketAddress
作用:包含IP和埠信息,常用於Socket通信。此類實現 IP 套接字地址(IP 地址 + 埠號),不依賴任何協議。
InetSocketAddress的使用
import java.net.InetSocketAddress;
public class Test4 {
public static void main(String[] args) {
InetSocketAddress socketAddress = new InetSocketAddress("127.0.0.1", 8080);
InetSocketAddress socketAddress2 = new InetSocketAddress("localhost", 9000);
System.out.println(socketAddress.getHostName());
System.out.println(socketAddress2.getAddress());
}
}
URL類
IP地址唯一標識了Internet上的電腦,而URL則標識了這些電腦上的資源。類 URL 代表一個統一資源定位符,它是指向互聯網“資源”的指針。資源可以是簡單的文件或目錄,也可以是對更為複雜的對象的引用,例如對資料庫或搜索引擎的查詢。
為了方便程式員編程,JDK中提供了URL類,該類的全名是java.net.URL,有了這樣一個類,就可以使用它的各種方法來對URL對象進行分割、合併等處理。
URL類的使用
import java.net.MalformedURLException;
import java.net.URL;
public class Test5 {
public static void main(String[] args) throws MalformedURLException {
URL u = new URL("http://www.google.cn:80/webhp#aa?canhu=33");
System.out.println("獲取與此url關聯的協議的預設埠:" + u.getDefaultPort());
System.out.println("getFile:" + u.getFile()); // 埠號後面的內容
System.out.println("主機名:" + u.getHost()); // www.google.cn
System.out.println("路徑:" + u.getPath()); // 埠號後,參數前的內容
// 如果www.google.cn:80則返回80.否則返回-1
System.out.println("埠:" + u.getPort());
System.out.println("協議:" + u.getProtocol());
System.out.println("參數部分:" + u.getQuery());
System.out.println("錨點:" + u.getRef());
URL u1 = new URL("http://www.abc.com/aa/");
URL u2 = new URL(u, "2.html"); // 相對路徑構建url對象
System.out.println(u2.toString()); // http://www.abc.com/aa/2.html
}
}
最簡單的網路爬蟲
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
public class Test6 {
public static void main(String[] args) {
basicSpider();
}
//網路爬蟲
static void basicSpider() {
URL url = null;
InputStream is = null;
BufferedReader br = null;
StringBuilder sb = new StringBuilder();
String temp = "";
try {
url = new URL("http://www.baidu.com");
is = url.openStream();
br = new BufferedReader(new InputStreamReader(is));
/*
* 這樣就可以將網路內容下載到本地機器。
* 然後進行數據分析,建立索引。這也是搜索引擎的第一步。
*/
while ((temp = br.readLine()) != null) {
sb.append(temp);
}
System.out.println(sb);
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
基於TCP協議的Socket編程和通信
在網路通訊中,第一次主動發起通訊的程式被稱作客戶端(Client)程式,簡稱客戶端,而在第一次通訊中等待連接的程式被稱作伺服器端(Server)程式,簡稱伺服器。一旦通訊建立,則客戶端和伺服器端完全一樣,沒有本質的區別。
“請求-響應”模式:
Socket類:發送TCP消息。
- ServerSocket類:創建伺服器。
套接字是一種進程間的數據交換機制。這些進程既可以在同一機器上,也可以在通過網路連接的不同機器上。換句話說,套接字起到通信端點的作用。單個套接字是一個端點,而一對套接字則構成一個雙向通信通道,使非關聯進程可以在本地或通過網路進行數據交換。一旦建立套接字連接,數據即可在相同或不同的系統中雙向或單向發送,直到其中一個端點關閉連接。套接字與主機地址和埠地址相關聯。主機地址就是客戶端或伺服器程式所在的主機的IP地址。埠地址是指客戶端或伺服器程式使用的主機的通信埠。
在客戶端和伺服器中,分別創建獨立的Socket,並通過Socket的屬性,將兩個Socket進行連接,這樣,客戶端和伺服器通過套接字所建立的連接使用輸入輸出流進行通信。
TCP/IP套接字是最可靠的雙向流協議,使用TCP/IP可以發送任意數量的數據。
實際上,套接字只是電腦上已編號的埠。如果發送方和接收方電腦確定好埠,他們就可以通信了。
如圖所示為客戶端與伺服器端的通信關係圖:
TCP/IP通信連接的簡單過程:
位於A電腦上的TCP/IP軟體向B電腦發送包含埠號的消息,B電腦的TCP/IP軟體接收該消息,併進行檢查,查看是否有它知道的程式正在該埠上接收消息。如果有,他就將該消息交給這個程式。
要使程式有效地運行,就必須有一個客戶端和一個伺服器。
通過Socket的編程順序:
創建伺服器ServerSocket,在創建時,定義ServerSocket的監聽埠(在這個埠接收客戶端發來的消息)。
ServerSocket調用accept()方法,使之處於阻塞狀態。
創建客戶端Socket,並設置伺服器的IP及埠。
客戶端發出連接請求,建立連接。
分別取得伺服器和客戶端Socket的InputStream和OutputStream。
利用Socket和ServerSocket進行數據傳輸。
關閉流及Socket。
TCP:單向通信Socket之伺服器端
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
/**
* 最簡單的伺服器端代碼
* @author Administrator
*/
public class BasicSocketServer {
public static void main(String[] args) {
Socket socket = null;
BufferedWriter bw = null;
try {
// 建立伺服器端套接字:指定監聽的介面
ServerSocket serverSocket = new ServerSocket(8888);
System.out.println("服務端建立監聽");
// 監聽,等待客戶端請求,並願意接收連接
socket = serverSocket.accept();
// 獲取socket的輸出流,並使用緩衝流進行包裝
bw = new BufferedWriter(new
OutputStreamWriter(socket.getOutputStream()));
// 向客戶端發送反饋信息
bw.write("hhhh");
} catch (IOException e) {
e.printStackTrace();
} finally {
// 關閉流及socket連接
if (bw != null) {
try {
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
TCP:單向通信Socket之客戶端
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.net.Socket;
/**
* 最簡單的Socket客戶端
* @author Administrator
*/
public class BasicSocketClient {
public static void main(String[] args) {
Socket socket = null;
BufferedReader br = null;
try {
/*
* 創建Scoket對象:指定要連接的伺服器的IP和埠而不是自己機器的
* 埠。發送埠是隨機的。
*/
socket = new Socket(InetAddress.getLocalHost(), 8888);
//獲取scoket的輸入流,並使用緩衝流進行包裝
br = new BufferedReader(new
InputStreamReader(socket.getInputStream()));
//接收伺服器端發送的信息
System.out.println(br.readLine());
} catch (Exception e) {
e.printStackTrace();
} finally {
// 關閉流及socket連接
if (br != null) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
TCP:雙向通信Socket之伺服器端
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public static void main(String[] args){
Socket socket = null;
BufferedReader in = null;
BufferedWriter out = null;
BufferedReader br = null;
try {
//創建伺服器端套接字:指定監聽埠
ServerSocket server = new ServerSocket(8888);
//監聽客戶端的連接
socket = server.accept();
//獲取socket的輸入輸出流接收和發送信息
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
out = new BufferedWriter(new
OutputStreamWriter(socket.getOutputStream()));
br = new BufferedReader(new InputStreamReader(System.in));
while (true) {
//接收客戶端發送的信息
String str = in.readLine();
System.out.println("客戶端說:" + str);
String str2 = "";
//如果客戶端發送的是“end”則終止連接
if (str.equals("end")){
break;
}
//否則,發送反饋信息
str2 = br.readLine(); // 讀到\n為止,因此一定要輸入換行符!
out.write(str2 + "\n");
out.flush();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//關閉資源
if(in != null){
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(out != null){
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(br != null){
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(socket != null){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
TCP:雙向通信Socket之客戶端
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
public class Client {
public static void main(String[] args) {
Socket socket = null;
BufferedReader in = null;
BufferedWriter out = null;
BufferedReader wt = null;
try {
//創建Socket對象,指定伺服器端的IP與埠
socket = new Socket(InetAddress.getLocalHost(), 8888);
//獲取scoket的輸入輸出流接收和發送信息
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
out = new BufferedWriter(new
OutputStreamWriter(socket.getOutputStream()));
wt = new BufferedReader(new InputStreamReader(System.in));
while (true) {
//發送信息
String str = wt.readLine();
out.write(str + "\n");
out.flush();
//如果輸入的信息為“end”則終止連接
if (str.equals("end")) {
break;
}
//否則,接收並輸出伺服器端信息
System.out.println("伺服器端說:" + in.readLine());
}
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
// 關閉資源
if (out != null) {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (wt != null) {
try {
wt.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
執行結果如圖所示: 運行效果圖—伺服器端
運行效果圖—客戶端:
菜鳥雷區
運行時,要先啟動伺服器端,再啟動客戶端,才能得到正常的運行效果。
但是,上面這個程式,必須按照安排好的順序,伺服器和客戶端一問一答!不夠靈活!!可以使用多線程實現更加靈活的雙向通訊!!
伺服器端:一個線程專門發送消息,一個線程專門接收消息。
客戶端:一個線程專門發送消息,一個線程專門接收消息。
TCP:聊天室之伺服器端
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
public class ChatServer {
public static void main(String[] args) {
ServerSocket server = null;
Socket socket = null;
BufferedReader in = null;
try {
server = new ServerSocket(8888);
socket = server.accept();
//創建向客戶端發送消息的線程,並啟動
new ServerThread(socket).start();
// main線程負責讀取客戶端發來的信息
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
while (true) {
String str = in.readLine();
System.out.println("客戶端說:" + str);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (in != null) {
in.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (socket != null) {
socket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 專門向客戶端發送消息的線程
*
* @author Administrator
*
*/
class ServerThread extends Thread {
Socket ss;
BufferedWriter out;
BufferedReader br;
public ServerThread(Socket ss) {
this.ss = ss;
try {
out = new BufferedWriter(new OutputStreamWriter(ss.getOutputStream()));
br = new BufferedReader(new InputStreamReader(System.in));
} catch (IOException e) {
e.printStackTrace();
}
}
public void run() {
try {
while (true) {
String str2 = br.readLine();
out.write(str2 + "\n");
out.flush();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if(out != null){
out.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if(br != null){
br.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
TCP:聊天室之客戶端
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
public class ChatClient {
public static void main(String[] args) {
Socket socket = null;
BufferedReader in = null;
try {
socket = new Socket(InetAddress.getByName("127.0.1.1"), 8888);
// 創建向伺服器端發送信息的線程,並啟動
new ClientThread(socket).start();
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
// main線程負責接收伺服器發來的信息
while (true) {
System.out.println("伺服器說:" + in.readLine());
}
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (socket != null) {
socket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (in != null) {
in.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 用於向伺服器發送消息
*
* @author Administrator
*
*/
class ClientThread extends Thread {
Socket s;
BufferedWriter out;
BufferedReader wt;
public ClientThread(Socket s) {
this.s = s;
try {
out = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
wt = new BufferedReader(new InputStreamReader(System.in));
} catch (IOException e) {
e.printStackTrace();
}
}
public void run() {
try {
while (true) {
String str = wt.readLine();
out.write(str + "\n");
out.flush();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (wt != null) {
wt.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (out != null) {
out.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
執行結果如圖所示:運行效果圖—伺服器端
運行效果圖—客戶端:
UDP通訊的實現
DatagramSocket:用於發送或接收數據報包
當伺服器要向客戶端發送數據時,需要在伺服器端產生一個DatagramSocket對象,在客戶端產生一個DatagramSocket對象。伺服器端的DatagramSocket將DatagramPacket發送到網路上,然後被客戶端的DatagramSocket接收。
DatagramSocket有兩種常用的構造函數。一種是無需任何參數的,常用於客戶端;另一種需要指定埠,常用於伺服器端。如下所示:
DatagramSocket() :構造數據報套接字並將其綁定到本地主機上任何可用的埠。
DatagramSocket(int port) :創建數據報套接字並將其綁定到本地主機上的指定埠。
常用方法:
Ø send(DatagramPacket p) :從此套接字發送數據報包。
Ø receive(DatagramPacket p) :從此套接字接收數據報包。
Ø close() :關閉此數據報套接字。
▪ DatagramPacket:數據容器(封包)的作用
此類表示數據報包。 數據報包用來實現封包的功能。
常用方法:
Ø DatagramPacket(byte[] buf, int length) :構造數據報包,用來接收長度為 length 的數據包。
Ø DatagramPacket(byte[] buf, int length, InetAddress address, int port) :構造數據報包,用來將長度為 length 的包發送到指定主機上的指定埠號。
Ø getAddress() :獲取發送或接收方電腦的IP地址,此數據報將要發往該機器或者是從該機器接收到的。
Ø getData() :獲取發送或接收的數據。
Ø setData(byte[] buf) :設置發送的數據。
UDP通信編程基本步驟:
創建客戶端的DatagramSocket,創建時,定義客戶端的監聽埠。
創建伺服器端的DatagramSocket,創建時,定義伺服器端的監聽埠。
在伺服器端定義DatagramPacket對象,封裝待發送的數據包。
客戶端將數據報包發送出去。
伺服器端接收數據報包。
UDP:單向通信之客戶端
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
public class Client {
public static void main(String[] args) throws Exception {
byte[] b = "北京尚學堂".getBytes();
//必須告訴數據報包要發到哪台電腦的哪個埠,發送的數據以及數據的長度
DatagramPacket dp = new DatagramPacket(b,b.length,new
InetSocketAddress("localhost",8999));
//創建數據報套接字:指定發送信息的埠
DatagramSocket ds = new DatagramSocket(9000);
//發送數據報包
ds.send(dp);
//關閉資源
ds.close();
}
}
UDP:單向通信之伺服器端
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class Server {
public static void main(String[] args) throws Exception {
//創建數據報套接字:指定接收信息的埠
DatagramSocket ds = new DatagramSocket(8999);
byte[] b = new byte[1024];
//創建數據報包,指定要接收的數據的緩存位置和長度
DatagramPacket dp = new DatagramPacket(b, b.length);
//接收客戶端發送的數據報
ds.receive(dp); // 阻塞式方法
//dp.getLength()返回實際收到的數據的位元組數
String string = new String(dp.getData(), 0, dp.getLength());
System.out.println(string);
//關閉資源
ds.close();
}
}
通過位元組數組流ByteArrayInputStream、ByteArrayOutputStream與數據流DataInputStream、DataOutputStream聯合使用可以傳遞基本數據類型。
UDP:基本數據類型的傳遞之客戶端
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
public class Client {
public static void main(String[] args) throws Exception {
long n = 2000L;
ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(bos);
dos.writeLong(n);
//獲取位元組數組流中的位元組數組(我們要發送的數據)
byte[] b = bos.toByteArray();
//必須告訴數據報包要發到哪台電腦的哪個埠,發送的數據以及數據的長度
DatagramPacket dp = new DatagramPacket(b,b.length,new
InetSocketAddress("localhost",8999));
//創建數據報套接字:指定發送信息的埠
DatagramSocket ds = new DatagramSocket(9000);
//發送數據報包
ds.send(dp);
//關閉資源
dos.close();
bos.close();
ds.close();
}
}
UDP:基本數據類型的傳遞之伺服器端
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class Server {
public static void main(String[] args) throws Exception {
//創建數據報套接字:指定接收信息的埠
DatagramSocket ds = new DatagramSocket(8999);
byte[] b = new byte[1024];
//創建數據報包,指定要接收的數據的緩存位置和長度
DatagramPacket dp = new DatagramPacket(b, b.length);
//接收客戶端發送的數據報
ds.receive(dp); // 阻塞式方法
//dp.getData():獲取客戶端發送的數據,返回值是一個位元組數組
ByteArrayInputStream bis = new ByteArrayInputStream(dp.getData());
DataInputStream dis = new DataInputStream(bis);
System.out.println(dis.readLong());
//關閉資源
dis.close();
bis.close();
ds.close();
}
}
通過位元組數組流ByteArrayInputStream、ByteArrayOutputStream與數據流ObjectInputStream、ObjectOutputStream聯合使用可以傳遞對象。
UDP:對象的傳遞之Person類
import java.io.Serializable;
public class Person implements Serializable{
private static final long serialVersionUID = 1L;
int age;
String name;
public Person(int age, String name) {
super();
this.age = age;
this.name = name;
}
@Override
public String toString() {
return "Person [age=" + age + ", name=" + name + "]";
}
}
UDP:對象的傳遞之客戶端
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
public class Client {
public static void main(String[] args) throws Exception {
//創建要發送的對象
Person person = new Person(18, "高淇");
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(person);
//獲取位元組數組流中的位元組數組(我們要發送的數據)
byte[] b = bos.toByteArray();
//必須告訴數據報包要發到哪台電腦的哪個埠,發送的數據以及數據的長度
DatagramPacket dp = new DatagramPacket(b,b.length,new
InetSocketAddress("localhost",8999));
//創建數據報套接字:指定發送信息的埠
DatagramSocket ds = new DatagramSocket(9000);
//發送數據報包
ds.send(dp);
//關閉資源
oos.close();
bos.close();
ds.close();
}
}
UDP:對象的傳遞之伺服器端
import java.io.ByteArrayInputStream;
import java.io.ObjectInputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class Server {
public static void main(String[] args) throws Exception {
//創建數據報套接字:指定接收信息的埠
DatagramSocket ds = new DatagramSocket(8999);
byte[] b = new byte[1024];
//創建數據報包,指定要接收的數據的緩存位置和長度
DatagramPacket dp = new DatagramPacket(b, b.length);
//接收客戶端發送的數據報
ds.receive(dp); // 阻塞式方法
//dp.getData():獲取客戶端發送的數據,返回值是一個位元組數組
ByteArrayInputStream bis = new ByteArrayInputStream(dp.getData());
ObjectInputStream ois = new ObjectInputStream(bis);
System.out.println(ois.readObject());
//關閉資源
ois.close();
bis.close();
ds.close();
}
}
總結
埠是虛擬的概念,並不是說在主機上真的有若幹個埠。
在www上,每一信息資源都有統一且唯一的地址,該地址就叫URL(Uniform Resource Locator),它是www的統一資源定位符。
TCP與UDP的區別
1)TCP是面向連接的,傳輸數據安全,穩定,效率相對較低。
2)UDP是面向無連接的,傳輸數據不安全,效率較高。
Socket通信是一種基於TCP協議,建立穩定連接的點對點的通信。
網路編程是由java.net包來提供網路功能。
1)InetAddress:封裝電腦的IP地址和DNS(沒有埠信息!)。
2)InetSocketAddress:包含IP和埠,常用於Socket通信。
3)URL:以使用它的各種方法來對URL對象進行分割、合併等處理。
基於TCP協議的Socket編程和通信
1)“請求-響應”模式:
--Socket類:發送TCP消息。
--ServerSocket類:創建伺服器。
UDP通訊的實現
1)DatagramSocket:用於發送或接收數據報包。
2)常用方法:send()、receive()、 close()。
DatagramPacket:數據容器(封包)的作用
1)常用方法:構造方法、getAddrress(獲取發送或接收方電腦的IP地址)、getData(獲取發送或接收的數據)、setData(設置發送的數據)。\1. 埠是虛擬的概念,並不是說在主機上真的有若幹個埠。
在www上,每一信息資源都有統一且唯一的地址,該地址就叫URL(Uniform Resource Locator),它是www的統一資源定位符。
TCP與UDP的區別
1)TCP是面向連接的,傳輸數據安全,穩定,效率相對較低。
2)UDP是面向無連接的,傳輸數據不安全,效率較高。
Socket通信是一種基於TCP協議,建立穩定連接的點對點的通信。
網路編程是由java.net包來提供網路功能。
1)InetAddress:封裝電腦的IP地址和DNS(沒有埠信息!)。
2)InetSocketAddress:包含IP和埠,常用於Socket通信。
3)URL:以使用它的各種方法來對URL對象進行分割、合併等處理。
基於TCP協議的Socket編程和通信
1)“請求-響應”模式:
--Socket類:發送TCP消息。
--ServerSocket類:創建伺服器。
UDP通訊的實現
1)DatagramSocket:用於發送或接收數據報包。
2)常用方法:send()、receive()、 close()。
DatagramPacket:數據容器(封包)的作用
1)常用方法:構造方法、getAddrress(獲取發送或接收方電腦的IP地址)、getData(獲取發送或接收的數據)、setData(設置發送的數據)。