Java網路編程從入門到精通(13):使用Socket類接收和發送數據

火星人 @ 2014-03-10 , reply:0
←手機掃碼閱讀

網路應用分為客戶端和服務端兩部分,而Socket類是負責處理客戶端通信的Java類.通過這個類可以連接到指定IP或域名的伺服器上,並且可以和伺服器互相發送和接受數據.在本文及後面的數篇文章中將詳細討論Socket類的使用,內容包括Socket類基礎、各式各樣的連接方式、get和set方法、連接過程中的超時以及關閉網路連接等.

在本文中,我們將討論使用Socket類的基本步驟和方法.一般網路客戶端程序在連接服務程序時要進行以下三步操作.

1. 連接伺服器

2. 發送和接收數據

3. 關閉網路連接

一、連接伺服器

在客戶端可以通過兩種方式來連接伺服器,一種是通過IP的方式來連接伺服器,而另外一種是通過域名方式來連接伺服器.

其實這兩種方式從本質上來看是一種方式.在底層客戶端都是通過IP來連接伺服器的,但這兩種方式有一定的差異,如果通過IP方式來連接服務端程序,客戶端只簡單地根據IP進行連接,如果通過域名來連接伺服器,客戶端通過DNS將域名解析成IP,然後再根據這個IP來進行連接.

在很多程序設計語言或開發工具中(如C/C 、Delphi)使用域名方式連接伺服器時自己先將域名解析成IP,然後再通過IP進行連接,而在Java中已經將域名解析功能包含在了Socket類中,因此,我們只需象使用IP一樣使用域名即可.

通過Socket類連接伺服器程序最常用的方法就是通過Socket類的構造函數將IP或域名以及埠號作為參數傳入Socket類中.Socket類的構造函數有很多重載形式,在這一節只討論其中最常用的一種形式:public Socket(String host, int port).從這個構造函數的定義來看,只需要將IP或域名以及埠號直接傳入構造函數即可.下面的代碼是一個連接服務端程序的例子程序:

package mysocket;

import java.net.*;

public class MyConnection
{


public static void main(String[] args)
{
try
{
if (args.length > 0)
{
Socket socket
= new Socket(args[0], 80);
System.out.println(args[
0] "已連接成功!");
}
else
System.out.println(
"請指定IP或域名!");
}
catch (Exception e)
{
System.err.println(
"錯誤信息:" e.getMessage());
}
}
}

在上面的

中,通過命令行參數將IP或域名傳入程序,然後通過Socket socket = new Socket(args[0], 80)連接通過命令行參數所指定的IP或域名的80埠.Socket類的構造函數在定義時使用了throws,因此,在調用Socket類的構造函數時,使用try…catch語句來捕捉錯誤,或者對main函數使用throws語句來拋出錯誤.

測試正確的IP

java mysocket.MyConnection 127.0.0.1

輸出結果:127.0.0.1已經連接成功!

測試錯誤的IP

java mysocket.MyConnection 10.10.10.10

輸出結果:錯誤信息:Connection timed out: connect

註:10.10.10.10是一個並不存在的IP,如果這個IP在你的網路中存在,請使用其它的不存在的IP.

測試正確的域名

java mysocket.MyConnection www.ptpress.com.cn

輸出結果:www.ptpress.com.cn已經連接成功!

測試錯誤的域名

java mysocket.MyConnection www.ptpress1.com.cn

輸出結果:錯誤信息:www.ptpress1.com.cn

使用Socket類連接伺服器可以判斷一台主機有哪些埠被打開.下面的代碼是一個掃描本機有哪些埠被打開的程序.

package mysocket;

import java.net.*;

public class MyConnection1 extends Thread
{
private int minPort, maxPort;

public MyConnection1(int minPort, int maxPort)
{
this.minPort = minPort;
this.maxPort = maxPort;
}

public void run()
{
for (int i = minPort; i <= maxPort; i )
{
try
{
Socket socket
= new Socket("127.0.0.1", i);
System.out.println(String.valueOf(i)
":ok");
socket.close();
}
catch (Exception e)
{
}
}
}
public static void main(String[] args)
{
int minPort = Integer.parseInt(args[0]), maxPort = Integer
.parseInt(args[
1]);
int threadCount = Integer.parseInt(args[2]);
int portIncrement = ((maxPort - minPort 1) / threadCount)
(((maxPort - minPort 1) % threadCount) == 0 ? 0 : 1);
MyConnection1[] instances = new MyConnection1[threadCount];
for (int i = 0; i < threadCount; i )
{
instances[i]
= new MyConnection1(minPort portIncrement * i, minPort
portIncrement - 1 portIncrement * i);
instances[i].start();
}
}
}

上面代碼通過一個指定的埠範圍(如11000),並且利用多線程將這個埠範圍分成不同的段進行掃描,這樣可以大大提高掃描的效率.

可通過如下命令行去運行常式4-2.

java mysocket.MyConnection1 1000 3000 20

二、發送和接收數據

Socket類中最重要的兩個方法就是getInputStreamgetOutputStream.這兩個方法分別用來得到用於讀取和寫入數據的InputStreamOutputStream對象.在這裡的InputStream讀取的是伺服器程序向客戶端發送過來的數據,而OutputStream是客戶端要向服務端程序發送的數據.

在編寫實際的網路客戶端程序時,是使用getInputStream,還是使用getOutputStream,以及先使用誰后使用誰由具體的應用決定.如通過連接郵電出版社網站(www.ptpress.com.cn)80埠(一般為HTTP

協議所使用的默認埠),並且發送一個字元串,再讀取從www.ptpress.com.cn返回的信息.

package mysocket;

import java.net.*;
import java.io.*;

public class MyConnection2
{
public static void main(String[] args) throws Exception
{
Socket socket
= new Socket("www.ptpress.com.cn", 80);
// 向服務端程序發送數據
OutputStream ops = socket.getOutputStream();
OutputStreamWriter opsw
= new OutputStreamWriter(ops);
BufferedWriter bw
= new BufferedWriter(opsw);

bw.write(
"hello worldrnrn");
bw.flush();

// 從服務端程序接收數據

InputStream ips = socket.getInputStream();
InputStreamReader ipsr
= new InputStreamReader(ips);
BufferedReader br
= new BufferedReader(ipsr);
String s
= "";
while((s = br.readLine()) != null)
System.out.println(s);
socket.close();
}
}

在編寫上面代碼時要注意如下兩點:

1. 為了提高數據傳輸的效率,Socket類並沒有在每次調用write方法后都進行數據傳輸,而是將這些要傳輸的數據寫到一個緩衝區里(默認是8192個位元組),然後通過flush方法將這個緩衝區里的數據一起發送出去,因此,bw.flush();的.

2. 在發送字元串時之Hello World后加上 「rnrn」,這是HTTP協議頭是以「rnrn」作為結束標誌(HTTP協議的詳細內容將在以後講解),因此,通過在發送字元串后加入

「rnrn」,可以使服務端程序認為HTTP頭已經結束,可以處理了.如果不加「rnrn」,那麼服務端程序將一直等待HTTP頭的結束,也就是「rnrn」.如果是這樣,服務端程序就不會向客戶端發送響應信息,而br.readLine()將因無法讀以響應信息面被阻塞,直到連接超時.

三、關閉網路連接

到現在為止,我們對Socket類的基本使用方法已經有了初步的了解,但在Socket類處理完數據后,最合理的收尾方法是使用Socket類的close方法關閉網路連接.雖然在中已經使用了close方法,但使網路連接關閉的方法不僅僅只有close方法,下面就讓我們看看Java在什麼情況下可以使網路連接關閉.

可以引起網路連接關閉的情況有以下4種:

1. 直接調用Socket類的close方法.

2. 只要Socket類的InputStreamOutputStream有一個關閉,網路連接自動關閉(通過調用InputStreamOutputStreamclose方法關閉流,才能使網路可愛接自動關閉).

3. 在程序退出時網路連接自動關閉.

4. 將Socket對象設為null或未關閉最使用new Socket(…)建立新對象后,由JVM的垃圾回收器回收為Socket對象分配的內存空間后自動關閉網路連接.

雖然這4種方法都可以達到同樣的目的,但一個健壯的網路程序最好使用第1種或第2種方法關閉網路連接.這是3種和第4種方法一般並不會馬上關閉網路連接,如果是這樣的話,對於某些應用程序,將會遺留大量無用的網路連接,這些網路連接會佔用大量的系統資源.

Socket對象被關閉后,我們可以通過isClosed方法來判斷某個Socket對象是否處於關閉狀態.然而使用isClosed方法所返回的只是Socket對象的當前狀態,也就是說,不管Socket對象是否曾經連接成功過,只要處於關閉狀態,isClosde就返回true.如果只是建立一個未連接的Socket對象,isClose也同樣返回true.如下面的代碼將輸出false.

Socket socket = new Socket();
System.out.println(socket.isClosed());

除了isClose方法,Socket類還有一個isConnected方法來判斷Socket對象是否連接成功.看到這個名字,也許讀者會產生誤解.其實isConnected方法所判斷的並不是Socket對象的當前連接狀態,而是Socket對象是否曾經連接成功過,如果成功連接過,即使現在isClose返回true,isConnected仍然返回true.因此,要判斷當前的Socket對象是否處於連接狀態,同時使用isCloseisConnected方法,即只有當isClose返回false,isConnected返回true的時候Socket對象才處於連接狀態.下面的代碼演示了上述Socket對象的各種狀態的產生過程.

package mysocket;

import java.net.*;

public class MyCloseConnection
{
public static void printState(Socket socket, String name)
{
System.out.println(name
".isClosed():" socket.isClosed());
System.out.println(name
".isConnected():" socket.isConnected());
if (socket.isClosed() == false && socket.isConnected() == true)
System.out.println(name
"處於連接狀態!");
else
System.out.println(name
"處於非連接狀態!");
System.out.println();
}


public static void main(String[] args) throws Exception
{
Socket socket1
= null, socket2 = null;

socket1
= new Socket("www.ptpress.com.cn", 80);
printState(socket1,
"socket1");

socket1.getOutputStream().close();
printState(socket1,
"socket1");

socket2
= new Socket();
printState(socket2,
"socket2");

socket2.close();
printState(socket2,
"socket2");
}
}

運行上面的代碼后,將有如下的輸出結果:

socket1.isClosed():false

socket1.isConnected():true

socket1處於連接狀態!

socket1.isClosed():true

socket1.isConnected():true

socket1處於非連接狀態!

socket2.isClosed():false

socket2.isConnected():false

socket2處於非連接狀態!

socket2.isClosed():true

socket2.isConnected():false

socket2處於非連接狀態!

從輸出結果可以看出,在socket1OutputStream關閉后,socket1也自動關閉了.而在上面的代碼我們可以看出,對於一個並未連接到服務端的Socket對象socket2,它的

isClosed方法為false,而要想讓socket2isClosed方法返回true,使用socket2.close顯示地調用close方法.

雖然在大多數的時候可以直接使用Socket類或輸入輸出流的close方法關閉網路連接,但有時我們只希望關閉OutputStreamInputStream,而在關閉輸入輸出流的同時,並不關閉網路連接.這就需要用到Socket類的另外兩個方法:shutdownInputshutdownOutput,這兩個方法只關閉相應的輸入、輸出流,而它們並沒有同時關閉網路連接的功能.和isClosedisConnected方法一樣,Socket類也提供了兩個方法來判斷Socket對象的輸入、輸出流是否被關閉,這兩個方法是isInputShutdown()isOutputShutdown().下面的代碼演示了只關閉輸入、輸出流的過程:

package mysocket;

import java.net.*;

public class MyCloseConnection1
{


public static void printState(Socket socket)
{
System.out.println(
"isInputShutdown:" socket.isInputShutdown());
System.out.println(
"isOutputShutdown:" socket.isOutputShutdown());
System.out.println(
"isClosed:" socket.isClosed());
System.out.println();
}

public static void main(String[] args) throws Exception
{
Socket socket
= new Socket("www.ptpress.com.cn", 80);
printState(socket);

socket.shutdownInput();
printState(socket);

socket.shutdownOutput();
printState(socket);
}
}

在運行上面的代

,將得到如下的輸出結果

isInputShutdown:false

isOutputShutdown:false

isClosed:false

isInputShutdown:true

isOutputShutdown:false

isClosed:false

isInputShutdown:true

isOutputShutdown:true

isClosed:false

從輸出結果可以看出,isClosed方法一直返回false,因此,可以肯定,shutdownInputshutdownOutput並不影響Socket對象的狀態.





[火星人 via ] Java網路編程從入門到精通(13):使用Socket類接收和發送數據已經有407次圍觀

http://www.coctec.com/docs/java/show-post-61665.html