網(wǎng)絡(luò)程序設(shè)計基礎(chǔ)
局域網(wǎng)與互聯(lián)網(wǎng)
為了實現(xiàn)兩臺計算機的通信,必須用一個網(wǎng)絡(luò)線路連接兩臺計算機。如下圖所示
?網(wǎng)絡(luò)協(xié)議
1.IP協(xié)議
IP是Internet Protocol的簡稱,是一種網(wǎng)絡(luò)協(xié)議。Internet 網(wǎng)絡(luò)采用的協(xié)議是TCP/IP協(xié)議,其全稱是Transmission Control Protocol/Internet Protocol。Internet 依靠TCP/IP協(xié)議,在全球范圍內(nèi)實現(xiàn)了不同硬件結(jié)構(gòu)、不同操作系統(tǒng)、不同網(wǎng)絡(luò)系統(tǒng)間的互聯(lián)。在Internet 網(wǎng)絡(luò)上存在著數(shù)以億計的主機,每臺主機都用網(wǎng)絡(luò)為其分配的 Internet 地址代表自己,這個地址就是I地址。到目前為止,I地址用4個字節(jié),也就是32位的二進制數(shù)來表示,稱為IPv4。為了便于使用,通常取用每個字節(jié)的十進制數(shù),并且每個字節(jié)之間用圓點隔開來表示I地址,如192.168.1.1。現(xiàn)在人們正在試驗使用16個字節(jié)來表示I地址,這就是IPv6,但IPv6還沒有投入使用。
TCP/IP 模式是一種層次結(jié)構(gòu),共分為4層,分別為應(yīng)用層、傳輸層、互聯(lián)網(wǎng)層和網(wǎng)絡(luò)層。各層實現(xiàn)特定的功能,提供特定的服務(wù)和訪問接口,并具有相對的獨立性,如圖所示。
?文章來源地址http://www.zghlxwxcb.cn/news/detail-754987.html
2. TCP與UDP協(xié)議
在TCPAIP 協(xié)議棧中,有兩個高級協(xié)議是網(wǎng)絡(luò)應(yīng)用程序編寫者應(yīng)該了解的,即傳輸控制協(xié)議(Transmission Control Protocol,TCP)與用戶數(shù)據(jù)報協(xié)議(User Datagram Protocol, UDP)。
TCP 協(xié)議是一種以固接連線為基礎(chǔ)的協(xié)議,它提供兩臺計算機間可靠的數(shù)據(jù)傳送。TCP可以保證數(shù)據(jù)從一端送至連接的另一端時,能夠確實送達,而且抵達的數(shù)據(jù)的排列順序和送出時的順序相同。因此,TCP協(xié)議適合可靠性要求比較高的場合。就像撥打電話,必須先撥號給對方,等兩端確定連接后,相互才能聽到對方說話,也知道對方回應(yīng)的是什么。
HTTP、FTP 和Telnet 等都需要使用可靠的通信頻道。例如,HTTP從某個URL讀取數(shù)據(jù)時,如果收到的數(shù)據(jù)順序與發(fā)送時不相同,可能就會出現(xiàn)一個混亂的HTML文件或是一些無效的信息。
UDP是無連接通信協(xié)議,不保證數(shù)據(jù)的可靠傳輸,但能夠向若干個目標發(fā)送數(shù)據(jù),或接收來自若干個源的數(shù)據(jù)。UDP以獨立發(fā)送數(shù)據(jù)包的方式進行。這種方式就像郵遞員送信給收信人,可以寄出很多信給同一個人,且每一封信都是相對獨立的,各封信送達的順序并不重要,收信人接收信件的順序也不能保證與寄出信件的順序相同。
UDP 協(xié)議適合于一些對數(shù)據(jù)準確性要求不高,但對傳輸速度和時效性要求非常高的網(wǎng)站,如網(wǎng)絡(luò)聊天室、在線影片等。這是由于TCP協(xié)議在認證上存在額外耗費,可能使傳輸速度減慢,而UDP協(xié)議即使有一小部分數(shù)據(jù)包遺失或傳送順序有所不同,也不會嚴重危害該項通信。
端口與套接字
一般而言,一臺計算機只有單一的連到網(wǎng)絡(luò)的物理連接(Physical Connection),所有的數(shù)據(jù)都通過此連接對內(nèi)、對外送達特定的計算機,這就是端口。網(wǎng)絡(luò)程序設(shè)計中的端口(port)并非真實的物理存在,而是一個假想的連接裝置。端口被規(guī)定為一個在0~65535的整數(shù)。HTTP服務(wù)一般使用80端口,FTP 服務(wù)使用21端口。假如一臺計算機提供了HTTP、FTP等多種服務(wù),那么客戶機會通過不同的端口來確定連接到服務(wù)器的哪項服務(wù)上,如圖所示。
?
通常,0~1023的端口數(shù)用于一些知名的網(wǎng)絡(luò)服務(wù)和應(yīng)用,用戶的普通網(wǎng)絡(luò)應(yīng)用程序應(yīng)該使用1024以上的端口數(shù),以避免端口號與另一個應(yīng)用或系統(tǒng)服務(wù)所用端口沖突。
網(wǎng)絡(luò)程序中的套接字(Socket)用于將應(yīng)用程序與端口連接起來。套接字是一個假想的連接裝置,就像插座一樣可連接電器與電線,如圖所示。
Java 將套接字抽象化為類,程序設(shè)計者只需創(chuàng)建Socket類對象,即可使用套接字。
TCP程序
TCP網(wǎng)絡(luò)程序設(shè)計是指利用Socket 類編寫通信程序。利用TCP協(xié)議進行通信的兩個應(yīng)用程序是有主次之分的,一個稱為服務(wù)器程序,另一個稱為客戶機程序,兩者的功能和編寫方法大不一樣。服務(wù)器端與客戶端的交
?
互過程如圖所示。
服務(wù)器程序創(chuàng)建一個 ServerSocket(服務(wù)器端套接字)對象,調(diào)用accept0方法等待客戶機來連接
客戶端程序創(chuàng)建一個Socket對象,請求與服務(wù)器建立連接
服務(wù)器接收客戶機的連接請求,同時創(chuàng)建一個新的Socket 對象與客戶建立連接。隨后服務(wù)器繼續(xù)等待新的請求
InetAddress 類
java.net包中的InetAddress類是與IP地址相關(guān)的類,利用該類可以獲取IP地址、主機地址等信息。
?
?
例題21.1
import java.net.*; //導出Java.net包
public class Address { //創(chuàng)建類
public static void main(String[] args) {
InetAddress ip; //創(chuàng)建InetAddress對象
try { //捕捉可能出現(xiàn)的異常
ip = InetAddress.getLocalHost(); //實例化對象
String locaIname = ip.getHostName(); //獲取本機名
String localip = ip.getHostAddress(); //獲取本機IP地址
System.out.println("本機名:"+locaIname); //將本機名輸出
System.out.println("本機名IP地址:"+localip); //將本機IP地址輸出
}catch(UnknownHostException e){
e.printStackTrace(); //輸出異常信息
}
}
}//例題21.1
?
ServerSocket 類
ServerSocker 類的構(gòu)造方法通常會拋出1OException異常,具體有以下幾種形式:
ServerSocket():創(chuàng)建非綁定服務(wù)器套接字。
ServerSocket(int port):創(chuàng)建綁定到特定端口的服務(wù)器套接字。
ServerSocket(int port, int backlog):利用指定的backlog創(chuàng)建服務(wù)器套接字,并將其綁定到指定的本地端口號上。
ServerSocket(int port, int backlog, InetAddress bindAddress):使用指定的端口、偵聽backlog和要綁定到的本地IP地址創(chuàng)建服務(wù)器。這種情況適用于計算機上有多塊網(wǎng)卡和多個I地址的情況,用戶可以明確規(guī)定ServerSocket在哪塊網(wǎng)卡或哪個IP地址上等待客戶的連接請求。
ServerSocket 類的常用方法如表21.2所示。
TCP網(wǎng)絡(luò)程序設(shè)計
例題21.2
import java.io.*;
import java.net.*;
public class MyServer {
private ServerSocket server; // 服務(wù)器套接字
private Socket socket; // 客戶端套接字
void start() {// 啟動服務(wù)器
try {
server = new ServerSocket(8998); // 服務(wù)器啟用8998端口
System.out.println("服務(wù)器套接字已經(jīng)創(chuàng)建成功");
while (true) {
System.out.println("等待客戶端的連接");
socket = server.accept(); // 服務(wù)器監(jiān)聽客戶端連接
// 根據(jù)套接字字節(jié)流創(chuàng)建字符輸入流
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
while (true) {// 循環(huán)接受信息
String message = reader.readLine();// 讀取一行文本
if ("exit".equals(message)) {// 如果客戶端發(fā)來的內(nèi)容為“exit”
System.out.println("客戶端退出");
break;// 停止接受信息
}
System.out.println("客戶端:" + message);
}
reader.close(); // 關(guān)閉流
socket.close(); // 關(guān)閉套接字
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
MyServer tcp = new MyServer();
tcp.start(); // 啟動服務(wù)器
}
}
//21.2
運行結(jié)果如下:
?
編寫客戶端程序,將用戶在文本框中輸入的信息發(fā)送至服務(wù)端,并將文本框中輸入的信息顯示再客戶端的文本域中
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.net.Socket;
import javax.swing.*;
public class MyClient extends JFrame {
private PrintWriter writer;// 根據(jù)套接字字節(jié)流創(chuàng)建的字符輸出流
Socket socket; // 客戶端套接字
private JTextArea area = new JTextArea();// 展示信息的文本域
private JTextField text = new JTextField(); // 發(fā)送信息的文本框
public MyClient() {
setTitle("向服務(wù)器送數(shù)據(jù)");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Container c = getContentPane(); // 主容器
JScrollPane scrollPane = new JScrollPane(area);// 滾動面板
getContentPane().add(scrollPane, BorderLayout.CENTER);
c.add(text, "South"); // 將文本框放在窗體的下部
text.addActionListener(new ActionListener() {// 文本框觸發(fā)回車事件
public void actionPerformed(ActionEvent e) {
writer.println(text.getText().trim()); // 將文本框中的信息寫入流
area.append(text.getText() + '\n'); // 將文本框中的信息顯示在文本域中
text.setText(""); // 將文本框清空
}
});
}
private void connect() { // 連接服務(wù)器方法
area.append("嘗試連接\n"); // 文本域中提示信息
try {
socket = new Socket("127.0.0.1", 8998); // 連接本地計算機的8998端口
writer = new PrintWriter(socket.getOutputStream(), true);
area.append("完成連接\n");
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
MyClient clien = new MyClient();
clien.setSize(200, 200); // 窗體大小
clien.setVisible(true); // 顯示窗體
clien.connect(); // 連接服務(wù)器
}
}
//21.2
?
UDP程序
基于 UDP通信的基本模式如下:
將數(shù)據(jù)打包(稱為數(shù)據(jù)包),然后將數(shù)據(jù)包發(fā)往目的地。
接收別人發(fā)來的數(shù)據(jù)包,然后查看數(shù)據(jù)包。
發(fā)送數(shù)據(jù)包的步驟如下:
(1)使用DatagramSocketO創(chuàng)建一個數(shù)據(jù)包套接字。
(2)使用DatagramPacket(byte[] buf,int offset, int length,InetAddress address,int port)創(chuàng)建要發(fā)送的數(shù)據(jù)包。
(3)使用DatagramSocket 類的sendO方法發(fā)送數(shù)據(jù)包。
接收數(shù)據(jù)包的步驟如下:
(1)使用DatagramSocket(int port)創(chuàng)建數(shù)據(jù)包套接字,綁定到指定的端口。
? (2)使用 DatagramPacket(byte buf, int length)創(chuàng)建字節(jié)數(shù)組來接收數(shù)據(jù)包。
(3)使用DatagramPacket 類的receive0方法接收UDP包。
DatagramPacket類
java.net 包的DatagramPacket 類用來表示數(shù)據(jù)包。DatagramPacket 類的構(gòu)造方法如下:
DatagramPacket(byte[] buf, int length)
DatagramPacket(bytel] buf, int length, InetAddress address, int port)
第一種構(gòu)造方法在創(chuàng)建DatagramPacket 對象時,指定了數(shù)據(jù)包的內(nèi)存空間和大小。
第二種構(gòu)造方法不僅指定了數(shù)據(jù)包的內(nèi)存空間和大小,還指定了數(shù)據(jù)包的目標地址和端口。
DatagramSocker 類
java.net 包中的()
DatagramSocket(int port)
DatagramSocket(int port, InetAddress addr)
第一種構(gòu)造方法創(chuàng)建DatagramSocket對象,構(gòu)造數(shù)據(jù)報套接字,并將其綁定到本地主機任何可用的端口上。
第二種構(gòu)造方法創(chuàng)建DatagramSocket對象,創(chuàng)建數(shù)據(jù)報套接字,并將其綁定到本地主機的指定端口上。
第三種構(gòu)造方法創(chuàng)建DatagramSocket對象,創(chuàng)建數(shù)據(jù)報套接字,并將其綁定到指定的端口和指定的本地地址上。第三種構(gòu)造函數(shù)適用于有多塊網(wǎng)卡和多個I地址的情況。
UDP網(wǎng)絡(luò)程序設(shè)計
例題21.3
(1)廣播主機程序不斷地向外播放信息,代碼如下:
import java.io.IOException;
import java.net.*;
public class Notification extends Thread {
String weather = "節(jié)目預報:八點有大型晚會,請收聽";// 發(fā)送的消息
int port = 9898; // 端口
InetAddress iaddress = null;
MulticastSocket socket = null; // 多點廣播套接字
Notification() {
try {
iaddress = InetAddress.getByName("224.255.10.0"); // 實例化InetAddress,指定地址
socket = new MulticastSocket(port); // 實例化多點廣播套接字
socket.setTimeToLive(1); // 指定發(fā)送范圍是本地網(wǎng)絡(luò)
socket.joinGroup(iaddress); // 加入廣播組
} catch (IOException e) {
e.printStackTrace(); // 輸出異常信息
}
}
public void run() {
while (true) {
DatagramPacket packet = null; // 數(shù)據(jù)包
byte data[] = weather.getBytes(); // 字符串消息的字節(jié)數(shù)組
packet = new DatagramPacket(data, data.length, iaddress, port); // 將數(shù)據(jù)打包
System.out.println(weather); // 控制臺打印消息
try {
socket.send(packet); // 發(fā)送數(shù)據(jù)
sleep(3000); // 線程休眠
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Notification w = new Notification();
w.start(); // 啟動線程
}
}
//21.3
?
?
?
2)接收廣播程序。單機“開始接收”按鈕,系統(tǒng)開始接收主機播出的信息;單機“停止接收”按鈕,系統(tǒng)停止接收廣播主機播出的信息。代碼如下:
import java.awt.*;
import java.awt.event.*;
import java.io.IOException;
import java.net.*;
import javax.swing.*;
public class Receive extends JFrame implements Runnable, ActionListener {
int port; // 端口
InetAddress group = null; // 廣播組地址
MulticastSocket socket = null; // 多點廣播套接字對象
JButton inceBtn = new JButton("開始接收");
JButton stopBtn = new JButton("停止接收");
JTextArea inceAr = new JTextArea(10, 10); // 顯示接收廣播的文本域
JTextArea inced = new JTextArea(10, 10);
Thread thread;
boolean stop = false; // 停止接受信息狀態(tài)
public Receive() {
setTitle("廣播數(shù)據(jù)報");
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
thread = new Thread(this);
inceBtn.addActionListener(this); // 綁定按鈕ince的單擊事件
stopBtn.addActionListener(this); // 綁定按鈕stop的單擊事件
inceAr.setForeground(Color.blue); // 指定文本域中文字的顏色
JPanel north = new JPanel(); // 創(chuàng)建Jpanel對象
north.add(inceBtn); // 將按鈕添加到面板north上
north.add(stopBtn);
add(north, BorderLayout.NORTH); // 將north放置在窗體的上部
JPanel center = new JPanel(); // 創(chuàng)建面板對象center
center.setLayout(new GridLayout(1, 2)); // 設(shè)置面板布局
center.add(inceAr); // 將文本域添加到面板上
center.add(inced);
add(center, BorderLayout.CENTER); // 設(shè)置面板布局
validate(); // 刷新
port = 9898; // 設(shè)置端口號
try {
group = InetAddress.getByName("224.255.10.0"); // 指定接收地址
socket = new MulticastSocket(port); // 綁定多點廣播套接字
socket.joinGroup(group); // 加入廣播組
} catch (IOException e) {
e.printStackTrace(); // 輸出異常信息
}
setBounds(100, 50, 360, 380); // 設(shè)置布局
setVisible(true); // 將窗體設(shè)置為顯示狀態(tài)
}
public void run() { // run()方法
while (!stop) {
byte data[] = new byte[1024]; // 創(chuàng)建緩存字節(jié)數(shù)組
DatagramPacket packet = null;
packet = new DatagramPacket(data, data.length, group, port); // 待接收的數(shù)據(jù)包
try {
socket.receive(packet); // 接收數(shù)據(jù)包
String message = new String(packet.getData(), 0, packet.getLength()); // 獲取數(shù)據(jù)包中的內(nèi)容
inceAr.setText("正在接收的內(nèi)容:\n" + message); // 將接收內(nèi)容顯示在文本域中
inced.append(message + "\n"); // 每條信息為一行
} catch (IOException e) {
e.printStackTrace(); // 輸出異常信息
}
}
}
public void actionPerformed(ActionEvent e) { // 單擊事件
if (e.getSource() == inceBtn) { // 單擊按鈕ince觸發(fā)的事件
inceBtn.setBackground(Color.red); // 設(shè)置按鈕顏色
stopBtn.setBackground(Color.yellow);
if (!(thread.isAlive())) { // 如線程不處于“新建狀態(tài)”
thread = new Thread(this); // 實例化Thread對象
}
thread.start(); // 啟動線程
stop = false; // 開始接受信息
}
if (e.getSource() == stopBtn) { // 單擊按鈕stop觸發(fā)的事件
inceBtn.setBackground(Color.yellow); // 設(shè)置按鈕顏色
stopBtn.setBackground(Color.red);
stop = true; // 停止接受信息
}
}
public static void main(String[] args) {
Receive rec = new Receive();
rec.setSize(460, 200);
}
}
?
?文章來源:http://www.zghlxwxcb.cn/news/detail-754987.html
?
到了這里,關(guān)于第二十一章網(wǎng)絡(luò)通信的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!