OSI七層網絡模型
OSI七層網絡模型(從下往上):
OSI是一個理想的模型,一般的網絡系統只涉及其中的幾層,在七層模型中,每一層都提供一個特殊 的網絡功能,從網絡功能角度觀察:
- 下面4層(物理層、數據鏈路層、網絡層和傳輸層)主要提供數據傳輸和交換功能, 即以節點到節點之間的通信爲主
第4層作爲上下兩部分的橋樑,是整個網絡體系結構中最關鍵的部分; - 上3層(會話層、表示層和應用層)則以提供用戶與應用程序之間的信息和數據處理功能爲主
簡言之,下4層主要完成通信子網的功能,上3層主要完成資源子網的功能。
OSI七層模型詳解
TCP/IP四層模型
TCP/UDP區別
Socket開發相關的一些概念名詞:
IP地址
IP地址是IP協議提供的一種統一的地址格式,它爲互聯網上的每一個網絡和每一臺主機分配一個邏輯地址,以此來屏蔽物理地址的差異。
- 1
- 2
端口
- 用於區分不同的應用程序
- 端口號的範圍爲0-65535,其中0-1023未系統的保留端口,我們的程序儘可能別使用這些端口!
- IP地址和端口號組成了我們的Socket,Socket是網絡運行程序間雙向通信鏈路的終結點, 是TCP和UDP的基礎!
- 常用協議使用的端口:HTTP:80,FTP:21,TELNET:23
TCP協議與UDP協議
TCP協議流程詳解
首先TCP/IP是一個協議簇,裏面包括很多協議的。UDP只是其中的一個。之所以命名爲TCP/IP協議, 因爲TCP,IP協議是兩個很重要的協議,就用他兩命名了。
下面我們來講解TCP協議和UDP協議的區別:
TCP(Transmission Control Protocol,傳輸控制協議)是面向連接的協議,即在收發數據錢 ,都需要與對面建立可靠的鏈接,這也是面試經常會問到的TCP的三次握手以及TCP的四次揮手!
三次握手: 建立一個TCP連接時,需要客戶端和服務端總共發送3個包以確認連接的建立, 在Socket編程中,這一過程由客戶端執行connect來觸發,具體流程圖如下:
- 第一次握手:Client將標誌位SYN置爲1,隨機產生一個值seq=J,並將該數據包發送給Server, Client進入SYN_SENT狀態,等待Server確認。
- 第二次握手:Server收到數據包後由標誌位SYN=1知道Client請求建立連接,Server將標誌位 SYN和ACK都置爲1,ack=J+1,隨機產生一個值seq=K,並將該數據包發送給Client以確認連接請求 ,Server進入SYN_RCVD狀態。
- 第三次握手:Client收到確認後,檢查ack是否爲J+1,ACK是否爲1,如果正確則將標誌位ACK 置爲1,ack=K+1,並將該數據包發送給Server,Server檢查ack是否爲K+1,ACK是否爲1,如果正確則 連接建立成功,Client和Server進入ESTABLISHED狀態,完成三次握手,隨後Client與Server之間可以 開始傳輸數據了。
四次揮手: 終止TCP連接,就是指斷開一個TCP連接時,需要客戶端和服務端總共發送4個包以確認連接的斷開。 在Socket編程中,這一過程由客戶端或服務端任一方執行close來觸發,具體流程圖如下
- 第一次揮手:Client發送一個FIN,用來關閉Client到Server的數據傳送,Client進入 FIN_WAIT_1狀態
- 第二次揮手:Server收到FIN後,發送一個ACK給Client,確認序號爲收到序號+1(與SYN相同, 一個FIN佔用一個序號),Server進入CLOSE_WAIT狀態。
- 第三次揮手:Server發送一個FIN,用來關閉Server到Client的數據傳送,Server進入LAST_ACK 狀態。
- 第四次揮手:Client收到FIN後,Client進入TIME_WAIT狀態,接着發送一個ACK給Server,確認序號爲收到序號+1,Server進入CLOSED狀態,完成四次揮手。 另外也可能是同時發起主動關閉的情況:
另外還可能有一個常見的問題就是:爲什麼建立連接是三次握手,而關閉連接卻是四次揮手呢? 答:因爲服務端在LISTEN狀態下,收到建立連接請求的SYN報文後,把ACK和SYN放在一個報文裏 發送給客戶端。而關閉連接時,當收到對方的FIN報文時,僅僅表示對方不再發送數據了但是還 能接收數據,己方也未必全部數據都發送給對方了,所以己方可以立即close,也可以發送一些 數據給對方後,再發送FIN報文給對方來表示同意現在關閉連接,因此,己方ACK和FIN一般都會 分開發送。
UDP協議詳解
UDP(User Datagram Protocol)用戶數據報協議,非連接的協議,傳輸數據之前源端和終端不 建立連接,當它想傳送時就簡單地去抓取來自應用程序的數據,並儘可能快地把它扔到網絡上。 在發送端,UDP傳送數據的速度僅僅是受應用程序生成數據的速度、計算機的能力和傳輸帶寬 的限制;在接收端,UDP把每個消息段放在隊列中,應用程序每次從隊列中讀一個消息段。 相比TCP就是無需建立鏈接,結構簡單,無法保證正確性,容易丟包
Java中對於網絡提供的幾個關鍵類:
針對不同的網絡通信層次,Java給我們提供的網絡功能有四大類:
- InetAddress: 用於標識網絡上的硬件資源
- URL: 統一資源定位符,通過URL可以直接讀取或者寫入網絡上的數據
- Socket和ServerSocket: 使用TCP協議實現網絡通信的Socket相關的類
- Datagram: 使用UDP協議,將數據保存在數據報中,通過網絡進行通信
InetAddress:
public class InetAddressTest {
public static void main(String[] args) throws Exception{
//獲取本機InetAddress的實例:
InetAddress address = InetAddress.getLocalHost();
System.out.println("本機名:" + address.getHostName());
System.out.println("IP地址:" + address.getHostAddress());
byte[] bytes = address.getAddress();
System.out.println("字節數組形式的IP地址:" + Arrays.toString(bytes));
System.out.println("直接輸出InetAddress對象:" + address);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
基於TCP協議的Socket通信-簡易聊天室
基本介紹和使用
什麼是Socket
Socket通信模型
Socket通信實現步驟解析:
- Step 1:創建ServerSocket和Socket
- Step 2:打開連接到的Socket的輸入/輸出流
- Step 3:按照協議對Socket進行讀/寫操作
- Step 4:關閉輸入輸出流,以及Socket
我們接下來寫一個簡單的例子,開啓服務端後,客戶端點擊按鈕然後鏈接服務端, 並向服務端發送一串字符串,表示通過Socket鏈接上服務器~
Socket服務端的編寫
步驟
Step 1:創建ServerSocket對象,綁定監聽的端口
Step 2:調用accept()方法監聽客戶端的請求
Step 3:連接建立後,通過輸入流讀取客戶端發送的請求信息
Step 4:通過輸出流向客戶端發送響應信息
Step 5:關閉相關資源
Code
在Eclipse下創建一個Java項目,代碼如下:
package com.turing.server;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
/**
* Step 1:創建ServerSocket對象,綁定監聽的端口
* Step 2:調用accept()方法監聽客戶端的請求
* Step 3:連接建立後,通過輸入流讀取客戶端發送的請求信息
* Step 4:通過輸出流向客戶端發送響應信息
* Step 5:關閉相關資源
* *
*/
public class SocketServer {
public static void main(String[] args) {
try {
// 1. 創建一個服務端Socket,即ServerSocket,指定綁定的端口,並監聽此端口
ServerSocket serverSocket = new ServerSocket(9999);
// 獲取服務端IP
InetAddress inetAddress = InetAddress.getLocalHost();
String ip = inetAddress.getHostAddress();
System.out.println("服務端已就緒,等待客戶端接入,服務端ip地址: " + ip);
// 2. 調用accept等待客戶端連接
Socket socket = serverSocket.accept();
// 3. 連接後獲取輸入流,讀取客戶端信息
InputStream is = null;
InputStreamReader isr = null;
BufferedReader br = null;
OutputStream os = null;
PrintWriter pw = null;
// 獲取輸入流
is = socket.getInputStream();
isr = new InputStreamReader(is, "UTF-8");
br = new BufferedReader(isr);
String info = null;
while ((info = br.readLine()) != null) {
System.out.println("客戶端發送過來的信息:" + info);
}
socket.shutdownInput();// 關閉輸入流
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
運行服務端
Socket客戶端的編寫
Android客戶端
步驟
Step 1:創建Socket對象,指明需要鏈接的服務器的地址和端號
Step 2:鏈接建立後,通過輸出流向服務器發送請求信息
Step 3:通過輸出流獲取服務器響應的信息
Step 4:關閉相關資源
Code
package com.turing.base.activity.socket.baseuse;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import com.turing.base.R;
import com.turing.base.utils.AlertUtil;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.Socket;
/**
* Step 1:創建Socket對象,指明需要鏈接的服務器的地址和端號
Step 2:鏈接建立後,通過輸出流向服務器發送請求信息
Step 3:通過輸出流獲取服務器響應的信息
Step 4:關閉相關資源
服務端Java工程:D:\workspace\ws-java-base\SocketServer
*/
public class SocketClientAct extends AppCompatActivity implements View.OnClickListener {
private Button socketSend ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_socket_client);
socketSend = (Button) findViewById(R.id.id_btn_sendMsg);
socketSend.setOnClickListener(this);
}
@Override
public void onClick(View v) {
AlertUtil.showToastShort(this,"觀察服務端日誌~");
//Android不允許在主線程(UI線程)中做網絡操作,
// 所以這裏需要我們自己 另開一個線程來連接Socket!
new Thread() {
@Override
public void run() {
try {
acceptServer();
} catch (IOException e) {
e.printStackTrace();
}
}
}.start();
}
/**
* 連接ServerSocket發送消息
*/
private void acceptServer() throws IOException{
//1.創建客戶端Socket,指定服務器地址和端口
Socket socket = new Socket("192.168.56.1", 9999);
//2.獲取輸出流,向服務器端發送信息
OutputStream os = socket.getOutputStream();//字節輸出流
PrintWriter pw = new PrintWriter(os);//將輸出流包裝爲打印流
//獲取客戶端的IP地址
InetAddress address = InetAddress.getLocalHost();
String ip = address.getHostAddress();
// 將這個信息發送給服務端
pw.write("客戶端" + ip + "接入服務器!!");
pw.flush();
socket.shutdownOutput();//關閉輸出流
socket.close();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
因爲Android不允許在主線程(UI線程)中做網絡操作,所以這裏需要我們自己 另開一個線程來連接Socket!
運行結果:
點擊按鈕後,服務端控制檯打印
基於UDP協議的Socket通信
UDP以數據報作爲數據的傳輸載體,在進行傳輸時 首先要把傳輸的數據定義成數據報(Datagram),在數據報中指明數據要到達的Socket(主機地址 和端口號),然後再將數據以數據報的形式發送出去,然後就沒有然後了,服務端收不收到我就 不知道了,除非服務端收到後又給我回一段確認的數據報。
服務端實現步驟
Step 1:創建DatagramSocket,指定端口號
Step 2:創建DatagramPacket
Step 3:接收客戶端發送的數據信息
Step 4:讀取數據
public class UPDServer {
public static void main(String[] args) throws IOException {
/*
* 接收客戶端發送的數據
*/
// 1.創建服務器端DatagramSocket,指定端口
DatagramSocket socket = new DatagramSocket(8888);
// 2.創建數據報,用於接收客戶端發送的數據
byte[] data = new byte[1024];// 創建字節數組,指定接收的數據包的大小
DatagramPacket packet = new DatagramPacket(data, data.length);
// 3.接收客戶端發送的數據
System.out.println("服務器端已經啓動,等待客戶端發送數據");
socket.receive(packet);// 此方法在接收到數據報之前會一直阻塞
// 4.讀取數據
String info = new String(data, 0, packet.getLength());
System.out.println("我是服務器,客戶端說:" + info);
/*
* 向客戶端響應數據
*/
// 1.定義客戶端的地址、端口號、數據
InetAddress address = packet.getAddress();
int port = packet.getPort();
byte[] data2 = "歡迎您!".getBytes();
// 2.創建數據報,包含響應的數據信息
DatagramPacket packet2 = new DatagramPacket(data2, data2.length, address, port);
// 3.響應客戶端
socket.send(packet2);
// 4.關閉資源
socket.close();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
客戶端實現步驟
Step 1:定義發送信息
Step 2:創建DatagramPacket,包含將要發送的信息
Step 3:創建DatagramSocket
Step 4:發送數據
public class UDPClient {
public static void main(String[] args) throws IOException {
/*
* 向服務器端發送數據
*/
// 1.定義服務器的地址、端口號、數據
InetAddress address = InetAddress.getByName("localhost");
int port = 8888;
byte[] data = "用戶名:admin;密碼:123".getBytes();
// 2.創建數據報,包含發送的數據信息
DatagramPacket packet = new DatagramPacket(data, data.length, address, port);
// 3.創建DatagramSocket對象
DatagramSocket socket = new DatagramSocket();
// 4.向服務器端發送數據報
socket.send(packet);
/*
* 接收服務器端響應的數據
*/
// 1.創建數據報,用於接收服務器端響應的數據
byte[] data2 = new byte[1024];
DatagramPacket packet2 = new DatagramPacket(data2, data2.length);
// 2.接收服務器響應的數據
socket.receive(packet2);
// 3.讀取數據
String reply = new String(data2, 0, packet2.getLength());
System.out.println("我是客戶端,服務器說:" + reply);
// 4.關閉資源
socket.close();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
總結
將數據轉換爲字節,然後放到DatagramPacket(數據報包中),發送的 時候帶上接受者的IP地址和端口號,而接收時,用一個字節數組來緩存!發送的時候需要創建一個 DatagramSocket(端到端通信的類)對象,然後調用send方法給接受者發送數據報包