參考資料:百度百科TCP協議
本文涉及Java IO流、例外的知識,可參考我的另外的博客
一文簡述Java IO
一文簡述JAVA內部類和例外
1.概述
計算機網路相關知識:
OSI七層模型

一個報文可以類似于一封信,就像下圖(引自狂神說Java)非常生動,
網路編程的目的:資料交換、通信
網路通信的要素:
如何實作網路通信?
通信雙方地址:
- ip
- 埠號
網路協議:
HTTP, FTP, TCP, UDP 等等
1.1 IP
IP地址:InetAddress(無構造器)
- 唯一定位一臺網路上計算機
- 127.0.0.1 :本機,localhost
- ip地址分類:ipv4(4個位元組)/ipv6(128位,8個無符號整陣列成),公網(ABCD類地址)/私網(局域網)
- 域名:記憶ip問題
主機名決議
主機名稱到IP地址決議是通過使用本地機器配置資訊和網路命名服務(如域名系統(DNS)和網路資訊服務(NIS))的組合來實作的,所使用的特定命名服務是默認配置的本地機器,對于任何主機名,回傳其對應的IP地址,反向名稱決議意味著對于任何IP地址,回傳與IP地址關聯的主機,
InetAddress類提供了將主機名決議為其IP地址的方法,反之亦然,
InetAddress常用方法:

舉例:
public static void main(String[] args) throws UnknownHostException {
//查詢本機地址
InetAddress inetAddress = InetAddress.getByName("127.0.0.1");
System.out.println(inetAddress);
InetAddress i1 = InetAddress.getByName("localhost");
System.out.println(i1);
InetAddress i2 = InetAddress.getLocalHost();
System.out.println(i2);
//查詢網站ip
InetAddress i3 = InetAddress.getByName("www.baidu.com");
System.out.println(i3);
//常用方法
System.out.println(i3.getAddress()); //回傳的是byte[],所以輸出了亂碼
System.out.println(i3.getCanonicalHostName());//規范的名字
System.out.println(i3.getHostAddress());//ip
System.out.println(i3.getHostName());//主機名
}
InetAddress沒有構造器,所以需要呼叫靜態方法進行構造,上述代碼結果為:

1.2 埠
埠表示計算機上的一個程式的行程
- 不同的行程有不同的埠號,用來區分軟體,
- 一般被規定為0~65535
- TCP埠和UDP埠,均有65536個,兩個互不沖突,單個協議下埠是不能沖突的,例:TCP占用8080后,不能再次占用此TCP埠了
- 埠分類:公有埠01023,HTTP:80,HTTPS:443,FTP:21,Telent:23,程式注冊的埠102449151,用來分配給用戶或者程式,Tomcat:8080,MySQL:3306,Oracle:1521,動態、私有:49152~65535,盡量不要用這里的埠,
netstat -ano #這條命令用于查看所有埠
netstat -ano|findstr "8080" #查看指定的埠
tasklist|findstr "8696" #查看指定埠的行程
以上均為Linux命令
InetSocketAddress
該類實作IP套接字地址(IP地址+埠號)它也可以是一對(主機名+埠號),在這種情況下將嘗試決議主機名,如果解決方案失敗,那么該地址被認為是未解決的,但在某些情況下仍可以使用,例如通過代理連接,
它提供了用于系結,連接或回傳值的套接字所使用的不可變物件,
通配符是一個特殊的本地IP地址, 通常意味著“任何”,只能用于bind操作,
InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1",8080);
InetSocketAddress inetSocketAddress1 = new InetSocketAddress("localhost",8080);
System.out.println(inetSocketAddress);
System.out.println(inetSocketAddress1);
System.out.println(inetSocketAddress.getAddress());
System.out.println(inetSocketAddress.getHostName());
System.out.println(inetSocketAddress.getPort());
以上為相關代碼,
1.3 通信協議
網路通信協議可能涉及到:速率,傳輸碼率,代碼結構,傳輸控制等等
主要涉及的是以下兩個:
TCP:用戶傳輸協議(3次握手,確定回傳資訊,以后網路相關知識具體說,不在本篇贅述)
UDP:用戶資料報協議(不確定回傳資訊)
TCP和UDP對比
TCP就像打電話,需要連接,穩定
UDP就像發短信,不需要連接,發完即結束,不穩定
-
基于連接與無連接;
-
對系統資源的要求(TCP較多,UDP少);
-
UDP程式結構較簡單;
-
流模式與資料報模式 ;(從下面demo中即可看出)
-
TCP保證資料正確性,UDP可能丟包;
-
TCP保證資料順序,UDP不保證,
2. TCP協議

TCP三次握手的程序如下:
- 客戶端發送SYN(SEQ=x)報文給服務器端,進入SYN_SEND狀態,
- 服務器端收到SYN報文,回應一個SYN (SEQ=y)ACK(ACK=x+1)報文,進入
SYN_RECV狀態, - 客戶端收到服務器端的SYN報文,回應一個ACK(ACK=y+1)報文,進入
Established狀態,
三次握手完成,TCP客戶端和服務器端成功地建立連接,可以開始傳輸資料了,

TCP連接終止程序:
建立一個連接需要三次握手,而終止一個連接要經過四次握手,這是由TCP的半關閉(half-close)造成的,具體程序如上圖所示,
(1) 某個應用行程首先呼叫close,稱該端執行“主動關閉”(active close),該端的TCP于是發送一個FIN分節,表示資料發送完畢,
(2) 接收到這個FIN的對端執行 “被動關閉”(passive close),這個FIN由TCP確認,
注意:FIN的接收也作為一個檔案結束符(end-of-file)傳遞給接收端應用行程,放在已排隊等候該應用行程接收的任何其他資料之后,因為,FIN的接收意味著接收端應用行程在相應連接上再無額外資料可接收,
(3) 一段時間后,接收到這個檔案結束符的應用行程將呼叫close關閉它的套接字,這導致它的TCP也發送一個FIN,
(4) 接收這個最終FIN的原發送端TCP(即執行主動關閉的那一端)確認這個FIN, [3]
既然每個方向都需要一個FIN和一個ACK,因此通常需要4個分節,
TCP協議相關資料參考自百度百科,計算機網路相關知識不再詳細描述,
2.1 TCP連接的實作
服務端:
public class TestTCPserver {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(9999);
Socket accept = serverSocket.accept();//等待client連接
InputStream is = accept.getInputStream();
//管道流,將一個輸入流通過管道轉化為一個合適的輸出流,不用管道流直接String可能會輸出亂碼
ByteArrayOutputStream 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());
baos.close();
is.close();
accept.close();
serverSocket.close();
//正式寫代碼的程序中,一定要用try,catch,finally,為了代碼的安全,出了事故容易判斷
}
}
用到了socket類,其中涉及了IO流部分的知識,
客戶端:
public class TestTCPclient {
public static void main(String[] args) throws IOException {
InetAddress serverIp = InetAddress.getByName("127.0.0.1");
int port = 9999;
//創建一個socket連接,連接的是本機
Socket socket = new Socket(serverIp,port);
OutputStream os = socket.getOutputStream();
os.write("hello".getBytes());
os.close();
socket.close();
}
}
注意:socket是需要關閉的

-
socket該類實作客戶端套接字(也稱為“套接字”),套接字是兩臺機器之間通訊的端點,套接字的實際作業由SocketImpl類的實體執行, 應用程式通過更改創建套接字實作的套接字工廠,可以配置自己創建適合本地防火墻的套接字, -
ServerSocket該類實作了服務器套接字, 服務器套接字等待通過網路進入的請求, 它根據該請求執行一些操作,然后可能將結果回傳給請求者,
服務器套接字的實際作業由SocketImpl類的實體執行, 應用程式可以更改創建套接字實作的套接字工廠,以配置自己創建適合本地防火墻的套接字,
客戶端:連接服務器socket,發送訊息
服務器:建立服務的埠 ServerSocket,等待用戶連接,接受用戶訊息
2.1 TCP實作檔案上傳
與訊息傳遞類似,只是IO操作稍微變了一下
服務端:
public class TCPserverdemo1 {
public static void main(String[] args) throws IOException {
//創建服務
ServerSocket serverSocket = new ServerSocket(9000);
//監聽客戶端的連接
Socket accept = serverSocket.accept(); //阻塞式監聽,會一定等待客戶端連接
InputStream is = accept.getInputStream();
FileOutputStream fos = new FileOutputStream(new File("copide1.jpg"));
byte[] buffer = new byte[1024];
int len;
while((len = is.read(buffer))!=-1){
fos.write(buffer,0,len);
}
fos.close();
is.close();
accept.close();
serverSocket.close();
}
}
客戶端:
public class TCPclientdemo1 {
public static void main(String[] args) throws IOException {
//創建一個socket連接
Socket socket = new Socket(InetAddress.getByName("127.0.0.1"),9000);
//創建一個輸出流
OutputStream os = socket.getOutputStream(); //.getOutputStream獲得了一個SocketOutputStream實體
//讀取檔案
FileInputStream fis = new FileInputStream(new File("image.jpg"));
byte[] buffer = new byte[1024];
int len;
while((len=fis.read(buffer))!=-1){
os.write(buffer,0,len); //涉及到BIO
}
fis.close();
os.close();
socket.close();
}
}
上面用的是位元組流,位元組緩沖流也可以使用,字符流不可以,因為可能會讀其他檔案的型別,位元組流比較穩妥,
服務器接收完資訊后其實是可以回傳訊息到客戶端的,socket通信:
服務端可增加:
accept.shutdownInput();
//通知客戶端已經接收完畢
OutputStream os = accept.getOutputStream();
os.write("ending".getBytes()); //發送給客戶端
客戶端可增加:
socket.shutdownOutput();//通知服務器傳輸完畢
InputStream is = socket.getInputStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer2 = new byte[1024];
int len2;
while((len2 = is.read(buffer))!=-1){
baos.write(buffer,0,len2);
}
System.out.println(baos);
客戶端讀入了服務端回傳的ending字串,
實作兩端通信之后再決定是否進行其他操作,例如close()等等,
3. UDP
無需連接,但是需要知道對方地址
3.1 UDP訊息發送
主要依賴的是DatagramSocket和DatagramPacket
DatagramSocket此類表示用于發送和接收資料報資料包的套接字, 資料報套接字是分組傳送服務的發送或接收點, 在資料報套接字上發送或接收的每個資料包都被單獨尋址和路由, 從一個機器發送到另一個機器的多個分組可以不同地路由,并且可以以任何順序到達, 在可能的情況下,新構建的DatagramSocket啟用了SO_BROADCAST套接字選項,以允許廣播資料報的傳輸, 為了接收廣播資料包,DatagramSocket應該系結到通配符地址, 在一些實作中,當DatagramSocket系結到更具體的地址時,也可以接收廣播分組,DatagramPacket該類表示資料報包, 資料報包用于實作無連接分組傳送服務, 僅基于該資料包中包含的資訊,每個訊息從一臺機器路由到另一臺機器, 從一臺機器發送到另一臺機器的多個分組可能會有不同的路由,并且可能以任何順序到達, 包傳送不能保證,
以下實作UDP訊息傳送的一個簡單例子:
public class TestUDP1 {
//不需要連接服務器
public static void main(String[] args) throws IOException {
//建立Socket
DatagramSocket datagramSocket = new DatagramSocket(); //為空將默認系結一個可用埠
//建個資料報
String message = "hello,server";
InetAddress inetAddress = InetAddress.getByName("localhost");
int port = 9000;
int len = message.getBytes().length;
//資料,資料的長度起始位置,要發送給誰
DatagramPacket datagramPacket = new DatagramPacket(message.getBytes(), 0,len, inetAddress, port);
datagramSocket.send(datagramPacket); //進行發送
datagramSocket.close();
}
}
UDP發送完就不需要其他操作了,為了驗證我們發送的訊息,建立了一個接收端:
public class UDPaccept {
public static void main(String[] args) throws IOException {
//開放埠
DatagramSocket socket = new DatagramSocket(9000);
//接收資料報
byte[] buffer = new byte[1024];
DatagramPacket packet = new DatagramPacket(buffer,0,buffer.length); //接收并不需要對方地址和埠
socket.receive(packet); //阻塞式接收
System.out.println(packet.getAddress());
System.out.println(new String(packet.getData(),0,packet.getData().length));
socket.close();
}
}
其中收發訊息用到了兩個方法DatagramSocket.send()和DatagramSocket.receive()
3.2 UDP聊天的實作(單向)
通過UDP協議進行一個發送端和接收端的demo,當出現"bye"時,結束對話,
發送端:
public class UDPsender {
public static void main(String[] args) throws IOException {
DatagramSocket socket = new DatagramSocket(8080);//發送埠
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
//BufferedInputStream bis = new BufferedInputStream(System.in);
int len;
while(true){
String data = https://www.cnblogs.com/gaoyuan206/archive/2021/04/17/reader.readLine(); //接收鍵盤輸入的資訊
DatagramPacket packet = new DatagramPacket(data.getBytes(),0,data.getBytes().length,new InetSocketAddress("localhost",6000));
socket.send(packet);
if(data.equals("bye"))
break;
}
reader.close();
socket.close();
}
}
接收端:
public class UDPreceiver {
public static void main(String[] args) throws IOException {
DatagramSocket socket = new DatagramSocket(6000);//打開接收埠
while(true){
byte[] container = new byte[1024]; //用來裝資料報內容
DatagramPacket packet = new DatagramPacket(container,0,container.length); //接受時候只需要一個空byte[]
socket.receive(packet);
byte[]data = https://www.cnblogs.com/gaoyuan206/archive/2021/04/17/packet.getData();
String datas = new String(data,0,data.length); //仍帶有byte[]的其他資訊,若轉為真正字串需trim()
System.out.println(datas);
if(datas.trim().equals("bye"))
break;
}
socket.close();
}
}
這個demo只實作了單向發訊息,后續將實作雙向發送,
3.3 雙向聊天(多執行緒)
雙向聊天和以上內容相似,只需要每個端開啟兩個執行緒(接收執行緒和發送執行緒),以下為代碼演示:
public class TalkSend implements Runnable{ //發送執行緒
DatagramSocket socket = null;
BufferedReader reader = null;
private String ToIP;
private int ToPort;
public TalkSend(String toString, int toPort) {
ToIP = toString;
ToPort = toPort;
}
@Override
public void run() {
try {
socket = new DatagramSocket();//發送埠
reader = new BufferedReader(new InputStreamReader(System.in));
} catch (SocketException e) {
e.printStackTrace();
}
//BufferedInputStream bis = new BufferedInputStream(System.in);
while(true){
String data = https://www.cnblogs.com/gaoyuan206/archive/2021/04/17/null;
try {
data = reader.readLine();
DatagramPacket packet = new DatagramPacket(data.getBytes(),0,data.getBytes().length,new InetSocketAddress(this.ToIP,this.ToPort));
socket.send(packet);
if(data.equals("bye"))
break;
} catch (IOException e) {
e.printStackTrace();
}
}
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
socket.close();
}
}
public class TalkReceive implements Runnable{ //接收執行緒
DatagramSocket socket = null;
private int FromPort;
private String Person;
public TalkReceive(int fromPort,String person) {
FromPort = fromPort;
Person = person;
}
@Override
public void run() {
try {
socket = new DatagramSocket(this.FromPort);//打開接收埠
} catch (SocketException e) {
e.printStackTrace();
}
while(true){
byte[] container = new byte[1024]; //用來裝資料報內容
DatagramPacket packet = new DatagramPacket(container,0,container.length);
try {
socket.receive(packet);
} catch (IOException e) {
e.printStackTrace();
}
byte[]data = https://www.cnblogs.com/gaoyuan206/archive/2021/04/17/packet.getData();
String datas = new String(data,0,data.length);
System.out.println(Person+":"+datas);
if(datas.trim().equals("bye"))
break;
}
socket.close();
}
}
然后需要設定兩個端進行聊天:
new Thread(new TalkSend("localhost",8080)).start();
new Thread(new TalkReceive(6000,"老師")).start();
這里設定為學生端,然后開啟兩個執行緒,設定發送埠和接收埠
new Thread(new TalkSend("localhost",6000)).start();
new Thread(new TalkReceive(8080,"學生")).start();
這里設定為老師端,然后開啟執行緒,設定埠,其實打開了4個埠,因為學生端和老師端發送執行緒中,DatagramSocket默認系結的還有兩個埠,


4. URL
https://www.baidu.com/
統一資源定位符:定位互聯網上的某個資源
DNS域名決議: www.baidu.com ——》xxx.xxx.xxx.xxx
協議://ip地址:埠/專案名/資源
URL類的基本使用
URL url = new URL("http://localhost:8080/helloworld/index.jsp?username=yuan&password=123");
System.out.println(url.getProtocol());//得到協議名
System.out.println(url.getHost());//主機
System.out.println(url.getPort());//埠
System.out.println(url.getPath());//檔案
System.out.println(url.getFile());//檔案全路徑
System.out.println(url.getQuery()); //得到url查詢的部分(引數)
Java萬物皆物件
下載一個URL資源
public class UrlDown {
public static void main(String[] args) throws IOException {
URL url = new URL("http://localhost:8080/gaoyuan/SecurityFile.txt");
//連接到這個資源
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(); //需要轉換型別,因為回傳的是URPConnection
InputStream is = urlConnection.getInputStream();
FileOutputStream fos = new FileOutputStream("SecurityFile.txt");
byte[] buffer = new byte[1024];
int len;
while((len = is.read(buffer))!=-1){
fos.write(buffer,0,len);
}
fos.close();
is.close();
urlConnection.disconnect();//斷開連接
}
}
這是打開Tomcat服務器后,把一個檔案添加到相應目錄之后下載的,
網路編程基礎部分結束,例如:計算機網路,BIO等知識將會在后續博客中發布,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/277296.html
標籤:其他
上一篇:python基礎(補充):python三大器之裝飾器
下一篇:第四課-Java方法
