1. 程式人生 > >Socket程式設計(一)

Socket程式設計(一)

1.套接字地址

一個客戶端要發起一次通訊,首先要知道執行伺服器端程式的主機IP地址。然後由網路基礎設施利用目標地址將客戶端傳送的資訊傳遞到正確主機上。在Java中,地址可以由一個字串定義,可以是數字型的地址(192.168.1.10(IPv4地址),fe20:12a0::0abc:1234(IPv6地址)),也可以是主機名(www.baidu.com)。在後面的例子中,主機名必須被解析出數字型地址才能用來進行通訊。

InetAddress類代表了一個網路目標地址,包括主機名和數字型的地址資訊。該類有兩個子類Inet4Address和Inet6Address分別對應了目前IP地址的兩個版本。

為了獲取本地主機地址,這裡用了NetworkInterface類的功能,該類提供了訪問主機所有資訊的功能。


import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.UnknownHostException;
import java.util.Enumeration;

public class InetAddressExample {
    public static void main(String[] args)
    {
        // Get the network interfaces and associated for this host
        try
        {
            NetworkInterface networkInterface = null;
            // 獲得該主機每一個介面的資訊
            Enumeration<NetworkInterface> interfaceList = NetworkInterface.getNetworkInterfaces();
            if(interfaceList == null)
            {
                System.out.println("--沒有發現介面--");
            }//if
            else
            {
                while(interfaceList.hasMoreElements())
                {
                     networkInterface = interfaceList.nextElement();
                     // 介面名稱
                     System.out.println("介面名稱->"+networkInterface.getName());
                     // 獲取與介面相關聯的地址 根據主機的不同配置 可能包含IPV4或IPV6地址
                     Enumeration<InetAddress> inetAddressList = networkInterface.getInetAddresses();
                     if(inetAddressList == null)
                     {
                         System.out.println("--此網路介面沒有地址--");
                     }//if
                     else
                     {
                         InetAddress address = null;
                         while(inetAddressList.hasMoreElements())
                         {
                             address = inetAddressList.nextElement();
                             // 對每個地址進行檢測判斷屬於哪個IP地址子類
                             System.out.print(address instanceof Inet4Address ? "(v4)" : (address instanceof Inet6Address ? "(v6)" : "(?)"));
                             // 列印IP地址
                             System.out.println(":"+address.getHostAddress());
                         }//while
                     }
                }//while
            }
        }
        catch (Exception e) 
        {
        }
 
        System.out.println("-------------------------------------------------------------------------");
        String host = "www.baidu.com";
        try
        {
            // 一個名字可能關聯了多個數字地址 該方法返回一組與給定主機名相關聯的所有地址的例項
            InetAddress[] addressesList = InetAddress.getAllByName(host);
            for(InetAddress address : addressesList)
            {
                System.out.println(address.getHostName()+"  "+address.getHostAddress());
            }//for
            // 給定主機名,確定主機的IP地址。
            InetAddress address2 = InetAddress.getByName(host);
            System.out.println(address2.getHostName()+" "+address2.getHostAddress());
            // 返回主機名
            InetAddress address3 = InetAddress.getLocalHost();
            System.out.println(address3.getHostName()+" "+address3.getHostAddress());
 
        } 
        catch (UnknownHostException e) 
        {
            e.printStackTrace();
        }
    }
}

1.獲取主機的網路介面列表:

Enumeration<NetworkInterface> interfaceList = NetworkInterface.getNetworkInterfaces();

getNetworkInterfaces()方法返回了一個列表,其中包括了該主機的每個介面對應的NetWorkInterface類的例項。

2.空列表檢測

            if(interfaceList == null)
            {
                System.out.println("--沒有發現介面--");
            }

3.獲取並列印列表中的每個介面地址:

                while(interfaceList.hasMoreElements())
                {
                     networkInterface = interfaceList.nextElement();
                     // 介面名稱
                     System.out.println("介面名稱->"+networkInterface.getName());
                     // 獲取與介面相關聯的地址 根據主機的不同配置 可能包含IPV4或IPV6地址
                     Enumeration<InetAddress> inetAddressList = networkInterface.getInetAddresses();
                     if(inetAddressList == null)
                     {
                         System.out.println("--此網路介面沒有地址--");
                     }//if
                     else
                     {
                         InetAddress address = null;
                         while(inetAddressList.hasMoreElements())
                         {
                             address = inetAddressList.nextElement();
                             // 對每個地址進行檢測判斷屬於哪個IP地址子類
                             System.out.print(address instanceof Inet4Address ? "(v4)" : (address instanceof Inet6Address ? "(v6)" : "(?)"));
                             // 列印IP地址
                             System.out.println(":"+address.getHostAddress());
                         }//while
                     }
                }//while

這裡通過getName()方法為介面返回了一個本地名稱。介面的本地名稱通常由字母和數字聯合組成,如:lo,net0

然後通過getInetAddresses()方法返回Enumeration<InetAddress>列表。

然後判斷該列表是否為空:

                     if(inetAddressList == null)
                     {
                         System.out.println("--此網路介面沒有地址--");
                     }

最後通過instanceof判斷其真實的例項,然後通過getHostAddress()方法返回字串來代表主機的數字型地址。

在下劃線以下的部分:

        String host = "www.baidu.com";
        try
        {
            // 一個名字可能關聯了多個數字地址 該方法返回一組與給定主機名相關聯的所有地址的例項
            InetAddress[] addressesList = InetAddress.getAllByName(host);
            for(InetAddress address : addressesList)
            {
                System.out.println(address.getHostName()+"  "+address.getHostAddress());
            }//for
            // 給定主機名,確定主機的IP地址。
            InetAddress address2 = InetAddress.getByName(host);
            System.out.println(address2.getHostName()+" "+address2.getHostAddress());
            // 返回主機名
            InetAddress address3 = InetAddress.getLocalHost();
            System.out.println(address3.getHostName()+" "+address3.getHostAddress());
 
        } 

(其他方法請看api文件)

2.TCP套接字

Java為Tcp協議提供了兩個類:Socket類和ServerSocket類。Socket例項代表TCP連線的一段,TCP連線是一條抽象的雙向通道,兩端分別由IP地址和埠號確定。在開始通訊前需要建立一個TCP連線,這需要TCP向伺服器端TCP傳送連線請求。ServerSocket例項監聽TCP連線請求,併為每個請求建立Socket例項。(伺服器端要同時處理ServerSocket例項和Socket例項,客戶端只需要使用Socket例項。)

package com.socket;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;

public class Client {
    public static final int port = 8080;   
    public static final String host = "localhost";
    public static void main(String[] args) {    
        System.out.println("Client啟動...");    
        while (true) {    
            Socket socket = null;  
            try {  
                //建立一個流套接字並將其連線到指定主機上的指定埠號  
                socket = new Socket(host,port);    

                //讀取伺服器端資料    
                BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));    
                //向伺服器端傳送資料    
                PrintStream out = new PrintStream(socket.getOutputStream());    
                System.out.print("請輸入: \t");    
                String str = new BufferedReader(new InputStreamReader(System.in)).readLine();    
                out.println(str);    

                String ret =null;
                boolean flag=false;
                do {
                	ret=input.readLine();
                	if(null==ret)
                		break;
                	System.out.println("伺服器端返回過來的是: " + ret);  
                	 // 如接收到 "OK" 則斷開連線    
                    if ("OK".equals(ret)) {  
                    	flag=true;
                        System.out.println("客戶端將關閉連線");    
                        Thread.sleep(500);    
                        break;    
                    } 
                }while(ret!=null);         
                if(flag)
                	break;

                out.close();  
                input.close();  
            } catch (Exception e) {  
                System.out.println("客戶端異常:" + e.getMessage());   
            } finally {  
                if (socket != null) {  
                    try {  
                        socket.close();  
                    } catch (IOException e) {  
                        socket = null;   
                        System.out.println("客戶端 finally 異常:" + e.getMessage());   
                    }  
                }  
            }  
        }    
    }    
}
package com.socket;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;

public class Server {
	public static final int port = 8080;// 監聽的埠號

	public static void main(String[] args) throws IOException {
		System.out.println("Server啟動...");
		Server server = new Server();
		// 建立一個ServerSocket,這裡可以指定連線請求的佇列長度
		// new ServerSocket(port,3);意味著當佇列中有3個連線請求是,如果Client再請求連線,就會被Server拒絕
		ServerSocket serverSocket = new ServerSocket(port);
		while (true) {
			// 從請求佇列中取出一個連線
			Socket socket = serverSocket.accept();
//			System.out.println(socket.getInetAddress());
			// 處理這次連線
			try {
				// 讀取客戶端資料
				BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
				String clientInputStr = input.readLine();// 這裡要注意和客戶端輸出流的寫方法對應,否則會拋 EOFException
				// 處理客戶端資料
				System.out.println("客戶端發過來的內容:" + clientInputStr);

				// 向客戶端回覆資訊
				PrintStream out = new PrintStream(socket.getOutputStream());
				System.out.print("請輸入:\t");
				// 傳送鍵盤輸入的一行
				String s = new BufferedReader(new InputStreamReader(System.in)).readLine();
				out.println(s);
				out.write(s.getBytes());
				
				out.close();
				input.close();
			} catch (Exception e) {
				System.out.println("伺服器 run 異常: " + e.getMessage());
			} finally {
				if (socket != null) {
					try {
						socket.close();
					} catch (Exception e) {
						socket = null;
						System.out.println("服務端 finally 異常:" + e.getMessage());
					}
				}
			}
		}
	}
}

服務端:

1.首先通過ServerSocket serverSocket = new ServerSocket(port);使其偵聽指定的埠。

2.通過死迴圈迭代新的連線請求:

當SocketServer例項通過accept()方法偵聽到有客戶端連線,它會返回一個Socket例項,該例項會連線到遠端的客戶端並能進行讀寫資料等操作。

這裡通過:

// 讀取客戶端資料
				BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
				String clientInputStr = input.readLine();// 這裡要注意和客戶端輸出流的寫方法對應,否則會拋 EOFException
				// 處理客戶端資料
				System.out.println("客戶端發過來的內容:" + clientInputStr);

這裡通過soket.getInputStream()獲取客戶端輸入流,然後通過InputStreamReader()和BufferedReader()獲取到Reader流物件,通過readLine()獲取客戶端輸入的一行資料,最後列印。

然後通過:

// 向客戶端回覆資訊
				PrintStream out = new PrintStream(socket.getOutputStream());
				System.out.print("請輸入:\t");
				// 傳送鍵盤輸入的一行
				String s = new BufferedReader(new InputStreamReader(System.in)).readLine();
				out.println(s);

這裡通過soket.getOutputStream()獲取客戶端的輸出流物件,並將其封裝入PrintStream中,然後通過String s = new BufferedReader(new InputStreamReader(System.in)).readLine();獲取一行輸入,最後out.println(s)將資料傳送至客戶端。

3.最後通過close方法關閉各個流物件。

客戶端:

1.通過socket = new Socket(host,port);    獲取一個套接字物件並繫結到指定的埠號。

2.向指定的服務端傳送資料:

                //向伺服器端傳送資料    
                PrintStream out = new PrintStream(socket.getOutputStream());    
                System.out.print("請輸入: \t");    
                String str = new BufferedReader(new InputStreamReader(System.in)).readLine();    
                out.println(str);    

3.從服務端接收資料:

                BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));      
                String ret = input.readLine();     
                System.out.println("伺服器端返回過來的是: " + ret);    

4.最後檢測是否是OK,如果是就跳出迴圈否則就再繫結服務端傳送資料。

3.UDP套接字

UDP協議提供了一種不同於TCP協議的端對端服務。實際上UDP協議只實現了兩個功能:1.在IP協議的基礎上添加了另一層地址(埠)2.對資料傳輸過程中可能產生的資料錯誤進行了檢測,並拋棄以及損壞的資料。

Java中通過DatagramPacket類和DatagramSocket類使用UDP套接字。客戶端和服務端都使用DatagramSocket來發送資料,使用DatagramPacket來接受資料。

與TCP協議傳送和接受位元組流不同,UDP終端交換的是一種稱為資料報文的自包含資訊。這種資訊在Java中表示為DatagramPacket類的例項。傳送資訊時,java建立一個待發送資訊的DatagramPacket例項,然後將其作為引數傳遞給DatagramSocket類的send()方法。接受訊息時,java首先建立一個DatagramSocket例項,該例項預先分配了一些空間。然後將接收到的資訊存放在該空間,然後把該例項作為引數傳遞給DatagramSocket的receive()方法。

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

public class UDPClient {

	public static void main(String[] args) {
		try {
			DatagramSocket socket = new DatagramSocket();
			String text = "客戶端傳送資訊";
			byte[] buf = text.getBytes();
			DatagramPacket packet = new DatagramPacket(buf, buf.length, InetAddress.getLocalHost(), 9002);
			socket.send(packet);
			socket.close();
		} catch (Exception e) {
			e.printStackTrace();

		}
	}
}
import java.net.DatagramPacket;
import java.net.DatagramSocket;

public class UDPServer {
    
    public static void main(String[] args) {
    	 try {
             System.out.println("UDP服務端啟動……");
             DatagramSocket socket = new DatagramSocket(9002);
             while(true){ 
                 byte[] buf = new byte[2048];
                 DatagramPacket packet = new DatagramPacket(buf, buf.length);
                 socket.receive(packet);
                 byte[] data = packet.getData();
                 String msg = new String(data, 0, packet.getLength());
                 System.out.println(msg);
             }
         } catch (Exception e) {
             e.printStackTrace();
             
         }
    }
}

客戶端:

這裡首先建立一個DatagramPacket例項封裝資料,然後通過DatagramSocket例項的send()方法傳送資料。最後通過服務端的DatagramSocket例項的receive方法將資料儲存到byte[]陣列中。需要注意的是當buf陣列設定過小是,當資料大於該陣列時那麼receive()方法只返回這條訊息的前n個位元組。超出部分會自動被丟棄,而且對接程式不會有任何丟失資訊的提示。(UDP資料報文所能負載的最多資料為65507位元組。)

另一個需要注意的是,DatagramPacket的getData()方法,該方法返回的是緩衝區的原始大小。

OK~簡單的TCP,UDP的Demo就完成了。