JAVA網絡編程入門


JAVA網絡編程入門

軟件結構

  1. C/S結構

    1568454627749

  2. B/S結構

1568454649812
無論哪一種結構,都離不開網絡的支持。網絡編程,就是在網絡的條件下實現機器間的通信的過程

網絡通信協議

網絡通信協議:通信雙方必須同時遵守才能完成數據交換

1568454801380
1568455164895
1568455186394
1568456326267
UDP:無連接性,數據被限制在64kb,適用於丟包問題不太大的情況,效率高

1568456464658
TCP:面向連接,可靠無差錯,三次握手

網絡編程三要素:協議,IP地址和端口號

  1. ip地址

    1568456757829
    查看本機Ip地址:控制台輸入ipconfig

    查看是否可以和某一台計算機進行網絡交換:ping ip地址

  2. 端口號

1568456899432
用協議+IP地址+端口號可以唯一的表示網絡中的進程並進行進程間的通信了

軟件在啟動時可以和操作系統要指定的端口號,或者由操作系統分配隨機的端口號

端口號由兩個字節組成,范圍為0-65535

1568457087569

TCP通信程序

TCP通信可以實現兩台計算機之間的數據交互,通信的兩端要嚴格區分客戶端和服務器端

1568457259222
客戶端和服務器端進行一個數據交流,一共需要4個IO流對象:

1568457536889
1568457609557
? Socket s1=server.accept();//獲取客戶端的流對象

1568457680300
java.net

套接字(Socket):包含了ip地址和端口號的網絡單位(可以借此識別設備和進程)

  1. 客戶端

1568458230273
這里使用Socket的輸入流來讀,輸出流來寫,不要混了

  1. 服務器端

1568458675650
服務器端必須明確知道是哪個客戶端請求的服務器,所以要使用accept方法

一個TCP通信的小例子:

TCPServer.java

import java.io.IOException;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

import org.omg.CORBA.portable.InputStream;

/*
*@author JiaDing
*服務器端
*/
public class TCPServer {
	public static void main(String[]args)throws IOException{
		//創建使用特定端口的對象
		ServerSocket server=new ServerSocket(8888);
		Socket socket=server.accept();
		java.io.InputStream is=socket.getInputStream();
		byte[]bytes=new byte[1024];
		int len=is.read(bytes);
		System.out.println(new String(bytes,0,len));//利用字符數組創建字符串
		OutputStream os=socket.getOutputStream();
		os.write("收到謝謝".getBytes());//要將字符串轉換為字符數組才行
		socket.close();//銷毀socket對象
		server.close();//銷毀server對象
;	}
}

TCPClient.java

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

/*
*@author JiaDing
*TCP通信的客戶端
*使用host(服務器主機的名稱/IP地址)和port(端口號)的構造方法
*getOutputStream 返回套接字的輸出流,使用其write方法給服務器發送數據
*getInputStream 返回套接字的輸入流,使用其read方法讀取服務器回寫數據
*close關閉套接字
*/

public class TCPClient {
	public static void main (String[] args) throws IOException {
		Socket socket=new Socket("127.0.0.1",8888);	
		OutputStream os=socket.getOutputStream();
		os.write("你好服務器".getBytes());
		InputStream is=socket.getInputStream();
		byte[]bytes=new byte[1024];
		int len=is.read(bytes);
		System.out.println(new String(bytes,0,len));
		socket.close();
	}
	
}

TCP通信往往是按字節處理而不是字符處理,因為不是所有類型都是字符

127.0.0.1是什么地址?*

127.0.0.1/8整個都是環回地址,用來測試本機的TCP/IP協議棧,發往這段A類地址數據包不會出網卡,網絡設備不會對其做路由。

實例:文件上傳

1568465232380
Client.java

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;

/*
*@author JiaDing
*文件上傳案例的客戶端
*/
public class Client {
	public static void main(String[]args)throws IOException {
		FileInputStream fis=new FileInputStream("E:\\out.txt");
		Socket socket=new Socket("127.0.0.1",8888);
		java.io.OutputStream os=socket.getOutputStream();
		byte[] re=new byte[1024];//准備了1kb的大小
		int len=0;
		while((len=fis.read(re))!=-1) {//這里一定要給len=fis.read(re)加一個括號,否則兩個等號無法區分優先級
			os.write(re,0,len);
		}
		/*
		 * 注意!這里有一個很重要的問題!
		 * 由於我們的判斷條件是len!=-1,所以我們無法將文件的結束標記上傳過去,這樣服務器會一直處於等待狀態,造成阻塞
		 * 而對應的,由於服務器阻塞,所以無法執行回顯,is.read()也就無法讀入數據,同樣陷入阻塞
		 * 解決辦法就是在客戶端利用套接字的方法人工結束輸出流
		 */
		socket.shutdownOutput();
		InputStream is=socket.getInputStream();
		while((len=is.read(re))!=-1) {//這里一定要給len=fis.read(re)加一個括號,否則兩個等號無法區分優先級
			System.out.println(new String(re,0,len));
		}
		fis.close();
		socket.close();
		}
	
	
}

Server.java

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

/*
*@author JiaDing
*文件上傳案例的服務器端
*/
public class Server {
	public static void main(String[]args) throws IOException{
		ServerSocket ss=new ServerSocket(8888);
		Socket socket=ss.accept();
		File file=new File("E:\\update");
		if(!file.exists())
			file.mkdir();
		byte[] by=new byte[1024];
		InputStream is=socket.getInputStream();
		FileOutputStream out=new FileOutputStream("E:\\update\\t1.txt");
		int len=0;
		while(len!=-1) {
			out.write(by, 0, len);
			len=is.read(by);
		}
		OutputStream os=socket.getOutputStream();
		os.write("Update is finished!".getBytes());
		socket.close();
		out.close();
		ss.close();
		
	}
}

優化:

  1. 名稱寫死了:自定義服務器端命名規則,防止同名文件被覆蓋
//自定義文件名規則:域名+毫秒值+隨機數
		//毫秒值廣泛應用在服務器端文件名保存上
		String name="jd"+System.currentTimeMillis()+new Random().nextInt(999999)+".txt";
		FileOutputStream out=new FileOutputStream("E:\\update\\"+name);
  1. 服務器上傳后就停止:讓服務器一直處於監聽狀態:使用死循環,服務器不關閉

B\S服務器的模擬

服務器代碼-返回請求版:

import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

/*
*@author JiaDing
*BS版本的TCP服務器
*/
public class Server {
	public static void main(String[]args)throws IOException{
		ServerSocket server=new ServerSocket(8080);
		Socket socket=server.accept();
		InputStream is=socket.getInputStream();
		byte[]by=new byte[1024];
		int len=0;
		while((len=is.read(by))!=-1) {
			System.out.println(new String(by,0,len));
		}
	}
	
	
}

訪問時要指明文件夾名\要訪問的資源名,如果端口不是80還要在IP后面加上端口號

服務器端結果(其實就是客戶端的請求信息):

GET /src/Test1.html HTTP/1.1
Host: 127.0.0.1:8080
Connection: keep-alive
DNT: 1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.75 Safari/537.36
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,/;q=0.8,application/signed-exchange;v=b3
Sec-Fetch-Site: none
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8

服務器應該回寫應該網頁(文件),我們需要讀取文件地址,地址已經包含在了請求第一行

可以使用BufferedReader中的方法readList讀取第一行

1568471404194
服務器代碼—返回網頁版:

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

/*
*@author JiaDing
*BS版本的TCP服務器
*/
public class Server {
	public static void main(String[]args)throws IOException{
		ServerSocket server=new ServerSocket(8080);
		Socket socket=server.accept();
		InputStream is=socket.getInputStream();
		/*byte[]by=new byte[1024];
		int len=0;
		while((len=is.read(by))!=-1) {
			System.out.println(new String(by,0,len));
		}*/
		//把is網絡字節輸入流對象轉換為字符緩存輸入流
		BufferedReader br=new BufferedReader(new InputStreamReader(is));
		String line=br.readLine();
		String htmlpath=line.split(" ")[1].substring(1);//取第一行中間的地址,去掉最前面的、
		//System.out.println(htmlpath);
		FileInputStream fis=new FileInputStream(htmlpath);
		OutputStream os=socket.getOutputStream();
		//寫入HTTP協議響應頭,固定寫法
		os.write("HTTP/1.1 200 OK\r\n".getBytes());
		os.write("Content-Type:text/html\r\n".getBytes());
		//必須寫入空行,否則瀏覽器不解析
		os.write("\r\n".getBytes());
		int len=0;
		byte[]by=new byte[1024];
		while((len=fis.read(by))!=-1) {
			os.write(by,0,len);
		}
		fis.close();
		socket.close();
		server.close();
	}
	
	
}

1568472450459

  • 為什么服務器要是多線程呢?

    如果不采用多線程,那么服務器只能線型地處理瀏覽器的請求,瀏覽器上的資源只能一個一個打開,體驗會很差

采樣頭多線程之后的代碼:

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

/*
*@author JiaDing
*BS版本的TCP服務器
*/
public class Server {
	public static void main(String[]args)throws IOException{
		ServerSocket server=new ServerSocket(8080);
		while(true) {
			Socket socket=server.accept();
			new Thread(new Runnable() {
				@Override
				public void run() {
					try {
						InputStream is=socket.getInputStream();
						//把is網絡字節輸入流對象轉換為字符緩存輸入流
						BufferedReader br=new BufferedReader(new InputStreamReader(is));
						String line=br.readLine();
						String htmlpath=line.split(" ")[1].substring(1);//取第一行中間的地址,去掉最前面的、
						//System.out.println(htmlpath);
						FileInputStream fis=new FileInputStream(htmlpath);
						OutputStream os=socket.getOutputStream();
						//寫入HTTP協議響應頭,固定寫法
						os.write("HTTP/1.1 200 OK\r\n".getBytes());
						os.write("Content-Type:text/html\r\n".getBytes());
						//必須寫入空行,否則瀏覽器不解析
						os.write("\r\n".getBytes());
						int len=0;
						byte[]by=new byte[1024];
						while((len=fis.read(by))!=-1) {
							os.write(by,0,len);
						}
						fis.close();
						socket.close();
					}catch(IOException e) {
						e.printStackTrace();
					}
				}
			}).start();
			
		}
	}
	
	
}

注意這里會報一個錯誤:java.io.FileNotFoundException: favicon.ico。這是因為瀏覽器會自動訪問網站的圖標而我們的網站沒有的原因,這個圖標必須放在根目錄,例如如果訪問的地址是“http://127.0.0.1/src/Test1.html”,那么就要放在src文件夾的外面

另外,實驗了一下,將服務器的端口改成了80好像並沒有什么問題,因為服務器監聽的是本機的80端口,而如果我們訪問外網的話,應該是訪問外網網站服務器的80端口,兩者並不沖突。部署在本機上的服務器只能監聽到訪問本機且本端口的請求。所以,網頁中如果有外鏈,那么打開這個外鏈的過程和我們的服務器是沒有絲毫關系的,我們自建的服務器的好壞不會對其造成影響


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM