黑馬程序員—網絡編程

---------------------- JavaEE+Android、Java培訓、期待與您交流! ----------------------


網絡編程

IP地址:它是網絡中的設備標識,不易記憶,可用主機名錶示,兩者存在映射關係。本機迴環地址:127.0.0.1,主機名爲:localhost

端口號:用於標識進程的邏輯地址,不用進程的標識。有效端口:0 ~ 65535,系統使用或保留的端口是:0 ~ 1024.

傳輸協議:TCP和UDP協議

UDP協議特點:

a、面向無連接,即將數據及源和目的封裝成數據包中,不建立鏈接的發送

b、每個數據報的大小限制在64K之內

c、因無連接,是不可靠的協議

d、不建立連接,速度快

TCP協議特點:

a、面向連接,在建立連接後,形成傳輸數據的通道

b、在連接中進行大數據量的傳輸

c、通過三次握手完成連接,是可靠的協議

d、必須建立連接,效率稍慢

三次握手:第一次本方發送請求,第二次對方確認連接,第三次本方再次確認連接成功。

通信的步驟

1)找到IP地址

2)數據要發送到對象指定應用程序,爲標識這些應用程序,所以給這些網絡應用程序都用數字標識,爲方便稱呼這個數字,叫做端口,即邏輯端口。

3)定義通信規則,稱之爲協議。國際組織定義了通用協議,即TCP/IP。

注意:必須要有數字標識才能將數據發送到應用程序上。

InetAddress類

1、無構造函數,可通過getLocalHost()方法獲取InetAddress對象,此方法是靜態的,返回此對象。

       InetAddress i = InetAddress.getLocalHost();

2、方法:

1)static InetAddress getByName(String host):在給定主機名的情況下獲取主機的IP地址

2)static InetAddress[] getAllByName(String host):在給定主機名的情況下,根據系統上配置的名稱服務返回IP地址所組成的數組。返回對象不唯一時,用此方法。

3)String getHostAddress():返回IP地址字符串文本形式,以這個爲主,即以IP地址爲主。

4)String getHostName():返回IP地址主機名。

/*
獲取IP地址和IP地址主機名
*/
import java.net.*;
class InetDemo
{
	public static void main(String[] args) throws Exception
	{
		//獲取本機IP地址
		InetAddress ia = InetAddress.getLocalHost();
		System.out.println(ia.toString());

		//通過百度主機名獲取其IP地址
		InetAddress i = InetAddress.getByName("www.baidu.com");
		System.out.println("name:" + i.getHostName());
		System.out.println("address:" + i.getHostAddress());
	}
}

Socket

它被稱之爲插座,相當於港口一樣,是網絡服務提供的一種機制。通信兩端都要有Socket,才能建立服務。網絡通信其實就是Socket間的通信,數據在兩個Socket間通過IO傳輸。

UDP傳輸

1、通過類DatagramSocket,此類表示用發送和接收數據報的套接字,即Socket

2、方法:

1)創建 UDPSocket發送服務對象:DatagramSocket(),可不指定端口

2)創建 UDPSocket接收服務對象:DatagramSocket(int port)

3)發送:void send(DatagramPacket p)

4)接收:void receive(DatagramPacket p)

其中DatagramPacket:數據報包用來實現無連接包投遞服務的,每條報文僅根據該包中包含的信息從一臺機器路由到另一臺機器中。凡是帶地址(InetAddress)的都是用於發送包的。

3、流程:

1)發送數據:

思路:

a.建立UDPSocket服務,在此無需指定端口,也可以將端口加入。如果不指定的話,系統會隨機分配一個端口,如第一次運行時端口爲1093,那麼第二次就會順延爲1094,再運行會一直順延,因爲之前的短路還沒有得到釋放,所以會順延端口號值。

b.提供數據,並將數據封裝到數據包中

c.通過socket服務的發送功能,將數據包發送出去

d.關閉資源

2)接收數據:

思路:

a.定義UDPSocket服務。通常會監聽一個端口,其實就是給這個接收網路應用程序定義數字標識方便於明確那些數據過來該應用程序可以處理。

b.定義一個數據包,因爲要存儲接收到的字節數據,因爲數據包對象中有更多功能可以提取字節數據中的不同數據信息。

c.通過socket服務的receive方法接收到的數據存入已定義好的數據包中

d.通過數據包對象的特有功能,將這些不同的數據取出,打印在控制檯上

e.關閉資源

注意:

在定義接收數據的方法中,仍會在DatagramSocket構造函數中傳入DatagramPacket的參數,這是因爲收到的數據太多,需要解析,通過將數據封裝成對象,易於解析,所以需要傳入參數。

練習:編寫一個簡單的聊天程序

分析:有收數據的部分,有發數據的部分,這兩部分需要同時執行,那就需要多線程技術,一個線程控制接收,一個線程控制發。因爲收和發的動作不一致,所以要定義兩個run方法,而且這個兩個方法要封裝到不同的類中。

import java.io.*;
import java.net.*;
//發送數據
class SendSocket implements Runnable
{
	//定義全局變量
	private DatagramSocket ds;
	//初始化發送類對象的參數
	public SendSocket(DatagramSocket ds)
	{
		this.ds = ds;
	}
	//覆寫run方法,此線程發送鍵盤錄入的數據
	public void run()
	{
		try
		{
			//創建讀取流,讀取鍵盤數據
			BufferedReader bufr =
				new BufferedReader(new InputStreamReader(System.in));
			String line = null;
			while((line=bufr.readLine())!=null)
			{
				if("886".equals(line))
					break;
				byte[] b = line.getBytes();
				//創建發送服務對象,將數據發送出去
				DatagramPacket dp =
				new DatagramPacket(b,b.length,InetAddress.getByName("192.168.1.255"),10001);
				ds.send(dp);
			}
			//關閉資源
			ds.close();
		}
		catch (Exception e)
		{
			throw new RuntimeException("發送失敗");
		}
	}
}

//接收數據
class ReceSocket implements Runnable
{
	//定義全局變量
	private DatagramSocket ds;
	//初始化接收類對象的參數
	public ReceSocket(DatagramSocket ds)
	{
		this.ds = ds;
	}
	//覆寫run方法,此線程接收數據
	public void run()
	{
		try
		{
			//循環讀取接收到的數據
			while(true)
			{
				//創建字節數組存儲數據
				byte[] by = new byte[1024];
				//創建接收數據的對象,接收數據
				DatagramPacket dp = new DatagramPacket(by,by.length);
				ds.receive(dp);
				//獲取發送方的ip地址
				String ip = dp.getAddress().getHostAddress();
				//getData獲取byte數組中的數據
				String data = new String(dp.getData(),0,dp.getLength());
				System.out.println(ip+":"+data);
			}
		}
		catch (Exception e)
		{
			throw new RuntimeException("接收失敗");
		}
	}
}
//測試
class SocketDemo 
{
	public static void main(String[] args) throws Exception
	{
		//創建發送和接收服務的對象
		DatagramSocket sendSocket = new DatagramSocket();
		DatagramSocket receSocket = new DatagramSocket(10001);
		//創建兩個線程,同時執行
		new Thread(new SendSocket(sendSocket)).start();
		new Thread(new ReceSocket(receSocket)).start();
	}
}

TCP傳輸

1、包括客戶端和服務端,即Socket爲客戶端,ServerSocket爲服務端。

2、方法:

1)創建客戶端對象:

      Socket():創建空參數的客戶端對象,一般用於服務端接收數據

      Socket(String host,int port),指定要接收的IP地址和端口號

2)創建服務端對象:ServerSocket(int port):指定接收的客戶端的端口

3)Socket accept():真挺並接受到此套接字的連接

4)void shutdownInput():此套接字的輸入流至於“流的末尾”

5)void shutdownOutput():禁用此套接字的輸出流

6)InputStream getInputStream():返回此套接字的輸入流

7)OutputStream getOutputStream():返回套接字的輸出流

3、流程:

1)建立客戶端和服務器端。

2)建立連接後,通過Socket中的IO流進行數據的傳輸。

3)關閉socket

同樣的,客戶端與服務端是兩個獨立的應用程序。

4、客戶端:在該對象建立時就可去連接指定主機,因爲TCP是面向倆接的,所以在建立Socket服務時,就要有服務端存在,並連接成功,形成通路後,再通過該通道進行數據的傳輸。

步驟:

1)創建Socket服務,並指定要連接的主機端口。通路一建立,就會產生Socket流(包括輸入流和輸出流),通過方法獲取

2)爲了發送數據,應獲取Socket中的輸出流,如果要接收服務端的反饋信息,需要獲取Socket的輸入流

3)通過write()方法將信息寫入到流中。

4)關閉Socket流資源

5、服務端:定義端連接數據,並存放指定地方,需監聽一個端口。

步驟:

1)建立服務端的Socket服務,通過ServerSocet帶端口參數的構造函數

2)獲取連接過來的客戶對象,通過ServerSocket的accept()方法,此方法是阻塞式的,如果服務端沒有連接到就會等待。

3)客戶端若發來數據,則服務端要使用對應的客戶端對象,並獲取到該客戶端對象的讀取流發來的數據,並輸出到指定目的地。

4)關閉服務端。一般服務端是常開的,因爲在實際應用中,隨時有客戶端在請求連接和服務,所有這裏需要定時關閉客戶端對象流,避免某一個客戶端長時間佔用服務器端。

練習

1、文本轉換器,客戶端通過鍵盤發送信息,服務端將信息以大寫形式返回到控制檯上,形成客戶端和服務端的互訪。

分析:既然是設備上的數據,則可用IO技術,並依IO操作規律操作。

1、客戶端:

源:鍵盤錄入;目的:網絡設備,即網絡輸出流。-->操作的是文本數據,可選字符流,並加入高效緩衝區。

步驟:

1)建立服務。

2)獲取鍵盤錄入

3)將數據發給服務端

4)之後去服務端返回數據

5)結束,關閉資源

2、服務端:

源:socket讀取流;目的:socket輸出流。

注:socket的close()方法中加入了結束標記-1,所以客戶端結束了,服務端也結束了。

3、該例可能出現的問題:

現象:客戶端和服務端都在莫名的等待,似乎數據都沒有傳輸過去。

原因:因爲客戶端和服務端都有阻塞方法,這些方法沒有讀到結束標記,就會一直等待,而導致兩端都在等待。

解決:需要用到刷新和換行的方式將寫入和讀取的數據從流中刷新到內存中

           方式一:可用高效緩衝區類的newLine()換行作爲結束標記,並用flush()進行刷新。

           方式二:可用PrintWriter(s.getOutputStrean(),true)創建輸出流對象,true作用是刷新,通過打印方法println()換行,“ln”表示換行。

import java.io.*;
import java.net.*;

//客戶端
class TransClient
{
	public static void main(String[] args) throws Exception
	{
		//創建Socket服務
		Socket s = new Socket("192.168.1.101",10003);
		//定義讀取鍵盤數據的流對象
		BufferedReader bufr =
			new BufferedReader(new InputStreamReader(System.in));
		//定義目的,將數據寫入到socket輸出流,發送服務端
		//BufferedWriter bufOut = 
		//	new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
		PrintWriter out = new PrintWriter(s.getOutputStream(),true);
		//定義一個socket讀取流,讀取服務端返回的信息,數據
		BufferedReader bufIn = 
			new BufferedReader(new InputStreamReader(s.getInputStream()));

		String line = null;
		while((line=bufr.readLine())!=null)
		{
			if("over".equals(line))
				break;
			out.println(line);
			//bufOut.write(line);
			//bufOut.newLine();
			//bufOut.flush();

			String str = bufIn.readLine();
			System.out.println("Server:" + str);
		}
		bufr.close();
		s.close();
	}
}

import java.io.*;
import java.net.*;

//服務端
class TransSever
{
	public static void main(String[] args) throws Exception
	{
		ServerSocket ss = new ServerSocket(10003);
		Socket s = ss.accept();
		
		String ip = s.getInetAddress().getHostAddress();
		System.out.println(ip+"....connected");

		BufferedReader bufIn =
			new BufferedReader(new InputStreamReader(s.getInputStream()));
		//BufferedWriter bufOut =
		//	new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
		PrintWriter out = new PrintWriter(s.getOutputStream(),true);
		String line = null;
		while((line=bufIn.readLine())!=null)
		{
			System.out.println(line);
			out.println(line.toUpperCase());
			//bufOut.write(line.toUpperCase());
			//bufOut.newLine();
			//bufOut.flush();
		}
		s.close();
		ss.close();
	}
}

2、TCP複製文件

1、客戶端:

源:硬盤上的文件;目的:網絡設備,即網絡輸出流。-->若操作的是文本數據,可選字符流,並加入高效緩衝區。若是媒體文件,用字節流。

2、服務端:

源:socket讀取流;目的:socket輸出流。

3、出現的問題:

a.文件已經上傳成功了,但是沒有得到服務端的反饋信息。

b.即使得到反饋信息,但得到的是null,而不是“上傳成功”的信息

原因:

a.因爲客戶端將數據發送完畢後,服務端仍然在等待這讀取數據,並沒有收到結束標記,就會一直等待讀取。

b.上個問題解決後,收到的不是指定信息而是null,是因爲服務端寫入數據後,也需要刷新,才能將信息反饋給客服端。

解決:

方法一:定義結束標記,先將結束標記發送給服務端,讓服務端接收到結束標記,然後再發送上傳的數據。但是這樣定義可能會發生定義的標記和文件中的數據重複而導致提前結束。

方法二:定義時間戳,由於時間是唯一的,在發送數據前,先獲取時間,發送完後在結尾處寫上相同的時間戳,在服務端,接收數據前先接收一個時間戳,然後在循環中判斷時間戳以結束標記。

方法三:通過socket方法中的shutdownOutput(),關閉輸入流資源,從而結束傳輸流,以給定結束標記。這裏主要用這個方法。

import java.io.*;
import java.net.*;

//客戶端
class CopyClient
{
	public static void main(String[] args) 
	{
		//定義全局變量
		Socket s = null;
		try
		{
			s = new Socket("192.168.1.101",10003);
		}
		catch (IOException e)
		{
			throw new RuntimeException("創建連接失敗");
		}
		BufferedReader bufr = null;
		try
		{
			//創建讀取流,讀取指定文件
			bufr = new BufferedReader(new FileReader("D:\\File\\udp.java"));
			//創建寫入緩衝區,寫入網絡輸出流中
			BufferedWriter bufOut = 
				new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
			
			//讀取文本,並寫入流中
			String line = null;
			while((line=bufr.readLine())!=null)
			{
				bufOut.write(line);
				bufOut.newLine();
				bufOut.flush();
			}
			//關閉客戶端的輸出流。
			s.shutdownOutput();
			//創建讀取流,讀取網絡輸入流
			BufferedReader bufIn =
				new BufferedReader(new InputStreamReader(s.getInputStream()));
			//讀取反饋信息
			String str = bufIn.readLine();
			System.out.println(str);
		}
		catch (IOException e)
		{
			throw new RuntimeException("發送失敗");
		}
		//關閉流資源
		finally
		{
			try
			{
				if(bufr!=null)
					bufr.close();
				s.close();
			}
			catch (IOException e)
			{
				throw new RuntimeException("資源關閉失敗");
			}
		}
	}
}


import java.io.*;
import java.net.*;

//服務端
class CopyServer
{
	public static void main(String[] args) 
	{
		//定義全局變量
		ServerSocket ss = null;
		Socket s= null;
		try
		{
			//創建服務,接收客戶端數據
			ss = new ServerSocket(10003);
			s= ss.accept();
			String ip = s.getInetAddress().getHostAddress();
			System.out.println(ip+"....connected");
		}
		catch (IOException e)
		{
			throw new RuntimeException("連接失敗");
		}
		BufferedWriter bufw = null;
		try
		{
			//創建寫入流
			bufw = new BufferedWriter(new FileWriter("server.txt"));
			//創建讀取流
			BufferedReader bufIn =
				new BufferedReader(new InputStreamReader(s.getInputStream()));
			String line = null;
			//讀取網絡輸入流數據
			while((line=bufIn.readLine())!=null)
			{
				bufw.write(line);
				bufw.newLine();
				bufw.flush();
			}
			//創建寫入流,反饋給客戶端信息
			BufferedWriter bufOut = 
				new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
			bufOut.write("上傳成功");
			bufOut.flush();

		}
		catch (IOException e)
		{
			throw new RuntimeException("發送失敗");
		}
		//關閉流資源
		finally 
		{
			try
			{
				if(bufw!=null)
					bufw.close();
				s.close();
				ss.close();
			}
			catch (IOException e)
			{
				throw new RuntimeException("資源關閉失敗");
			}
		}
	}
}


TCP併發執行請求

圖片上傳

客戶端:

1、創建服務端點

2、讀取客戶端以後圖片數據

3、通過Socket輸出流將數據發給服務端

4、讀取服務端反饋信息

5、關閉客戶端

服務端:

對於客戶端併發上傳圖片,服務端如果單純的使用while(true)循環式有侷限性的,當A客戶端連接上以後,被服務端獲取到,服務端執行具體的流程,這時B客戶端連接就只有等待了,因爲服務端還未處理完A客戶端的請求,還有循環來回執行下須accept方法,所以暫時獲取不到B客戶端對象,那麼爲了可讓多個客戶端同時併發訪問服務端,那麼服務端最好就是將每個客戶端封裝到一個單獨的線程,這樣就可以同時處理多個客戶端的請求。如何定義線程呢?只要明確每個客戶端要在服務端執行的代碼即可,將改代碼存入到run方法中。

import java.io.*;
import java.net.*;


//客戶端
class UploadClient 
{
	public static void main(String[] args) 
	{
		//判斷上傳的文件是否符合要求
		if(args.length!=1)
		{
			System.out.println("請選擇一個jpg格式的圖片");
			return ;
		}
		File file = null;
		try{
			file = new File(args[0]);
			if(!(file.exists() && file.isFile()))
			{
				System.out.println("該文件有問題,要麼補存在,要麼不是文件");
				return ;
			}

			if(!file.getName().endsWith(".jpg"))
			{
				System.out.println("圖片格式錯誤,請重新選擇");
				return ;
			}
			if(file.length()>1024*1024*5)
			{
				System.out.println("文件過大,沒安好心");
				return ;
			}
		}catch (Exception e){
			throw new RuntimeException("獲取文件失敗");
		}
		//創建客戶端,接收客戶端流對象
		Socket s = null;
		FileInputStream fis = null;
		try{
			s = new Socket("192.168.1.101",10003);
			fis = new FileInputStream(file);
			OutputStream out = s.getOutputStream();
			//將數據寫入到讀取流中,傳送給服務端
			byte[] b = new byte[1024];
			int len = 0;
			while((len=fis.read(b))!=-1)
			{
				out.write(b,0,len);
			}
			s.shutdownOutput();
			//獲取服務端反饋的信息
			InputStream in = s.getInputStream();
			byte[] by = new byte[1024];
			int n = in.read(by);
			System.out.println(new String(by,0,n));

		}catch (IOException e){
			throw new RuntimeException("服務創建或讀取流失敗");
		}
		finally{
			try{
				if(fis!=null)
					fis.close();
			}catch (IOException e){
				throw new RuntimeException("讀取流關閉失敗");
			}
			try{
				if(s!=null)
					s.close();
			}catch (IOException e){
				throw new RuntimeException("資源關閉失敗");
			}
		}
	}
}

//服務端
//創建服務端多線程
class ServerThread implements Runnable
{
	private Socket s;
	ServerThread(Socket s)
	{
		this.s = s;
	}
	public void run()
	{
		//獲取客戶端的ip
		int count = 1;
		String ip = s.getInetAddress().getHostAddress();
		System.out.println(ip+"...connected");
		FileOutputStream fos = null;
		try{
			//獲取客戶端數據,判斷文件是否重名
			InputStream in = s.getInputStream();
			File dir = new File("D:\\File\\pics");
			File file = new File(dir,ip+".jpg");
			while(file.exists())
				file = new File(dir,ip+"("+(count++)+").jpg");
			//將數據寫入到指定文件中
			fos = new FileOutputStream(file);
			byte[] b = new byte[1024];
			int len = 0;
			while((len=in.read(b))!=-1)
			{
				fos.write(b,0,len);
			}
			//將信息反饋給客戶端
			OutputStream out = s.getOutputStream();
			out.write("上傳成功".getBytes());
		}catch (Exception e){
			throw new RuntimeException(ip+"上傳失敗");
		}
		finally{
			try{
				if(fos!=null)
					fos.close();
			}catch (IOException e){
				throw new RuntimeException("寫入流關閉失敗");
			}
			try{
				if(s!=null)
					s.close();
			}catch (IOException e){
				throw new RuntimeException("資源關閉失敗");
			}
		}
	}
}

//服務端
class UploadServer
{
	public static void main(String[] args) 
	{
		ServerSocket ss = null;
		Socket s = null;
		try{
			//創建服務端,並接受多個客戶端併發訪問
			ss = new ServerSocket(10003);
			while(true)
			{
				s = ss.accept();
				new Thread(new ServerThread(s)).start();
			}
		}catch (IOException e){
			throw new RuntimeException("上傳失敗");
		}
		
		finally{
			try{
				if(s!=null)
					s.close();
			}catch (IOException e){
				throw new RuntimeException("資源關閉失敗");
			}
		}
	}
}

客戶端併發登陸

客戶端通過鍵盤錄入用戶名。服務端對這個用戶名進行校驗。如果該用戶存在,在服務端顯示xxx,已登陸。並在客戶端顯示 xxx,歡迎光臨。如果該用戶存在,在服務端顯示xxx,嘗試登陸。並在客戶端顯示 xxx,該用戶不存在。最多就登錄三次。

import java.io.*;
import java.net.*;

class LoginClient 
{
	public static void main(String[] args) throws Exception
	{
		//創建服務
		Socket s = new Socket("192.168.1.101",10003);
		//讀取鍵盤錄入
		BufferedReader bufr = 
			new BufferedReader(new InputStreamReader(System.in));
		PrintWriter out = new PrintWriter(s.getOutputStream(),true);
		BufferedReader in =
			new BufferedReader(new InputStreamReader(s.getInputStream()));
		//最多三次登陸
		for(int i=0;i<3;i++)
		{
			String line = bufr.readLine();
			if(line==null)
				break;
			out.println(line);
			String info = in.readLine();
			System.out.println(info);
			if(info.contains("歡迎"))
				break;
		}
		//關閉流資源
		bufr.close();
		s.close();
	}
}
//服務端的多線程,使服務端共享
class UserThread implements Runnable
{
	private Socket s;
	UserThread(Socket s)
	{
		this.s = s;
	}
	public void run()
	{
		String ip = s.getInetAddress().getHostAddress();
		System.out.println(ip+"........connected");
		try{
			//最多登陸三次
			for(int i=0;i<3;i++)
			{
				BufferedReader in =
					new BufferedReader(new InputStreamReader(s.getInputStream()));
				String name = in.readLine();
				if(name==null)
					break;
				
				BufferedReader bufr =
					new BufferedReader(new FileReader("user.txt"));
				PrintWriter out = new PrintWriter(s.getOutputStream(),true);
				String line = null;
				boolean flag = false;

				while((line=bufr.readLine())!=null)
				{
					if(line.equals(name)){
						flag = true;
						break;
					}
				}
				//對用戶進行判斷
				if(flag){
					System.out.println(name+",已登錄");
					out.println(name+"歡迎光臨");
					break;
				}else{
					System.out.println(name+",嘗試登陸");
					out.println(name+",用戶名不存在");
				}
			}
			s.close();
		}catch (Exception e){
			throw new RuntimeException(ip+"校驗失敗");
		}
	}
}
//創建服務端
class LoginServer
{
	public static void main(String[] args) throws Exception
	{
		ServerSocket ss = new ServerSocket(10003);
		//客戶端併發訪問
		while(true)
		{
			Socket s = ss.accept();
			new Thread(new UserThread(s)).start();
		}
	}
}




---------------------- JavaEE+Android、Java培訓、期待與您交流! ----------------------

詳細請查看: http://edu.csdn.net

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章