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三次握手建立過程

服務端向客戶端發信息

