餐前甜點
unix的輸入輸出(io)系統遵循open-read-write-close這樣的操作範本。當一個使用者程序進行io操作之前,它需要調用open來指定并擷取待操作檔案或裝置讀取或寫入的權限。一旦io操作對象被打開,那麼這個使用者程序可以對這個對象進行一次或多次的讀取或寫入操作。read操作用來從io操作對象讀取資料,并将資料傳遞給使用者程序。write操作用來将使用者程序中的資料傳遞(寫入)到io操作對象。
當所有的read和write操作結束之後,使用者程序需要調用close來通知系統其完成對io對象的使用。
在unix開始支援程序間通信(interprocess
communication,簡稱ipc)時,ipc的接口就設計得類似檔案io操作接口。在unix中,一個程序會有一套可以進行讀取寫入的io描述符。io描述符可以是檔案,裝置或者是通信通道(socket套接字)。一個檔案描述符由三部分組成:建立(打開socket),讀取寫入資料(接受和發送到socket)還有銷毀(關閉socket)。
在unix系統中,類bsd版本的ipc接口是作為tcp和udp協定之上的一層進行實作的。消息的目的地使用socket位址來表示。一個socket位址是由網絡位址和端口号組成的通信辨別符。
程序間通信操作需要一對兒socket。程序間通信通過在一個程序中的一個socket與另一個程序中得另一個socket進行資料傳輸來完成。當一個消息執行發出後,這個消息在發送端的socket中處于排隊狀态,直到下層的網絡協定将這些消息發送出去。當消息到達接收端的socket後,其也會處于排隊狀态,直到接收端的程序對這條消息進行了接收處理。
tcp和udp通信
關于socket程式設計我們有兩種通信協定可以進行選擇。一種是資料報通信,另一種就是流通信。
資料報通信
資料報通信協定,就是我們常說的udp(user data protocol
使用者資料報協定)。udp是一種無連接配接的協定,這就意味着我們每次發送資料報時,需要同時發送本機的socket描述符和接收端的socket描述符。是以,我們在每次通信時都需要發送額外的資料。
流通信
流通信協定,也叫做tcp(transfer control
protocol,傳輸控制協定)。和udp不同,tcp是一種基于連接配接的協定。在使用流通信之前,我們必須在通信的一對兒socket之間建立連接配接。其中一個socket作為伺服器進行監聽連接配接請求。另一個則作為用戶端進行連接配接請求。一旦兩個socket建立好了連接配接,他們可以單向或雙向進行資料傳輸。
讀到這裡,我們多少有這樣的疑問,我們進行socket程式設計使用udp還是tcp呢。選擇基于何種協定的socket程式設計取決于你的具體的用戶端-伺服器端程式的應用場景。下面我們簡單分析一下tcp和udp協定的差別,或許可以幫助你更好地選擇使用哪種。
在udp中,每次發送資料報時,需要附帶上本機的socket描述符和接收端的socket描述符。而由于tcp是基于連接配接的協定,在通信的socket對之間需要在通信之前建立連接配接,是以會有建立連接配接這一耗時存在于tcp協定的socket程式設計。
在udp中,資料報資料在大小上有64kb的限制。而tcp中也不存在這樣的限制。一旦tcp通信的socket對建立了連接配接,他們之間的通信就類似io流,所有的資料會按照接受時的順序讀取。
udp是一種不可靠的協定,發送的資料報不一定會按照其發送順序被接收端的socket接受。然後tcp是一種可靠的協定。接收端收到的包的順序和包在發送端的順序是一緻的。
簡而言之,tcp适合于諸如遠端登入(rlogin,telnet)和檔案傳輸(ftp)這類的網絡服務。因為這些需要傳輸的資料的大小不确定。而udp相比tcp更加簡單輕量一些。udp用來實作實時性較高或者丢包不重要的一些服務。在區域網路中udp的丢包率都相對比較低。
java中的socket程式設計
下面的部分我将通過一些示例講解一下如何使用socket編寫用戶端和伺服器端的程式。
注意:在接下來的示例中,我将使用基于tcp/ip協定的socket程式設計,因為這個協定遠遠比udp/ip使用的要廣泛。并且所有的socket相關的類都位于java.net包下,是以在我們進行socket程式設計時需要引入這個包。
用戶端編寫
開啟socket
如果在用戶端,你需要寫下如下的代碼就可以打開一個socket。
string host = "127.0.0.1"; int port = 8919; socket client = new socket(host, port);
上面代碼中,host即用戶端需要連接配接的機器,port就是伺服器端用來監聽請求的端口。在選擇端口時,需要注意一點,就是0~1023這些端口都已經被系統預留了。這些端口為一些常用的服務所使用,比如郵件,ftp和http。當你在編寫伺服器端的代碼,選擇端口時,請選擇一個大于1023的端口。
寫入資料
接下來就是寫入請求資料,我們從用戶端的socket對象中得到outputstream對象,然後寫入資料後。很類似檔案io的處理代碼。
public class clientsocket { public static void main(string args) {
string host = "127.0.0.1"; int port = 8919; try { socket client = new
socket(host, port); writer writer = new
outputstreamwriter(client.getoutputstream); writer.write("hello from
client"); writer.flush; writer.close; client.close; } catch
(ioexception e) { e.printstacktrace; } } }
關閉io對象
類似檔案io,在讀寫資料完成後,我們需要對io對象進行關閉,以確定資源的正确釋放。
伺服器端編寫
打開伺服器端的socket
int port = 8919; serversocket server = new serversocket(port); socket socket = server.accept;
上面的代碼建立了一個伺服器端的socket,然後調用accept方法監聽并擷取用戶端的請求socket。accept方法是一個阻塞方法,在伺服器端與用戶端之間建立聯系之前會一直等待阻塞。
讀取資料
通過上面得到的socket對象擷取inputstream對象,然後安裝檔案io一樣讀取資料即可。這裡我們将内容列印出來。
public class serverclient { public static void main(string args) {
int port = 8919; try { serversocket server = new serversocket(port);
socket socket = server.accept; reader reader = new
inputstreamreader(socket.getinputstream); char chars = new char[1024];
int len; stringbuilderbuilder = new stringbuilder; while
((len=reader.read(chars)) != -1) { builder.append(new string(chars, 0,
len)); } system.out.println("receive from client message=: " + builder);
reader.close; socket.close; server.close; } catch (exception e) {
e.printstacktrace; } } }
還是不能忘記的,最後需要正确地關閉io對象,以確定資源的正确釋放。
附注一個例子
這裡我們增加一個例子,使用socket實作一個回聲伺服器,就是伺服器會将用戶端發送過來的資料傳回給用戶端。代碼很簡單。
import java.io.*; import java.net.*; public class echoserver { public
static void main(string args) { // declaration section: // declare a
server socket and a client socket for the server // declare an input
and an output stream serversocket echoserver = null; string line;
datainputstream is; printstream os; socket clientsocket = null; // try
to open a server socket on port 9999 // note that we can't choose a
port less than 1023 if we are not // privileged users (root) try {
echoserver = new serversocket(9999); } catch (ioexception e) {
system.out.println(e); } // create a socket object from the serversocket
to listen and accept // connections. // open input and output streams
try { clientsocket = echoserver.accept; is = new
datainputstream(clientsocket.getinputstream); os = new
printstream(clientsocket.getoutputstream); // as long as we receive
data, echo that data back to the client. while (true) { line =
is.readline; os.println(line); } } catch (ioexception e) {
system.out.println(e); } } }
編譯運作上面的代碼,進行如下請求,就可以看到用戶端請求攜帶的資料的内容。
15:00 $ curl http://127.0.0.1:9999/?111 get /?111 http/1.1 user-agent: curl/7.37.1 host: 127.0.0.1:9999 accept: */*總結
進行用戶端-伺服器端程式設計還是比較有趣的,同時在java中進行socket程式設計要比其他語言(如c)要簡單快速編寫。
java.net這個包裡面包含了很多強大靈活的類供開發者進行網絡程式設計,在進行網絡程式設計中,建議使用這個包下面的api。同時sun.*這個包也包含了很多的網絡程式設計相關的類,但是不建議使用這個包下面的api,因為這個包可能會改變,另外這個包不能保證在所有的平台都有包含。
來源:51cto