TCP和UDP 在java上通信上的實現

TCP/IP 實現通信圖示:

這裏寫圖片描述
服務器端: ServerSocket
第一步: 服務器端啓動一個ServerSocket, 綁定到特定的端口號上,並對該端口號進行監聽,等待用戶的請求

ServerSocket serverSocket = new ServerSocket(5566);

第二步: 調用accept() 方法,監聽端口號, 如果沒有用戶發送請求連接,這個accept方法是會一致阻塞的,用戶發送了請求之後就會被這個方法監聽到,並返回一個發送方的socket,(類型是Socket)

socket = serverSocket.accept()

第三步: 建立了連接之後,就通過I/O的方式進行通信,做數據的傳遞

InputStream  In = socket.getInputStream(); //節點流
OutputStream out = socket.getOutputStream(); // 節點流

需要注意的一點是,服務器一直在監聽,當它捕獲到一個請求時,會爲此請求啓動一個線程,爲此分配一個綁定到不同端口地址的套接字,而之前的那個端口號繼續進行監聽

客戶端: Socket(套接字): 連接運行在網絡上的兩個程序間的雙向通信的端點
第一步: 向服務器端發送連接請求(需要知道服務器的IP和端口號)

        Socket socket = new Socket(InetAddress.getLocalHost(), 5566);

第二步: 上一步建好連接之後,就通過I/O的方式進行通信

InputStream  In = socket.getInputStream(); //節點流
OutputStream out = socket.getOutputStream(); // 節點流

一個程序是隻能佔用一個端口號的,下面是一個小例子,通過線程的方式實現服務器端和客戶端的通信,將讀寫操作分別建立線程

服務器端代碼

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
// 
public class ServerSocketTest {
    public static void main(String[] args) throws Exception {
        ServerSocket serverSocket = new ServerSocket(5566);
        Socket socket;
        while (true){
            socket = serverSocket.accept(); // 如果沒有客戶端的請求,這一句是會一直阻塞的
            // 啓動讀線程
            serverReadThread readThread = new serverReadThread(socket);
            readThread.start();
            // 啓動寫線程
            User user = new User("liuxw",18,"server");
            serverWriteThread writeThread = new serverWriteThread(socket,user);
            writeThread.start();
        }
    }
}
class serverReadThread extends Thread {
    private Socket socket;
    private User user;
    public serverReadThread(Socket socket) {
        this.socket = socket;
    }
    public void run() {
        try {
            // try 語句不要寫在循環裏面,要儘量寫在循環外面,因爲異常對象的創建與處理消耗時間
            while (true) {
                ObjectInputStream in = new ObjectInputStream( socket
                        .getInputStream());
                ArrayList<User> list = (ArrayList<User>) in.readObject();
                System.out.println(list.get(0).getName());
                System.out.println(list.get(1).getName());
                // 如何關閉呢, 如果直接加上in.close() 這樣的話就會頻繁的進行創建銷燬,代價較大
                // 解決方法,要麼連接一直開着,要麼定義一個特定字符來關閉流,要麼使用連接池來管理連接
            }
        } catch (IOException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

class serverWriteThread extends Thread {
    private Socket socket;
    private User user;
    public serverWriteThread(Socket socket, User user) {
        this.socket = socket;
        this.user = user;
    }
    public void run() {
        try {
            // try 語句不要寫在循環裏面,要儘量寫在循環外面,因爲異常對象的創建與處理消耗時間
            while (true) {
                ObjectOutputStream objOut = new ObjectOutputStream(socket.getOutputStream());
                //  ObjectOutputStream 也是可以把一個集合寫進去的,讀的時候按照集合的方式來讀就行了
                ArrayList<User> list = new ArrayList<User>();
                User user2 = new User("cao",22,"sss");
                list.add(user);
                list.add(user2);
                objOut.writeObject(list);
            }
        } catch (IOException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        } 
    }
}

客戶端程序:

import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;

public class ClientSocketTest {
    public static void main(String[] args) throws Exception {
        Socket socket = new Socket(InetAddress.getLocalHost(), 5566);
        ObjectOutputStream objOut = new ObjectOutputStream(socket.getOutputStream());
        User user = new User("wangsw", 20, "client");
        // 啓動讀線程, 其實這裏的操作和服務器端的操作是一致的,因此我們就不再重複的寫另外一個類
        serverReadThread readThread = new serverReadThread(socket);
        readThread.start();
        // 啓動寫線程
        serverWriteThread writeThread = new serverWriteThread(socket,user);
        writeThread.start();
    }
}

UDP 實現網絡通信:
UDP 是面向的無連接的通信方式,因此UDP基本上是不區分服務器端和客戶端的,但是我們在這裏可以進行區分一下,我們把首先啓動進行監聽UDP報文消息的叫做服務器端,把先進行發送數據的叫做客戶端。

爲什麼我會想着在這裏做一下區分呢?這是因爲我在寫下面的測試程序的時候,發現兩個用戶如果啓動的順序不對,那麼是收不到報文的,當時我就想爲什麼順序不對會接收不到呢?
這是因爲,監聽的一方先啓動,那麼就會一直對網絡進行監聽,當有人向他發送消息時他能捕獲到,但是當發送的一方先啓動,那麼報文傳輸到網絡中,一定時間內找不到要發送的對象,那麼就會發生報文丟失的現象

當時爲了弄懂這個原因,我還看了幫助文檔,卻意外的發現了有一個connect方法,不是面向無連接的嗎?爲什麼有一個connect連接方法,這個我會在下一篇中詳細進行講解。

現在先讓我們來看看如何在java中實現UDP通信吧
java中使用DatagramSocket 和DatagramPacket 來實現UDP通信,Socket類是用來創建一個socket的,Packet是用來創建一個要發送的報文的。
socket.send(), socket.recieve()用來發送和接收數據包
這裏寫圖片描述

需要注意的是,我們在發送數據包的時候,數據包裏面要設置被髮送方的IP和Port, 但是隱藏的我們自身的IP 和Port也會添加到這個數據包裏面, 這樣對方接收到我們發送的數據包,就能夠從數據包裏面獲取到我們的IP和Port 然後就可以向我們發送數據包。

流程如下:
服務器端(先進行監聽的一方)
第一步: 首選創建一個DatagramSocket,爲其分配一個端口號,然後就會一直監聽從這個端口號傳進來的UDP數據包

DatagramSocket socket = new DatagramSocket(7000);

第二步: 創建一個DatagramPacket 包用來存儲從該端口接收到的數據包,然後調用socket.receive() 方法,將收到的數據包放在創建的對象裏。

        byte[] buff = new byte[100];
        DatagramPacket packetRecieve = new DatagramPacket(buff, 0, buff.length);
        socket.receive(packetRecieve);

第三步: 獲取數據包中的信息,並且根據數據包中發送方的IP和Port向客戶端發送數據包。

        String str = "world";
        DatagramPacket packetSend = new DatagramPacket(str.getBytes(),
                str.length(), packetRecieve.getAddress(),
                packetRecieve.getPort());
        socket.send(packetSend);

第四步:關閉socket

        socket.close();

客戶端的代碼和這個差不多,只不過順序顛倒一下,先發送後接收。

下面給出了一個實現了UDP通信的小例子
服務器端(先監聽的)

// UDP傳輸是沒有IO的,每次發送的信息的包裏都包含了要發送對象的IP 和端口號 ,而TCP、IP 一旦建立連接就通過流來讀取接收數據
public class UDPServerTest {
    // 服務器端先接收消息,然後在發送消息
    public static void main(String[] args) throws Exception {
        // 自己開一個7000端口號,監聽網絡中的UDP數據包
        DatagramSocket socket = new DatagramSocket(7000);
        // 接收一個用戶的packet
        // 首先定義一個packet,這個packet 用來存儲接收到的packet
        byte[] buff = new byte[100];
        DatagramPacket packetRecieve = new DatagramPacket(buff, 0, buff.length);
        socket.receive(packetRecieve);
        System.out.println(new String(buff, 0, packetRecieve.getLength())); // 輸出接收到的消息

        // 然後再向用戶發送消息
        // 首先將要發送的數據包封裝到一個packet裏面,  通過接收到的包獲取對方的Ip 和端口號
        String str = "world";
        DatagramPacket packetSend = new DatagramPacket(str.getBytes(),
                str.length(), packetRecieve.getAddress(),
                packetRecieve.getPort());
        socket.send(packetSend);
        socket.close();
    }
}

客戶端(先發送的)

public class UDPClientTest {
    // 客戶端先發送消息
    public static void main(String[] args) throws Exception {
        // 先new 一個 socket ,其中設置你自己的端口號
        DatagramSocket socket = new DatagramSocket(7001);
    //  socket.connect(InetAddress.getLocalHost(), 7000);
    //   socket.disconnect();
        // 將你要發送的消息,封裝到一個data
        String str = "hello";
        // 注意的是packet包中都是字節數組,所以數據要轉化爲字節數組
        // 首先將消息封裝到一個packet中,packet中包含了指定的對方的ip,port,還有發送給對方的消息(隱藏的:自己的ip,port也會在這個packet中)
        DatagramPacket packetSend = new DatagramPacket(str.getBytes(), str.length(), InetAddress.getLocalHost(), 7000);
        // 發送
        socket.send(packetSend);

        byte[] buff = new byte[100];
        DatagramPacket packetRecieve = new DatagramPacket(buff, 0, buff.length);
        socket.receive(packetRecieve);
        System.out.println(new String(buff, 0, packetRecieve.getLength())); // 輸出接收到的消息
        socket.close();
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章