java中no1_【Java】-- 网络编程のNo.1
在現(xiàn)有的網(wǎng)絡(luò)中,網(wǎng)絡(luò)通訊的方式主要有兩種:
TCP(傳輸控制協(xié)議)方式
UDP(用戶數(shù)據(jù)報協(xié)議)方式
在網(wǎng)絡(luò)通訊中,TCP方式就類似于撥打電話,使用該種方式進行網(wǎng)絡(luò)通訊時,需要建立專門的虛擬連接,然后進行可靠的數(shù)據(jù)傳輸,如果數(shù)據(jù)發(fā)送失敗,則客戶端會自動重發(fā)該數(shù)據(jù)。
而UDP方式就類似于發(fā)送短信,使用這種方式進行網(wǎng)絡(luò)通訊時,不需要建立專門的虛擬連接,傳輸也不是很可靠,如果發(fā)送失敗則客戶端無法獲得。
這兩種傳輸方式都是實際的網(wǎng)絡(luò)編程中進行使用,重要的數(shù)據(jù)一般使用TCP方式進行數(shù)據(jù)傳輸,而大量的非核心數(shù)據(jù)則都通過UDP方式進行傳遞,在一些程序中甚至結(jié)合使用這兩種方式進行數(shù)據(jù)的傳遞。
由于TCP需要建立專用的虛擬連接以及確認傳輸是否正確,所以使用TCP方式的速度稍微慢一些,而且傳輸時產(chǎn)生的數(shù)據(jù)量要比UDP稍微大一些。
無論使用TCP方式還是UDP方式進行網(wǎng)絡(luò)通訊,網(wǎng)絡(luò)編程都是由客戶端和服務(wù)器端組成當然,B/S結(jié)構(gòu)的編程中只需要實現(xiàn)服務(wù)器端即可。所以,下面介紹網(wǎng)絡(luò)編程的步驟時,均以C/S結(jié)構(gòu)為基礎(chǔ)進行介紹。
網(wǎng)絡(luò)編程技術(shù)
1、客戶端網(wǎng)絡(luò)編程步驟
客戶端(Client)是指網(wǎng)絡(luò)編程中首先發(fā)起連接的程序,客戶端一般實現(xiàn)程序界面和基本邏輯實現(xiàn),在進行實際的客戶端編程時,無論客戶端復(fù)雜還是簡單,以及客戶端實現(xiàn)的方式,客戶端的編程主要由三個步驟實現(xiàn):
建立網(wǎng)絡(luò)連接
客戶端網(wǎng)絡(luò)編程的第一步都是建立網(wǎng)絡(luò)連接。在建立網(wǎng)絡(luò)連接時需要指定連接到的服務(wù)器的IP地址和端口號,建立完成以后,會形成一條虛擬的連接,后續(xù)的操作就可以通過該連接實現(xiàn)數(shù)據(jù)交換了。
交換數(shù)據(jù)
連接建立以后,就可以通過這個連接交換數(shù)據(jù)了。交換數(shù)據(jù)嚴格按照請求響應(yīng)模型進行,由客戶端發(fā)送一個請求數(shù)據(jù)到服務(wù)器,服務(wù)器反饋一個響應(yīng)數(shù)據(jù)給客戶端,如果客戶端不發(fā)送請求則服務(wù)器端就不響應(yīng)。根據(jù)邏輯需要,可以多次交換數(shù)據(jù),但是還是必須遵循請求響應(yīng)模型。
關(guān)閉網(wǎng)絡(luò)連接
在數(shù)據(jù)交換完成以后,關(guān)閉網(wǎng)絡(luò)連接,釋放程序占用的端口、內(nèi)存等系統(tǒng)資源,結(jié)束網(wǎng)絡(luò)編程。
在實際實現(xiàn)時,步驟2會出現(xiàn)重復(fù),在進行代碼組織時,由于網(wǎng)絡(luò)編程是比較耗時的操作,所以一般開啟專門的現(xiàn)場進行網(wǎng)絡(luò)通訊。
2、服務(wù)器端網(wǎng)絡(luò)編程步驟
服務(wù)器端(Server)是指在網(wǎng)絡(luò)編程中被動等待連接的程序,服務(wù)器端一般實現(xiàn)程序的核心邏輯以及數(shù)據(jù)存儲等核心功能。服務(wù)器端的編程步驟和客戶端不同,是由四個步驟實現(xiàn),依次是:
監(jiān)聽端口
服務(wù)器端屬于被動等待連接,所以服務(wù)器端啟動以后,不需要發(fā)起連接,而只需要監(jiān)聽本地計算機的某個固定端口即可。
這個端口就是服務(wù)器端開放給客戶端的端口,服務(wù)器端程序運行的本地計算機的IP地址就是服務(wù)器端程序的IP地址。
獲得連接
當客戶端連接到服務(wù)器端時,服務(wù)器端就可以獲得一個連接,這個連接包含客戶端的信息,例如客戶端IP地址等等,服務(wù)器端和客戶端也通過該連接進行數(shù)據(jù)交換。
一般在服務(wù)器端編程中,當獲得連接時,需要開啟專門的線程處理該連接,每個連接都由獨立的線程實現(xiàn)。
交換數(shù)據(jù)
服務(wù)器端通過獲得的連接進行數(shù)據(jù)交換。服務(wù)器端的數(shù)據(jù)交換步驟是首先接收客戶端發(fā)送過來的數(shù)據(jù),然后進行邏輯處理,再把處理以后的結(jié)果數(shù)據(jù)發(fā)送給客戶端。簡單來說,就是先接收再發(fā)送,這個和客戶端的數(shù)據(jù)交換數(shù)序不同。
其實,服務(wù)器端獲得的連接和客戶端連接是一樣的,只是數(shù)據(jù)交換的步驟不同。
當然,服務(wù)器端的數(shù)據(jù)交換也是可以多次進行的。
在數(shù)據(jù)交換完成以后,關(guān)閉和客戶端的連接。
關(guān)閉連接
當服務(wù)器程序關(guān)閉時,需要關(guān)閉服務(wù)器端,通過關(guān)閉服務(wù)器端使得服務(wù)器監(jiān)聽的端口以及占用的內(nèi)存可以釋放出來,實現(xiàn)了連接的關(guān)閉。
TCP方式是需要建立連接的,對于服務(wù)器端的壓力比較大,而UDP是不需要建立連接的,對于服務(wù)器端的壓力比較小罷了。
Java網(wǎng)絡(luò)編程技術(shù)
和網(wǎng)絡(luò)編程有關(guān)的基本API位于java.net包中,該包中包含了基本的網(wǎng)絡(luò)編程實現(xiàn),該包是網(wǎng)絡(luò)編程的基礎(chǔ)。該包中既包含基礎(chǔ)的網(wǎng)絡(luò)編程類,也包含封裝后的專門處理WEB相關(guān)的處理類。
InetAddress類
該類的功能是代表一個IP地址,并且將IP地址和域名相關(guān)的操作方法包含在該類的內(nèi)部。
先來個Demo
publicstaticvoidmain(String[]?args)throwsIOException?{
try{
//使用域名創(chuàng)建對象
InetAddress?address=InetAddress.getByName("www.163.com");
System.out.println(address);
//使用ip創(chuàng)建對象
InetAddress?address2=InetAddress.getByName("222.184.115.167");
System.out.println(address2);
//獲得本機地址對象
InetAddress?address3?=?InetAddress.getLocalHost();
System.out.println(address3);
//獲得對象中存儲的域名
System.out.println("域名:"+address3.getHostName());
//獲得對象中存儲的ip地址
System.out.println("IP地址:"+address3.getHostAddress());
}?catch(Exception?e)?{
//?TODO:?handle?exception
}
}
由于該代碼中包含一個互聯(lián)網(wǎng)的網(wǎng)址,所以運行該程序時需要聯(lián)網(wǎng),否則將產(chǎn)生異常。
在后續(xù)的使用中,經(jīng)常包含需要使用InetAddress對象代表IP地址的構(gòu)造方法,當然,該類的使用不是必須的,也可以使用字符串來代表IP地址進行實現(xiàn)。
TCP編程
在Java語言中,對于TCP方式的網(wǎng)絡(luò)編程提供了良好的支持,在實際實現(xiàn)時,以java.net.Socket類代表客戶端連接,以java.net.ServerSocket類代表服務(wù)器端連接。在進行網(wǎng)絡(luò)編程時,底層網(wǎng)絡(luò)通訊的細節(jié)已經(jīng)實現(xiàn)了比較高的封裝,所以在程序員實際編程時,只需要指定IP地址和端口號碼就可以建立連接了。正是由于這種高度的封裝,一方面簡化了Java語言網(wǎng)絡(luò)編程的難度,另外也使得使用Java語言進行網(wǎng)絡(luò)編程時無法深入到網(wǎng)絡(luò)的底層,所以使用Java語言進行網(wǎng)絡(luò)底層系統(tǒng)編程很困難,具體點說,Java語言無法實現(xiàn)底層的網(wǎng)絡(luò)嗅探以及獲得IP包結(jié)構(gòu)等信息。但是由于Java語言的網(wǎng)絡(luò)編程比較簡單,所以還是獲得了廣泛的使用。
在使用TCP方式進行網(wǎng)絡(luò)編程時,需要按照前面介紹的網(wǎng)絡(luò)編程的步驟進行,下面分別介紹一下在Java語言中客戶端和服務(wù)器端的實現(xiàn)步驟。
在客戶端網(wǎng)絡(luò)編程中,首先需要建立連接,在Java API中以java.net.Socket類的對象代表網(wǎng)絡(luò)連接,所以建立客戶端網(wǎng)絡(luò)連接,也就是創(chuàng)建Socket類型的對象,該對象代表網(wǎng)絡(luò)連接
//?socket1實現(xiàn)的是連接到IP地址是192.168.1.103的計算機的10000號端口
Socket?socket1?=?newSocket("192.168.1.103",10000);
//?socket2實現(xiàn)的是連接到域名是www.sohu.com的計算機的80號端口
Socket?socket2?=?newSocket("www.sohu.com",80);
底層網(wǎng)絡(luò)如何實現(xiàn)建立連接,對于程序員來說是完全透明的。如果建立連接時,本機網(wǎng)絡(luò)不通,或服務(wù)器端程序未開啟,則會拋出異常。
連接一旦建立,則完成了客戶端編程的第一步,緊接著的步驟就是按照“請求-響應(yīng)”模型進行網(wǎng)絡(luò)數(shù)據(jù)交換,在Java語言中,數(shù)據(jù)傳輸功能由Java IO實現(xiàn),也就是說只需要從連接中獲得輸入流和輸出流即可,然后將需要發(fā)送的數(shù)據(jù)寫入連接對象的輸出流中,在發(fā)送完成以后從輸入流中讀取數(shù)據(jù)即可。
//獲得輸出流
OutputStream?outputStream?=?socket1.getOutputStream();
//獲得輸入流
InputStream?inputStream=socket1.getInputStream();
這里獲得的只是最基本的輸出流和輸入流對象,還可以根據(jù)前面學(xué)習(xí)到的IO知識,使用流的嵌套將這些獲得到的基本流對象轉(zhuǎn)換成需要的裝飾流對象,從而方便數(shù)據(jù)的操作。
最后當數(shù)據(jù)交換完成以后,關(guān)閉網(wǎng)絡(luò)連接,釋放網(wǎng)絡(luò)連接占用的系統(tǒng)端口和內(nèi)存等資源,完成網(wǎng)絡(luò)操作,示例代碼如下:
socket1.close();
以上就是最基本的網(wǎng)絡(luò)編程功能介紹。
接下來寫個客戶端的Demo,程序在客戶端發(fā)送字符串到服務(wù)器,并將服務(wù)器端的反饋顯示到控制臺,數(shù)據(jù)交換只進行一次,當數(shù)據(jù)交換進行完成以后關(guān)閉網(wǎng)絡(luò)連接,程序結(jié)束。
先來客戶端的代碼
importjava.io.InputStream;
importjava.io.OutputStream;
importjava.net.Socket;
publicclassClient?{
publicstaticvoidmain(String[]?args)?{
Socket?socket?=?null;
InputStream?is?=?null;
OutputStream?os?=?null;
try{
String?msg?=?"Hello";
String?ip?=?"localhost";
intport?=9898;
//?建立連接
socket?=?newSocket(ip,?port);
//?發(fā)送數(shù)據(jù)
os?=?socket.getOutputStream();
os.write(msg.getBytes());
//?接收數(shù)據(jù)
is?=?socket.getInputStream();
byteb[]=newbyte[1024];
intn?=is.read(b);
System.out.println(newString(b,0,n));
}?catch(Exception?e)?{
//?TODO:?handle?exception
e.printStackTrace();
}?finally{
try{
//關(guān)閉連接和流
is.close();
os.close();
socket.close();
}?catch(Exception?e2)?{
//?TODO:?handle?exception
e2.printStackTrace();
}
}
}
}
代碼中建服務(wù)器端的代碼:
publicclassServer?{
publicstaticvoidmain(String[]?args)?{
ServerSocket?serverSocket=null;
Socket?socket=null;
InputStream?is?=null;
OutputStream?os?=null;
try{
serverSocket?=?newServerSocket(9898);
socket?=?serverSocket.accept();
is?=?socket.getInputStream();
byteb[]?=newbyte[1024];
intn?=?is.read(b);
System.out.println("客戶端發(fā)送了:"+newString(b,0,n));
os?=?socket.getOutputStream();
os.write("接收成功!".getBytes());
}?catch(Exception?e)?{
//?TODO:?handle?exception
e.printStackTrace();
}finally{
try{
is.close();
os.close();
socket.close();
serverSocket.close();
}?catch(Exception?e2)?{
//?TODO:?handle?exception
}
}
}
}
先運行服務(wù)器端,然后運行客戶端,服務(wù)器接收到數(shù)據(jù)將數(shù)據(jù)打印出來之后再返回數(shù)據(jù)到客戶端,客戶端打印出來
在該示例代碼中建立了一個監(jiān)聽當前計算機9898號端口的服務(wù)器端Socket連接,然后獲得客戶端發(fā)送過來的連接,如果有連接到達時,讀取連接中發(fā)送過來的內(nèi)容,并將發(fā)送的內(nèi)容在控制臺進行輸出,輸出完成以后將客戶端發(fā)送的內(nèi)容再反饋給客戶端。最后關(guān)閉流和連接對象,結(jié)束程序。
在服務(wù)器端程序編程中,由于服務(wù)器端實現(xiàn)的是被動等待連接,所以服務(wù)器端編程的第一個步驟是監(jiān)聽端口,也就是監(jiān)聽是否有客戶端連接到達。實現(xiàn)服務(wù)器端監(jiān)聽的代碼為:
//?該代碼實現(xiàn)的功能是監(jiān)聽當前計算機的9898號端口,如果在執(zhí)行該代碼時,
//?10000號端口已經(jīng)被別的程序占用,那么將拋出異常。否則將實現(xiàn)監(jiān)聽。
serverSocket?=?newServerSocket(9898);
服務(wù)器端編程的第二個步驟是獲得連接。該步驟的作用是當有客戶端連接到達時,建立一個和客戶端連接對應(yīng)的Socket連 接對象,從而釋放客戶端連接對于服務(wù)器端端口的占用。通過獲得連接,使得客戶端的連接在服務(wù)器端獲得了保持,另外使得服務(wù)器端的端口釋放出來,可以繼續(xù)等待其它的客戶端連接。 實現(xiàn)獲得連接的代碼是:
socket?=?serverSocket.accept();
該代碼實現(xiàn)的功能是獲得當前連接到服務(wù)器端的客戶端連接。需要說明的是accept和前面IO部分介紹的read方法一樣,都是一個阻塞方法,也就是當無連接時,該方法將阻塞程序的執(zhí)行,直到連接到達時才執(zhí)行該行代碼。另外獲得的連接會在服務(wù)器端的該端口注冊,這樣以后就可以通過在服務(wù)器端的注冊信息直接通信,而注冊以后服務(wù)器端的端口就被釋放出來,又可以繼續(xù)接受其它的連接了。
連接獲得以后,后續(xù)的編程就和客戶端的網(wǎng)絡(luò)編程類似了,這里獲得的Socket類型的連接就和客戶端的網(wǎng)絡(luò)連接一樣了,只是服務(wù)器端需要首先讀取發(fā)送過來的數(shù)據(jù),然后進行邏輯處理以后再發(fā)送給客戶端,也就是交換數(shù)據(jù)的順序和客戶端交換數(shù)據(jù)的步驟剛好相反。這部分的內(nèi)容和客戶端很類似。
--------------------------
上面這個示例只是演示了網(wǎng)絡(luò)編程的基本步驟以及各個功能方法的基本使用,只是為網(wǎng)絡(luò)編程打下了一個基礎(chǔ),下面將就幾個問題來深入介紹網(wǎng)絡(luò)編程深層次的一些知識。
1.如何復(fù)用Socket連接?
撥通一次電話以后多次對話,這就是復(fù)用客戶端連接。
建立連接以后,將數(shù)據(jù)交換的邏輯寫到一個循環(huán)中,只要循環(huán)不結(jié)束則連接就不會被關(guān)閉,按照這種思路,可以改造一下上面的代碼,讓該程序可以在建立連接一次以后,發(fā)送三次數(shù)據(jù),當然這里的次數(shù)也可以是多次
現(xiàn)在看下新的服務(wù)器代碼和客戶端代碼:
importjava.io.InputStream;
importjava.io.OutputStream;
importjava.net.ServerSocket;
importjava.net.Socket;
/**
*?服務(wù)器代碼
*?*/
publicclassServer?{
publicstaticvoidmain(String[]?args)?{
ServerSocket?serverSocket?=?null;
Socket?socket?=?null;
InputStream?is?=?null;
OutputStream?os?=?null;
try{
serverSocket?=?newServerSocket(9898);
socket?=?serverSocket.accept();
is?=?socket.getInputStream();
os?=?socket.getOutputStream();
byteb[]?=newbyte[1024];
for(inti?=0;?i?<3;?i++)?{
intn?=?is.read(b);
os.write(("客戶端發(fā)送的內(nèi)容:"+newString(b,0,?n)).getBytes());
}
}?catch(Exception?e)?{
//?TODO:?handle?exception
e.printStackTrace();
}?finally{
try{
is.close();
os.close();
socket.close();
serverSocket.close();
}?catch(Exception?e2)?{
//?TODO:?handle?exception
}
}
}
}
再看下新的客戶端代碼:
importjava.io.InputStream;
importjava.io.OutputStream;
importjava.net.Socket;
/**
*?客戶端代碼
*?*/
publicclassClient?{
publicstaticvoidmain(String[]?args)?{
Socket?socket?=?null;
InputStream?is?=?null;
OutputStream?os?=?null;
try{
String?msg[]?=?{?"one","two","three"};
String?ip?=?"localhost";
intport?=9898;
//?建立連接
socket?=?newSocket(ip,?port);
//?發(fā)送數(shù)據(jù)
os?=?socket.getOutputStream();
//?接收數(shù)據(jù)
is?=?socket.getInputStream();
byteb[]?=newbyte[1024];
for(inti?=0;?i?
os.write(msg[i].getBytes());
intn?=?is.read(b);
System.out.println(newString(b,0,?n));
}
}?catch(Exception?e)?{
//?TODO:?handle?exception
e.printStackTrace();
}?finally{
try{
//?關(guān)閉連接和流
is.close();
os.close();
socket.close();
}?catch(Exception?e2)?{
//?TODO:?handle?exception
e2.printStackTrace();
}
}
}
}
上面的代碼雖然比較簡單,但是通用性比較差。
在該程序中,比較明顯的體現(xiàn)出了“請求-響應(yīng)”模型,也就是在客戶端發(fā)起連接以后,首先發(fā)送字符串“First”給服務(wù)器端,服務(wù)器端輸出客戶端發(fā)送的內(nèi)容“First”,然后將客戶端發(fā)送的內(nèi)容再反饋給客戶端,這樣客戶端也輸出服務(wù)器反饋“First”,這樣就完成了客戶端和服務(wù)器端的一次對話
三次會話的過程一樣,在這個過程中,每次都是客戶端程序首先發(fā)送數(shù)據(jù)給服務(wù)器端,服務(wù)器接收數(shù)據(jù)以后,將結(jié)果反饋給客戶端,客戶端接收到服務(wù)器端的反饋,從而完成一次通訊過程。
2、如何使服務(wù)器端支持多個客戶端同時工作?
一個服務(wù)器端一般都需要同時為多個客戶端提供通訊,如果需要同時支持多個客戶端,則必須使用前面介紹的線程的概念。簡單來說,也就是當服務(wù)器端接收到一個連接時,啟動一個專門的線程處理和該客戶端的通訊。
改造之后的服務(wù)器代碼,可以接收多個客戶端的數(shù)據(jù)。
在該示例代碼中,實現(xiàn)了一個while形式的死循環(huán),由于accept方法是阻塞方法,所以當客戶端連接未到達時,將阻塞該程序的執(zhí)行,當客戶端到達時接收該連接,并啟動一個新的LogicThread線程處理該連接,然后按照循環(huán)的執(zhí)行流程,繼續(xù)等待下一個客戶端連接。這樣當任何一個客戶端連接到達時,都開啟一個專門的線程處理,通過多個線程支持多個客戶端同時處理。
/**
*?支持多客戶端的服務(wù)器代碼
*?*/
publicclassServer?{
publicstaticvoidmain(String[]?args)?{
ServerSocket?serverSocket?=?null;
Socket?socket?=?null;
try{
serverSocket?=?newServerSocket(9898);
while(true)?{
socket?=?serverSocket.accept();
//?啟動線程
//?實現(xiàn)接收客戶端連接,然后開啟專門的邏輯線程處理該連接,
//?LogicThread類實現(xiàn)對于一個客戶端連接的邏輯處理,將處理的邏輯放置在該類的run方法中。
newLogicThread(socket);
}
}?catch(Exception?e)?{
//?TODO:?handle?exception
e.printStackTrace();
}?finally{
try{
socket.close();
serverSocket.close();
}?catch(Exception?e2)?{
//?TODO:?handle?exception
}
}
}
staticclassLogicThreadextendsThread?{
Socket?socket?=?null;
publicLogicThread(Socket?socket)?{
this.socket?=?socket;
start();
}
@Override
publicvoidrun()?{
byteb[]?=newbyte[1024];
InputStream?is?=?null;
OutputStream?os?=?null;
try{
is?=?socket.getInputStream();
os?=?socket.getOutputStream();
intn?=?is.read(b);
os.write(("客戶端發(fā)送的內(nèi)容:"+newString(b,0,?n)).getBytes());
}?catch(Exception?e)?{
//?TODO:?handle?exception
e.printStackTrace();
}?finally{
try{
is.close();
os.close();
socket.close();
}?catch(Exception?e2)?{
//?TODO:?handle?exception
}
}
}
}
}
這里的示例還只是基礎(chǔ)的服務(wù)器端實現(xiàn),在實際的服務(wù)器端實現(xiàn)中,由于硬件和端口數(shù)的限制,所以不能無限制的創(chuàng)建線程對象,而且頻繁的創(chuàng)建線程對象效率也比較低,所以程序中都實現(xiàn)了線程池來提高程序的執(zhí)行效率。
這里簡單介紹一下線程池的概念,線程池(Thread pool)是池技術(shù)的一種,就是在程序啟動時首先把需要個數(shù)的線程對象創(chuàng)建好,例如創(chuàng)建5000個線程對象,然后當客戶端連接到達時從池中取出一個已經(jīng)創(chuàng)建完成的線程對象使用即可。當客戶端連接關(guān)閉以后,將該線程對象重新放入到線程池中供其它的客戶端重復(fù)使用,這樣可以提高程序的執(zhí)行速度,優(yōu)化程序?qū)τ趦?nèi)存的占用等。
關(guān)于基礎(chǔ)的TCP方式的網(wǎng)絡(luò)編程就介紹這么多,下面一章介紹UDP方式的網(wǎng)絡(luò)編程在Java語言中的實現(xiàn)。
總結(jié)
以上是生活随笔為你收集整理的java中no1_【Java】-- 网络编程のNo.1的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java web redis_java
- 下一篇: php 中断输出,PHP捕捉异常中断的方