Java實現簡單網絡聊天程序


1.socket

在進行網絡編程前,我們需要了解socket。我們知道IP協議對應於網絡層,TCP協議對應於傳輸層,而HTTP協議對應於應用層。
TCP/IP協議是傳輸層協議,主要解決數據如何在網絡中傳輸,而HTTP協議是應用層協議,主要解決如何包裝數據。

那么socket是啥呢?
  • 首先呢,socket就是網絡通信的工具,任何一門語言都有socket,他不是任何一個語言的專有名詞,而是大家通過自己的程序與其他電腦進行網絡通信的時候都用它。
  • 實際上socket是對TCP/IP協議的封裝,它的出現只是使得程序員更方便地使用TCP/IP協議棧而已。socket本身並不是協議,它是應用層與TCP/IP協議族通信的中間軟件抽象層,是一組調用接口(TCP/IP網絡的API函數)。
  • socket非常類似於電話插座。以一個國家級電話網為例。電話的通話雙方相當於相互通信的2個進程,區號是它的網絡地址;區內一個單位的交換機相當於一台主機,主機分配給每個用戶的局內號碼相當於socket號。任何用戶在通話之前,首先要占有一部電話機,相當於申請一個socket;同時要知道對方的號碼,相當於對方有一個固定的socket。然后向對方撥號呼叫,相當於發出連接請求。對方假如在場並空閑,拿起電話話筒,雙方就可以正式通話,相當於連接成功。雙方通話的過程,是一方向電話機發出信號和對方從電話機接收信號的過程,相當於向socket發送數據和從socket接收數據。通話結束后,一方掛起電話機相當於關閉socket,撤消連接。
socket在網路中的位置:

2.Socket的TCP和UDP通信

socket有兩種建立通信的方式,一種是基於TCP的可靠傳輸,一種是基於UDP的不可靠傳輸

TCP
  • 可靠的、面向連接的協議(eg:打電話)、傳輸效率低全雙工通信(發送緩存&接收緩存)、面向字節流。使用TCP的應用:Web瀏覽器;文件傳輸程序。
  • 面向連接的協議,在socket之間進行數據傳輸之前必須要建立連接,所以在TCP中需要連接時間
  • TCP傳輸數據無大小限制,一旦建立連接,雙方socket就可以按統一的格式傳輸大的數據(無限制)。
  • TCP是一個可靠的協議,它確保接收方完全正確地獲取發送方所發送的全部數據。
UDP
  • 不可靠的、無連接的服務,傳輸效率高(發送前時延小),一對一、一對多、多對一、多對多、面向報文(數據包),盡最大努力服務,無擁塞控制。使用UDP的應用:域名系統 (DNS);視頻流;IP語音(VoIP)。
  • 每個數據包中都給出了完整的地址信息,因此無需建立發送方和接收方的連接。
  • UDP傳輸數據時是有大小限制的,每個被傳輸的數據包必須限定在64KB之內。
  • UDP是一個不可靠的協議,發送方所發送的數據包並不一定以相同的順序到達接收方。
socket中TCP和UDP對比:

3.Java中Socket方法

方法 說明
ServerSocket(int port) 創建ServerSocket
bind(SocketAddress bindpoint) 將套接字綁定到本地地址
accept() 阻塞方法,也就是說調用accept方法后程序會停下來等待連接請求
close() 關閉此套接字
connect(SocketAddress endpoint) 將此套接字連接到服務器
InetAddress getInetAddress() 返回套接字的連接地址
InetAddress getLocalAddress() 獲取套接字綁定的本地地址
InputStream getInputStream() 返回此套接字的輸入流
OutputStream getOutputStream() 返回此套接字的輸出流
SocketAddress getLocalSocketAddress() 返回此套接字綁定的端點地址,如果尚未綁定則返回 null
SocketAddress getRemoteSocketAddress() 返回此套接字的連接的端點地址,如果尚未連接則返回 null
int getLoacalPort() 返回此套接字綁定的本地端口
int getPort() 返回此套接字連接的遠程端口

4.基於Java的socket網絡編程

4.1代碼結構

4.2基於TCP實現
服務端
  • 創建一個服務器端socket套接字(套接字會在制定的端口上監聽)
  • 當有使用ServerSocket中的accept()獲取客戶端socket對象
  • 使用多線程實現聊天:
    • 1.MyClientThread線程負責接收客戶端發送給服務器端的消息
    • 2.MyServerThread線程負責向客戶端發送消息
package socket.chat;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

/**
 * socket實現聊天
 * 1.創建一個服務器端socket套接字(套接字會在制定的端口上監聽);
 * 2.當有使用ServerSocket中的accept()獲取客戶端socket對象
 * 3.使用多線程實現聊天:1.MyClientThread線程負責接收客戶端發送給服務器端的消息;
 *                      2.MyServerThread線程負責向客戶端發送消息
 */
class MyServerSocket{

    public static void main(String[] args) {
        ServerSocket serverSocket = null;//服務器端socket
        Socket clientSocket = null;//客戶端socket

        try {
            //創建一個服務器端socket服務
            serverSocket = new ServerSocket(8888);
            while (true) {//使用while死循環模擬客戶端一直啟動
                clientSocket = serverSocket.accept();//獲取連接服務端的客戶端socket
                //該線程用於接收客戶端發送的消息,並將該消息打印到控制台
                MyClientThread myClientThread = new MyClientThread(clientSocket);
                //該線程用於向客戶端發送的消息
                MyServerThread myServerThread = new MyServerThread(clientSocket);

                //啟動線程
                myClientThread.start();
                myServerThread.start();
            }
        } catch (IOException io) {
            io.printStackTrace();
        } finally {
            if (clientSocket != null) {
                try {
                    clientSocket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (serverSocket != null) {
                try {
                    serverSocket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }//main--end
}//MyServerSocket--end

/**
 * 接收客戶端的內容
 * 1.根據客戶端Socket獲取指向客戶端Socket對象的輸入流對象(輸入源)
 * 2.通過輸入流對象將客戶端輸入的信息讀取到內存中
 * 3.通過輸出流對象(System.out.print)將內存中的數據打印到控制台
 */
class MyClientThread extends Thread {
    private DataInputStream dataInputStream = null;

    public MyClientThread(Socket socket) {
        try {
            //獲取客戶端的輸入流對象
            this.dataInputStream = new DataInputStream(socket.getInputStream());
        } catch (IOException io) {
            io.printStackTrace();
        }
    }//MyClientThread--end
    @Override
    public void run() {
        String tellClient = null;
        try {
            while (true) {
                tellClient = this.dataInputStream.readUTF();//將客戶端發送的信息寫入到內存中
                System.out.println("客戶端說:"+tellClient);//將讀取的客戶端信息打印到控制台
            }
        } catch (IOException io) {
            io.printStackTrace();
        }
    }//run--end
}//MyClientThread--end

/**
 * 向發送客戶端的消息
 * 1.根據客戶端Socket獲取指向客戶端Socket對象的輸出流對象(輸出目的地)
 * 2.獲取控制台輸入流對象(輸入源)
 * 3.通過輸入流對象將控制台輸入的信息讀取到內存中
 * 4.通過輸出流對象將內存中的數據返回給服務器端
 */
class MyServerThread extends Thread {

    private DataOutputStream dataOutputStream = null;//用於輸出服務器返回給客戶端的信息
    private Scanner in = null;//用於將服務器端在控制台輸入的信息讀取到內存中

    public MyServerThread(Socket socket) {
        try {
            //根據客戶端獲取輸出流對象
            this.dataOutputStream = new DataOutputStream(socket.getOutputStream());
            //獲取控制台輸入流對象
            in = new Scanner(System.in);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }//MyServerThread--end

    @Override
    public void run() {
        String tellServer = null;

        while (true) {
            try {
                //將控制台中的信息讀入到內存中
                tellServer = in.nextLine();
                //服務器端向客戶端發送消息
                this.dataOutputStream.writeUTF(tellServer);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }//run
}//server
客戶端
  • 根據IP和port獲取和服務端連接的Socket對象
  • 通過服務端Socket對象獲取指向服務端Socket對象的輸入流/輸出流,獲取服務器端發送的信息或者向服務器發送消息
package socket.chat;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.util.Scanner;

/**
 * 客戶端socket
 * 1.根據IP和port獲取和服務端連接的Socket對象
 * 2.通過服務端Socket對象獲取指向服務端Socket對象的輸入流/輸出流,獲取服務器端發送的信息或者向服務器發送消息
 */
class MyClientSocket {

    public static void main(String[] args) {
        Socket serverSocket = null;
        try {
            serverSocket = new Socket("127.0.0.1", 8888);

            MyClientToSerThread myClientToSerThread = new MyClientToSerThread(serverSocket);
            MyAcceptServerThread myAcceptServerThread = new MyAcceptServerThread(serverSocket);

            myClientToSerThread.start();
            myAcceptServerThread.start();
        } catch (IOException io) {
            io.printStackTrace();
        }

    }//main--end
}//MyClientSocket--end

/**
 * 向服務端發送消息
 */
class MyClientToSerThread extends Thread {

    private DataOutputStream dataOutputStream = null;
    private Scanner in = null;

    public MyClientToSerThread(Socket socket) {
        try {
            this.dataOutputStream = new DataOutputStream(socket.getOutputStream());
            this.in = new Scanner(System.in);
        } catch (IOException io) {
            io.printStackTrace();
        }
    }//MyClientToSerThread--end

    @Override
    public void run() {
        String tell = null;
        while (true) {
            try {
                tell = in.nextLine();
                dataOutputStream.writeUTF(tell);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }//run--end
}//MyClientToSerThread--end
/**
 *接收客戶端的信息
 **/
class MyAcceptServerThread extends Thread {

    private DataInputStream dataInputStream = null;

    public MyAcceptServerThread(Socket socket) {
        try {
            this.dataInputStream = new DataInputStream(socket.getInputStream());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }//MyAcceptServerThread--end

    @Override
    public void run() {
        String tell = null;
        while (true) {
            try {
                tell = this.dataInputStream.readUTF();
                System.out.println("服務端:"+tell);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }//run--end
}//MyAcceptServerThread--end
4.3運行結果

5.抓包分析

使用wireshark抓取本地TCP包,選擇回環網卡

TCP三次握手建立過程

服務端向客戶端發信息


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM