1、網(wǎng)絡(luò)編程三要素
IP地址
要想讓網(wǎng)絡(luò)中的計算機能夠互相通信,必須為每臺計算機指定一個標識號,通過這個標識號來指定要接收數(shù)據(jù)的計算機和識別發(fā)送的計算機,而IP地址就是這個標識號。也就是設(shè)備的標識端口
網(wǎng)絡(luò)的通信,本質(zhì)上是兩個應用程序的通信。每臺計算機都有很多的應用程序,那么在網(wǎng)絡(luò)通信時,如何區(qū)分這些應用程序呢?如果說IP地址可以唯一標識網(wǎng)絡(luò)中的設(shè)備,那么端口號就可以唯一標識設(shè)備中的應用程序了。也就是應用程序的標識協(xié)議
通過計算機網(wǎng)絡(luò)可以使多臺計算機實現(xiàn)連接,位于同一個網(wǎng)絡(luò)中的計算機在進行連接和通信時需要遵守一定的規(guī)則,這就好比在道路中行駛的汽車一定要遵守交通規(guī)則一樣。在計算機網(wǎng)絡(luò)中,這些連接和通信的規(guī)則被稱為網(wǎng)絡(luò)通信協(xié)議,它對數(shù)據(jù)的傳輸格式、傳輸速率、傳輸步驟等做了統(tǒng)一規(guī)定,通信雙方必須同時遵守才能完成數(shù)據(jù)交換。常見的協(xié)議有UDP協(xié)議和TCP協(xié)議-
端口號- 用兩個字節(jié)表示的整數(shù),它的取值范圍是065535。其中,01023之間的端口號用于一些知名的網(wǎng)絡(luò)服務和應用,普通的應用程序需要使用1024以上的端口號。如果端口號被另外一個服務或應用所占用,會導致當前程序啟動失敗
獲取當前主機IP
InetAddress address = InetAddress.getByName("localhost");
String hostName = address.getHostName();
System.out.println("主機名為" + hostName);
String ip = address.getHostAddress();
System.out.println("IP為" + ip);
2、UDP通信
-
UDP發(fā)送數(shù)據(jù)
方法名 說明 DatagramSocket() 創(chuàng)建數(shù)據(jù)報套接字并將其綁定到本機地址上的任何可用端口 DatagramPacket(byte[] buf,int len,InetAddress add,int port) 創(chuàng)建數(shù)據(jù)包,發(fā)送長度為len的數(shù)據(jù)包到指定主機的指定端口 void send(DatagramPacket p) 發(fā)送數(shù)據(jù)報包 void close() 關(guān)閉數(shù)據(jù)報套接字 void receive(DatagramPacket p) 從此套接字接受數(shù)據(jù)報包 -
UDP接收數(shù)據(jù)
方法名 說明 DatagramPacket(byte[] buf, int len) 創(chuàng)建一個DatagramPacket用于接收長度為len的數(shù)據(jù)包 byte[] getData() 返回數(shù)據(jù)緩沖區(qū) int getLength() 返回要發(fā)送的數(shù)據(jù)的長度或接收的數(shù)據(jù)的長度 客戶端
import java.io.IOException;
import java.net.*;
import java.util.Scanner;
public class ClientDemo {
public static void main(String[] args) throws IOException {
Scanner sc = new Scanner(System.in);
//創(chuàng)建接收端的Socket對象
DatagramSocket ds = new DatagramSocket();
while (true) {
String s = sc.nextLine();
byte[] bytes = s.getBytes();
// 構(gòu)造數(shù)據(jù)報套接字并將其綁定到本地主機上的任何可用端口
InetAddress address = InetAddress.getByName("127.0.0.1");
int port = 10000;
//調(diào)用DatagramSocket對象的方法接收數(shù)據(jù)
DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,port);
ds.send(dp);
// 結(jié)束
if("404".equals(s)){
break;
}
}
//關(guān)閉此數(shù)據(jù)報套接字
ds.close();
}
}
服務端
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class ServerDemo {
public static void main(String[] args) throws IOException {
//創(chuàng)建接收端的Socket對象
DatagramSocket ds = new DatagramSocket(10000);
while (true) {
//創(chuàng)建一個數(shù)據(jù)包,用于接收數(shù)據(jù)
byte [] bytes = new byte[1024];
//調(diào)用DatagramSocket對象的方法接收數(shù)據(jù)
DatagramPacket dp = new DatagramPacket(bytes,bytes.length);
ds.receive(dp);
byte[] data = dp.getData();
int length = dp.getLength();
String s = new String(data, 0, length);
System.out.println(s);
// 結(jié)束
if("404".equals(s)){
break;
}
}
ds.close();
}
}
3、UDP三種通訊方式
- 單播
單播用于兩個主機之間的端對端通信 - 組播
組播用于對一組特定的主機進行通信 - 廣播
廣播用于一個主機對整個局域網(wǎng)上所有主機上的數(shù)據(jù)通信
組播
// 發(fā)送端
public class ClinetDemo {
public static void main(String[] args) throws IOException {
// 1. 創(chuàng)建發(fā)送端的Socket對象(DatagramSocket)
DatagramSocket ds = new DatagramSocket();
String s = "hello 組播";
byte[] bytes = s.getBytes();
InetAddress address = InetAddress.getByName("224.0.1.0");
int port = 10000;
// 2. 創(chuàng)建數(shù)據(jù),并把數(shù)據(jù)打包(DatagramPacket)
DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,port);
// 3. 調(diào)用DatagramSocket對象的方法發(fā)送數(shù)據(jù)(在單播中,這里是發(fā)給指定IP的電腦但是在組播當中,這里是發(fā)給組播地址)
ds.send(dp);
// 4. 釋放資源
ds.close();
}
}
// 接收端
public class ServerDemo {
public static void main(String[] args) throws IOException {
// 1. 創(chuàng)建接收端Socket對象(MulticastSocket)
MulticastSocket ms = new MulticastSocket(10000);
// 2. 創(chuàng)建一個箱子,用于接收數(shù)據(jù)
DatagramPacket dp = new DatagramPacket(new byte[1024],1024);
// 3. 把當前計算機綁定一個組播地址,表示添加到這一組中.
ms.joinGroup(InetAddress.getByName("224.0.1.0"));
// 4. 將數(shù)據(jù)接收到箱子中
ms.receive(dp);
// 5. 解析數(shù)據(jù)包,并打印數(shù)據(jù)
byte[] data = dp.getData();
int length = dp.getLength();
System.out.println(new String(data,0,length));
// 6. 釋放資源
ms.close();
}
}
-
廣播// 發(fā)送端 public class ClientDemo { public static void main(String[] args) throws IOException { // 1. 創(chuàng)建發(fā)送端Socket對象(DatagramSocket) DatagramSocket ds = new DatagramSocket(); // 2. 創(chuàng)建存儲數(shù)據(jù)的箱子,將廣播地址封裝進去 String s = "廣播 hello"; byte[] bytes = s.getBytes(); InetAddress address = InetAddress.getByName("255.255.255.255"); int port = 10000; DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,port); // 3. 發(fā)送數(shù)據(jù) ds.send(dp); // 4. 釋放資源 ds.close(); } } // 接收端 public class ServerDemo { public static void main(String[] args) throws IOException { // 1. 創(chuàng)建接收端的Socket對象(DatagramSocket) DatagramSocket ds = new DatagramSocket(10000); // 2. 創(chuàng)建一個數(shù)據(jù)包,用于接收數(shù)據(jù) DatagramPacket dp = new DatagramPacket(new byte[1024],1024); // 3. 調(diào)用DatagramSocket對象的方法接收數(shù)據(jù) ds.receive(dp); // 4. 解析數(shù)據(jù)包,并把數(shù)據(jù)在控制臺顯示 byte[] data = dp.getData(); int length = dp.getLength(); System.out.println(new String(data,0,length)); // 5. 關(guān)閉接收端 ds.close(); } }
4.TCP通信程序
-
TCP發(fā)送數(shù)據(jù)
方法名 說明 Socket(InetAddress address,int port) 創(chuàng)建流套接字并將其連接到指定IP指定端口號 Socket(String host, int port) 創(chuàng)建流套接字并將其連接到指定主機上的指定端口號 InputStream getInputStream() 返回此套接字的輸入流 OutputStream getOutputStream() 返回此套接字的輸出流 -
TCP接收數(shù)據(jù)
方法名 說明 ServletSocket(int port) 創(chuàng)建綁定到指定端口的服務器套接字 Socket accept() 監(jiān)聽要連接到此的套接字并接受它 -
注意事項
- accept方法是阻塞的,作用就是等待客戶端連接
- 客戶端創(chuàng)建對象并連接服務器,此時是通過三次握手協(xié)議,保證跟服務器之間的連接
- 針對客戶端來講,是往外寫的,所以是輸出流
針對服務器來講,是往里讀的,所以是輸入流 - read方法也是阻塞的
- 客戶端在關(guān)流的時候,還多了一個往服務器寫結(jié)束標記的動作
- 最后一步斷開連接,通過四次揮手協(xié)議保證連接終止
一次TCP通信
- 客戶端
import java.io.*;
import java.net.Socket;
public class ClientDemo {
public static void main(String[] args) throws IOException {
// 創(chuàng)建socket 綁定IP和端口
Socket socket = new Socket("127.0.0.1",10000);
// 創(chuàng)建socket發(fā)送流
OutputStream os = socket.getOutputStream();
os.write("hello".getBytes());
//僅僅關(guān)閉輸出流.并寫一個結(jié)束標記,對socket沒有任何影響
socket.shutdownOutput();
// 創(chuàng)建socket接收流
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line;
while((line = br.readLine())!=null){
System.out.println(line);
}
// 關(guān)閉資源
br.close();
os.close();
socket.close();
}
}
- 服務器
public class ServerDemo {
public static void main(String[] args) throws IOException {
// 創(chuàng)建socServerSocketket 監(jiān)聽端口
ServerSocket ss = new ServerSocket(10000);
//等待客戶端連接
Socket accept = ss.accept();
// 輸入流
InputStream is = accept.getInputStream();
int b;
while((b = is.read())!=-1){
System.out.println((char) b);
}
// 輸出流
System.out.println("看看我執(zhí)行了嗎?");
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(accept.getOutputStream()));
bw.write("你誰啊?");
bw.newLine();
bw.flush();
// 關(guān)閉資源
bw.close();
is.close();
accept.close();
ss.close();
}
}
TCP上傳文件
| 方法名 | 說明 |
|---|---|
| void shutdownInput() | 將此套接字的輸入流放置在“流的末尾” |
| void shutdownOutput() | 禁止用此套接字的輸出流 |
客戶端
import java.io.*;
import java.net.Socket;
public class ClientDemo {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1",10000);
//是本地的流,用來讀取本地文件的.
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("1.jpg"));
//寫到服務器 --- 網(wǎng)絡(luò)中的流
OutputStream os = socket.getOutputStream();
BufferedOutputStream bos = new BufferedOutputStream(os);
int b;
while((b = bis.read())!=-1){
bos.write(b);//通過網(wǎng)絡(luò)寫到服務器中
}
bos.flush();
//給服務器一個結(jié)束標記,告訴服務器文件已經(jīng)傳輸完畢
socket.shutdownOutput();
//接收流
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line;
while((line = br.readLine()) !=null){
System.out.println(line);
}
bis.close();
socket.close();
}
}
服務器
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class ServerDemo {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(10000);
Socket accept = ss.accept();
//網(wǎng)絡(luò)中的流,從客戶端讀取數(shù)據(jù)的
BufferedInputStream bis = new BufferedInputStream(accept.getInputStream());
//本地的IO流,把數(shù)據(jù)寫到本地中,實現(xiàn)永久化存儲
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("3.jpg"));
int b;
while((b = bis.read()) !=-1){
bos.write(b);
}
//將字節(jié)輸入流FileInputStream 轉(zhuǎn)成 字符輸入流 Fliereader
//通過轉(zhuǎn)換流
InputStreamReader inputStreamReader = new InputStreamReader(accept.getInputStream());
//通過緩沖輸入字符流
BufferedReader br = new BufferedReader(inputStreamReader);
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(accept.getOutputStream());
BufferedWriter bw = new BufferedWriter(outputStreamWriter);
bw.write("上傳成功");
bw.newLine();
bw.flush();
bos.close();
accept.close();
ss.close();
}
}
TCP服務端優(yōu)化
異步線程完成socket接收流程
import java.io.*;
import java.net.Socket;
import java.util.UUID;
// 線程任務類
public class ThreadSocket implements Runnable {
private Socket acceptSocket;
public ThreadSocket(Socket accept) {
this.acceptSocket = accept;
}
@Override
public void run() {
BufferedOutputStream bos = null;
try {
//網(wǎng)絡(luò)中的流,從客戶端讀取數(shù)據(jù)的
BufferedInputStream bis = new BufferedInputStream(acceptSocket.getInputStream());
//本地的IO流,把數(shù)據(jù)寫到本地中,實現(xiàn)永久化存儲
bos = new BufferedOutputStream(new FileOutputStream( UUID.randomUUID().toString() + ".jpg"));
int b;
while((b = bis.read()) !=-1){
bos.write(b);
}
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(acceptSocket.getOutputStream()));
bw.write("上傳成功");
bw.newLine();
bw.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(bos != null){
try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (acceptSocket != null){
try {
acceptSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
線程池控制socket創(chuàng)建
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ServerDemo {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(10000);
ThreadPoolExecutor pool = new ThreadPoolExecutor(
3,//核心線程數(shù)量
10, //線程池的總數(shù)量
60, //臨時線程空閑時間
TimeUnit.SECONDS, //臨時線程空閑時間的單位
new ArrayBlockingQueue<>(5),//阻塞隊列
Executors.defaultThreadFactory(),//創(chuàng)建線程的方式
new ThreadPoolExecutor.AbortPolicy()//任務拒絕策略
);
while (true) {
Socket accept = ss.accept();
ThreadSocket ts = new ThreadSocket(accept);
pool.submit(ts);
}
//ss.close();
}
}