目錄
- 1、網路程式設計的基本概念
- 2、IP地址及埠號
- 3、通訊協議
- 4、TCP通訊程式碼實踐
- 4.1訊息傳遞
- 4.2檔案上傳
- 5、UDP通訊程式碼實踐
- 5.1 UDP實現訊息傳送
- 5.2 使用UDP迴圈傳送和接收訊息
- 5.3使用UDP實現聊天
1、網路程式設計的基本概念
引例
在學習網路程式設計之前,我們先看這樣一個例子。一般處於我們這個年齡段的同學,大都經歷過寫信/寄信的經歷,在寫信的時候,我們一般都需要明確傳送的地址、聯絡人資訊以及所在地區的郵編,郵遞員可以根據這上面的資訊找到接收信件的人,接收人在閱讀過信件的內容後可以以同樣的方式回信,這樣就使得身處異地的朋友間可以進行通訊。
在這種情況下,身處異地的朋友如果要進行通訊,必須同時滿足以下條件
1.接收人的所在地區及詳細的住址
2.需要有快遞員接收併傳送
慢慢的,隨著科技的發展,我們有手機通訊、QQ聊天和微信聊天等多種通訊方式,採用這些方式我們可以更便捷的進行通訊,但是無論是那種方式,通訊過程中總是需要這樣兩個元素:地址及傳輸過程。
在網路通訊中,這兩個元素可以使用更專業的名詞來代替:
1、IP地址和埠號
IP地址可以幫助我們找到通訊的接收方,埠號則可以幫助我們找到計算機中的具體應用,如QQ、微信等
2、傳輸協議
協議是為了幫助我們更好的進行傳輸,如TCP、UDP、FTP、SMTP和HTTP協議等
2、IP地址及埠號
IP地址是網際網路協議地址,它是一種統一的地址格式,網際網路中的每一臺主機都會有一個邏輯地址。在計算機網路中,localhost(意為“本地主機”,指“這臺計算機”)是給回的一個標準主機名,相對應的IP地址為127.0.0.1
通過IP地址,我們就可以連線到指定的計算機了,但是如果要訪問目標計算機中的某個應用程式,還需要知道埠號。在計算中,不同的應用程式就是通過埠號來進行區分的。
在網路程式設計中,可以使用InetAddress進行主機名解析和反向解析,即給定確定的主機名,返回確定的IP地址;給定IP地址,返回主機名
localhost解析為127.0.0.1
127.0.0.1反向解析為localhost
NOTE:不同協議的埠是可以重複的,如UDP和TCP
埠的查詢操作
netstat -ano #檢視所有的埠
netstat -ano | findstr "XXX" # 檢視指定的埠
3、通訊協議
IP地址及埠號解決了通訊過程中的地址問題,但是在計算機中,我們還要解決如何通訊問題,所謂通訊就是計算機間如何交流,而通訊協議就是將計算機雙方遵循的一種規則和約定(如同普通話、英語),它可以通過通訊通道將處於不同地理位置的裝置連線起來,能夠實現資訊的交換和資源共享。
在計算機網路中,常用的協議就是TCP/IP,它是協議簇,由多個子協議組成了,如我們常見的TCP、IP、UDP、ARP等,我們主要講解網路程式設計中常用的TCP、UDP和IP
- TCP
TCP協議是一種傳輸協議,面向連線、可靠的、基於位元組流的傳輸層通訊協議
- UDP
UDP是一種無連線的傳輸協議,無需建立連線就可以傳送資料包
- IP
IP協議整個TCP/IP協議族的核心,對上可載送傳輸層各種協議的資訊,例如TCP、UDP等;對下可將IP資訊包放到鏈路層,通過乙太網等各種技術來傳送。
TCP和UDP對比
TCP可以類比於打電話,它具有以下特點
- 在資料傳輸前,需要建立連線(三次握手),所以連線穩定可靠
- 有客戶端、服務端的概念,客戶端傳送,服務端接收
- 傳輸完成後,會釋放連線(四次揮手)
UDP可以類比於發簡訊,它具有以下特點
- 資料傳輸前,不需要建立連線,所以不可靠,不穩定
- 客戶端和服務端沒有明確界限,客戶端和服務端都可以進行收/發
- 不需要建立連線,所以速度較快
4、TCP通訊程式碼實踐
TCP網路程式設計需要以下Java類
InetAddress:表示IP協議的地址,可以解析IP地址和主機名
Socket:實現客戶端的套接字,建立連線,套接字就是兩臺機器間通訊的端點
ServerSocket:實現服務端的套接字
4.1訊息傳遞
客戶端
1.拿到服務端的地址及埠,InetAddress
2.建立Socket連線
3.傳送訊息
public class TcpClient {
public static void main(String[] args) throws IOException {
Socket socket=null;
OutputStream os=null;
try {
//1、得到服務端的地址,埠號
InetAddress inetAddress=InetAddress.getByName("127.0.0.1");
int port=9898;
//2、建立Socket連線
socket=new Socket(inetAddress,port);
//3、傳送訊息
os=socket.getOutputStream();
os.write("hello,Simon".getBytes());
}catch (Exception e){
e.printStackTrace();
}finally {
//關閉資源
os.close();
socket.close();
}
}
}
服務端
1.用ServerSocket設定自己的埠號
2.等待連線,Socket
2.1:可以等待一次連線,接收到訊息後關閉
2.2:等待多次連線,用while(true)把等待連線的方法包裹起來,重複接收訊息
3、獲取傳送端的訊息
public class TcpServer {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket=null;
Socket socket=null;
InputStream is=null;
ByteArrayOutputStream baos=null;
try {
//1、設定自己的埠號
serverSocket=new ServerSocket(9898);
//2、等待客戶端的連線
socket=serverSocket.accept();
//3、讀取客戶端的訊息
is=socket.getInputStream();
baos=new ByteArrayOutputStream();
byte[] buffer=new byte[1024];
int len;
while ((len=is.read(buffer))!=-1){
baos.write(buffer,0,len);
}
System.out.print(baos.toString());
}catch (Exception e){
e.printStackTrace();
}finally {
baos.close();
is.close();
socket.close();
serverSocket.close();
}
}
}
4.2檔案上傳
客戶端
1.建立一個連線
2.建立一個位元組輸出流用於通訊
建立一個檔案輸入流用於接收檔案,然後將接收後的檔案給位元組輸出流用於通訊
3.輸出結束後,呼叫shutdownInput方法通知服務端已經傳送完畢
4.關閉連線資源
public class TcpFileClient {
public static void main(String[] args) throws Exception {
//1、建立連線
Socket socket = new Socket(InetAddress.getByName("127.0.0.1"), 9987);
//2、定義一個輸出流用於通訊
OutputStream os = socket.getOutputStream();
FileInputStream fis = new FileInputStream(new File("simon.png"));
byte[] buffer = new byte[1024];
int len;
while ((len = fis.read(buffer)) != -1) {
os.write(buffer, 0, len);
}
//3、通知伺服器,我已經結束了
socket.shutdownOutput();
//確定伺服器接收完畢,才可以斷開連線,這一段程式碼主要是接收服務端發出的結束資訊
InputStream inputStream= socket.getInputStream();
byte[] buffer2=new byte[1024];
ByteArrayOutputStream baos=new ByteArrayOutputStream();
int len2;
while ((len2=inputStream.read(buffer2))!=-1){
baos.write(buffer2,0,len2);
}
System.out.print(baos.toString());
//關閉連線
baos.close();
inputStream.close();
os.close();
fis.close();
socket.close();
}
}
服務端
1.建立一個連線用於等待客戶端的接入
2.建立一個輸入流
建立一個檔案輸出流,將輸入流輸出
3.關閉資源
public class TcpFileServerDemo02 {
public static void main(String[] args) throws Exception {
//設定埠號
ServerSocket serverSocket=new ServerSocket(9987);
//等待連線
Socket socket=serverSocket.accept();
//獲取輸入流
InputStream is=socket.getInputStream();
//檔案輸出
FileOutputStream fos=new FileOutputStream(new File("snow.png"));
byte[] buffer=new byte[1024];
int len;
while ((len=is.read(buffer))!=-1){
fos.write(buffer,0,len);
}
//通知客戶端接收完畢
OutputStream os=socket.getOutputStream();
os.write("我已經接收結束,你可以斷開了".getBytes());
//關閉資源
fos.close();
is.close();
socket.close();
serverSocket.close();
}
}
5、UDP通訊程式碼實踐
UDP不需要連線,但是需要知道對方的地址和埠號,主要用到了以下Java類
DatagramPacket:表示資料包,用於實現無連線分組傳送服務
DatagramSocket:表示用於傳送和接收資料包的套接字
UDP是不存在客戶端和服務端的概念,但是為了程式設計模擬方便,我們假定存在客戶端和服務端
5.1 UDP實現訊息傳送
客戶端
- 建立連線
- 建立資料包
- 傳送資料包
public class UdpClientDemo1 {
public static void main(String[] args) throws Exception {
//1、建立連線(這個埠號是客戶端的)
DatagramSocket datagramSocket=new DatagramSocket(9888);
//2、建立資料包
String msg="hello,Simon";
InetAddress inetAddress=InetAddress.getByName("127.0.0.1");
//資料,資料的起始位置,要傳送的地址與埠號
DatagramPacket datagramPacket=new DatagramPacket(msg.getBytes(),0,msg.getBytes().length,inetAddress,9887);
//3、傳送資料包
datagramSocket.send(datagramPacket);
//4、關閉資料流
datagramSocket.close();
}
}
服務端
- 建立連線
- 接收資料
public class UdpServerDemo2 {
public static void main(String[] args) throws Exception{
//建立連線,開放的埠
DatagramSocket datagramSocket=new DatagramSocket(9887);
//接收資料包
byte[] buffer=new byte[1024];
DatagramPacket datagramPacket=new DatagramPacket(buffer,0,buffer.length);
datagramSocket.receive(datagramPacket);
//列印資料包資訊
System.out.println(datagramPacket.getAddress());
System.out.println(datagramPacket.getPort());
System.out.println(new String(datagramPacket.getData()));
//關閉連線
datagramSocket.close();
}
5.2 使用UDP迴圈傳送和接收訊息
使用while(true)方法將客戶端中的傳送資料和接收資料包裹起來,只要當他們滿足一定的條件時(如輸入的字串為"bye"),退出即可。這樣就可以達到迴圈傳送和接收訊息。
客戶端
public class UdpSend {
public static void main(String[] args) throws Exception {
//1、建立連線
DatagramSocket datagramSocket=new DatagramSocket(9888);
//2、建立資料包,從鍵盤輸入
InetAddress inetAddress=InetAddress.getByName("127.0.0.1");
int port=9887;
BufferedReader bufferedReader=new BufferedReader(new InputStreamReader(System.in));
while (true){
String s=bufferedReader.readLine();
DatagramPacket datagramPacket=new DatagramPacket(s.getBytes(),0,s.getBytes().length,inetAddress,9887);
//3、傳送資料
datagramSocket.send(datagramPacket);
if(s.equals("bye")){
break;
}
}
//4、關閉資料
datagramSocket.close();
}
}
服務端
public class UdpReceive{
public static void main(String[] args) throws Exception {
//1、建立連線
DatagramSocket datagramSocket=new DatagramSocket(9887);
while (true){
//2、接收資料包
byte[] buffer=new byte[1024];
DatagramPacket datagramPacket=new DatagramPacket(buffer,0,buffer.length);
datagramSocket.receive(datagramPacket);
//3、斷開連線
byte[] data=datagramPacket.getData();
String receiveData=new String(data,0,data.length);
System.out.println(receiveData);
if(receiveData.equals("bye")){
break;
}
}
datagramSocket.close();
}
}
5.3使用UDP實現聊天
使用UDP實現聊天,則客戶端和服務端(其實不存在客戶端和服務端的概念)既需要接收資訊也需要傳送訊息,這就需要多執行緒的支援了。
我們首先構造兩個接收類和傳送的執行緒類,然後構造倆個使用者類進行通訊。
傳送類
public class TalkSend implements Runnable {
DatagramSocket datagramSocket = null;
BufferedReader bufferedReader = null;
private int fromPort;
private int toPort;
private String toIp;
public TalkSend(int fromPort,int toPort,String toIp){
this.fromPort=fromPort;
this.toPort=toPort;
this.toIp=toIp;
//1、建立連線
try {
datagramSocket = new DatagramSocket(fromPort);
//2、建立資料包,從鍵盤輸入
bufferedReader = new BufferedReader(new InputStreamReader(System.in));
} catch (SocketException e) {
e.printStackTrace();
}
}
@Override
public void run() {
while (true) {
try {
String s = bufferedReader.readLine();
DatagramPacket datagramPacket = new DatagramPacket(s.getBytes(), 0, s.getBytes().length, new InetSocketAddress(toIp,toPort));
//3、傳送資料
datagramSocket.send(datagramPacket);
if (s.equals("bye")) {
break;
}
} catch (IOException e) {
e.printStackTrace();
}
}
//4、關閉資料
datagramSocket.close();
}
}
接收類
public class TalkReceive implements Runnable{
DatagramSocket datagramSocket=null;
private int port;
private String msgFrom;
public TalkReceive(int port,String msgFrom) {
this.port=port;
this.msgFrom=msgFrom;
//1、建立連線
try {
datagramSocket=new DatagramSocket(port);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void run() {
while (true){
try {
//2、接收資料包
byte[] buffer=new byte[1024];
DatagramPacket datagramPacket=new DatagramPacket(buffer,0,buffer.length);
datagramSocket.receive(datagramPacket);
//3、斷開連線
byte[] data=datagramPacket.getData();
String receiveData=new String(data,0,data.length);
System.out.println(msgFrom+": "+receiveData);
if(receiveData.equals("bye")){
break;
}
} catch (Exception e) {
e.printStackTrace();
}
}
datagramSocket.close();
}
}
使用者類1
public class TalkStudent {
public static void main(String[] args) {
new Thread(new TalkSend(7777,9999,"localhost")).start();
new Thread(new TalkReceive(8888,"小包")).start();
}
}
使用者類2
public class TalkTeacher {
public static void main(String[] args){
new Thread(new TalkSend(5555,8888,"localhost")).start();
new Thread(new TalkReceive(9999,"小郎")).start();
}
}
覺得此文寫的還不錯的朋友可以轉發+關注下小編哦,持續分享技術系列文章中!
看完三件事❤️
如果你覺得這篇內容對你還蠻有幫助,我想邀請你幫我三個小忙:
-
點贊,轉發,有你們的 『點贊和評論』,才是我創造的動力。
-
關注公眾號 『 Java鬥帝 』,不定期分享原創知識。
-
同時可以期待後續文章ing?