(四十四)、網絡基礎與TCP,UDP協議

計算機網絡基礎知識

通訊協議

  • 協議protocol:通信雙方必須遵循的規矩

  • 網絡通信協議:網絡協議是構成網絡的基本組件之一,協議是若干規則和協定的組合。

  • 網絡通信協議是分層的:一般指A機器的第n層與B機器的第n層的對話,這種對話中所使用的若干規則和約束便稱爲第n層網絡協議。

TCP/IP協議

在Internet中TCP/IP協議是使用最爲廣泛的通訊協議(互聯網上的一種事實的標準)。TCP/IP是英文Transmission Control Protocol/Internet Protocol的縮寫,意思是“傳輸控制協議/網際協議”

TCP/IP 協議是一個工業標準協議套件,專爲跨廣域網(WAN)的大型互聯網絡而設計。

TCP/IP 網絡體系結構模型就是遵循TCP/IP 協議進行通信的一種分層體系,現今,Internet和Intranet所使用的協議一般都爲TCP/IP 協議。
在瞭解該協議之前,我們必須掌握基於該協議的體系結構層次,而TCP/IP體系結構分爲四層。

  • 第一層 網絡接口層
    包括用於協作IP數據在已有網絡介質上傳輸的協議,提供TCP/IP協議的數據結構和實際物理硬件之間的接口。比如地址解析協議(Address Resolution Protocol, ARP )等。
  • 第二層 網絡層
    對應於OSI模型的網絡層,主要包含了IP、RIP等相關協議,負責數據的打包、尋址及路由。還包括網間控制報文協議(ICMP)來提供網絡診斷信息。
  • 第三層 傳輸層
    對應於OSI的傳輸層,提供了兩種端到端的通信服務,分別是TCP和UDP協議。
  • 第四層 應用層
    對應於OSI的應用層、表達層和會話層,提供了網絡與應用之間的對話接口。包含了各種網絡應用層協議,比如Http、FTP等應用協議。

IP地址和端口號

  • IP地址:網絡中每臺計算機的一個標識號

    是一個邏輯地址
    127.0.0.1 代表本機地址    localhost
    
  • 端口號:具有網絡功能的應用軟件的標識號 //邏輯上的端口 501

    端口是一個軟件結構,被客戶程序或服務程序用來發送和接收數據,一臺服務器有 256*256個端口。 0 - 65535
    0-1023是公認端口號,即已經公認定義或爲將要公認定義的軟件保留的
    1024-65535是並沒有公共定義的端口號,用戶可以自己定義這些端口的作用。

  • 端口與協議有關:TCP和UDP的端口互不相干

InetAdress類

Java一切皆對象:這個類是對IP地址的封裝

java.net.InetAddress類是java的IP地址封裝類,內部隱藏了IP地址,可以通過它很容易的使用主機名以及IP地址。一般供各種網絡類使用。直接由Object類派生並實現了序列化接口。該類用兩個字段表示一個地址:hostName與address。hostName包含主機名,address包含IP地址。InetAddress支持ipv4與ipv6地址。

一些常用方法如下:
•static InetAddress getLocalHost():返回本地計算機的InetAddress。
•String getHostName():返回指定InetAddress對象的主機名。
•String getHostAddress():返回指定InetAddress對象的主機地址的字符串形式
•static InetAddress getByName(String hostname):使用DNS查找指定主機名或域名爲hostname的IP地址,並返回InetAddress。
•byte[] getAddress():返回指定對象的IP地址的以網絡字節爲順序的4個元素的字節數組。

應用舉例:

InetAddress addr = InetAddress.getByName("java.sun.com")
System.out.println(addr);

以上代碼將打印網址域名爲java.sun.com的對應主機和IP地址信息。因此,在網絡編程中,我們可以很方便的使用InetAddress類實現Ip地址的各種操作。

示例如下:

import java.net.*;
public class TestNet {
    public static void main(String[] args) throws Exception{
        // 獲得本地主機的相關信息
        InetAddress ia = InetAddress.getLocalHost();
        // 獲取本地ip地址
        System.out.println(ia.getHostAddress());
        // 獲取本地主機名
        System.out.println(ia.getHostName());
        // 獲取主機名爲xiaoxiao的ip地址
        System.out.println(InetAddress.getByName("xiaoxiao").getHostAddress());
        // 獲得指定域名的主機信息
        System.out.println(InetAddress.getByName("java.sun.com"));
        // 獲得本地PC機名爲PC-20131114BGRJ的所有ip地址
        InetAddress[] ias = InetAddress.getAllByName("PC-20131114BGRJ");
        for(InetAddress i : ias){
            System.out.println(i.getHostAddress());
        }
    }
}

Main.java

public class Main {
    public static void main(String[] args) throws IOException {
        InetAddress localHost = InetAddress.getLocalHost();
        System.out.println(localHost.getHostName());// 獲取本機主機名
        System.out.println(localHost.getHostAddress());// 獲取本機IP地址

        InetAddress byName = InetAddress.getByName("192.168.2.12");
        System.out.println(byName.isReachable(2000));// 測試這個主機是否存1

        for (int i = 1; i < 255; i++) {
            final String ip = "192.168.2." + i;
            final InetAddress address = InetAddress.getByName(ip);
            // if (address.isReachable(2000)) {
            // System.out.println(ip);
            // }
            new Thread(new Runnable() {

                @Override
                public void run() {
                    try {
                        if (address.isReachable(2500)) {
                            System.out.println(ip + "存在");
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    }
}

TCP

TCP協議

TCP(Transmission Control Protocol 傳輸控制協議)是一種面向連接(連接導向)的、可靠的、基於IP的傳輸層協議。彌補了IP協議的不足,屬於一種較高級的協議,它實現了數據包的有力捆綁,通過排序和重傳來確保數據傳輸的可靠(即數據的準確傳輸以及完整性)。排序可以保證數據的讀取是按照正確的格式進行,重傳則保證了數據能夠準確傳送到目的地!

Java對tcp協議的支持

java.net包中定義了兩個類ServerSocket 和Socket ,分別用來實現雙向連接的server 端和client 端。

Socket

     該類爲建立連向服務器套接字及啓動協議交換而設計,當進程通過網絡進行通信的時候,java技術使用流模型來實現數據的通信。

一個Socket包括兩個流,分別爲一個輸入流和一個輸出流,一個進程如果要通過網絡向另一個進程發送數據,只需簡單地寫入與Socket相關聯的輸出流,同樣,一個進程通過從與Socket相關聯的輸入流來讀取另一個進程所寫的數據。
如果通過TCP協議建立連接,則服務器必須運行一個單獨的進程來等待連接,而某一客戶機必須試圖到達服務器,就好比某人打電話,必須保證另一方等待電話呼叫,這樣才能實現兩人之間的通信。

示例:客戶端

public class ClientSocket {
    public static void main(String[] args) throws UnknownHostException,
            IOException {
        // 創建一個socket
        Socket client = new Socket("192.168.1.105", 10000);
        // 獲取客戶端輸出流
        OutputStream os = client.getOutputStream();
        os.write(("客戶端連接成功 \n").getBytes());
        client.shutdownOutput();
        os.close();
    }
}

服務器端

public class ServerDemo {
    public static void main(String[] args) throws IOException {
        ServerSocket server = new ServerSocket(1314);
        Socket socket = server.accept();// 阻塞式方法,返回客戶端對象
        InputStream is = socket.getInputStream();
        BufferedReader bufferedReader = new BufferedReader(
                new InputStreamReader(is));
        System.out.println(bufferedReader.readLine());
    }
}

客戶端向服務器發送了一條消息

tcp實現羣聊示例

客戶端:

public class TcpClinet {
    public static void main(String[] args) throws UnknownHostException,
            IOException {
        // 1、創建客戶端套接字
        Socket client = new Socket("192.168.1.105", 10000);
        new ClientSend(client).start(); // 啓動客戶端發送信息的線程
        new ClientReceive(client).start(); // 啓動客戶端接受信息的線程
    }
}

服務器端:

/*
 c/s    client server 客戶端/服務器       
 b/s    browser server 
 */
public class TcpServer {
    public static void main(String[] args) throws IOException,
            InterruptedException {

        List<Socket> list = new ArrayList<>();
        // 啓動一個新的線程去處理這個客戶端的交流
        // 創建服務器斷的套接字
        ServerSocket server = new ServerSocket(10000);
        // 等待客戶端的鏈接。這個方法是一個阻塞式方法。當有客戶端鏈接的時候,這個方法就會返回,返回鏈接的那個客戶端
        while (true) {
            Socket socket = server.accept();
            synchronized (list) {
                list.add(socket);
            }
            new HandleSocket(socket, list).start();
        }
    }
}

客戶端發送線程:

/**
 * 客戶端向服務端發送信息的線程
 * 
 * @author Administrator
 *
 */
public class ClientSend extends Thread {
    private Scanner scanner;
    private Socket socket;

    public ClientSend(Socket socket) {
        this.socket = socket;
    }
    @Override
    public void run() {
        scanner = new Scanner(System.in);
        try {
            PrintStream ps = new PrintStream(socket.getOutputStream());
            String line = "";
            while ((line = scanner.nextLine()) != null) {
                ps.println(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

客戶端接收線程:

public class ClientReceive extends Thread {

    private Socket socket;

    public ClientReceive(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        try {
            BufferedReader reader = new BufferedReader(new InputStreamReader(
                    socket.getInputStream()));
            String line = "";
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

服務器處理請求線程:

/**
 * 處理每個鏈接到服務器的客戶端
 * 
 * @author Administrator
 *
 */
public class HandleSocket extends Thread {

    private Socket socket;
    private List<Socket> list;

    public HandleSocket(Socket socket, List<Socket> list) {
        this.socket = socket;
        this.list = list;
    }

    @Override
    public void run() {
        InetAddress address = socket.getInetAddress();// 獲取連接到客戶端的這個socket的地址
        String ip = address.getHostAddress();
        System.out.println(ip + "上線了");
        // 關閉指定IP
        if (ip.equals("192.168.1.105")) {
            list.remove(socket);
            System.out.println("拉入黑名單");
            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            return;
        }
        try {
            BufferedReader reader = new BufferedReader(new InputStreamReader(
                    socket.getInputStream()));
            String line = "";
            while ((line = reader.readLine()) != null) {
                String msg = ip + "說:" + line;
                System.out.println(msg); // 輸出到服務器的控制檯
                sendToAll(msg);
            }

        } catch (IOException e) {
            System.out.println(ip + "下線了");
            synchronized (list) {
                list.remove(socket);
            }
        }

    }

    private void sendToAll(String msg) {
        synchronized (list) {
            for (Socket s : list) {
                if (s != socket) {
                    try {
                        PrintStream ps = new PrintStream(s.getOutputStream());
                        ps.println(msg);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}

以上代碼可以實現一個簡單的羣聊功能

UDP

UDP協議

UDP(User Datagrams Protocol)用戶數據報協議,是一種使用數據報的機制來傳遞信息的協議。

數據報(Datagrams)是一種在不同機器之間傳遞的信息包,該信息包一旦從某一機器被髮送給指定目標,那麼該發送過程並不會保證數據一定到達目的地,甚至不保證目的地的存在真實性。反之,數據報被接受時,不保證數據沒有受損,也不保證發送該數據報的機器仍在等待響應。

由此可見,UDP協議是一種基於數據報的快速的(因爲它無需花時間去保證數據是否損壞,無需花時間確定接受方是否存在並等待響應)、無連接的、不可靠的數據報傳輸協議。

在java中,通過兩個特定類來實現UDP協議頂層數據報,分別是DatagramPacket和DatagramSocket,其中類DatagramPacket是一個數據容器,是數據報包,用來保存即將要傳輸的數據,將地址信息和要發送的數據以字節數組的方式同時壓縮入這個類創建的對象中;而類DatagramSocket表示用來發送和接收DatagramPacket的套接字,實現了數據報的通信方式。數據報套接字是包投遞服務的發送或接收點。

DatagramSocket

DatagramSocket類常見的構造方法如下:

  • •DatagramSocket():創建一個以當前計算機的任意可用端口爲發送端口的數據報連接
  • •DatagramSocket(int port):創建一個以當前計算機port端口爲發送端口的數據報連接
  • •DatagramScoket(int port, InetAddress address):創建一個以當前計算機的port端口爲發送端口、address爲IP地址的發送數據報連接

DatagramSocket類常用的幾個方法如下:

  • •void close() throws IOException:關閉數據報連接
  • •void recieve(DatagramPacket packet):接收來自於packet數據報的信息,阻塞式方法
  • •void send(DatagramPacket packet):發送packet數據報
  • •void connect(InetAddress address, int port):以指定端口port爲發送端口,向IP地址爲address的計算機發送數據報連接
  • •void disconnect():斷開連接
  • •DatagramChannel getChannel():和SocketChannel類似

DatagramSocket類在客戶端創建自尋址套接字與服務器端進行通信連接,併發送和接受自尋址套接字。雖然有多個構造方法可供選擇,但發現創建客戶端自尋址套接字最便利的選擇是DatagramSocket()方法,而服務器端則是DatagramSocket(int port)方法,如果未能創建自尋址套接字或綁定自尋址套接字到本地端口,那麼這兩個方法都將拋出一個SocketException對象,一旦程序創建了DatagramSocket對象,那麼程序分別調用send(DatagramPacket dgp)和receive(DatagramPacket dgp)來發送和接收自尋址數據包。

DatagramPacket

DatagramPacket類常見的構造方法如下:

  • •DatagramPacket(byte[] buff, int length);
  • •DatagramPacket(byte[] buf, int offset, int length);
  • •DatagramPacket(byte[] buf, int length, InetAddress address, int port);
  • •DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port);

    第一個構造方法用於創建一個指定數據緩衝區大小和信息包的容量大小的DatagramPacket,但沒有數據包的地址和端口信息,這些信息可以通過調用方法setAddress(InetAddress addr)和setPort(int port)添加上。


第二個構造方法用於創建一個長度大小爲length的緩衝區,並指定數據存儲(讀取)的偏移地址爲offset的DatagramPacket。

第三個創建一個指定緩衝區大小、傳送(接受)IP地址、端口號的DatagramPacket。一般情況下,發送地址是由DatagramSocket指定。

DatagramPacket類常用的幾個方法如下:
  • •byte[] getData():用於得到發送過來的DatagramPacket中的數據
  • •void setData(byte[] buf):用於將buf中的數據寫入DatagramPacket中,以備發送
  • •InetAddress getAddress():返回目標的InetAddress
  • •int getLength():返回將要發送或接收到的數據的長度
  • •int getPort():返回某臺遠程主機的端口號,此數據報將要發往該主機或者是從該主機接收到的
  • •SocketAddress getSocketAddress():獲取要將此包發送到的或發出此數據報的遠程主機的 SocketAddress(通常爲IP地址+端口號)

示例:發送端

public class UdpSender {
    public static void main(String[] args) throws IOException {
        Scanner scanner = new Scanner(System.in);
        // 創建一個DatagramsSocket 快遞公司
        DatagramSocket socket = new DatagramSocket(10000);
        byte[] data = "今天晚上洗腳".getBytes();
        // 把信息打包 參數1:發送的字節數組 參數2:數組中要發出去多少個字節。 參數3:對方的ip地址 參數四:對方的端口號
        DatagramPacket p = new DatagramPacket(data, data.length,
                InetAddress.getByName("192.168.1.255"), 20000); // xx.xx.xx.255這個地址是廣播地址
        // 發送包裹
        String line = "";
        while ((line = scanner.nextLine()) != null) {
            data = line.getBytes();
            // 沒有發送一次創建一個包裹,只需要每次修改包裹的內容,和內容長度
            p.setData(data);
            p.setLength(data.length);
            socket.send(p);
        }

        // socket.close();
    }
}

接收端:

public class UdpReceiver {
    public static void main(String[] args) throws IOException {
        DatagramSocket socket = new DatagramSocket(20000); // 綁定了指定的端口

        // 創建一個用來接受發來的數據報的數據報包
        DatagramPacket p = new DatagramPacket(new byte[1024], 1024);
        // 把收到的信息封裝到DatagramPacket
        while (true) {
            socket.receive(p); // 也是一個阻塞式方法。一直等到有人發來數據報
            InetAddress address = p.getAddress(); // 發送人
            int port = p.getPort(); // 發送方的端口
            byte[] data = p.getData(); // 存儲發送過來的數據的字節數組
            int length = p.getLength(); // 發送過來的信息的實際長度
            System.out.println(address + " " + port + "  "
                    + new String(data, 0, length));

        }
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章