1. 程式人生 > >基本套接字TCP和UDP

基本套接字TCP和UDP

TCP的Java支援

     協議相當於相互通訊的程式間達成的一種約定,它規定了分組報文的結構、交換方式、包含的意義以及怎樣對報文所包含的資訊進行解析,TCP/IP協議族有IP協議、TCP協議和UDP協議現在TCP/IP協議族中的主要socket型別為流套接字(使用TCP協議)和資料報套接字(使用UDP協議)。

    TCP協議提供面向連線的服務,通過它建立的是可靠地連線。Java為TCP協議提供了兩個類:Socket類和ServerSocket類。一個Socket例項代表了TCP連線的一個客戶端,而一個ServerSocket例項代表了TCP連線的一個伺服器端,一般在TCP Socket程式設計中,客戶端有多個,而伺服器端只有一個,客戶端TCP向伺服器端TCP傳送連線請求,伺服器端的ServerSocket例項則監聽來自客戶端的TCP連線請求,併為每個請求建立新的Socket例項,由於服務端在呼叫accept()等待客戶端的連線請求時會阻塞,直到收到客戶端傳送的連線請求才會繼續往下執行程式碼,因此要為每個Socket連線開啟一個執行緒。伺服器端要同時處理ServerSocket例項和Socket例項,而客戶端只需要使用Socket例項。另外,每個Socket例項會關聯一個InputStream和OutputStream物件,我們通過將位元組寫入套接字的OutputStream來發送資料,並通過從InputStream來接收資料。

TCP連線的建立步驟

客戶端向伺服器端傳送連線請求後,就被動地等待伺服器的響應。典型的TCP客戶端要經過下面三步操作:

   1、建立一個Socket例項:建構函式向指定的遠端主機和埠建立一個TCP連線;

   2.通過套接字的I/O流與服務端通訊;

   3、使用Socket類的close方法關閉連線。

 服務端的工作是建立一個通訊終端,並被動地等待客戶端的連線。典型的TCP服務端執行如下兩步操作:

     1、建立一個ServerSocket例項並指定本地埠,用來監聽客戶端在該埠傳送的TCP連線請求;

     2、重複執行:

           1)呼叫ServerSocket的accept()方法以獲取客戶端連線,並通過其返回值建立一個Socket例項;

           2)為返回的Socket例項開啟新的執行緒,並使用返回的Socket例項的I/O流與客戶端通訊;

           3)通訊完成後,使用Socket類的close()方法關閉該客戶端的套接字連線。

DEMO

  客戶端

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Enumeration;

public class Exercise {
	public void tcpExample(){
		try {
			Socket socket = new Socket("192.168.138.46",8344);
			System.out.println("Connection ....to server sending echo string");
			OutputStream out = socket.getOutputStream();
			System.out.println("傳送資料……");
			out.write("測試連線併發送資料到伺服器成功……".getBytes());
			socket.close();
			System.out.println("客戶端已經關閉");
			
		} catch (UnknownHostException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		
	}
	public static void main(String[] args) {
		Exercise e = new Exercise();
		//e.getInterfaceExample();
		e.tcpExample();
		
	}
}

服務端
package NET;

import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;

public class TCPServer {
	//設定緩衝區的大小
	private static final int BUFSIZE = 50;
	
	public static void tcpsever(){
		try {
			ServerSocket serSocket = new ServerSocket(8344);
			int recvMsgSize = 0;
			int total=0;
			byte[] receive =  new byte[BUFSIZE];
			while(true){
				//此方法返回一個伺服器的關聯socket,用此進行資料交換
				Socket clntSock = serSocket.accept();
				SocketAddress socadd = clntSock.getRemoteSocketAddress();
				System.out.println("clint address:"+socadd);
				InputStream in = clntSock.getInputStream();
				while((total=in.read(receive, recvMsgSize, BUFSIZE-recvMsgSize))!=-1){
					recvMsgSize += total;
				}
				System.out.println("從客戶端收到的資料:"+new String(receive));
				clntSock.close();
				System.out.println("關閉和此客戶端的連線……");
			}
			
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
	public static void main(String args[]){
		tcpsever();
	}

}

UDP的Java支援

    UDP協議提供的服務不同於TCP協議的端到端服務,它是面向非連線的,屬不可靠協議,UDP套接字在使用前不需要進行連線。實際上,UDP協議只實現了兩個功能:

    1)在IP協議的基礎上添加了埠;

    2)對傳輸過程中可能產生的資料錯誤進行了檢測,並拋棄已經損壞的資料。

    Java通過DatagramPacket類和DatagramSocket類來使用UDP套接字,客戶端和伺服器端都通過DatagramSocket的send()方法和receive()方法來發送和接收資料,用DatagramPacket來包裝需要傳送或者接收到的資料。傳送資訊時,Java建立一個包含待發送資訊的DatagramPacket例項,並將其作為引數傳遞給DatagramSocket例項的send()方法;接收資訊時,Java程式首先建立一個DatagramPacket例項,該例項預先分配了一些空間,並將接收到的資訊存放在該空間中,然後把該例項作為引數傳遞給DatagramSocket例項的receive()方法。在建立DatagramPacket例項時,要注意:如果該例項用來包裝待接收的資料,則不指定資料來源的遠端主機和埠,只需指定一個快取資料的byte陣列即可(在呼叫receive()方法接收到資料後,源地址和埠等資訊會自動包含在DatagramPacket例項中),而如果該例項用來包裝待發送的資料,則要指定要傳送到的目的主機和埠。

UDP的通訊建立的步驟

UDP客戶端首先向被動等待聯絡的伺服器傳送一個數據報文。一個典型的UDP客戶端要經過下面三步操作:

    1、建立一個DatagramSocket例項,可以有選擇地對本地地址和埠號進行設定,如果設定了埠號,則客戶端會在該埠號上監聽從伺服器端傳送來的資料;

    2、使用DatagramSocket例項的send()和receive()方法來發送和接收DatagramPacket例項,進行通訊;

    3、通訊完成後,呼叫DatagramSocket例項的close()方法來關閉該套接字。

由於UDP是無連線的,因此UDP服務端不需要等待客戶端的請求以建立連線。另外,UDP伺服器為所有通訊使用同一套接字,這點與TCP伺服器不同,TCP伺服器則為每個成功返回的accept()方法建立一個新的套接字。一個典型的UDP服務端要經過下面三步操作:

1、建立一個DatagramSocket例項,指定本地埠號,並可以有選擇地指定本地地址,此時,伺服器已經準備好從任何客戶端接收資料報文;

    2、使用DatagramSocket例項的receive()方法接收一個DatagramPacket例項,當receive()方法返回時,資料報文就包含了客戶端的地址,這樣就知道了回覆資訊應該傳送到什麼地方;

    3、使用DatagramSocket例項的send()方法向伺服器端返回DatagramPacket例項。

UDP Socket Demo

這裡有一點需要注意:

UDP程式在receive()方法處阻塞,直到收到一個數據報文或等待超時。由於UDP協議是不可靠協議,如果資料報在傳輸過程中發生丟失,那麼程式將會一直阻塞在receive()方法處,這樣客戶端將永遠都接收不到伺服器端傳送回來的資料,但是又沒有任何提示。為了避免這個問題,我們在客戶端使用DatagramSocket類的setSoTimeout()方法來制定receive()方法的最長阻塞時間,並指定重發資料報的次數,如果每次阻塞都超時,並且重發次數達到了設定的上限,則關閉客戶端。


DEMO‘
package NET;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;

import com.sun.jmx.snmp.InetAddressAcl;

public class UDP {
	private final static int  TIMEOUT = 3000;
	private final static int BUFSIZE = 50;
	private final static int tries = 5;
	/**
	 * udp連線的客戶端
	 * @throws SocketException 
	 * @throws UnknownHostException 
	 */
	public void udpclnt() throws SocketException, UnknownHostException{
		int tril = 0;
		DatagramSocket socket = new DatagramSocket();
		String art = "UDP連線測試成功……";
		byte[] by = art.getBytes();
		InetAddress address = null;
		address = InetAddress.getByName("lenovo-PC");
		socket.setSoTimeout(0);
		DatagramPacket send = new DatagramPacket(by,by.length,address,30000 );
		  try {
			  socket.send(send); 
			  
			  } catch (IOException e) {
				  tril++;
				  System.out.println("進行重發:剩餘重發次數"+ (tries-tril));
			  }
		  socket.close();
	}
    /**
     * udp連線的伺服器
     */
    public void udpServer(){
    	try {
			DatagramSocket socket = new DatagramSocket(30000);
			socket.setSoTimeout(TIMEOUT);
			DatagramPacket receive = new DatagramPacket(new byte[50], BUFSIZE);
			socket.receive(receive);
			System.out.println("連線的客戶端IP:"+receive.getAddress().getHostAddress()+"埠:"+receive.getPort());
		   byte[]re= receive.getData();
		    System.out.println("收到的資料:"+new String(re));
    	} catch (SocketException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
    }
    public static void main(String []args){
    	final UDP udp = new UDP();
    	Thread thread = new Thread(){
    			   public void run(){
    			     try {
						udp.udpclnt();
					} catch (SocketException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					} catch (UnknownHostException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
    			   }
    			};
    	thread.start();
    	udp.udpServer();
    	
    }
}


  總結:

1、編寫TCP客戶端程式,在例項化Socket類時,要注意,底層的TCP協議只能處理IP協議,如果傳遞的第一個引數是主機名字而不是你IP地址,Socket類具體實現的時候會將其解析成相應的地址,若因為某些原因連線失敗,建構函式會丟擲一個IOException異常。

2、TCP協議讀寫資料時,read()方法在沒有可讀資料時會阻塞等待,直到有新的資料可讀。另外,TCP協議並不能確定在read()和write()方法中所傳送資訊的界限,接收或傳送的資料可能被TCP協議分割成了多個部分。說到這裡,其實是與內部結構相關的

TCP在傳送之後會把所有的資料複製到緩衝區中,而UDP則send之後所有的訊息都在傳送途中。

3、編寫TCP伺服器端的程式將在accept()方法處阻塞,以等待客戶端的連線請求,一旦取得連線,便要為每個客戶端的連線建立一個Socket例項來進行資料通訊。

4、在UDP程式中,建立DatagramPacket例項時,如果沒有指定遠端主機地址和埠,則該例項用來接收資料(儘管可以呼叫setXXX()等方法指定),如果指定了遠端主機地址和埠,則該例項用來發送資料。

5、UDP程式在receive()方法處阻塞,直到收到一個數據報文或等待超時。由於UDP協議是不可靠協議,如果資料報在傳輸過程中發生丟失,那麼程式將會一直阻塞在receive()方法處,這對客戶端來說是肯定不行的,為了避免這個問題,我們在客戶端使用DatagramSocket類的setSoTimeout()方法來制定receive()方法的最長阻塞時間,並指定重發資料報的次數,如果每次阻塞都超時,並且重發次數達到了設定的上限,則關閉客戶端。

6、UDP伺服器為所有通訊使用同一套接字,這點與TCP伺服器不同,TCP伺服器則為每個成功返回的accept()方法建立一個新的套接字。

7、在UDP程式中,DatagramSocket的每一次receive()呼叫最多隻能接收呼叫一次send()方法所傳送的資料,而且,不同的receive()方法呼叫絕對不會返回同一個send()方法所傳送的額資料。

8、在UDP套接字程式設計中,如果receive()方法在一個緩衝區大小為n的DatagramPscket例項中呼叫,而接受佇列中的第一個訊息長度大於n,則receive()方法只返回這條訊息的前n個位元組,超出的其他位元組部分將自動被丟棄,而且也沒有任何訊息丟失的提示。因此,接受者應該提供一個足夠大的快取空間的DatagramPacket例項,以完整地存放呼叫receive()方法時應用程式協議所允許的最大長度的訊息。一個DatagramPacket例項中所執行傳輸的最大資料量為65507個位元組,即UDP資料報文所能負載的最多資料,因此,使用一個有65600位元組左右快取陣列的資料總是安全的。

9、在UDP套接字程式設計中,每一個DatagramPacket例項都包含一個內部訊息長度值,而該例項一接收到新訊息,這個長度值便可能改變(以反映實際接收的訊息的位元組數)。如果一個應用程式使用同一個DatagramPacket例項多次呼叫receive()方法,每次呼叫前就必須顯式地將訊息的內部長度重置為緩衝區的實際長度。

10、另一個潛在問題的根源是DatagramPacket類的getData()方法,該方法總是返回緩衝區的原始大小,忽略了實際資料的內部偏移量和長度資訊。

11.TCP是需要連線的,UDP不用連線,是不可靠的傳輸,所以使用UDP要重排亂序訊息或者處理丟失訊息,因此這就加大了伺服器的負擔。但是之所以使用UDP是因為在傳輸資訊很少時,TCP握手花費的時間基本是傳輸的2倍,還有在沒有可靠性要求時,UDP很靈活。