Android Socket編程實踐

概述
什麼是Socket
網絡上的兩個程序經過一個雙向的通信鏈接實現數據的交換,這個雙向鏈路的一端稱爲一個Socket。Socket一般用來實現客戶端和服務端的鏈接。Socket是TCP/IP協議的一個十分流行的編程實現,一個Socket由一個IP地址和一個端口號惟一肯定。
可是,Socket所支持的協議種類也不光TCP/IP一種,所以二者之間是沒有必然聯繫的。在Java環境下,Socket編程主要是指基於TCP/IP協議的網絡編程。java

Socket通信的過程
Server端Listen(監聽)某個端口是否有鏈接請求,Client端向Server端發出Connect(鏈接)請求,Server端向Client端發回Accept(接受)消息。一個鏈接就創建起來了。Server端和Client 端均可以經過Send,Write等方法與對方通訊。
對於一個功能齊全的Socket,都要包含如下基本結構,其工做過程包含如下四個基本的步驟:
  (1) 建立Socket;
  (2) 打開鏈接到Socket的輸入/出流;
  (3) 按照必定的協議對Socket進行讀/寫操做;
  (4) 關閉Socket。android

InetAdress
首先說明一下InetAdress類的用法,它表明一個IP地址對象,是網絡通訊的基礎,後面講TCP/UDP編程會大量使用該類。
Java提供了InetAdress類來表明IP地址,InetAdress下還有兩個子類:Inet4Adress(IPv4)和Inet6Adress(IPv6)。
InetAdress類沒有提供構造器,而是提供了下面兩個靜態方法來獲取InetAdress對象:編程

InetAdress getByName(String ip):根據主機IP獲取對應InetAdress對象。
InetAdress getByAddress(Byte[] addr ):根據IP地址獲取對應InetAdress對象。
InetAdress getLocalHost():獲取本地機器的InetAdress對象。
InetAdress還提供了以下幾個方法來獲取IP地址和主機名:數組

String getCanonicalHostName():獲取全限定域名
String getHostAdress():獲取IP地址字符串
String getHostName():獲取主機名
Boolean isReachable(int time):測試指定時間內(ms)是否能夠到達該地址
網絡編程中兩個主要的問題
一個是如何準確的定位網絡上一臺或多臺主機,另外一個就是找到主機後如何可靠高效的進行數據傳輸。
(1)在TCP/IP協議中IP層主要負責網絡主機的定位,數據傳輸的路由,由IP地址能夠惟一地肯定Internet上的一臺主機。
(2)而TCP層則提供面向應用的可靠(TCP)的或非可靠(UDP)的數據傳輸機制,這是網絡編程的主要對象,通常不須要關心IP層是如何處理數據的。
目前較爲流行的網絡編程模型是客戶機/服務器(C/S)結構。即通訊雙方一方做爲服務器等待客戶提出請求並予以響應。客戶則在須要服務時向服務器提出申請。服務器通常做爲守護進程始終運行,監聽網絡端口,一旦有客戶請求,就會啓動一個服務進程來響應該客戶,同時本身繼續監聽服務端口,使後來的客戶也能及時獲得服務。服務器

兩類傳輸協議:TCP、UDP
TCP是Tranfer Control Protocol的簡稱,是一種面向鏈接的保證可靠傳輸的協議。經過TCP協議傳輸,獲得的是一個順序的無差錯的數據流。發送方和接收方的成對的兩個socket之間必須創建鏈接,以便在TCP協議的基礎上進行通訊,當一個socket(一般都是server socket)等待創建鏈接時,另外一個socket能夠要求進行鏈接,一旦這兩個socket鏈接起來,它們就能夠進行雙向數據傳輸,雙方均可以進行發送或接收操做。
UDP是User Datagram Protocol的簡稱,是一種面向無鏈接的協議,每一個數據報都是一個獨立的信息,包括完整的源地址或目的地址,它在網絡上以任何可能的路徑發往目的地,所以可否到達目的地,到達目的地的時間以及內容的正確性都是不能被保證的。網絡

比較:socket

TCP:ide

1,面向鏈接的協議,在socket之間進行數據傳輸以前必然要創建鏈接,因此在TCP中須要鏈接時間。
2,TCP傳輸數據無大小限制,一旦鏈接創建起來,雙方的socket就能夠按統一的格式傳輸大的數據。
3,TCP是一個可靠的協議,它的重發機制確保接收方徹底正確地獲取發送方所發送的所有數據。
UDP:函數

1,每一個數據報中都給出了完整的地址信息,所以無須要創建發送方和接收方的鏈接。
2,UDP傳輸數據時是有大小限制的,每一個被傳輸的數據報必須限定在64KB以內。
3,UDP是一個不可靠的協議,發送方所發送的數據報並不必定以相同的次序到達接收方。
應用:oop

1,TCP在網絡通訊上有極強的生命力,例如遠程鏈接(Telnet)和文件傳輸(FTP)都須要不定長度的數據被可靠地傳輸。可是可靠的傳輸是要付出代價的,對數據內容正確性的檢驗必然佔用計算機的處理時間和網絡的帶寬,所以TCP傳輸的效率不如UDP高。
2,UDP操做簡單,並且僅須要較少的監護,所以一般用於局域網高可靠性的分散系統中client/server應用程序。例如視頻會議系統,並不要求音頻視頻數據絕對的正確,只要保證連貫性就能夠了,這種狀況下顯然使用UDP會更合理一些。
TCP編程
原理
TCP編程是基於ServerSocket和Socket來實現的。TCP協議控制兩個通訊實體相互通訊的示意圖以下:

從圖上看兩個通訊實體並無服務器、客戶端之分,但那是兩個通訊實體已經創建鏈路後的示意圖。在鏈路創建前,必需要有一個通訊實體作出「主動姿態」,主動接收來自其餘通訊實體的鏈接請求,這方就是服務器端了。Java中能接收其餘通訊實體鏈接請求的類是ServerSocket,ServerSocket對象用來監聽來自客戶端的Socket鏈接,若是沒有鏈接,它將一直處於等待狀態。ServerSocket包含一個監聽來自客戶端鏈接請求的方法:

Socket accept();
若是接收到一個客戶端的Socket鏈接請求,會返回一個與客戶端Socket對應的服務端Socket,不然該方法一直處於等待狀態,線程阻塞。
ServerSocket類提供以下構造器:

ServerSocket(int port);
使用指定端口來建立,port範圍:0-65535。注意,在選擇端口時,必須當心。每個端口提供一種特定的服務,只有給出正確的端口,才能得到相應的服務。0~1023的端口號爲系統所保留,例如http服務的端口號爲80,telnet服務的端口號爲21,ftp服務的端口號爲23, 因此咱們在選擇端口號時,最好選擇一個大於1023的數以防止發生衝突。
ServerSocket(int port, int backlog);
增長一個用來改變鏈接隊列長度的參數backlog。
ServerSocket(int port, int backlog, InetAddress bindAddr);
在機器存在多個IP地址的狀況下,bindAddr參數指定將ServerSocket綁定到指定IP。
當ServerSocket使用完畢,應使用close()方法來關閉此ServerSocket。一般狀況下,服務器不該該只接收一個客戶端請求,而應該不斷接收來自客戶端的請求,因此程序能夠經過循環,不斷調用ServerSocket的accept方法:

ServerSocket ss = new ServerSocket(30000);
while(true) {
Socket s = ss.accept();
...
}

客戶端一般使用Socket來鏈接指定服務器,Socket類提供以下構造器:

Socket(InetAddress/String remoteAddress, int port);
建立鏈接到指定遠程主機、遠程端口的Socket,本地IP地址和端口使用默認值。
Socket(InetAddress/String remoteAddress, int port, InetAddress localAddr, int localPort);
綁定本地IP地址和端口,適用於本地主機有多個IP地址的情形。
上面兩個構造器指定遠程主機時既可使用InetAddress來指定,也能夠直接使用String對象來指定遠程IP。本地主機只有一個IP地址時,使用第一個方法更簡單。

實踐
咱們這裏實現一個功能,客戶端經過TCP協議傳輸一張圖片到服務器端,並顯示傳輸進度。
這裏將服務端程序寫成一個Android應用:

public class MainActivity extends AppCompatActivity {
private TextView text;
public static Toast toast = null;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
text = (TextView) findViewById(R.id.text);
text.setText("IP地址:"+getLocalHostIp());

//啓動服務器監聽線程
new ServerListener().start();
}

public class ServerListener extends Thread {
@Override
public void run() {
try {
ServerSocket serverSocket = new ServerSocket(30000);
// 循環的監聽
while (true) {
Socket socket = serverSocket.accept();// 阻塞
runOnUiThread(new Runnable(){
@Override
public void run() {
showToast("有客戶端鏈接到本機的30000端口!");
}
});
// 將socket傳給新的線程
TransportSocket ts = new TransportSocket(socket);
ts.start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}

public class TransportSocket extends Thread {
Socket socket;
int progress = 0;

public TransportSocket(Socket s) {
this.socket = s;
}

@Override
public void run() {
try {
File file = new File("/sdcard/receive.png"); //接收文件
if (!file.exists()) {
file.createNewFile();
}
BufferedInputStream is = new BufferedInputStream(socket.getInputStream()); // 讀進
BufferedOutputStream os = new BufferedOutputStream(new FileOutputStream(file));// 寫出
byte[] data = new byte[1024*5];// 每次讀取的字節數
int len= -1;
while ((len=is.read(data) )!= -1) {
os.write(data,0,len);
progress+=len;//進度
runOnUiThread(new Runnable() {
@Override
public void run() {
showToast("接收進度:" + progress);
}
});
}
progress = 0;
is.close();
os.flush();
os.close();
socket.close(); //關閉socket
runOnUiThread(new Runnable() {
@Override
public void run() {
showToast("接收完成!");
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
}

//及時toast
public void showToast(String info) {
if (toast == null) {
toast = Toast.makeText(this, "", Toast.LENGTH_SHORT);
}
toast.setText(info);
toast.show();
}


// 獲取本機IPv4地址
public static String getLocalHostIp() {
String ipaddress = "";
try {
Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces();
// 遍歷所用的網絡接口
while (en.hasMoreElements()) {
NetworkInterface nif = en.nextElement();// 獲得每個網絡接口綁定的全部ip
Enumeration<InetAddress> inet = nif.getInetAddresses();
// 遍歷每個接口綁定的全部ip
while (inet.hasMoreElements()) {
InetAddress ip = inet.nextElement();
if (!ip.isLoopbackAddress() && ip instanceof Inet4Address) {
return ipaddress = ip.getHostAddress();
}
}
}
} catch (SocketException e) {
System.out.print("獲取IP失敗");
e.printStackTrace();
}
return ipaddress;
}
}

添加權限:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>

再來看看客戶端代碼,一樣是一個Android應用:

public class MainActivity extends AppCompatActivity {
private Button button;
private int progress = 0;
private Toast toast = null;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button = (Button) findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
ClientSocket cs = new ClientSocket();
cs.start();
}
});
}

public class ClientSocket extends Thread {
int progress = 0;

@Override
public void run() {
try {
Socket socket = new Socket("192.168.1.43", 30000); //服務器IP及端口
File file = new File("/sdcard/send.png");//發送文件,記得客戶端此路徑下必需要有此文件存在
BufferedInputStream is = new BufferedInputStream(new FileInputStream(file));
BufferedOutputStream os =new BufferedOutputStream(socket.getOutputStream());
// 讀文件
double n = 1;
byte[] data = new byte[1024*5];//每次讀取的字節數
int len=-1;
while ((len=is.read(data))!= -1) {
os.write(data,0,len);
progress+=len;//進度
runOnUiThread(new Runnable(){
@Override
public void run() {
showToast("發送進度:"+progress);
}
});
}
progress=0;
is.close();
os.flush();
os.close();
socket.close(); //關閉socket
runOnUiThread(new Runnable(){
@Override
public void run() {
showToast("發送完成!");
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
}

public void showToast(String info) {
if (toast == null) {
toast = Toast.makeText(this, "", Toast.LENGTH_SHORT);
}
toast.setText(info);
toast.show();
}
}

一樣添加權限:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>

注意,這裏Socket鏈接的服務器IP地址和端口必須保持和服務端應用的一致。上面服務端應用已經將其IP地址首頁顯示出來了。
同時運行服務端和客戶端應用,這裏要確保它們處於聯網狀態且處於同一個局域網。點擊客戶端開始傳輸按鈕:

服務端顯示:

發送完成後,能夠看到,在服務器sdcard目錄下會生成一張receive.png圖片,內容與客戶端的send.png徹底一致。

UDP編程
原理
UDP是一種不可靠網絡協議,雙方Socket之間並無虛擬鏈路,這兩個Socket只是發送、接收數據報的對象。Java提供了DatagramSocket做爲基於UDP協議的Socket,使用DatagramPacket表明DatagramSocket發送、接收的數據報。
DatagramSocket自己只是碼頭,不維護狀態,不能產生IO流,惟一做用就是接收和發送數據報。看一下DatagramSocket構造函數:

DatagramSocket();
建立對象,綁定到本機默認IP地址,本機全部可用端口隨機選擇某個端口。
DatagramSocket(int port);
綁定到本機默認IP地址,指定端口。
DatagramSocket(int port, InetAdress addr);
綁定到指定IP地址,指定端口。
一般建立服務器時,咱們建立指定端口DatagramSocket對象—-這樣保證其餘客戶端能夠將數據發送到該服務器。一旦獲得DatagramSocket對象以後,能夠經過以下兩個方法接收和發送數據:

receive(DatagramPacket p);
從該DatagramSocket中接收數據。receive將一直等待(也就是說會阻塞調用該方法的線程),直到收到一個數據報爲止。
send(DatagramPacket p);
以該DatagramSocket向外發送數據
使用DatagramSocket發送數據時,DatagramSocket並不知道數據的目的地,而是由DatagramPacket自身決定的。
當C/S程序使用UDP時,實際上並無明顯的客戶端、服務器之分,雙方都要創建一個DatagramSocket對象來發送和接收數據,通常把固定IP,固定端口的DatagramSocket所在程序做爲服務器,由於該DatagramSocket能夠主動接受客戶端數據。
接下來看看DatagramPacket構造函數:

DatagramPacket(byte buf[], int length);
以一個空數組來建立DatagramPacket對象,該對象做用是接收DatagramSocket中的數據。
DatagramPacket(byte buf[], int offset, int length);
以一個空數組來建立DatagramPacket對象,並指定接收到的數據放入buf數組時從offset開始,最多放length個字節。
DatagramPacket(byte buf[], int length, InetAdress addr, int port);
以一個包含數據的數組來建立DatagramPacket對象,還指定了IP和端口—決定該數據目的地。
DatagramPacket(byte buf[], int offset, int length, InetAdress addr, int port);
建立用於發送的DatagramPacket對象,多指定了一個offset參數。
注:DatagramPacket同時還提供了getData()和setData()函數用來獲取和設置封裝在DatagramPacket對象裏面的字節數組,即構造器裏面的字節數組實參。

DatagramPacket提供了以下三個方法來獲取發送者的IP和端口:

InetAdress getAdress();
當程序準備發送此數據報時,返回數據報目標主機的IP地址;當程序接收到一個數據報時,返回該數據報發送主機的IP地址。
int getPort();
和上面函數相似,只是獲取的是端口。
SocketAdress getSocketAdress();
和上面函數相似,只是獲取的是SocketAdress對象,SocketAdress封裝了一個InetAdress對象和一個整形端口信息。
實踐
咱們接下來實現一個客戶端和服務器的局域網聊天系統,消息的發送和接收是基於UDP協議的。
首先看客戶端代碼:

public class MainActivity extends AppCompatActivity {
private Button send_message;
private EditText edit;
private TextView content;
private Toast toast = null;
private DatagramSocket sendDS; //udp發送socket
private DatagramSocket receiveDS; //udp接收socket

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
edit = (EditText) findViewById(R.id.edit);
send_message = (Button) findViewById(R.id.send_message);
content = (TextView) findViewById(R.id.content);

send_message.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
UdpSend us = new UdpSend();
us.start();
}
});
//啓動UDP監聽線程
new UdpReceive().start();
}

// 發送消息線程
class UdpSend extends Thread {

@Override
public void run() {
try {
runOnUiThread(new Runnable(){
@Override
public void run() {
if (edit.getText().toString().trim().isEmpty()) {
showToast("請輸入消息!");
return;
} else {
content.setText(content.getText()+"\n我說:"+edit.getText().toString());
edit.setText("");
}
}
});

byte[] data = edit.getText().toString().getBytes();
if (sendDS == null) {
sendDS = new DatagramSocket(null);
sendDS.setReuseAddress(true);
sendDS.bind(new InetSocketAddress(20000)); //發送端口20000
}
DatagramPacket packet = new DatagramPacket(data, data.length, InetAddress.getByName("192.168.1.43"), 25000); //接收端口25000
packet.setData(data);
sendDS.send(packet);
// sendDS.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}

// 接收消息線程
class UdpReceive extends Thread {

@Override
public void run() {
//消息循環
while(true) {
try {
if(receiveDS == null){
receiveDS = new DatagramSocket(null);
receiveDS.setReuseAddress(true);
receiveDS.bind(new InetSocketAddress(25000)); //接收端口25000
}
byte[] data = new byte[1024 * 4];
DatagramPacket dp = new DatagramPacket(data, data.length);
dp.setData(data);
receiveDS.receive(dp); //阻塞式,等待服務器消息
// receiveDS.close();
final byte[] data2 = data.clone();
runOnUiThread(new Runnable(){
@Override
public void run() {
content.setText(content.getText()+"\n服務器說:"+new String(data2));
}
});
} catch (Exception e) {
e.printStackTrace();
}
}

}
}

public void showToast(String info) {
if (toast == null) {
toast = Toast.makeText(this, "", Toast.LENGTH_SHORT);
}
toast.setText(info);
toast.show();
}
}

服務器代碼以下:

public class MainActivity extends AppCompatActivity {
private Button send_message;
private EditText edit;
private TextView content;
public static Toast toast = null;
public InetAddress clientAdress; //記錄客戶端IP地址
private DatagramSocket sendDS; //udp發送socket
private DatagramSocket receiveDS; //udp接收socket

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
edit = (EditText) findViewById(R.id.edit);
send_message = (Button) findViewById(R.id.send_message);
content = (TextView) findViewById(R.id.content);

send_message.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
UdpSend us = new UdpSend();
us.start();
}
});
//啓動UDP服務器監聽線程
new UdpReceive().start();
}

// 發送消息線程
class UdpSend extends Thread {

@Override
public void run() {
try {
runOnUiThread(new Runnable(){
@Override
public void run() {
if (edit.getText().toString().trim().isEmpty()) {
showToast("請輸入消息!");
return;
} else {
if (clientAdress == null) {
showToast("未獲取客戶端信息!");
return;
}
content.setText(content.getText()+"\n我說:"+edit.getText().toString());
edit.setText("");
}
}
});

byte[] data = edit.getText().toString().getBytes();
if (sendDS == null) {
sendDS = new DatagramSocket(null);
sendDS.setReuseAddress(true);
sendDS.bind(new InetSocketAddress(20000)); //發送端口20000
}
DatagramPacket packet = new DatagramPacket(data, data.length, clientAdress, 25000);
packet.setData(data);
sendDS.send(packet);
// sendDS.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}


// 接收消息線程
class UdpReceive extends Thread {

@Override
public void run() {
//消息循環
while(true) {
try {
if(receiveDS == null){
receiveDS = new DatagramSocket(null);
receiveDS.setReuseAddress(true);
receiveDS.bind(new InetSocketAddress(25000)); //接收端口25000
}
byte[] data = new byte[1024 * 4];
DatagramPacket dp = new DatagramPacket(data, data.length);
dp.setData(data);
receiveDS.receive(dp); //阻塞式,等待客戶端消息
// receiveDS.close();
clientAdress = dp.getAddress(); //獲取客戶端IP,後面就能夠發消息給客戶端了
final byte[] data2 = data.clone();
runOnUiThread(new Runnable(){
@Override
public void run() {
content.setText(content.getText()+"\n客戶端說:"+new String(data2));

}
});
} catch (Exception e) {
e.printStackTrace();
}
}
}
}


public void showToast(String info) {
if (toast == null) {
toast = Toast.makeText(this, "", Toast.LENGTH_SHORT);
}
toast.setText(info);
toast.show();
}
}

能夠看到,服務器端代碼和客戶端基本同樣,除了下面部分

clientAdress = dp.getAddress(); /**獲取客戶端IP,後面就能夠發消息給客戶端了*/
...
DatagramPacket packet = new DatagramPacket(data, data.length, clientAdress, 25000);

服務器端IP是固定的,但客戶端卻不是,因此服務器端須要利用客戶端發過來的數據報獲取客戶端的IP地址並記錄下來,以後就可使用剛纔獲取的客戶端IP地址clientAdress來向客戶端發消息啦。
同時打開客戶端和服務端應用,客戶端先發送一條信息給客戶端,客戶端效果以下圖:

服務端效果圖:


ServerSocket 選項
ServerSocket 有如下 3 個選項.

SO_TIMEOUT: 表示等待客戶鏈接的超時時間.
SO_REUSEADDR: 表示是否容許重用服務器所綁定的地址.
SO_RCVBUF: 表示接收數據的緩衝區的大小.
SO_TIMEOUT 選項

設置該選項: public void setSoTimeout(int timeout) throws SocketException
讀取該選項: public int getSoTimeout() throws SocketException
這個選項與Socket 的選項相同。SO_TIMEOUT,以毫秒爲單位,將此選項設爲非零的超時值時,在與此 Socket 關聯的 InputStream 上調用 read() 將只阻塞此時間長度。若是超過超時值,將引起 java.net.SocketTimeoutException,雖然 Socket 仍舊有效。選項必須在進入阻塞操做前被啓用才能生效。超時值必須是 大於 0 的數。超時值爲 0 被解釋爲無窮大超時值。

Socket設置讀寫超時:

Socket s = new Socket("172.0.0.1", 30000);
//讀取服務器的進程一直阻塞,超過10s,拋出SocketTimeoutException異常
s.setSoTimeout(10000);
1
2
3
上面方法說白了只是設置read方法的超時時間,這個方法是堵塞的!Socket其實還有一個超時時間,即鏈接超時,能夠經過下面方法設置。

Socket設置鏈接超時:

Socket s = new Socket();
//該Socket超過10s尚未鏈接到遠程服務器,認爲鏈接超時
s.connect(new InetAddress(host,port), 10000);
1
2
3
SO_REUSEADDR 選項

設置該選項: public void setResuseAddress(boolean on) throws SocketException
讀取該選項: public boolean getResuseAddress() throws SocketException
這個選項與Socket 的選項相同, 用於決定若是網絡上仍然有數據向舊的 ServerSocket 傳輸數據, 是否容許新的 ServerSocket 綁定到與舊的 ServerSocket 一樣的端口上。SO_REUSEADDR 選項的默認值與操做系統有關, 在某些操做系統中, 容許重用端口, 而在某些操做系統中不容許重用端口。
當 ServerSocket 關閉時, 若是網絡上還有發送到這個 ServerSocket 的數據, 這個ServerSocket 不會當即釋放本地端口, 而是會等待一段時間, 確保接收到了網絡上發送過來的延遲數據, 而後再釋放端口。因爲端口已經被佔用, 使得程序沒法綁定到該端口, 程序運行失敗, 並拋出 BindException:
java.net.BindException: bind failed: EADDRINUSE (Address already in use)

爲了確保一個進程關閉了 ServerSocket 後, 即便操做系統還沒釋放端口, 同一個主機上的其餘進程還能夠當即重用該端口, 能夠調用 ServerSocket 的 setResuseAddress(true) 方法,並且必須在 ServerSocket 尚未綁定到一個本地端口以前調用, 不然執行serverSocket.setReuseAddress(true) 方法無效。 此外, 兩個共用同一個端口的進程必須都調用 serverSocket.setResuseAddress(true) 方法, 才能使得一個進程關閉 ServerSocket 後, 另外一個進程的 ServerSocket 還可以馬上重用相同的端口。

if(receiveDS == null){
receiveDS = new DatagramSocket(null);
receiveDS.setReuseAddress(true);
receiveDS.bind(new InetSocketAddress(25000));
}

SO_RCVBUF 選項

設置該選項: public void setReceiveBufferSize(int size) throws SocketException
讀取該選項: public int getReceiveBufferSize() throws SocketException
SO_RCVBUF 表示服務器端的用於接收數據的緩衝區的大小, 以字節爲單位。 通常說來, 傳輸大的連續的數據塊(基於HTTP 或 FTP 協議的數據傳輸) 可使用較大的緩衝區, 這能夠減小傳輸數據的次數, 從而提升傳輸數據的效率. 而對於交互頻繁且單次傳送數量比較小的通訊(Telnet 和 網絡遊戲), 則應該採用小的緩衝區, 確保能及時把小批量的數據發送給對方。
SO_RCVBUF 的默認值與操做系統有關. 例如, 在Windows 2000 中運行如下代碼時, 顯示 SO_RCVBUF 的默認值爲 8192。

不管在 ServerSocket綁定到特定端口以前或以後, 調用 setReceiveBufferSize() 方法都有效. 例外狀況下是若是要設置大於 64 KB 的緩衝區, 則必須在 ServerSocket 綁定到特定端口以前進行設置纔有效。 執行 serverSocket.setReceiveBufferSize() 方法, 至關於對全部由 serverSocket.accept() 方法返回的 Socket 設置接收數據的緩衝區的大小。--------------------- 做者:huaxun66 來源:CSDN 原文:https://blog.csdn.net/huaxun66/article/details/53008542 版權聲明:本文爲博主原創文章,轉載請附上博文連接!

相關文章
相關標籤/搜索