黑馬程序員——淺談java中的網絡編程

---------------------- <a href="http://edu.csdn.net"target="blank">ASP.Net+Android+IOS開發</a>、<a href="http://edu.csdn.net"target="blank">.Net培訓</a>、期待與您交流! ----------------------

1、網絡模型:OSI參考模型和TCP/IP參考模型

圖示:

        一般來說開發處於傳輸層和網際層,應用層爲:FTPHTTP協議等,傳輸層爲:UDPTCP等,網際層爲:IP

        通常用戶操作的是應用層,而編程人員需要做的是傳輸層和網際層,用戶在應用層操作的數據,經過逐層封包,最後到物理層發送到另一個模型中,再進行逐層解包,圖示爲:

 

2、網絡通信三要素:IP地址,端口號,傳輸協議

AIP地址

        a、它是網絡中的設備標識

        b、不易記憶,可用主機名錶示,兩者存在映射關係

        c、本機迴環地址:127.0.0.1,主機名爲:localhost

IP地址:java中對應的是InetAddress類,存在於java.net包中。

InetAddress類:

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

                  InetAddress i = InetAddress.getLocalHost();

     (二)方法:

                1static InetAddress getByName(String host):獲取指定主機的IP和主機名。(最好用ip地址去獲取,主機名需要解析)

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

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

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

     (三)如何獲取任意一臺主機的IP地址對象:

                1)功能:返回InetAddress對象

                2)對於任意主機,需要指定傳入主機名的參數

注意:如果IP地址和對應的主機名,這種映射關係沒有在網絡上,就不會解析成功,返回的還是指定的IP

示例:

import java.net.*;
class IPDemo 
{
	public static void main(String[] args)throws Exception 
	{
		//獲取本類對象
		InetAddress ia=InetAddress.getLocalHost();
		
		//ip
		String address=ia.getHostAddress();
		//主機名
		String name=ia.getHostName();
		System.out.println("IP="+address+"\tname="+name);

		//獲取指定主機的ip信息
		InetAddress i=InetAddress.getByName("192.168.1.175");

		String add=i.getHostAddress();
		String na=i.getHostName();
		System.out.println("addIP="+add+"\tiname="+na);

		//獲取指定主機名的ip信息
		InetAddress[] baidu=InetAddress.getAllByName("www.baidu.com");
		for (InetAddress b :baidu)
		{
			String baddress=b.getHostAddress();
			String bname=b.getHostName();
			System.out.println("baiduIP="+baddress+"\tbaiduname="+bname);
		}
	}
}

B、端口號:

        a、用於標識進程的邏輯地址,不用進程的標識。

        b、有效端口:0 ~65535,系統使用或保留的端口是:0~ 1024

C、傳輸協議:

        即通信規則,包含TCPUDP協議

UDP

        是面向無連接,明確了對方的端口,無論在不在網上,只管傳輸,不在就會丟失數據。只求速度,應用於網絡視頻會議和聊天等應用程序中。

協議特點:

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

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

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

         d、不建立連接,速度快。

TCP

        是面向連接的,必須連接成功才能傳輸數據,應用於下載等程序上

協議特點:

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

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

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

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

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

3、通信的步驟:

        1)找到IP地址

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

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

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

 

第二講     傳輸協議

一、Socket

        1、它被稱之爲插座,相當於港口一樣,是網絡服務提供的一種機制。

        2、通信兩端都要有Socket,才能建立服務。

        3、網絡通信其實就是Socket間的通信,數據在兩個Socket間通過IO傳輸。

 

二、UDP傳輸

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

2、方法:

        1)創建 UDPSocket發送服務對象:

              DatagramSocket(),不指定端口。DatagramSocket(int port),指定端口。

        2)發送:void send(DatagramPacket p)

        3)接收: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的參數,這是因爲收到的數據太多,需要解析,通過將數據封裝成對象,易於解析,所以需要傳入參數。

注意:

        1、發送端與接收端是兩個獨立的運行程序。

        2、在發送端,要在數據包對象中明確目的地IP及端口。

        3、在接收端,要指定監聽的端口。

示例:

/*
udp發送端:
需求:通過udp傳輸方式,將一段文字數據發送出去。
*/

import java.net.*;
class UdpSend 
{
	public static void main(String[] args)throws Exception 
	{
		//1、創建udp服務。通過DatagramSocket對象
		DatagramSocket ds=new DatagramSocket();
		//2、確定數據,並封裝成數據包。DatagramPacket(byte[] buf, int length, InetAddress address, int port) 
		byte[] buf="udp shi shen ma".getBytes();
		DatagramPacket dp=new DatagramPacket(buf,buf.length,InetAddress.getByName("192.168.1.175"),10000);

		//3、通過Socket服務,將已有的數據包發送出去。通過send方法。
		ds.send(dp);
		//4、關閉資源
		ds.close();
	}
}

/*
udp接收端
需求:定義一個應用程序,用於接收udp協議傳輸的數據並處理。
*/

class UdpReceive
{
	public static void main(String[] args)throws Exception
	{
		//1、創建udp Socket服務,建立端點
		//DatagramSocket ds=new DatagramSocket(10000);
		
		//一直處於接收狀態
		while(true)
		{
			//1、創建udp Socket服務,建立端點
			DatagramSocket ds=new DatagramSocket(10000);
			//2、定義數據包。用於存儲數據
			byte[] buf=new byte[1024];
			DatagramPacket dp=new DatagramPacket(buf,buf.length);
			//3、通過Socket服務的receive方法將接收到的數據存入數據包中
			ds.receive(dp);//阻塞式方法
			//4、通過數據包的方法獲取其中的數據
			String ip=dp.getAddress().getHostName();
			String data=new String(dp.getData(),0,dp.getLength());
			int port=dp.getPort();
			System.out.println(ip+"::"+data+"::"+port);
			//5、關閉資源
			ds.close();
		}
		//5、關閉資源
		//ds.close();
	}
}

練習一

 
/*
用鍵盤錄入的方式,來發送數據
*/

import java.net.*;
import java.io.*;
//UDP 發送端
class UdpSend  
{
	public static void main(String[] args)throws Exception
	{
		//1、創建udp Socket服務
		DatagramSocket ds=new DatagramSocket(9999);
		//2、確定數據,從鍵盤錄入,並把鍵盤錄入的數據封裝成數據包
		DatagramPacket dp=null;
		BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
		String line=null;
		while((line=br.readLine())!=null)
		{
			byte[] buf=line.getBytes();
			dp=new DatagramPacket(buf,buf.length,InetAddress.getByName("192.168.1.255"),10000);
			//3、通過Socket服務,將已有的數據包發送出去
			ds.send(dp);
			if ("886".equals(line))
			{
				break;
			}
		}
		//4、關閉資源
		ds.close();
		
	}
}

//UDP 接收端
class UdpReceive
{
	public static void main(String[] args)throws Exception
	{
		//1、創建udp Socket服務
		DatagramSocket ds=new DatagramSocket(10000);
		//一直處於接收狀態
		while (true)
		{
			//2、定義數據包,用於存儲數據
			byte[] buf=new byte[1024];
			DatagramPacket dp=new DatagramPacket(buf,buf.length);
			//3、通過Socket服務,將數據接收並存儲進數據包中
			ds.receive(dp);
			//4、通過數據包的方法獲取其中的數據
			String ip=dp.getAddress().getHostAddress();
			String data=new String(dp.getData(),0,dp.getLength());
			int port=dp.getPort();

			System.out.println("IP:"+ip+"=="+data);
		}
		//5、關閉資源
		//ds.close();
	}
}	

練習二

/*
編寫一個聊天程序。
有收數據的部分,和發數據的部分。
這兩部分需要同時執行。
那就需要用到多線程技術。
一個線程控制收,一個線程控制發。

因爲收和發動作是不一致的,所以要定義兩個run方法。
而且這個兩個方法要封裝到不同的類中。

*/
//Udp發送線程

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

class UdpSend implements Runnable
{
	//定義Socket服務引用
	private	DatagramSocket ds;
	UdpSend(DatagramSocket ds)
	{
		this.ds=ds;
	}
	//複寫run方法
	public void run()
	{
		try
		{
			//2、確定數據,從鍵盤錄入,並把鍵盤錄入的數據封裝成數據包
			DatagramPacket dp=null;
			BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
			String line=null;
			while((line=br.readLine())!=null)
			{
				byte[] buf=line.getBytes();
				dp=new DatagramPacket(buf,buf.length,InetAddress.getByName("192.168.1.255"),10000);
				//3、通過Socket服務,將已有的數據包發送出去
				ds.send(dp);
				if ("886".equals(line))
				{
					break;
				}
			}
			//4、關閉資源
			ds.close();
		}
		catch (Exception e)
		{
			throw new RuntimeException("發送數據失敗");
		}		
	}
}

//Udp接收線程
class UdpReceive implements Runnable
{
	//定義Socket服務引用
	private	DatagramSocket ds;
	UdpReceive(DatagramSocket ds)
	{
		this.ds=ds;
	}
	//複寫run方法
	public void run()
	{
		try
		{
			//一直處於接收狀態
			while (true)
			{
				//2、定義數據包,用於存儲數據
				byte[] buf=new byte[1024];
				DatagramPacket dp=new DatagramPacket(buf,buf.length);
				//3、通過Socket服務,將數據接收並存儲進數據包中
				ds.receive(dp);
				//4、通過數據包的方法獲取其中的數據
				String ip=dp.getAddress().getHostAddress();
				String data=new String(dp.getData(),0,dp.getLength());
				int port=dp.getPort();

				System.out.println("IP:"+ip+"=="+data);
			}
			//5、關閉資源
			//ds.close();
		}
		catch (Exception e)
		{
			throw new RuntimeException("接收端接收數據失敗");
		}
	}
}

class UdpChatDemo
{
	public static void main(String[] args)throws Exception
	{
		new Thread(new UdpSend(new DatagramSocket())).start();
		new Thread(new UdpReceive(new DatagramSocket(10000))).start();
	}
}

三、TCP傳輸

1TCP分客戶端和服務端。客戶端對應的對象是Socket,服務端對應的對象是ServerSocket

2、方法:

        1)創建客戶端對象:

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

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

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

        3Socket accept():監聽並接受到此套接字的連接

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

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

        6InputStream getInputStream():返回此套接字的輸入流,Socket對象調用

        7OutputStream getOutputStream():返回套接字的輸出流,Socket對象調用

3、基本思路

客戶端:

        1)客戶端需要明確服務器的ip地址以及端口,這樣纔可以去試着建立連接,如果連接失敗,會出現異常。

        2)連接成功,說明客戶端與服務端建立了通道,那麼通過IO流就可以進行數據的傳輸,而Socket對象已經提供了輸入流和輸出流對象,通過getInputStream(),getOutputStream()獲取即可。

        3)與服務端通訊結束後,關閉Socket

服務端:

        1)服務端需要明確它要處理的數據是從哪個端口進入的。

        2)當有客戶端訪問時,要明確是哪個客戶端,可通過accept()獲取已連接的客戶端對象,並通過該對象與客戶端通過IO流進行數據傳輸。

        3)當該客戶端訪問結束,關閉該客戶端。

4、步驟

客戶端:

        通過查閱Socket對象的API文檔,發現在該對象在建立時,就可去連接指定主機,因爲TCP是面向連接的,所以在建立Socket服務時,就要有服務端存在,並連接成功,形成通路後,再通過該通道進行數據的傳輸。

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

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

         3)通過輸出流的write()方法將要發送的數據寫入到流中

         4)關閉Socket流資源

服務端:

        服務器套接字等待請求通過網絡傳入。它基於該請求執行某些操作,然後可能向請求者返回結果。需監聽一個端口。

         1)建立服務端的Socket服務,並監聽一個端口。通過ServerSocet帶端口參數的構造函數

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

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

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

示例:

/*
需求:客戶端給服務端發送數據,服務端收到後,給客戶端反饋信息。
*/
//客戶端
import java.net.*;
import java.io.*;
class  TcpClient
{
	public static void main(String[] args) throws Exception
	{
		//1、創建客戶端的Socket服務。指定目的主機和端口
		Socket s=new Socket("127.0.0.1",10000);
		//2、獲取Socket流中輸出流,發送數據
		OutputStream out=s.getOutputStream();
		out.write("你好!".getBytes());
		//3、獲取Socket流中的輸入流,用來接收服務端的反饋信息並打印
		InputStream in=s.getInputStream();
		
		byte[] buf=new byte[1024];
		int len=in.read(buf);//讀取反饋的數據

		//輸出接收的數據
		System.out.println(new String(buf,0,len));

		s.close();//關閉資源
	}
}


//服務端

class TcpServer
{
	public static void main(String[] args)throws Exception
	{
		//1、創建服務端的Socket服務,並監聽一個端口
		ServerSocket ss=new ServerSocket(10000);
		//2、通過accept方法獲取連接過來的客戶端對象。
		Socket s=ss.accept();
		//獲取客戶端ip
		String ip=s.getInetAddress().getHostName();
		System.out.println(ip+"connected....");
		//3、獲取對應客戶端對象的讀取流讀取發過來的數據,並打印
		InputStream in=s.getInputStream();
		byte[] buf=new byte[1024];
		int len=in.read(buf);
		System.out.println(new String(buf,0,len));

		//4、調用對應的客戶端的輸出流寫入返回數據
		OutputStream out=s.getOutputStream();
		out.write("哥們,收到!".getBytes());

		//關閉資源
		s.close();
		ss.close();//可選操作
	}
}

練習一

/*
練習
需求:建立一個文本轉換服務器
客戶端給服務端發送文本,服務端會將文本轉成大寫再返回給客戶端。
而且客戶端可以不斷的進行文本轉換。當客戶端輸入over時,轉換結束。

分析:
客戶端:
既然是操作設備上的數據,那麼就可以使用io技術,並按照io的操作規律來思考。
源:鍵盤錄入
目的:網絡設備,網絡輸出流。
而且操作的是文本數據。可以選擇字符流。

步驟:
1、建立服務
2、獲取鍵盤錄入
3、將數據發給服務端
4、獲取服務端返回的大寫數據
5、結束,管資源。

都是文本數據,可以使用字符流進行操作,同時提高效率,加入緩衝。


此練習出現的問題:
現象:客戶端和服務端都在莫名的等待。
原因:因爲客戶端和服務端都有阻塞式方法。這些方法沒有讀到結束標記。那麼就一直等。而導致兩端都在等待。
解決:需要用到刷新和換行的方式將寫入和讀取的數據從流中刷新到內存中
    方式一:可用高效緩衝區類的newLine()換行作爲結束標記,並用flush()進行刷新。
方式二:可用PrintWriter(s.getOutputStrean(),true)創建輸出流對象,true作用是刷新,通過打印方法println()換行,“ln”表示換行。

*/

import java.io.*;
import java.net.*;
class  TcpClient
{
	public static void main(String[] args) throws Exception
	{
		//創建Socket服務
		Socket s=new Socket("127.0.0.1",10000);
		
		//定義讀取鍵盤數據的流對象
		BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
		
		//定義目的,將數據寫入到Socket輸出流。發給服務端
		//BufferedWriter bwout=new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
		PrintWriter pw=new PrintWriter(s.getOutputStream(),true);
		
		//定義一個Socket讀取流,讀取服務端返回的大寫信息。
		BufferedReader brin=new BufferedReader(new InputStreamReader(s.getInputStream()));

		String line=null;
		while ((line=br.readLine())!=null)
		{
			
			if("over".equals(line))
				break;

			//bwout.write(line);//寫入輸出流
			//bwout.newLine();//換行
			//bwout.flush();//刷新
			pw.println(line);//將數據寫入流中

			String data=brin.readLine();//讀取返回的信息
			System.out.println(data);

		}
		br.close();//關流
		s.close();
	}
}
/*
服務端:
源:Socket讀取流
目的:Socket輸出流
都是文本,裝飾
*/

class TcpServer
{
	public static void main(String[] args)throws Exception
	{
		//創建服務端的ServerSocket服務,並指定監聽端口
		ServerSocket ss =new ServerSocket(10000);
		
		//獲取客戶端連接
		Socket s=ss.accept();

		//獲取客戶端ip
		System.out.println(s.getInetAddress().getHostName()+" connected.......");

		//讀取Socket讀取流中的數據
		BufferedReader brin=new BufferedReader(new InputStreamReader(s.getInputStream()));

		//將大寫數據寫入到Socket輸出流,併發送給客戶端。
		//BufferedWriter bwout=new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
		PrintWriter pw=new PrintWriter(s.getOutputStream(),true);

		String line=null;
		while ((line=brin.readLine())!=null)
		{
			System.out.println(line);

			//bwout.write(line.toUpperCase());//將讀到數據轉換爲大寫後返回
			//bwout.newLine();//換行
			//bwout.flush();//刷新

			pw.println(line.toUpperCase());//將讀到數據轉換爲大寫後返回
		}
		s.close();//關流
		ss.close();關閉資源(可選)
	}
}

練習二

/*
需求:向服務器上傳一個文件,服務返回一條信息
1、客戶端:
源:硬盤上的文件;目的:網絡設備,即網絡輸出流。
若操作的是文本數據,可選字符流,並加入高效緩衝區。若是媒體文件,用字節流。

2、服務端:
源:socket讀取流;目的:socket輸出流。

3、出現的問題: 
現象:
a、文件已經上傳成功了,但是沒有得到服務端的反饋信息。
b、即使得到反饋信息,但得到的是null,而不是“上傳成功”的信息

原因:
a、因爲客戶端將數據發送完畢後,服務端仍然在等待這讀取數據,並沒有收到結束標記,就會一直等待讀取。
b、上個問題解決後,收到的不是指定信息而是null,是因爲服務端寫入數據後,需要刷新,才能將信息反饋給客服端。

解決:
方法一:定義結束標記,先將結束標記發送給服務端,讓服務端接收到結束標記,然後再發送上傳的數據。但是這樣定義可能會發生定義的標記和文件中的數據重複,而導致提前結束。
   方法二:定義時間戳,由於時間是唯一的,在發送數據前,先獲取時間,發送完後在結尾處寫上相同的時間戳,在服務端,接收數據前先接收一個時間戳,然後在循環中判斷時間戳以結束標記。
  方法三:通過socket方法中的shutdownOutput(),關閉輸入流資源,從而結束傳輸流,以給定結束標記。通常用這個方法。
*/

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


//客戶端
class  TcpClient
{
	public static void main(String[] args) throws Exception
	{
		//創建Socket服務
		Socket s=new Socket("127.0.0.1",10000);
		
		//定義讀取流讀取文件數據
		BufferedReader br=new BufferedReader(new FileReader("TcpDemo.java"));

		//定義目的,將數據寫入到Socket輸出流。發給服務端
		//BufferedWriter bwout=new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
		PrintWriter pw=new PrintWriter(s.getOutputStream(),true);

		//定義一個Socket讀取流,讀取服務端返回信息。
		BufferedReader brin=new BufferedReader(new InputStreamReader(s.getInputStream()));

		String line=null;
		while ((line=br.readLine())!=null)
		{
			pw.println(line);
		}
		
		s.shutdownOutput();//關閉客戶端的輸出流。相當於給流中加入一個結束標記-1.

		System.out.println(brin.readLine());//接收返回信息
		
		br.close();
		s.close();
	}
}

//服務端
class TcpServer
{
	public static void main(String[] args)throws Exception
	{
		//創建服務端的ServerSocket服務,並指定監聽端口
		ServerSocket ss =new ServerSocket(10000);
		
		//獲取客戶端連接
		Socket s=ss.accept();

		//獲取客戶端ip
		System.out.println(s.getInetAddress().getHostName()+" connected.......");

		//讀取Socket讀取流中的數據
		BufferedReader brin=new BufferedReader(new InputStreamReader(s.getInputStream()));

		//將接收到的數據寫入文件中
		PrintWriter out=new PrintWriter(new FileWriter("TcpDemo.txt"),true);
		
		//將返回信息寫入Socket流的寫入流中
		BufferedWriter bwout=new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));

		String line=null;
		while ((line=brin.readLine())!=null)
		{
			out.println(line);
		}

		//PrintWriter pw = new PrintWriter(s.getOutputStream(),true);
		//pw.println("上傳成功");
		
		bwout.write("上傳成功!");
		bwout.newLine();//換行
		bwout.flush();//刷新

		out.close();//關流
		s.close();
		ss.close();
	}
}

 

第三講     應用

一、用TCP客戶端併發上傳圖片

1、一對一(單線程)上傳的思路:

客戶端

        a、服務端點。

        b、讀取客戶端已有的圖片數據

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

        d、讀取服務端反饋信息。

        e、關閉

服務端

        a、服務端服務,並監聽窗口

        b、獲取客戶端對象,並獲取客戶ip

        c、讀取客戶端輸入流數據

        d、寫入文件

        e、用客戶端輸出流反饋信息

        f、關流

2、單線程的服務端有個侷限性。當A客戶端連接上以後,被服務端獲取到。服務端執行具體流程。這時B客戶端連接,只能等待。因爲服務端還沒有處理完A客戶端的請求。還沒有循環回來執行下一次accept方法。所以,暫時獲取不到B客戶端對象。

        那麼爲了可以讓多個客戶端同時併發訪問服務端。服務端最好就是將每個客戶端封裝到一個單獨的線程中,這樣,就可以同時處理多個客戶端請求。

如何定義線程呢?

        只要明確了每一個客戶端要在服務端執行的代碼,將該代碼存入run方法即可。

代碼:

   
需求:併發上傳圖片
*/

import java.io.*;
import java.net.*;
//客戶端
class  PicClient
{
	public static void main(String[] args) throws Exception
	{
		//對傳入的值進行判斷
		if (args.length!=1)
		{
			System.out.println("請指定一個圖片文件!");
			return;
		}

		File 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;
		}

		//創建服務
		Socket s=new Socket("localhost",10000);
		//讀取圖片數據
		FileInputStream fis=new FileInputStream(file);
		
		//用Socket服務輸出流寫入數據
		OutputStream out =s.getOutputStream();

		BufferedReader in=new BufferedReader(new InputStreamReader(s.getInputStream()));

		byte[] buf=new byte[1024];

		int len=0;

		while ((len=fis.read(buf))!=-1)
		{
			out.write(buf,0,len);
		}

		//結束標記,表示文件數據已經上傳完了
		s.shutdownOutput();

		String info=in.readLine();//讀取返回信息
		System.out.println(info);

		fis.close();//關流
		s.close();

	}
}

//服務端
class PicServer
{
	public static void main(String[] args)throws Exception
	{
		//創建服務,監聽端口
		ServerSocket ss=new ServerSocket(10000);
		
		while (true)
		{
			//獲取客戶端對象
			Socket s=ss.accept();
			//客戶端執行線程
			new Thread(new PicThread(s)).start();
		}
		
		//ss.close();
	}
}

//利用多線程實現併發上傳
class PicThread implements Runnable
{
	private Socket s;
	PicThread(Socket s)
	{
		this.s=s;
	}
	public void run()
	{
		int count=1;
		//獲取客戶端ip
		String ip=s.getInetAddress().getHostAddress();
		try
		{		
			System.out.println(ip+"  connected.....");

			//通過客戶端的讀取流讀取數據
			InputStream in=s.getInputStream();
			//文件保存路徑
			File dir =new File("C:\\Users\\asus\\Desktop");
			//文件名
			File file=new File(dir,ip+".jpg");
			//判斷文件是否存在
			while(file.exists())
			{
				file=new File(dir,ip+"("+(count++)+").jpg");
			}

			//將數據寫入到指定文件中
			FileOutputStream fos=new FileOutputStream(file);

			byte[] buf=new byte[1024];
			int len=0;
			while ((len=in.read(buf))!=-1)
			{
				fos.write(buf,0,len);
			}

			//將收到圖片數據的信息返回給客戶端
			OutputStream out=s.getOutputStream();
			
			out.write("上傳成功!".getBytes());

			fos.close();//關流
			s.close();
		}
		catch (Exception e)
		{
			throw new RuntimeException(ip+"圖片上傳失敗");
		}
	}
}


二、客戶端併發登錄

        客戶端通過鍵盤錄入用戶名,服務端對這個用戶名進行校驗。

        如果該用戶存在,在服務端顯示xxx,已登陸;並在客戶端顯示xxx,歡迎光臨。

        如果用戶不存在,在服務端顯示xxx,嘗試登陸;並在客戶端顯示xxx,該用戶不存在。

        最多就登錄三次。

代碼: 

import java.io.*;
import java.net.*;
//客戶端
class  LoginClient
{
	public static void main(String[] args) throws Exception
	{
		//創建服務
		Socket s=new Socket("localhost",10000);
		//鍵盤錄入
		BufferedReader br=new BufferedReader(new InputStreamReader(System.in));

		
		//用Socket服務輸出流寫入數據
		PrintWriter out =new PrintWriter(s.getOutputStream(),true );

		//接收服務器返回的信息
		BufferedReader in=new BufferedReader(new InputStreamReader(s.getInputStream()));

		String line=null;

		for(int x=0;x<3;x++)
		{
			line=br.readLine();//讀取鍵盤錄入
			if (line==null)
			{
				break;//如果鍵盤沒有輸入,則直接結束
			}

			out.println(line);//將數據寫入流中

			String info=in.readLine();//讀取返回信息

			System.out.println(info);

			if (info.contains("歡迎"))//---------------
			{
				break;//如果登錄成功,就跳出循環
			}
		}

		br.close();//關流
		s.close();
	}
}

//服務端
class LoginServer
{
	public static void main(String [] args)throws Exception
	{
		//創建服務,監聽端口
		ServerSocket ss=new ServerSocket(10000);
		
		while (true)
		{
			//獲取客戶端對象
			Socket s=ss.accept();
		
			//客戶端執行線程
			new Thread(new LoginThread(s)).start();
		}
		
		//ss.close();
	}
}

//利用多線程實現併發登錄
class LoginThread implements Runnable
{
	private Socket s;
	LoginThread(Socket s)
	{
		this.s=s;
	}
	public void run()
	{
		//獲取客戶端ip
		String ip=s.getInetAddress().getHostAddress();
		System.out.println(ip+"  connected.....");
		try
		{		
			for (int x=0;x<3 ;x++ )
			{	
				//通過客戶端的讀取流讀取數據
				BufferedReader in=new BufferedReader(new InputStreamReader(s.getInputStream()));
				
				//讀取數據庫中的數據,這裏用文件來表示數據庫
				BufferedReader br=new BufferedReader(new FileReader("users.txt"));

				String line=in.readLine();//讀取客戶端數據
				if (line==null)//--------------
				{
					break;//如果客戶端沒有發送數據,則跳出循環
				}
				String data=null;
				boolean flag=false;//設置標記
				//讀取數據庫中的用戶數據
				while ((data=br.readLine())!=null)
				{
					if (line.equals(data))
					{
						flag=true;//如果用戶存在,則將標記設爲true
						break;
					}
				}

				//將數據寫入到指定文件中
				PrintWriter out=new PrintWriter(s.getOutputStream(),true);

				if (flag)
				{
					System.out.println(line+",已登陸!");
					
					out.println(line+",歡迎光臨!");

					break;//-----------
				}
				else
				{
					System.out.println(line+",嘗試登陸!");
					out.println(line+",用戶名不存在!");
				}	
			}
			s.close();//關流
		}
		catch (Exception e)
		{
			throw new RuntimeException("用戶登陸失敗");
		}	
	}
}


三、客戶端和服務的瀏覽器演示

        瀏覽器是一個標準的客戶端,它可以對服務端傳送過來的數據消息進行解析,把符合應用層協議的消息部分解析後,將頭信息拆包掉,傳送到應用層,只保留了正確的正文主題部分顯示在主體部分上。

        而由於使用java編譯是在傳輸層和網際層處理的,所以,會接受到全部的消息,包含了頭消息。而瀏覽器處於應用層,已將發送來的頭消息去除,只留下了主體信息。

示例:

        自定義服務器,用瀏覽器訪問:

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

//服務器
class  ServerDemo
{
	public static void main(String[] args)throws Exception 
	{
		//創建服務,監聽端口
		ServerSocket ss=new ServerSocket(10000);
		//獲取客戶端
		Socket s=ss.accept();
		//顯示ip
		String ip=s.getInetAddress().getHostAddress();

		System.out.println(ip);
		//讀取客戶端讀取流數據
		InputStream in=s.getInputStream();

		byte[] buf=new byte[1024];
		int len=in.read(buf);
		//顯示數據
		System.out.println(new String(buf,0,len));
		//返回信息寫入客戶端輸出流
		PrintWriter out=new PrintWriter(s.getOutputStream(),true);///true一定要記得寫

		out.println("<font color='red' size='7'>客戶端你好!</font>");

		s.close();//關流
		ss.close();
	}
}

 

四、URLURLConnection

1URL

        URI:範圍更大,條形碼也包含於此範圍

        URL:範圍較小,即域名

方法:

        1)構造函數:URL(String protocol,String host,int port,String file);//根據指定 protocolhostport號和 file 創建 URL對象。

        2String getProtocol();//獲取協議名稱

        3String getHost();//獲取主機名

        4int getPort();//獲取端口號

        5String getFile();//獲取URL文件名

        6String getPath();//獲取此URL的路徑部分

        7String getQuery();//獲取此URL的查詢部,客戶端傳輸的特定信息

注:一般輸入網址,是不帶端口號的,此時可進行獲取,通過獲取網址返回的port,若port-1,則分配一個默認的80端口,如

        int port = getPort();

        if(port == -1)

              port = 80;

2URLConnection

方法:

        1URLConnection openConnection();//URL調用此方法,返回一個 URLConnection 對象,它表示到 URL 所引用的遠程對象的連接。

        2InputStream getInputStream();//獲取輸入流

        3OutputStream getOutputStream();//獲取輸出流

示例:

/*
自定義瀏覽器,顯示網頁信息
*/

import java.io.*;
import java.awt.*;
import java.awt.event.*;
import java.net.*;

class MyIEGUIDemo
{
	//定義所需組件引用
	private Frame f;
	private Button but,bok;
	private TextField tf;
	private TextArea ta;

	//構造函數
	MyIEGUIDemo()
	{
		init();
	}

	//窗體基本設置於功能實現
	public void init()
	{
		//組件實例化
		f=new Frame("我的Window");
		but=new Button("跳轉");
		tf=new TextField(50);
		ta=new TextArea(25,60);

		//基本設置
		f.setBounds(300,150,500,500);
		f.setLayout(new FlowLayout());

		//添加組件
		f.add(tf);
		f.add(but);
		f.add(ta);

		//窗體事件
		myEvent();

		//窗體顯示
		f.setVisible(true);
	}

	//註冊事件
	public void myEvent()
	{
		//窗體關閉功能
		f.addWindowListener(new WindowAdapter()
		{
			public void windowClosing(WindowEvent e)
			{
				System.exit(0);
			}
		});

		//“跳轉”按鈕事件
		but.addActionListener(new ActionListener()
		{
			public void actionPerformed(ActionEvent e)
			{
				showFile();//顯示網頁內容在文本區中
			}
		});

		

		//文本框鍵盤事件
		tf.addKeyListener(new KeyAdapter()
		{
			public void keyPressed(KeyEvent e)
			{
				//如果鍵盤按下Enter鍵,就將網頁內容顯示在文本區中
				if(e.getKeyCode()==KeyEvent.VK_ENTER)
					showFile();
			}
		});
	}

	//顯示網頁內容
		private void showFile()
		{
			ta.setText("");
			String path=tf.getText();//獲取輸入的路徑
			try
			{
				//封裝地址對象
URL url =new URL(path);
連接網頁服務器
				URLConnection conn=url.openConnection();
				//讀取流,用於讀取服務器返回數據
				InputStream in=conn.getInputStream();

				byte[] buf=new byte[1024*1024];

				int len=in.read(buf);
				//將數據顯示在文本區中
				ta.append(new String(buf,0,len));
			}
			catch (Exception e)
			{
				throw new RuntimeException("連接"+path+"網站失敗");
			}
		}

	public static void main(String[] args) 
	{
		//運行窗體
		new MyIEGUIDemo();
	}
}

 

小知識點

1Socket類的構造函數中,有一個空參數的構造函數:

        Socket()//通過系統默認類型的 SocketImpl創建未連接套接字對象

       可以通過connect(SocketAddress endpoint)方法來連接服務器。而SocketAddress是一個抽象類,它的子類InetSocketAddress實現了IP套接字地址(IP地址+端口號)。所以就可以連接到服務器了。

2ServerSocket對象中的構造函數:

        ServerSocket(int port,int backlog),其中的backlog表示隊列的最大長度,即最多連入客戶端的個數,即最大連接數。

3、在瀏覽器輸入網址訪問一臺主機所做的操作:

       如輸入http://61.135.169.125,可以直接連接此ip的主機,而我們一般是輸入主機名:http:/www.baidu.ocm(百度主機對應的ip地址就是:61.135.169.125),那麼此時瀏覽器做了神馬操作呢?

       也就是說如何通過主機名獲取IP地址,從而連接到這臺主機呢?這就需要將主機名翻譯成IP地址,即域名解析:DNS

        在進行訪問的時候,會先在本地的hosts文件(c:\windows\system32\drivers\ext\host)中找對應的映射。若有,則直接返回請求;若無,則到公網的映射列表即DNS中找對應的映射,找到後,將主機名對應的IP地址返回給本機,本機通過這個IP地址找到對應的服務器。

示意圖:

 host應用:可屏蔽一些惡意網址,即將對應的映射關係寫入hosts中,將IP地址改爲本機的迴環地址,那麼會直接找到hosts,就不會將請求發送出去了。


---------------------- <a href="http://edu.csdn.net"target="blank">ASP.Net+Android+IOS開發</a>、<a href="http://edu.csdn.net"target="blank">.Net培訓</a>、期待與您交流! ----------------------


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