前言必讀
讀者手冊(必讀)_雲邊的快樂貓的部落格-CSDN部落格
一、1網絡通信的基本模式分為兩種
1.CS模式(Client---用戶端,Server---服務端)
用戶端是需要程式員去開發的,例如日常使用的各種的APP,服務端就是伺服器。
例子:端遊,依賴特定的PC端才能玩。
2.BS模式(Browser---浏覽器,Server---服務端)---重點學習
服務端是需要程式員去開發的。浏覽器就是我們日常使用的浏覽器去通路各種網站,隻要有浏覽器就可以通路,不依賴特定的PC端,在任意裝置上都可以通路網站伺服器 。
例子:網頁小遊戲,聯網浏覽器就可以玩。
一、2 請分析C/S和B/S架構相比的各自的優缺點?
1.CS(用戶端--伺服器結構)
優點 :技術成熟,互動性強,網絡通信量低,響應資料快。将任務分到了兩端,降低了系統的開銷。用戶端要處理大多數的業務邏輯和UI展示。他是胖用戶端。
缺點:更新太快,要求使用者有相同的作業系統,如果有不同的作業系統還要開發不同的版本,對于計算機電腦的配置要求也高
2。BS(浏覽器-伺服器結構)
優點:主要事務在服務端實作。分布性強、維護友善、開發簡單
共享性強、總體成本低,對用戶端的電腦組態要求較低
缺點:資料安全性問題,對伺服器要求高。資料傳輸速度較慢,軟體的個性化明顯降低,難以實作傳統模式下的特殊功能要求,他是瘦用戶端,大量的資料的傳輸都要通過伺服器與浏覽器進行互動,通信開銷大,難以實作複雜的應用構造
二、實作網絡程式設計的三種要素
IP位址可以了解為具體哪個計算機,端口了解計算機上的程式(一個程式一個端口),協定了解為電腦通過什麼方式和外界互動
要素一:IP位址:裝置在網絡中的位址,是唯一的辨別
詳解:
(1)常見的IP分類為:
IPv4(32比特4位元組)和IPv6(128位16個位元組)--稱号--可以标記地球上的每一粒沙子。
(2)IP位址的取經之路:
計算機:我要去找百度擷取資料。
DNS伺服器:發過來我看看哪個網址域名啊,給你指路具體的ip位址
計算機:知道了這個ip位址,我就可以去找具體要通路的伺服器了
伺服器:計算機老弟你來找我了啊,那我把你要的資料發給你吧。
(3)公網位址和私有位址(區域網路使用)
192.168開頭的就是常見的私有位址
(4)擷取IP位址的代碼
要去實作這個IP位址的擷取就要用到 InetAddress方法
代碼例子:
package bao;
import java.net.InetAddress;
public class Test {
public static void main(String[] args) throws Exception {
//1.擷取本機位址ip對象
InetAddress ip1 = InetAddress.getLocalHost();
System.out.println(ip1.getHostName());//擷取主機名字
System.out.println(ip1.getHostAddress());//擷取ip位址
//2.擷取域名ip對象
InetAddress ip2 = InetAddress.getByName("www.baidu.com");
System.out.println(ip2.getHostName());//擷取域名
System.out.println(ip2.getHostAddress());//擷取域名的ip位址
//3.擷取公網對象
InetAddress ip3 = InetAddress.getByName("112.80.248.76");
System.out.println(ip3.getHostName());//擷取公網名字
System.out.println(ip3.getHostAddress());//擷取公網ip位址
//判斷網絡是否能連接配接通信 ping 5s之前測試是否能通過
System.out.println(ip3.isReachable(5000));//通過會傳回true
}
}
運作結果:
涉及到個人隐私就不放到這裡來了,可以自己運作試試看
=========================================================================
要素二:端口:應用程式在裝置中的唯一辨別
一個主機裝置中,端口号是唯一的
(1)端口号:一個程式一個端口号,被規定為16位的二進制,範圍是0~65535
(2)周知端口:0~1023,被預先定義的知名應用占用。(例如:HTTP占用80端口,FTP占用21端口)
(3)注冊端口:1024~49151,配置設定給使用者程序或某些程式(例如:Tomcat占用8080端口)
(4)動态端口:49152~65535,不固定配置設定到某種程序,動态配置設定
=========================================================================
要素三:協定:資料在網絡中的傳輸協定,最常見的有UDP和TCP(重點)
(1)TCP協定: (安全,有連接配接确認可靠)
使用TCP協定,雙方必須先建立連接配接,它是一種面向連接配接的可靠通信協定,傳輸前,要建立三次握手方式建立連接配接确認。連接配接和發送資料都需要确認。傳輸完成後,還需要釋放已連接配接的通信,通信效率相對比較低。
使用場景:對安全需求較高的檔案下載下傳、金融資料通信等。
三次握手連接配接:
用戶端:在嗎?
服務端:在
用戶端:上号
四次握手斷開連接配接:
用戶端:不玩了
服務端:好吧
服務端:下次什麼時候玩?
用戶端:有空再玩
例子:
一收一發
發送端(用戶端)
package bao;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
//發送端
public class Test {
public static void main(String[] args) throws IOException {
System.out.println("==============用戶端啟動===============");
//1.建立發送通信管道
Socket socket = new Socket("127.0.0.1",7777);//參數一:服務端位址 參數二:服務端端口
//2.從scoket管道中獲得一個位元組輸出流,負責發送資料
OutputStream os = socket.getOutputStream();
//3.位元組流更新成列印流
PrintStream ps = new PrintStream(os);
//4.發送消息
ps.println("大哥,我來了");
ps.flush();//重新整理
}
}
接收端 (服務端)
package bao;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
//服務端
public class Test1 {
public static void main(String[] args) throws IOException {
System.out.println("========服務端啟動============");
//1.建立接收管道,注冊端口
ServerSocket serverSocket = new ServerSocket(7777);//參數一:定義服務端口
//2.等待管道連接配接
Socket accept = serverSocket.accept();
//3.從管道中擷取一個位元組輸入流
InputStream is = accept.getInputStream();
//4.位元組流更新生緩沖輸入流
BufferedReader br = new BufferedReader(new InputStreamReader(is));
//5.按照行讀取消息會更好
String a;
if ((a = br.readLine())!=null){
System.out.println(accept.getRemoteSocketAddress()+"說了:"+a);
}
}
}
運作結果:
/127.0.0.1:60316說了:大哥,我來了
==============用戶端啟動===============
多收多發
發送端(用戶端)
package bao;
import java.io.*;
import java.net.InetAddress;
import java.net.Socket;
import java.util.Scanner;
public class kehu {
public static void main(String[] args) throws Exception {
System.out.println("用戶端啟動");
//1.建立與服務端連接配接的管道
Socket s = new Socket(InetAddress.getLocalHost(), 9966);
//2.建立一個線程負責用戶端的消息讀取
new ClientReaderThread(s).start();
//3.建立一個位元組輸入流管道
OutputStream o = s.getOutputStream();
PrintStream p = new PrintStream(o);//更新流
//4.用戶端輸入資料
Scanner sc = new Scanner(System.in);
while (true){
System.out.println("請輸入:");
String s1 = sc.nextLine();
p.println(s1);//發送資料出去
p.flush();//重新整理流
}
}
}
class ClientReaderThread extends Thread{
private Socket socket;
public ClientReaderThread(Socket socket){
this.socket = socket;
}
@Override
public void run(){
try {
//把位元組輸入流包裝成字元輸入流
InputStream i = socket.getInputStream();
BufferedReader b = new BufferedReader(new InputStreamReader(i));
String s1;
while (true){
if ((s1=b.readLine())!=null){
System.out.println("收到了消息"+s1);
}
}
} catch (IOException e) {
System.out.println("伺服器把你提出群聊");
}
}
}
接收端 (服務端)
package bao;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
public class fuwu {
//1.定義一個靜态變量儲存全部管道
public static List<Socket> all_Sockets = new ArrayList<>();
public static void main(String[] args) throws IOException {
System.out.println("服務端啟動成功");
//2.服務端口注冊
ServerSocket ss = new ServerSocket(9966);
//3.管道死循環設定
while (true){
Socket s = ss.accept();
System.out.println(s.getRemoteSocketAddress()+"上線了");
all_Sockets.add(s);
new fuwuThread(s).start();
}
}
}
class fuwuThread extends Thread{
private Socket socket;
public fuwuThread(Socket socket){
this.socket=socket;
}
@Override
public void run(){
try {
InputStream i = socket.getInputStream();
BufferedReader b = new BufferedReader(new InputStreamReader(i));
String s1;
while ((s1=b.readLine())!=null){
System.out.println(socket.getRemoteSocketAddress()+"說"+s1);
sendMessage(s1);
}
} catch (IOException e) {
System.out.println(socket.getRemoteSocketAddress()+"離線了");
fuwu.all_Sockets.remove(socket);
}
}
private void sendMessage (String s1) throws IOException {
for (Socket s:fuwu.all_Sockets){
OutputStream o = s.getOutputStream();
PrintStream p = new PrintStream(o);
p.println(s1);
p.flush();
}
}
}
運作結果:
服務端啟動成功
/192.168.78.1:56384上線了
/192.168.78.1:56384說大哥
/192.168.78.1:56389上線了
/192.168.78.1:56389說小弟
/192.168.78.1:56384說
/192.168.78.1:56384說大哥11
用戶端啟動
請輸入:
大哥
請輸入:
收到了消息大哥
收到了消息小弟
大哥11
請輸入:
請輸入:
收到了消息
收到了消息大哥11
用戶端啟動
請輸入:
小弟
請輸入:
收到了消息小弟
收到了消息
收到了消息大哥11
=========================================================================
TCP和UDP的分界線
=========================================================================
(2)UDP協定:(速度快,無連接配接,不可靠)
不需要建立連接配接(因為把資料源IP、目的地IP、端口封裝成資料包),每個資料包在64KB内,隻管發,不管對方有沒有接到确認什麼的。
優點:可以廣播發送,發送資料結束時無需釋放資源,開銷小,速度快。
使用場景:語言通話、視訊通話等。
ps:這個就是一股腦的什麼都封裝一起,直接往外抛就什麼都不管了,當然快了。
例子:
一收一發
發送端(用戶端)
package bao;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class Test {
public static void main(String[] args) throws Exception {
//一、發送端(測試時候先啟動接收再發送端)
//1.建立發送端對象,發送端自帶預設端口号(人)
System.out.println("========用戶端啟動============");
DatagramSocket socket1 = new DatagramSocket();//不定義就預設端口
//2.建立一個要發送的資料容器(容器裡面有資料)
byte[] a ="我是水".getBytes();
//3.建立一個資料包對象把容器裝起來
DatagramPacket packet1 = new DatagramPacket(a,a.length, InetAddress.getLocalHost(),8899);//資料,大小,服務端的IP,服務端的端口
//4.發送出去
socket1.send(packet1);
//5.關閉資源,避免資源浪費
socket1.close();
}
}
接收端 (服務端)
package bao;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class Test1 {
public static void main(String[] args) throws Exception {
//二、接收端(測試時候先啟動接收再發送端)
//1.建立接收端對象,注冊端口(人)
System.out.println("=========接收端啟動===============");
DatagramSocket socket2 = new DatagramSocket(8899);
//2.建立一個要接收的資料容器(等待接收資料)
byte[]b =new byte[1024*64];
//3.把容器資料打包
DatagramPacket packet2 = new DatagramPacket(b,b.length);
//4.等待接收資料
socket2.receive(packet2);
//5.讀取多少倒出多少
int len = packet2.getLength();
String rs = new String(b,0,len);
System.out.println("接收到了資料了"+rs);
//6.關閉資源,避免資源浪費
socket2.close();
}
}
//額外知識點,擷取對方端口和ip位址
//String ip = packet2.getAddress().toString();
//System.out.println("對方IP位址為"+ip);
//int port = packet2.getPort();
//System.out.println("對方端口位置"+port);
運作結果:
========用戶端啟動============
=========接收端啟動===============
接收到了資料了我是水
多收多發
思想:把一收一發代碼拿來改進就好了
發送端:把主要發送的代碼寫入死循環并寫一個鍵盤輸入代碼,隻有使用者輸入exit才能退出循環。
接收端:把等待接收的封裝包開始寫入死循環裡面,然後再把釋放資源的代碼注釋掉才能一直接收
發送端(用戶端)
package bao;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Scanner;
//發送端
public class Test {
public static void main(String[] args) throws Exception {
//一、1.建立發送端對象,發送端自帶預設端口号(人)
DatagramSocket socket1 = new DatagramSocket();//不定義就預設端口
System.out.println("========用戶端啟動============");
//二.6建立鍵盤錄入
Scanner sc = new Scanner(System.in);
while (true) {//二、5.死循環把代碼封起來(多收多發步驟)
//二、6.接收鍵盤錄入
System.out.println("請輸入:");
String msg = sc.nextLine();
//二、7.設定exit退出
if ("exit".equals(msg)){
System.out.println("離線成功");
socket1.close();//釋放資源
break;
}
//2.建立一個要發送的資料容器(容器裡面有資料)
byte[] a =msg.getBytes();
//3.建立一個資料包對象把容器裝起來
DatagramPacket packet1 = new DatagramPacket(a,a.length, InetAddress.getLocalHost(),8899);//資料,大小,服務端的IP,服務端的端口
//4.發送出去
socket1.send(packet1);
}
}
}
接收端 (服務端)
package bao;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
//二、接收端
public class Test1 {
public static void main(String[] args) throws Exception {
//1.建立接收端對象,注冊端口(人)
System.out.println("=========接收端啟動===============");
DatagramSocket socket2 = new DatagramSocket(8899);
//2.建立一個要接收的資料容器(等待接收資料)
byte[]b =new byte[1024*64];
//3.把容器資料打包
DatagramPacket packet2 = new DatagramPacket(b,b.length);
while (true) {//二、6.把封裝代碼寫入死循環并删掉釋放資源的代碼(多收多發步驟)
//4.等待接收資料
socket2.receive(packet2);
//5.讀取多少倒出多少
int len = packet2.getLength();
String rs = new String(b,0,len);
System.out.println("接收到了來自:"+packet2.getAddress()+"對方端口是:"+packet2.getPort()+rs);
}
}
}
//額外知識點,擷取對方端口和ip位址
//String ip = packet2.getAddress().toString();
//System.out.println("對方IP位址為"+ip);
//int port = packet2.getPort();
//System.out.println("對方端口位置"+port);
運作結果:
========用戶端啟動============
請輸入:
你在嗎
請輸入:
在幹嘛
請輸入:
=========接收端啟動===============
接收到了來自:(隐私不展示)對方端口是:(隐私不展示)你在嗎
接收到了來自:(隐私不展示)對方端口是:(隐私不展示)在幹嘛
多開用戶端 步驟
一台主機中可以建立很多個用戶端給接收端發消息
1.在用戶端的頁面右上角點選
2.找到Allow multiple instances勾選完成
廣播(目前主機和所在網絡中的所有主機通信)
步驟:(前提在同一網段内)
發送端的參數位址指定為255.255.255.255,并且指定端口:例如6666
接收端的參數端口隻要比對成功就可以了,例如:6666
例子:
發送端
//修改前
//3.建立一個資料包對象把容器裝起來
DatagramPacket packet1 = new DatagramPacket(a,a.length, InetAddress.getLocalHost(),8899);//資料,大小,服務端的IP,服務端的端口
//修改後
//3.建立一個資料包對象把容器裝起來
DatagramPacket packet1 = new DatagramPacket(a,a.length, InetAddress.getByName("255.255.255.255"),6666);//資料,大小,服務端的IP,服務端的端口
接收端
//修改前
//1.建立接收端對象,注冊端口(人)
System.out.println("=========接收端啟動===============");
DatagramSocket socket2 = new DatagramSocket(8899);
//修改後
//1.建立接收端對象,注冊端口(人)
System.out.println("=========接收端啟動===============");
DatagramSocket socket2 = new DatagramSocket(6666);
多點傳播 (目前主機和所在網絡中的一組主機通信)
發送端和接收端都要用MulticastSocket綁定同一個IP位址和綁定同一個端口
範圍:224.0.0.0~~~~239.255.255.255
發送端
//修改前
//3.建立一個資料包對象把容器裝起來
DatagramPacket packet1 = new DatagramPacket(a,a.length, InetAddress.getByName("255.255.255.255"),6666);//資料,大小,服務端的IP,服務端的端口
//修改後
//3.建立一個資料包對象把容器裝起來
DatagramPacket packet1 = new DatagramPacket(a,a.length, InetAddress.getByName("224.0.1.1"),6666);//資料,大小,服務端的IP,服務端的端口
接收端
//修改前
//1.建立接收端對象,注冊端口(人)
System.out.println("=========接收端啟動===============");
DatagramSocket socket2 = new DatagramSocket(6666);
//修改後
//1.建立接收端對象,注冊端口(人)
System.out.println("=========接收端啟動===============");
MulticastSocket socket2 = new MulticastSocket(6666);
//綁定多點傳播IP(JDK開始過時的API)
socket2.joinGroup(InetAddress.getByName("224.0.1.1"));