Java Socket 簡介

一、網絡編程

1.1 網絡架構模型

網絡架構模型主要有OSI模型和TCP/IP模型java

1.1.1 OSI模型

OSI(Open System Interconnect),開放式系統互聯。OSI定義了網絡互連的七層框架(物理層、數據鏈路層、網絡層、傳輸層、會話層、表示層、應用層)。它實際上並無人推廣,只是一種理想化的模型。程序員

1.1.2 TCP/IP模型

  1. 應用層編程

    應用層最靠近用戶的一層,是爲計算機用戶提供應用接口,也爲用戶直接提供各類網絡服務。咱們常見應用層的網絡服務協議有:HTTP,HTTPS,FTP,TELNET等。瀏覽器

  2. 傳輸層服務器

    創建了主機端到端的連接,傳輸層的做用是爲上層協議提供端到端的可靠和透明的數據傳輸服務,包括處理差錯控制和流量控制等問題。該層向高層屏蔽了下層數據通訊的細節,使高層用戶看到的只是在兩個傳輸實體間的一條主機到主機的、可由用戶控制和設定的、可靠的數據通路。咱們一般說的,TCP UDP就是在這一層。端口號既是這裏的「端」。網絡

  3. 網絡層多線程

    本層經過IP尋址來創建兩個節點之間的鏈接,爲源端的運輸層送來的分組,選擇合適的路由和交換節點,正確無誤地按照地址傳送給目的端的運輸層。就是一般說的IP層。這一層就是咱們常常說的IP協議層。IP協議是Internet的基礎。架構

  4. 數據鏈路層框架

  5. 物理層socket

1.2 網絡結構模式

1.2.1 B/S

​ 即瀏覽器/服務器模式,B/S架構採起瀏覽器請求,服務器響應的工做模式。客戶則在須要服務時向服務器進行請求。服務器響應後及時返回,不須要實時監聽端口

1.2.2 C/S

​ 即客戶端/瀏覽器模式,通訊雙方一方做爲服務器等待客戶提出請求並予以響應。客戶則在須要服務時向服務器提出申請。服務器通常做爲守護進程始終運行,監聽網絡端口,一旦有客戶請求,就會啓動一個服務進程來響應該客戶,同時本身繼續監聽服務端口,使後來的客戶也能及時獲得服務。

1.3 TCP/UDP協議

1.3.1 TCP協議

傳輸控制協議(TCP,Transmission Control Protocol)是一種面向鏈接的、可靠的、基於字節流的傳輸層通訊協議。

經過TCP協議傳輸,獲得的是一個順序的無差錯的數據流。發送方和接收方的成對的兩個socket之間必須創建鏈接,當一個socket(一般都是server socket)等待創建鏈接時,另外一個socket能夠要求進行鏈接,一旦這兩個socket鏈接起來,它們就能夠進行雙向數據傳輸,雙方均可以進行發送或接收操做。

創建起一個TCP鏈接須要通過「三次握手」:

  • 第一次握手:客戶端發送syn包(syn=j)到服務器,並進入SYN_SEND狀態,等待服務器確認;

  • 第二次握手:服務器收到syn包,必須確認客戶的SYN(ack=j+1),同時本身也發送一個SYN包(syn=k),即SYN+ACK包,此時服務器進入SYN_RECV狀態;

  • 第三次握手:客戶端收到服務器的SYN+ACK包,向服務器發送確認包ACK(ack=k+1),此包發送完畢,客戶端和服務器進入ESTABLISHED狀態,完成三次握手。

握手過程當中傳送的包裏不包含數據,三次握手完畢後,客戶端與服務器才正式開始傳送數據。

理想狀態下,TCP鏈接一旦創建,在通訊雙方中的任何一方主動關閉鏈接以前,TCP 鏈接都將被一直保持下去。

斷開鏈接時服務器和客戶端都可以主動發起斷開TCP鏈接的請求。

1.3.2 UDP協議

UDP 爲應用程序提供了一種無需創建鏈接就能夠發送封裝的 IP 數據包的方法。

每一個數據報都是一個獨立的信息,包括完整的源地址或目的地址,它在網絡上以任何可能的路徑傳往目的地,所以可否到達目的地,到達目的地的時間以及內容的正確性都是不能被保證的

1.3.3 TCP與UDP的區別

TCP

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

UDP

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

二、Socket 網絡編程

2.1 什麼是Socket

Socket的英文原義是「孔」或「插座」。在網絡編程中,網絡上的兩個程序經過一個雙向的通訊鏈接實現數據的交換,這個鏈接的一端稱爲一個Socket。

Socket套接字是通訊的基石,是支持TCP/IP協議的網絡通訊的基本操做單元。它是網絡通訊過程當中端點的抽象表示,包含進行網絡通訊必須的五種信息:鏈接使用的協議,本地主機的IP地址,本地進程的協議端口,遠地主機的IP地址,遠地進程的協議端口。

Socket本質是編程接口(API),對TCP/IP的封裝,TCP/IP也要提供可供程序員作網絡開發所用的接口,這就是Socket編程接口;HTTP是轎車,提供了封裝或者顯示數據的具體形式;Socket是發動機,提供了網絡通訊的能力。

2.2 Socket 的原理

套接字之間的鏈接過程能夠分爲三個步驟:服務器監聽,客戶端請求,鏈接確認。

  • 服務器監聽:是服務器端套接字並不定位具體的客戶端套接字,而是處於等待鏈接的狀態,實時監控網絡狀態。
  • 客戶端請求:是指由客戶端的套接字提出鏈接請求,要鏈接的目標是服務器端的套接字。爲此,客戶端的套接字必須首先描述它要鏈接的服務器的套接字,指出服務器端套接字的地址和端口號,而後就向服務器端套接字提出鏈接請求。
  • 鏈接確認:是指當服務器端套接字監聽到或者說接收到客戶端套接字的鏈接請求,它就響應客戶端套接字的請求,創建一個新的線程,把服務器端套接字的描述發給客戶端,一旦客戶端確認了此描述,鏈接就創建好了。而服務器端套接字繼續處於監聽狀態,繼續接收其餘客戶端套接字的鏈接請求。

三、Java上的Socket網絡編程

Server端Listen監聽某個端口是否有鏈接請求,Client端向Server 端發出鏈接請求,Server端向Client端發回Accept接受消息。這樣一個鏈接就創建起來了。Server端和Client端均可以經過Send,Write等方法與對方通訊。

對於一個功能齊全的Socket,都要包含如下基本結構,其工做過程包含如下四個基本的步驟:

  • 建立Socket;
  • 打開鏈接到Socket的輸入/出流;
  • 按照必定的協議對Socket進行讀/寫操做;
  • 關閉Socket。

3.1 基於TCP

3.1.1 實現聊天

客戶端

import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.nio.charset.StandardCharsets;

//客戶端
public class TCPClient01 {
    public static void main(String[] args) {
        Socket socket = null;
        OutputStream os = null;
        try {
            //一、要知道服務器的地址
            InetAddress serverIp = InetAddress.getByName("127.0.0.1");
            //二、端口號
            int port = 9999;
            //三、建立一個Socket鏈接
            socket = new Socket(serverIp, port);
            //四、發送消息
            os = socket.getOutputStream();
            os.write("你好,歡迎光臨喵小決".getBytes(StandardCharsets.UTF_8));

        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            try {
                assert os != null;
                os.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

服務端

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

//服務端
public class TCPServer01 {
    public static void main(String[] args) {
        ServerSocket serverSocket = null;
        Socket socket = null;
        InputStream is = null;
        ByteArrayOutputStream baos = null;

        try {
            //一、擁有一個地址
            serverSocket = new ServerSocket(9999);
            //二、等待客戶端鏈接過來
            socket = serverSocket.accept();
            //三、讀取客戶端的消息
            is = socket.getInputStream();
            //管道流
            baos = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024];
            int len;
            while ((len = is.read(buffer)) != -1) {
                baos.write(buffer, 0, len);
            }
            System.out.println(baos.toString());


        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            //關閉資源
            try {
                assert baos != null;
                baos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                serverSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

3.1.2 實現文件傳輸

客戶端

import java.io.*;
import java.net.InetAddress;
import java.net.Socket;
//客戶端
public class TCPClient02 {
    public static void main(String[] args) throws Exception {
        //一、建立一個Socket鏈接
        Socket socket = new Socket(InetAddress.getByName("127.0.0.1"), 9000);
        //二、建立一個輸出流
        OutputStream os = socket.getOutputStream();
        //三、文件流
        FileInputStream fis = new FileInputStream(new File("D:\\Project\\java\\TCP\\TCP\\src\\bg.png"));
        //四、寫出文件
        byte[] buffer = new byte[1024];
        int len;
        while ((len = fis.read(buffer)) != -1) {
            os.write(buffer, 0, len);
        }

        //通知服務器,我已經結束了
        socket.shutdownOutput();//我已經傳輸完了

        //肯定服務器接收完畢,纔可以斷開鏈接
        InputStream is = socket.getInputStream();
        //String byte[]
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        byte[] buffer2 = new byte[1024];
        int len2;
        while ((len2 = is.read(buffer2)) != -1) {
            baos.write(buffer2, 0, len2);
        }
        System.out.println(baos.toString());

        //五、關閉資源
        baos.close();
        is.close();
        fis.close();
        os.close();
        socket.close();
    }
}

服務端

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;

//服務端
public class TCPServer02 {
    public static void main(String[] args) throws Exception {
        //一、建立服務
        ServerSocket serverSocket = new ServerSocket(9000);
        //二、監聽客戶端的鏈接
        Socket socket = serverSocket.accept();//阻塞式監聽,會一直等待客戶端鏈接
        //三、獲取輸入流
        InputStream is = socket.getInputStream();
        //四、文件輸出
        FileOutputStream fos = new FileOutputStream(new File("receive.png"));
        byte[] buffer = new byte[1024];
        int len;
        while ((len = is.read(buffer)) != -1) {
            fos.write(buffer, 0, len);
        }


        //通知客戶端我接受完畢了
        OutputStream os = socket.getOutputStream();
        os.write("我接收完畢了,你能夠斷開了".getBytes(StandardCharsets.UTF_8));

        //關閉資源
        fos.close();
        is.close();
        socket.close();
        serverSocket.close();
    }
}

3.2 基於UDP

3.2.1 消息發送

客戶端

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

//不須要鏈接服務器
public class UdpClient01 {
    public static void main(String[] args) throws Exception {
        //一、創建一個Socket
        DatagramSocket socket = new DatagramSocket();
        //二、建一個包
        String msg = "你好!,服務器";
        //發送給誰
        InetAddress localhost = InetAddress.getByName("localhost");
        int port = 9090;
        //數據,數據的長度起始,要發送給誰
        DatagramPacket packet = new DatagramPacket(msg.getBytes(), 0, msg.getBytes().length, localhost, port);
        //三、發送一個包
        socket.send(packet);
        //四、關閉流
        socket.close();
    }
}

服務器

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

//仍是要等待客戶端的鏈接
public class UdpServer01 {
    public static void main(String[] args) throws Exception {
        //開放端口
        DatagramSocket socket = new DatagramSocket(9090);
        //接收數據包
        byte[] buffer = new byte[1024];
        //接收
        DatagramPacket packet = new DatagramPacket(buffer, 0, buffer.length);
        socket.receive(packet);//阻塞接收
        System.out.println(new String(packet.getData(),0,packet.getLength()));
        //關閉
        socket.close();
    }
}

3.2.2 聊天實現

發送方

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;

public class UdpSender {
    public static void main(String[] args) throws Exception {
        DatagramSocket socket = new DatagramSocket(8888);

        //準備數據:控制檯讀取System.in
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));

        while (true) {
            String data = reader.readLine();
            byte[] dataBytes = data.getBytes(StandardCharsets.UTF_8);
            DatagramPacket packet = new DatagramPacket(dataBytes, 0, dataBytes.length, new InetSocketAddress("localhost", 6666));
            socket.send(packet);

            if (data.equals("bye")) {
                break;
            }
        }

        socket.close();
    }
}

接收方

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

public class UdpReceive {
    public static void main(String[] args) throws Exception {
        DatagramSocket socket = new DatagramSocket(6666);

        while (true) {
            //準備接收的包裹
            byte[] buffer = new byte[1024];
            DatagramPacket packet = new DatagramPacket(buffer, 0, buffer.length);
            socket.receive(packet);//阻塞式接收包裹

            //斷開鏈接  bye
            byte[] data = packet.getData();
            String receiveData = new String(data, 0, data.length);

            System.out.println(receiveData);

            if (receiveData.equals("bye")) {
                break;
            }
        }
        socket.close();
    }
}

四、Socket和Thread結合

使用SocketThread來實現多線程在線質詢聊天

老師

public class TalkTeacher {
    public static void main(String[] args) {
        new Thread(new TalkSend(6666, "localhost", 8888)).start();
        new Thread(new TalkReceive(9999, "學生")).start();
    }
}

學生

public class TalkStudent {
    public static void main(String[] args) {
        new Thread(new TalkSend(7777, "localhost", 9999)).start();
        new Thread(new TalkReceive(8888, "老師")).start();
    }
}

發送方

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;

public class UdpSender {
    public static void main(String[] args) throws Exception {
        DatagramSocket socket = new DatagramSocket(8888);

        //準備數據:控制檯讀取System.in
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));

        while (true) {
            String data = reader.readLine();
            byte[] dataBytes = data.getBytes(StandardCharsets.UTF_8);
            DatagramPacket packet = new DatagramPacket(dataBytes, 0, dataBytes.length, new InetSocketAddress("localhost", 6666));
            socket.send(packet);

            if (data.equals("bye")) {
                break;
            }
        }

        socket.close();
    }
}

接收方

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;

public class TalkReceive implements Runnable {
    DatagramSocket socket = null;
    private int port;
    private String msgFrom;

    public TalkReceive(int port, String msgFrom) {
        this.msgFrom = msgFrom;
        this.port = port;
        try {
            socket = new DatagramSocket(port);
        } catch (SocketException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        while (true) {

            try {
                byte[] container = new byte[1024];
                DatagramPacket packet = new DatagramPacket(container, 0, container.length);
                socket.receive(packet);
                byte[] data = packet.getData();
                String receiveData = new String(data, 0, data.length);
                System.out.println(msgFrom + ":" + receiveData);
                if (receiveData.equals("bye")) {
                    break;
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        socket.close();
    }
}
相關文章
相關標籤/搜索