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