1. 網(wǎng)絡(luò)編程概述
1.1 計算機網(wǎng)絡(luò)
是指將地理位置不同的具有獨立功能的多臺計算機及其外部設(shè)備,通過通信線路連接起來,在網(wǎng)絡(luò)操作系統(tǒng),網(wǎng)絡(luò)管理軟件及網(wǎng)絡(luò)通信協(xié)議的管理和協(xié)調(diào)下,實現(xiàn)資源共享和信息傳遞的計算機系統(tǒng)。
1.2 網(wǎng)絡(luò)編程
就是用來實現(xiàn)網(wǎng)絡(luò)互連的不同計算機上運行的程序間可以進(jìn)行數(shù)據(jù)交換。
1.3 網(wǎng)絡(luò)模型
計算機網(wǎng)絡(luò)之間以何種規(guī)則進(jìn)行通信,就是網(wǎng)絡(luò)模型研究問題。
網(wǎng)絡(luò)模型一般是指OSI(Open System Interconnection開放系統(tǒng)互連)參考模型或者TCP/IP參考模型。
應(yīng)用層:http、https、ftp,傳輸層:TCP、UDP,網(wǎng)絡(luò)層:IP,物理層,數(shù)據(jù)鏈路層
1.4 網(wǎng)絡(luò)模型7層概述
- 物理層
主要定義物理設(shè)備標(biāo)準(zhǔn),如網(wǎng)線的接口類型、光纖的接口類型、各種傳輸介質(zhì)的傳輸速率等。它的主要作用是傳輸比特流(就是由1、0轉(zhuǎn)化為電流強弱來進(jìn)行傳輸,到達(dá)目的地后在轉(zhuǎn)化為1、0,也就是我們常說的數(shù)模轉(zhuǎn)換與模數(shù)轉(zhuǎn)換)。這一層的數(shù)據(jù)叫做比特。
- 數(shù)據(jù)鏈路層
主要將從物理層接收的數(shù)據(jù)進(jìn)行MAC地址(網(wǎng)卡的地址)的封裝與解封裝。常把這一層的數(shù)據(jù)叫做幀。在這一層工作的設(shè)備是交換機,數(shù)據(jù)通過交換機來傳輸。
- 網(wǎng)絡(luò)層
主要將從下層接收到的數(shù)據(jù)進(jìn)行IP地址(例192.168.0.1)的封裝與解封裝。在這一層工作的設(shè)備是路由器,常把這一層的數(shù)據(jù)叫做數(shù)據(jù)包。
- 傳輸層
定義了一些傳輸數(shù)據(jù)的協(xié)議和端口號(WWW端口80等),如:TCP(傳輸控制協(xié)議,傳輸效率低,可靠性強,用于傳輸可靠性要求高,數(shù)據(jù)量大的數(shù)據(jù)),UDP(用戶數(shù)據(jù)報協(xié)議,與TCP特性恰恰相反,用于傳輸可靠性要求不高,數(shù)據(jù)量小的數(shù)據(jù),如QQ聊天數(shù)據(jù)就是通過這種方式傳輸?shù)模?主要是將從下層接收的數(shù)據(jù)進(jìn)行分段和傳輸,到達(dá)目的地址后再進(jìn)行重組。常常把這一層數(shù)據(jù)叫做段。
- 會話層
通過傳輸層(端口號:傳輸端口與接收端口)建立數(shù)據(jù)傳輸?shù)耐贰V饕谀愕南到y(tǒng)之間發(fā)起會話或者接受會話請求(設(shè)備之間需要互相認(rèn)識可以是IP也可以是MAC或者是主機名)
- 表示層
主要是進(jìn)行對接收的數(shù)據(jù)進(jìn)行解釋、加密與解密、壓縮與解壓縮等(也就是把計算機能夠識別的東西轉(zhuǎn)換成人能夠能識別的東西(如圖片、聲音等)。
- 應(yīng)用層
主要是一些終端的應(yīng)用,比如說FTP(各種文件下載),WEB(IE瀏覽),QQ之類的(可以把它理解成我們在電腦屏幕上可以看到的東西.就是終端應(yīng)用)。
PS:
- 每個網(wǎng)卡的MAC地址都是全球唯一的。
- 路由器實現(xiàn)將數(shù)據(jù)包發(fā)送到指定的地點。
- 應(yīng)用軟件之間通信的過程就是層與層之間封包、解封包的過程。
- OSI參考模型雖然設(shè)計精細(xì),但過于麻煩,效率不高,因此才產(chǎn)生了簡化版的TCP/IP參考模型。
1.5 封包、解封包的過程
2. 網(wǎng)絡(luò)編程三要素
網(wǎng)絡(luò)模型說完了,我們要進(jìn)行通訊,需要哪些要素呢?
比如說:我要跟你說話
第一個條件:我要先找到你 (IP)
第二個條件:你得有接收數(shù)據(jù)的地方,耳朵 (端口)
第三個條件:我跟你說話,你能接收到,咱按什么方式接收啊,我說英文你懂嗎,說韓文你懂嗎,不懂是吧,所以我還是說中文把(協(xié)議)
2.1 IP地址
網(wǎng)絡(luò)中計算機的唯一標(biāo)識,不易記憶,可用主機名。本地回環(huán)地址:127.0.0.1,主機名:localhost。計算機只能識別二進(jìn)制的數(shù)據(jù),所以我們的IP地址應(yīng)該是一個二進(jìn)制的數(shù)據(jù)。為了方便表示IP地址,我們就把IP地址的每一個字節(jié)上的數(shù)據(jù)換算成十進(jìn)制,然后用.分開來表示:"點分十進(jìn)制"。
所謂IP地址就是給每個連接在Internet上的主機分配的一個32bit地址。按照TCP/IP規(guī)定,IP地址用二進(jìn)制來表示,每個IP地址長32bit,比特?fù)Q算成字節(jié),就是4個字節(jié)。例如一個采用二進(jìn)制形式的IP地址是“00001010000000000000000000000001”,這么長的地址,人們處理起來也太費勁了。為了方便人們的使用,IP地址經(jīng)常被寫成十進(jìn)制的形式,中間使用符號“.”分開不同的字節(jié)。于是,上面的IP地址可以表示為“10.0.0.1”。IP地址的這種表示法叫做“點分十進(jìn)制表示法”,這顯然比1和0容易記憶得多。
通過ping 127.0.0.1可以測試網(wǎng)絡(luò)是不是通,如果不通,可能是網(wǎng)卡出問題了
通過ping命令還可以獲取到url對應(yīng)的IP地址,例如獲取網(wǎng)易新聞url(c.m.163.com)的IP地址
查看本機IP地址ipconfig
IP地址分類
IP地址的組成:IP地址 = 網(wǎng)絡(luò)號碼+主機地址
IPV4數(shù)量已經(jīng)不夠分配,所以產(chǎn)生了IPV6。
InetAddress類的使用
此類表示互聯(lián)網(wǎng)協(xié)議 (IP) 地址
返回值 | 方法 | 說明 |
---|---|---|
InetAddress | getByName(String host) | 根據(jù)主機名或者IP地址的字符串表示得到IP地址對象 |
String | getHostName() | 獲取此 IP 地址的主機名 |
String | getHostAddress() | 返回 IP 地址字符串 |
代碼示例:
package cn.itcast_01;
import java.net.InetAddress;
import java.net.UnknownHostException;
public class InetAddressDemo {
public static void main(String[] args) throws UnknownHostException {
// public static InetAddress getByName(String host)
// InetAddress address = InetAddress.getByName("liuyi");
// InetAddress address = InetAddress.getByName("192.168.12.92");
InetAddress address = InetAddress.getByName("192.168.12.63");
// 獲取兩個東西:主機名,IP地址
// public String getHostName()
String name = address.getHostName();
// public String getHostAddress()
String ip = address.getHostAddress();
System.out.println(name + "---" + ip);
}
}
運行結(jié)果:
2.2 端口號
正在運行的程序的標(biāo)識,用于標(biāo)識進(jìn)程的邏輯地址,不同進(jìn)程的標(biāo)識。有效端口:065535,其中01024系統(tǒng)使用或保留端口。
端口分為:物理端口,網(wǎng)卡口;邏輯端口,我們指的就是邏輯端口。
- A:每個網(wǎng)絡(luò)程序都會至少有一個邏輯端口
- B:用于標(biāo)識進(jìn)程的邏輯地址,不同進(jìn)程的標(biāo)識
- C:有效端口:065535,其中01024系統(tǒng)使用或保留端口。
- D:所謂防火墻,其功能就是將發(fā)送到某程序端口的數(shù)據(jù)屏蔽掉以及將從該程序端口發(fā)出的數(shù)據(jù)也屏蔽掉。
2.3 傳輸協(xié)議
傳輸協(xié)議就是通訊的規(guī)則,常見協(xié)議:TCP,UDP。
UDP將數(shù)據(jù)源和目的封裝成數(shù)據(jù)包中,不需要建立連接;每個數(shù)據(jù)報的大小在限制在64k;因無連接,是不可靠協(xié)議;不需要建立連接,速度快
TCP建立連接,形成傳輸數(shù)據(jù)的通道;在連接中進(jìn)行大數(shù)據(jù)量傳輸;通過三次握手完成連接,是可靠協(xié)議;必須建立連接,效率會稍低
UDP和TCP的特點
- UDP:面向無連接;不可靠;速度快;將數(shù)據(jù)封包傳輸,數(shù)據(jù)包最大64k
舉例:聊天留言,在線視頻,視頻會議,發(fā)短信,郵局包裹。 - TCP:面向連接;安全可靠效率稍低;通過三次握手確保連接的建立。
舉例:下載,打電話,QQ聊天(你在線嗎,在線,就回應(yīng)下,就開始聊天了)
2.4 域名解析
在瀏覽器中輸入新浪的域名,DNS解析域名成IP,然后計算機再通過獲取到的IP訪問新浪服務(wù)器。
域名解析,最先走是本地的hosts(C:\WINDOWS\system32\drivers\etc\hosts)文件,解析失敗了,才去訪問DNS服務(wù)器解析、獲取IP地址。
import java.net.InetAddress;
import java.net.UnknownHostException;
public class IPDemo
{
public static void main(String[] args) throws UnknownHostException {
InetAddress ip = InetAddress.getLocalHost();
ip = InetAddress.getByName("192.168.1.110");
System.out.println(ip.getHostAddress());
System.out.println(ip.getHostName());
}
}
運行結(jié)果
應(yīng)用:通過hosts文件可以屏蔽游戲網(wǎng)站內(nèi)容彈出,例如:在hosts文件中添加,127.0.0.1 www.game18.com
3. Socket套接字
3.1 Socket套接字
網(wǎng)絡(luò)上具有唯一標(biāo)識的IP地址和端口號組合在一起才能構(gòu)成唯一能識別的標(biāo)識符套接字。
3.2 Socket原理機制
- 通信的兩端都有Socket
- 網(wǎng)絡(luò)通信其實就是Socket間的通信
- 數(shù)據(jù)在兩個Socket間通過IO傳輸
3.3 Socket機制圖解
4. UDP編程
UDP:UDP 協(xié)議全稱是用戶數(shù)據(jù)報協(xié)議,在網(wǎng)絡(luò)中它與TCP 協(xié)議一樣用于處理數(shù)據(jù)包,是一種無連接的協(xié)議。在OSI 模型中,在第四層——傳輸層,處于IP 協(xié)議的上一層。UDP 有不提供數(shù)據(jù)包分組、組裝和不能對數(shù)據(jù)包進(jìn)行排序的缺點,也就是說,當(dāng)報文發(fā)送之后,是無法得知其是否安全完整到達(dá)的。UDP 用來支持那些需要在計算機之間傳輸數(shù)據(jù)的網(wǎng)絡(luò)應(yīng)用。包括網(wǎng)絡(luò)視頻會議系統(tǒng)在內(nèi)的眾多的客戶/服務(wù)器模式的網(wǎng)絡(luò)應(yīng)用都需要使用UDP協(xié)議。UDP 協(xié)議從問世至今已經(jīng)被使用了很多年,雖然其最初的光彩已經(jīng)被一些類似協(xié)議所掩蓋,但是即使是在今天UDP 仍然不失為一項非常實用和可行的網(wǎng)絡(luò)傳輸層協(xié)議。
4.1 UDP傳輸
- DatagramSocket與DatagramPacket
- 建立發(fā)送端,接收端
- 建立數(shù)據(jù)包
- 調(diào)用Socket的發(fā)送接收方法
- 關(guān)閉Socket
- 發(fā)送端與接收端是兩個獨立的運行程序
4.2 DatagramSocket
此類表示用來發(fā)送和接收數(shù)據(jù)報包的套接字
數(shù)據(jù)報套接字是包投遞服務(wù)的發(fā)送或接收點。每個在數(shù)據(jù)報套接字上發(fā)送或接收的包都是單獨編址和路由的。從一臺機器發(fā)送到另一臺機器的多個包可能選擇不同的路由,也可能按不同的順序到達(dá)
在 DatagramSocket 上總是啟用 UDP 廣播發(fā)送。為了接收廣播包,應(yīng)該將 DatagramSocket 綁定到通配符地址。在某些實現(xiàn)中,將 DatagramSocket 綁定到一個更加具體的地址時廣播包也可以被接收。
構(gòu)造方法
DatagramSocket(int port) // 創(chuàng)建數(shù)據(jù)報套接字并將其綁定到本地主機上的指定端口
DatagramSocket(int port, InetAddress laddr) // 創(chuàng)建數(shù)據(jù)報套接字,將其綁定到指定的本地地址
UDP傳輸-發(fā)送端思路
- 建立udp的socket服務(wù)
- 將要發(fā)送的數(shù)據(jù)封裝成數(shù)據(jù)包
- 通過udp的socket服務(wù),將數(shù)據(jù)包發(fā)送出
- 關(guān)閉資源
package cn.itcast_02;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
/*
* UDP協(xié)議發(fā)送數(shù)據(jù):
* A:創(chuàng)建發(fā)送端Socket對象
* B:創(chuàng)建數(shù)據(jù),并把數(shù)據(jù)打包
* C:調(diào)用Socket對象的發(fā)送方法發(fā)送數(shù)據(jù)包
* D:釋放資源
*/
public class SendDemo {
public static void main(String[] args) throws IOException {
// 創(chuàng)建發(fā)送端Socket對象
// DatagramSocket()
DatagramSocket ds = new DatagramSocket();
// 創(chuàng)建數(shù)據(jù),并把數(shù)據(jù)打包
// DatagramPacket(byte[] buf, int length, InetAddress address, int port)
// 創(chuàng)建數(shù)據(jù)
byte[] bys = "hello,udp,我來了".getBytes();
// 長度
int length = bys.length;
// IP地址對象
InetAddress address = InetAddress.getByName("192.168.12.92");
// 端口
int port = 10086;
DatagramPacket dp = new DatagramPacket(bys, length, address, port);
// 調(diào)用Socket對象的發(fā)送方法發(fā)送數(shù)據(jù)包
// public void send(DatagramPacket p)
ds.send(dp);
// 釋放資源
ds.close();
}
}
4.3 DatagramPacket
此類表示數(shù)據(jù)報包。數(shù)據(jù)報包用來實現(xiàn)無連接包投遞服務(wù)。每條報文僅根據(jù)該包中包含的信息從一臺機器路由到另一臺機器。從一臺機器發(fā)送到另一臺機器的多個包可能選擇不同的路由,也可能按不同的順序到達(dá)。不對包投遞做出保證。
構(gòu)造方法
DatagramPacket(byte[] buf, int length)
構(gòu)造 DatagramPacket,用來接收長度為 length 的數(shù)據(jù)包。DatagramPacket(byte[] buf, int length, InetAddress address, int port)
構(gòu)造數(shù)據(jù)報包,用來將長度為 length 的包發(fā)送到指定主機上的指定端口號。DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port)
構(gòu)造數(shù)據(jù)報包,用來將長度為 length 偏移量為 offset 的包發(fā)送到指定主機上的指定端口號。
UDP傳輸-接收端思路
- 建立udp的socket服務(wù).
- 通過receive方法接收數(shù)據(jù)
- 將收到的數(shù)據(jù)存儲到數(shù)據(jù)包對象中
- 通過數(shù)據(jù)包對象的功能來完成對接收到數(shù)據(jù)進(jìn)行解析
- 可以對資源進(jìn)行關(guān)閉
package cn.itcast_02;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
/*
* UDP協(xié)議接收數(shù)據(jù):
* A:創(chuàng)建接收端Socket對象
* B:創(chuàng)建一個數(shù)據(jù)包(接收容器)
* C:調(diào)用Socket對象的接收方法接收數(shù)據(jù)
* D:解析數(shù)據(jù)包,并顯示在控制臺
* E:釋放資源
*/
public class ReceiveDemo {
public static void main(String[] args) throws IOException {
// 創(chuàng)建接收端Socket對象
// DatagramSocket(int port)
DatagramSocket ds = new DatagramSocket(10086);
// 創(chuàng)建一個數(shù)據(jù)包(接收容器)
// DatagramPacket(byte[] buf, int length)
byte[] bys = new byte[1024];
int length = bys.length;
DatagramPacket dp = new DatagramPacket(bys, length);
// 調(diào)用Socket對象的接收方法接收數(shù)據(jù)
// public void receive(DatagramPacket p)
ds.receive(dp); // 阻塞式
// 解析數(shù)據(jù)包,并顯示在控制臺
// 獲取對方的ip
// public InetAddress getAddress()
InetAddress address = dp.getAddress();
String ip = address.getHostAddress();
// public byte[] getData():獲取數(shù)據(jù)緩沖區(qū)
// public int getLength():獲取數(shù)據(jù)的實際長度
byte[] bys2 = dp.getData();
int len = dp.getLength();
String s = new String(bys2, 0, len);
System.out.println(ip + "傳遞的數(shù)據(jù)是:" + s);
// 釋放資源
ds.close();
}
}
運行結(jié)果:
4.4 UDP案例
從鍵盤錄入數(shù)據(jù)進(jìn)行發(fā)送,如果輸入的是886那么客戶端就結(jié)束輸入數(shù)據(jù)。
發(fā)送端
package cn.itcast_04;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
/*
* 數(shù)據(jù)來自于鍵盤錄入
* 鍵盤錄入數(shù)據(jù)要自己控制錄入結(jié)束。
*/
public class SendDemo {
public static void main(String[] args) throws IOException {
// 創(chuàng)建發(fā)送端的Socket對象
DatagramSocket ds = new DatagramSocket();
// 封裝鍵盤錄入數(shù)據(jù)
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String line = null;
while ((line = br.readLine()) != null) {
if ("886".equals(line)) {
break;
}
// 創(chuàng)建數(shù)據(jù)并打包
byte[] bys = line.getBytes();
// DatagramPacket dp = new DatagramPacket(bys, bys.length,
// InetAddress.getByName("192.168.12.92"), 12345);
DatagramPacket dp = new DatagramPacket(bys, bys.length,
InetAddress.getByName("192.168.12.255"), 12345);
// 發(fā)送數(shù)據(jù)
ds.send(dp);
}
// 釋放資源
ds.close();
}
}
運行結(jié)果:
接收端
package cn.itcast_04;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
/*
* 多次啟動接收端:
* java.net.BindException: Address already in use: Cannot bind
* 端口被占用。
*/
public class ReceiveDemo {
public static void main(String[] args) throws IOException {
// 創(chuàng)建接收端的Socket對象
DatagramSocket ds = new DatagramSocket(12345);
while (true) {
// 創(chuàng)建一個包裹
byte[] bys = new byte[1024];
DatagramPacket dp = new DatagramPacket(bys, bys.length);
// 接收數(shù)據(jù)
ds.receive(dp);
// 解析數(shù)據(jù)
String ip = dp.getAddress().getHostAddress();
String s = new String(dp.getData(), 0, dp.getLength());
System.out.println("from " + ip + " data is : " + s);
}
// 釋放資源
// 接收端應(yīng)該一直開著等待接收數(shù)據(jù),是不需要關(guān)閉
// ds.close();
}
}
運行結(jié)果:
5. TCP編程
TCP/IP:Transmission Control Protocol/Internet Protocol 的簡寫,中譯名為傳輸控制協(xié)議/因特網(wǎng)互聯(lián)協(xié)議,又名網(wǎng)絡(luò)通訊協(xié)議,是Internet 最基本的協(xié)議、Internet 國際互聯(lián)網(wǎng)絡(luò)的基礎(chǔ),由網(wǎng)絡(luò)層的IP 協(xié)議和傳輸層的TCP協(xié)議組成。TCP/IP 定義了電子設(shè)備如何連入因特網(wǎng),以及數(shù)據(jù)如何在它們之間傳輸?shù)臉?biāo)準(zhǔn)。協(xié)議采用了4 層的層級結(jié)構(gòu),每一層都呼叫它的下一層所提供的協(xié)議來完成自己的需求。通俗而言:TCP 負(fù)責(zé)發(fā)現(xiàn)傳輸?shù)膯栴},一有問題就發(fā)出信號,要求重新傳輸,直到所有數(shù)據(jù)安全正確地傳輸?shù)侥康牡亍6鳬P 是給因特網(wǎng)的每一臺聯(lián)網(wǎng)設(shè)備規(guī)定一個地址。
TCP/IP 協(xié)議棧主要分為四層:應(yīng)用層、傳輸層、網(wǎng)絡(luò)層、數(shù)據(jù)鏈路層,每層都有相應(yīng)的協(xié)議,如下圖:
所謂的協(xié)議就是雙方進(jìn)行數(shù)據(jù)傳輸?shù)囊环N格式。
5.1 TCP傳輸
- Socket和ServerSocket
- 建立客戶端和服務(wù)器端
- 建立連接后,通過Socket中的IO流進(jìn)行數(shù)據(jù)的傳輸
- 關(guān)閉socket
- 同樣,客戶端與服務(wù)器端是兩個獨立的應(yīng)用程序。
5.2 Socket
此類實現(xiàn)客戶端套接字(也可以就叫“套接字”)。套接字是兩臺機器間通信的端點。
構(gòu)造方法
- Socket(String host, int port) :創(chuàng)建一個流套接字并將其連接到指定主機上的指定端口號。
- Socket(InetAddress address, int port) :創(chuàng)建一個流套接字并將其連接到指定 IP 地址的指定端口號。
TCP傳輸-客戶端思路
- 建立客戶端的Socket服務(wù),并明確要連接的服務(wù)器。
- 如果連接建立成功,就表明,已經(jīng)建立了數(shù)據(jù)傳輸?shù)耐ǖ?就可以在該通道通過IO進(jìn)行數(shù)據(jù)的讀取和寫入.該通道稱為Socket流,Socket流中既有讀取流,也有寫入流.
- 通過Socket對象的方法,可以獲取這兩個流
- 通過流的對象可以對數(shù)據(jù)進(jìn)行傳輸
- 如果傳輸數(shù)據(jù)完畢,關(guān)閉資源
package cn.itcast_06;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
/*
* TCP協(xié)議發(fā)送數(shù)據(jù):
* A:創(chuàng)建發(fā)送端的Socket對象
* 這一步如果成功,就說明連接已經(jīng)建立成功了。
* B:獲取輸出流,寫數(shù)據(jù)
* C:釋放資源
*
* 連接被拒絕。TCP協(xié)議一定要先看服務(wù)器。
* java.net.ConnectException: Connection refused: connect
*/
public class ClientDemo {
public static void main(String[] args) throws IOException {
// 創(chuàng)建發(fā)送端的Socket對象
// Socket(InetAddress address, int port)
// Socket(String host, int port)
// Socket s = new Socket(InetAddress.getByName("192.168.12.92"), 8888);
Socket s = new Socket("192.168.12.92", 8888);
// 獲取輸出流,寫數(shù)據(jù)
// public OutputStream getOutputStream()
OutputStream os = s.getOutputStream();
os.write("hello,tcp,我來了".getBytes());
// 釋放資源
s.close();
}
}
5.3 ServerSocket
此類實現(xiàn)服務(wù)器套接字。服務(wù)器套接字等待請求通過網(wǎng)絡(luò)傳入。它基于該請求執(zhí)行某些操作,然后可能向請求者返回結(jié)果。
構(gòu)造方法
ServerSocket(int port) // 創(chuàng)建綁定到特定端口的服務(wù)器套接字
TCP傳輸-服務(wù)器端思路
- 建立服務(wù)器端的socket服務(wù),需要一個端口
- 服務(wù)端沒有直接流的操作,而是通過accept方法獲取客戶端對象,在通過獲取到的客戶端對象的流和客戶端進(jìn)行通信
- 通過客戶端的獲取流對象的方法,讀取數(shù)據(jù)或者寫入數(shù)據(jù)
- 如果服務(wù)完成,需要關(guān)閉客戶端,然后關(guān)閉服務(wù)器,但是,一般會關(guān)閉客戶端,不會關(guān)閉服務(wù)器,因為服務(wù)端是一直提供服務(wù)的
package cn.itcast_06;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
/*
* TCP協(xié)議接收數(shù)據(jù):
* A:創(chuàng)建接收端的Socket對象
* B:監(jiān)聽客戶端連接。返回一個對應(yīng)的Socket對象
* C:獲取輸入流,讀取數(shù)據(jù)顯示在控制臺
* D:釋放資源
*/
public class ServerDemo {
public static void main(String[] args) throws IOException {
// 創(chuàng)建接收端的Socket對象
// ServerSocket(int port)
ServerSocket ss = new ServerSocket(8888);
// 監(jiān)聽客戶端連接。返回一個對應(yīng)的Socket對象
// public Socket accept()
Socket s = ss.accept(); // 偵聽并接受到此套接字的連接。此方法在連接傳入之前一直阻塞。
// 獲取輸入流,讀取數(shù)據(jù)顯示在控制臺
InputStream is = s.getInputStream();
byte[] bys = new byte[1024];
int len = is.read(bys); // 阻塞式方法
String str = new String(bys, 0, len);
String ip = s.getInetAddress().getHostAddress();
System.out.println(ip + "---" + str);
// 釋放資源
s.close();
// ss.close(); //這個不應(yīng)該關(guān)閉
}
}
5.4 TCP傳輸案例
客戶端鍵盤錄入,服務(wù)器輸出到控制臺
客戶端:
package cn.itcast_08;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.Socket;
/*
* 客戶端鍵盤錄入,服務(wù)器輸出到控制臺
*/
public class ClientDemo {
public static void main(String[] args) throws IOException {
// 創(chuàng)建客戶端Socket對象
Socket s = new Socket("192.168.12.92", 22222);
// 鍵盤錄入數(shù)據(jù)
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
// 把通道內(nèi)的流給包裝一下
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(
s.getOutputStream()));
String line = null;
while ((line = br.readLine()) != null) {
// 鍵盤錄入數(shù)據(jù)要自定義結(jié)束標(biāo)記
if ("886".equals(line)) {
break;
}
bw.write(line);
bw.newLine();
bw.flush();
}
// 釋放資源
// bw.close();
// br.close();
s.close();
}
}
運行結(jié)果:
服務(wù)器端:
package cn.itcast_08;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
public class ServerDemo {
public static void main(String[] args) throws IOException {
// 創(chuàng)建服務(wù)器Socket對象
ServerSocket ss = new ServerSocket(22222);
// 監(jiān)聽客戶端連接
Socket s = ss.accept();
// 包裝通道內(nèi)容的流
BufferedReader br = new BufferedReader(new InputStreamReader(
s.getInputStream()));
String line = null;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
// br.close();
s.close();
// ss.close();
}
}
運行結(jié)果:
5.5 上傳圖片案例
客戶端:
package cn.itcast_13;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
public class UploadClient {
public static void main(String[] args) throws IOException {
// 創(chuàng)建客戶端Socket對象
Socket s = new Socket("192.168.12.92", 19191);
// 封裝圖片文件
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(
"林青霞.jpg"));
// 封裝通道內(nèi)的流
BufferedOutputStream bos = new BufferedOutputStream(s.getOutputStream());
byte[] bys = new byte[1024];
int len = 0;
while ((len = bis.read(bys)) != -1) {
bos.write(bys, 0, len);
bos.flush();
}
s.shutdownOutput();
// 讀取反饋
InputStream is = s.getInputStream();
byte[] bys2 = new byte[1024];
int len2 = is.read(bys2);
String client = new String(bys2, 0, len2);
System.out.println(client);
// 釋放資源
bis.close();
s.close();
}
}
服務(wù)器端:
package cn.itcast_13;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class UploadServer {
public static void main(String[] args) throws IOException {
// 創(chuàng)建服務(wù)器Socket對象
ServerSocket ss = new ServerSocket(19191);
// 監(jiān)聽客戶端連接
Socket s = ss.accept();
// 封裝通道內(nèi)流
BufferedInputStream bis = new BufferedInputStream(s.getInputStream());
// 封裝圖片文件
BufferedOutputStream bos = new BufferedOutputStream(
new FileOutputStream("mn.jpg"));
byte[] bys = new byte[1024];
int len = 0;
while ((len = bis.read(bys)) != -1) {
bos.write(bys, 0, len);
bos.flush();
}
// 給一個反饋
OutputStream os = s.getOutputStream();
os.write("圖片上傳成功".getBytes());
bos.close();
s.close();
}
}
運行結(jié)果:
5.6 TCP傳輸容易出現(xiàn)的問題
- 客戶端連接上服務(wù)端,兩端都在等待,沒有任何數(shù)據(jù)傳輸
- 通過例程分析:因為read方法或者readLine方法是阻塞式
- 解決辦法:自定義結(jié)束標(biāo)記,使用shutdownInput,shutdownOutput方法
6. TCP、UDP 特點對比
TCP 協(xié)議是面向連接、保證高可靠性(數(shù)據(jù)無丟失、數(shù)據(jù)無失序、數(shù)據(jù)無錯誤、數(shù)據(jù)無重復(fù)到達(dá))傳輸層協(xié)議。UDP 協(xié)議也是傳輸層協(xié)議,它是無連接,不保證可靠的傳輸層協(xié)議。
TCP | UDP |
---|---|
面向連接 | 面向非連接 |
可靠的連接 | 不可靠的連接 |
速度慢 | 速度快 |
大文件、重要的數(shù)據(jù)等 | 適合小數(shù)據(jù)、不重要 |
7. TCP 三次握手過程
1、請求端(通常稱為客戶)發(fā)送一個SYN 段指明客戶打算連接的服務(wù)器的端口,以及初始序號(ISN)
2、服務(wù)器發(fā)回包含服務(wù)器的初始序號的SYN 報文段(報文段2)作為應(yīng)答。同時,將確認(rèn)序號設(shè)置為客戶的ISN加1 以對客戶的SYN 報文段進(jìn)行確認(rèn)。
3、客戶必須將確認(rèn)序號設(shè)置為服務(wù)器的ISN 加1 以對服務(wù)器的SYN 報文段進(jìn)行確認(rèn)(報文段3)這三個報文段完成連接的建立。這個過程也稱為三次握手(three-way handshake)。
上面的過程如下圖所示:
8. 客戶端和服務(wù)器端原理
8.1 常見的客戶端、服務(wù)器端
最常見的客戶端:瀏覽器,IE/chrome
最常見的服務(wù)端:服務(wù)器,Tomcat
8.2 常見網(wǎng)絡(luò)結(jié)構(gòu)
8.3 URL&URI
URI:統(tǒng)一資源標(biāo)識符
URI是統(tǒng)一資源標(biāo)識符,是一個用于標(biāo)識某一互聯(lián)網(wǎng)資源名稱的字符串。 該種標(biāo)識允許用戶對任何(包括本地和互聯(lián)網(wǎng))的資源通過特定的協(xié)議進(jìn)行交互操作。URI由包括確定語法和相關(guān)協(xié)議的方案所定義。由是三個組成部分:訪問資源的命名機制、存放資源的主機名、資源自身的名稱,由路徑表示。
URL:統(tǒng)一資源定位符
也就是說根據(jù)URL能夠定位到網(wǎng)絡(luò)上的某個資源,它是指向互聯(lián)網(wǎng)“資源”的指針。
每個URL都是URI,但不一定每個URI都是URL。這是因為URI還包括一個子類,即統(tǒng)一資源名稱(URN),它命名資源但不指定如何定位資源。
URL是統(tǒng)一資源定位,是對可以從互聯(lián)網(wǎng)上得到的資源的位置和訪問方法的一種簡潔的表示,是互聯(lián)網(wǎng)上標(biāo)準(zhǔn)資源的地址。互聯(lián)網(wǎng)上的每個文件都有一個唯一的URL,它包含的信息指出文件的位置以及瀏覽器應(yīng)該怎么處理它。
比如百度URL即是http://www.baidu.com。
9. TCP的三次握手/四次揮手
TCP是面向連接的運輸層協(xié)議,TCP協(xié)議提供可靠的連接服務(wù),所以用了建立鏈接的三次握手和關(guān)閉連接的四次揮手來保證可靠服務(wù)。
通過TCP通信就像是兩個應(yīng)用在打電話一樣,打電話前得先撥號建立連接,通話結(jié)束后要掛機釋放連接。
9.1 建立TCP連接的三次握手
TCP連接的三次握手分別為:
- 客戶端發(fā)送一個帶SYN標(biāo)志的TCP報文到服務(wù)器,表示告訴服務(wù)器我想建立一個連接。
- 服務(wù)器收到客戶端的帶SYN標(biāo)志的文后,就給客戶端回復(fù)一個帶ACK標(biāo)志和帶SYN標(biāo)志的報文,ACK表示回復(fù)客戶端:OK,我準(zhǔn)備好了建立連接;然后SYN表示服務(wù)器又問客戶端:你準(zhǔn)備好建立連接了么?
- 然后客戶端又要發(fā)送一個帶ACK標(biāo)志的TCP報文,回答服務(wù)器說:我準(zhǔn)備好了。
然后一個TCP連接就建立起來了。
SYN相當(dāng)于詢問的標(biāo)志,ACK相當(dāng)于回復(fù)的標(biāo)志。
這里有一個問題:為什么最后客戶端還要發(fā)送一次確認(rèn)呢?這主要是防止已經(jīng)失效了的請求報文段突然又傳到了服務(wù)器,因而產(chǎn)生錯誤。
“已經(jīng)失效了的請求報文段”大致是這樣產(chǎn)生的:A發(fā)出第一個連接請求報文段并沒有丟失,在一些網(wǎng)絡(luò)結(jié)點上面長時間滯留,以致延誤到連接釋放以后的某個時間才到達(dá)B。本來這是一個早已失效的報文段。但B收到這個失效的報文段后,就誤以為是A發(fā)出的又一次新的連接請求,于是就向A發(fā)出確認(rèn)報文段,同意建立連接,如果不采用三次握手,那么只要B發(fā)出確認(rèn)后,新的連接就建立了。
9.2 釋放TCP連接的四次揮手
由于TCP是全雙工的,所以在釋放TCP連接時,要雙方都得單獨關(guān)閉。意思就是服務(wù)器和客戶端都要釋放連接。原則是某一方主動關(guān)閉時,先發(fā)一個FIN報文來表示終止這個方向的連接,收到一個FIN報文就意味著這個方向不再有數(shù)據(jù)流動,但另一個方向仍可以有數(shù)據(jù)流動,當(dāng)這一個方向也發(fā)送了FIN報文后,那么這一方的連接也可以關(guān)閉了。
釋放TCP連接相對于要復(fù)雜點,具體釋放TCP連接的四次揮手流程如下:
- A發(fā)送一個FIN給B,說:我這邊要傳給你的數(shù)據(jù)已經(jīng)傳完了,我要關(guān)閉連接了。A進(jìn)入FIN-WAIT-1狀態(tài),等待B確認(rèn)。
- B收到了上面的FIN報文后,回復(fù)一個ACK報文說:OK。A就關(guān)閉了A->B的連接。但是此時B還能給A發(fā)送數(shù)據(jù),A也能接收B發(fā)來的數(shù)據(jù)。(此時A收到確認(rèn)后進(jìn)入FIN-WAIT-2狀態(tài)。TCP處于半關(guān)閉狀態(tài))
- 當(dāng)B也發(fā)送完數(shù)據(jù)后,就給A發(fā)送一個FIN報文說:我這邊要傳給你的數(shù)據(jù)也已經(jīng)傳完了,我也要關(guān)閉連接了。(B進(jìn)入LAST-ACK狀態(tài),等待A確認(rèn))
- A收到了上面的報文后,回復(fù)一個ACK報文說:OK。A進(jìn)入TIME-WAIT狀態(tài)。現(xiàn)在TCP連接還沒有釋放掉,然后經(jīng)過等待計時器(TIME-WAIT timer)設(shè)置的時間2MSL后,A才進(jìn)入CLOSE狀態(tài)。
然后,當(dāng)A撤銷相應(yīng)的傳輸控制塊TCB后,一個TCP連接就關(guān)閉了。
10. Http、Tcp、Udp、Socket的區(qū)別
IP,網(wǎng)絡(luò)層協(xié)議;TCP和UDP,傳輸層協(xié)議;HTTP,應(yīng)用層協(xié)議;SOCKET:TCP/IP網(wǎng)絡(luò)的API。
TCP/IP代表傳輸控制協(xié)議/網(wǎng)際協(xié)議,指的是一系列協(xié)議。
TCP和UDP使用IP協(xié)議從一個網(wǎng)絡(luò)傳送數(shù)據(jù)包到另一個網(wǎng)絡(luò)。把IP想像成一種高速公路,它允許其它協(xié)議在上面行駛并找到到其它電腦的出口。TCP和UDP是高速公路上的“卡車”,它們攜帶的貨物就是像HTTP,文件傳輸協(xié)議FTP這樣的協(xié)議等。
TCP和UDP是FTP,HTTP和SMTP之類使用的傳輸層協(xié)議。雖然TCP和UDP都是用來傳輸其他協(xié)議的,它們卻有一個顯著的不同:TCP提供有保證的數(shù)據(jù)傳輸,而UDP不提供。這意味著TCP有一個特殊的機制來確保數(shù)據(jù)安全的不出錯的從一個端點傳到另一個端點,而UDP不提供任何這樣的保證。
HTTP(超文本傳輸協(xié)議)是利用TCP在兩臺電腦(通常是Web服務(wù)器和客戶端)之間傳輸信息的協(xié)議。客戶端使用Web瀏覽器發(fā)起HTTP請求給Web服務(wù)器,Web服務(wù)器發(fā)送被請求的信息給客戶端。
記住,需要IP協(xié)議來連接網(wǎng)絡(luò);TCP是一種允許我們安全傳輸數(shù)據(jù)的機制,使用TCP協(xié)議來傳輸數(shù)據(jù)的HTTP是Web服務(wù)器和客戶端使用的特殊協(xié)議。
Socket 接口是TCP/IP網(wǎng)絡(luò)的API,Socket接口定義了許多函數(shù)或例程,用以開發(fā)TCP/IP網(wǎng)絡(luò)上的應(yīng)用程序。
本節(jié)原文鏈接:http://www.lxweimin.com/p/1f512687ea19
11. URL
URI:統(tǒng)一資源標(biāo)示符。
URL:統(tǒng)一資源定位符,也就是說根據(jù)URL能夠定位到網(wǎng)絡(luò)上的某個資源,它是指向互聯(lián)網(wǎng)“資源”的指針。
每個URL都是URI,但不一定每個URI都是URL。這是因為URI還包括一個子類,即統(tǒng)一資源名稱(URN),它命名資源但不指定如何定位資源。
public class URLDemo
{
public static void main(String[] args) throws MalformedURLException,IOException {
String str_url = "http://192.168.1.100:8080/myweb/1.html?name=lisi";
URL url = new URL(str_url);
System.out.println("getProtocol:" + url.getProtocol());
System.out.println("getHost:" + url.getHost());
System.out.println("getPort:" + url.getPort());
System.out.println("getFile:" + url.getFile());
System.out.println("getPath:" + url.getPath());
System.out.println("getQuery:" + url.getQuery());
InputStream in = url.openStream();//相當(dāng)于 url.openConnection().getInputStream();
byte[] buf = new byte[1024];
int len = in.read(buf);
String text = new String(buf,0,len);
System.out.println(text);
in.close();
}
}
運行結(jié)果
之所以運行結(jié)果中響應(yīng)頭不見了,只能看到主體數(shù)據(jù)的原因在于:URLConnection對象已經(jīng)把響應(yīng)頭
給解析了
12. URLConnection
public class URLDemo
{
public static void main(String[] args) throws MalformedURLException,IOException {
String str_url = "http://192.168.1.100:8080/myweb/1.html?name=lisi";
URL url = new URL(str_url);
//獲取url對象的Url連接器對象。將連接封裝成了對象:
// java中內(nèi)置的可以解析的具體協(xié)議對象+socket。
URLConnection conn = url.openConnection();
System.out.println(conn);
//由于URLConnection對象已經(jīng)把響應(yīng)頭給解析了,所以,
// 可以通過URLConnection對象獲取響應(yīng)頭某屬性名對應(yīng)的屬性值。
String value = conn.getHeaderField("Content-Type");
System.out.println(value);
}
}
運行結(jié)果
13. HttpURLConnection
URL newURL = new URL(url);
URLConnection urlConnection = newURL.openConnection();
urlConnection.setConnectTimeout(mConfig.connTimeOut);
urlConnection.setReadTimeout(mConfig.soTimeOut);
urlConnection.setDoInput(true);
urlConnection.setUseCaches(false);
// HttpsURLConnection
HttpsURLConnection.setDefaultSSLSocketFactory(sslFactory);
HttpsURLConnection.setDefaultHostnameVerifier();
HttpURLConnection常用方法
方法聲明 | 功能描述 |
---|---|
addRequestProperty() | 添加請求屬性 |
setRequestMethod() | 設(shè)置請求方式 |
connect() | 連接網(wǎng)絡(luò) |
disconnect() | 斷開連接 |
setDoOutput() | 設(shè)置打開連接對象輸出流,把要提交的數(shù)據(jù)寫入流中 |
setDoInput() | 設(shè)置打開連接對象輸入流 |
setConnectTimeout() | 設(shè)置連接超時 |
setReadTimeout() | 設(shè)置讀取超時 |
setUseCaches() | 設(shè)置是否使用緩存 |
getResponseCode() | 獲取響應(yīng)碼 |
getOutputStream() | 獲取輸出流 |
getInputStream() | 獲取輸入流 |
getErrorStream() | 獲取錯誤流 |
getResponseMessage() | 獲取響應(yīng)信息 |
getContentLength() | 獲取內(nèi)容長度 |
getContentEncoding() | 獲取內(nèi)容編碼 |
getContentType() | 獲取內(nèi)容類型 |
getHeaderFields() | 獲取所有的頭字段 |
setRequestProperty和addRequestProperty的區(qū)別
setRequestProperty和addRequestProperty的區(qū)別就是,setRequestProperty會覆蓋已經(jīng)存在的key的所有values,有清零重新賦值的作用。而addRequestProperty則是在原來key的基礎(chǔ)上繼續(xù)添加其他value。
/**
* Adds the given property to the request header. Existing properties with
* the same name will not be overwritten by this method.
*/
public void addRequestProperty(String field, String newValue) {
...
}
字節(jié)流轉(zhuǎn)換為字符
public class Tools {
public static String getTextFromStream(InputStream is) {
try {
byte[] b = new byte[1024];
int len;
ByteArrayOutputStream bos = new ByteArrayOutputStream();
while ((len = is.read(b)) != -1) {
bos.write(b, 0, len);
}
//把輸出流里的內(nèi)容轉(zhuǎn)換成字節(jié)數(shù)組
String text = new String(bos.toByteArray());
return text;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
14. URLEncoder和URLDecoder
URLEncoder.encode();
URLDecoder.decode();