黑馬程序員——Java基礎知識——網絡編程

------Java培訓、Android培訓、iOS培訓、.Net培訓、期待與您交流! 

一、概述

         首先通過下面的圖例簡單演示不同主機的程序之間的數據傳輸過程:


           在整個信息發送過程中,要找到對方的IP地址,將數據逐層封包,通過主機至網絡層發送到對方主機上,然後向上逐層拆包,找到對方的指定應用程序,將數據發到該指定應用程序上。這樣信息就在不同主機間發送成功了。整個過程就涉及到了我們要學習的網絡通信的重要知識點:網絡模型、網絡通信要素(IP地址、端口、傳輸協議)。

          網絡模型:有OSI參考模型和TCP/IP參考模型。二者的結構如下圖:

 

       我們接下來學習的網絡編程都處於傳輸層和網際層,每一個層都有對應的通信協議。應用層對應的是FTP和HTTP協議,傳輸層是TCP和UDP,網際層的協議爲IP。從上面的QQ發送信息的過程中可以看到,用戶在應用層傳入數據,經過逐層封包,最後到達主機至網絡層發送給另一臺主機,然後再逐級向上拆包。

        網絡通訊三要素:IP地址、端口、傳輸協議。

         (1)IP地址:它是網絡中的設備標識,可以用主機名來表示,兩者存在映射關係。本機的迴環地址是:127.0.0.1 (主機名:localhost)。Java中把IP地址封裝成了一個InetAddress類,存放在java.net包中。

       通過這個類的方法,可以實現對IP地址、主機名的獲得等功能,常用功能如下:

       1.獲得該類對象: InetAddress i=InetAddress.getByName(String host);獲取指定主機的InetAddress對象,注意最好用IP地址獲取,因爲主機名需要解析。

                                    InetAddress i=InetAddress.getByName(String host);獲取指定主機的所有InetAddress對象,返回一個數組

                                    InetAddress  i=InetAddress.getLocalHost();獲取本機的InetAddress對象。

       2.獲取IP地址和主機名

                                   i.getHostAddress();返回IP地址字符串(文本形式)。

                                   i.getHostName();返回IP地址對應的主機名。

       注意,通過上面的方法,傳入一個指定的主機名,就可以獲得它的IP地址對象,獲取其具體的一個或多個地址。但如果IP地址和對應的主機名的映射關係在網絡上不存在,就會解析失敗。

       下面通過一段代碼進行演示:

	public static void main(String[] args) throws UnknownHostException {
		//獲取sohu的IP地址對象
		InetAddress[] i=InetAddress.getAllByName("www.sohu.com");
		//獲取具體的IP地址
		for(InetAddress ia:i)
			System.out.println(ia.getHostAddress());
		//獲取本機的IP對象,並獲取具體IP地址和主機名
		InetAddress i1=InetAddress.getLocalHost();
		String name=i1.getHostName();
		String  ip=i1.getHostAddress();
		System.out.println(name+":"+ip);
	}
        (2)端口號:用於標示進程的邏輯地址,不同進程的標識。有效端口的設置範圍爲0~65535,注意系統會保留0~1024端口。所以設置時要避開。

        (3)傳輸協議: 就是通訊規則,主要是TCP和UDP協議。下面對這兩種協議的特點和應用分別進行介紹:

        UDP協議:面向無連接,不建立連接,將數據及源和目的封裝在數據包中來發送,每個數據包的大小限制住在64K以內,因無連接,速度快,但可能出現數據丟失,是不可靠協議。應用於聊天程序、桌面共享、視頻會議等。

        TCP協議:面向連接,在建立連接後,形成傳輸數據的通道,可以進行大數據量的傳輸。因通過三次握手建立連接,速度稍慢,但能保證數據的安全性,是可靠的協議。應用於下載文件、文件傳輸等程序中。

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

         網絡通信的步驟:

         (1)找到IP地址。

         (2)數據要發送到對方的指定應用程序上,這時就要用數字標識該應用程序上,就是用到端口。如果沒有定義端口,數據就無法發送到該應用程序上。

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

、TCP和UDP傳輸的建立與應用

       1.Socket:端點,就相當於港口,是網絡服務提供的一種機制,通信兩端都要有Socket對象,才能建立通信服務。網絡通信就是Socket之間的通信,數組在兩個Socket之間通過IO傳輸。

       2.UDP傳輸

      DatagramSocket類,表示用來發送和接收數據報包的套接字,即Socket;DatagramPacket類,表示數據報包。下面就通過建立這個兩個類的對象,調用它們的特有方法,實現數據的UDP傳輸。具體步驟如下:

       (1)發送端:

            1.通過創建DatagramSocket對象,建立Socket服務。可以指定本程序端口,也可不指定根據系統自動分配。

            2.確定數據,將要發送的數據封裝到數據報包DatagramPacket對象中,數據報包對象中還要指定接收數據的主機和端口。

            3.通過Socket服務中的send(DatagramPacket  dp),將數據報包發送出去。

            4.關閉資源。

          如同下面這段代碼:

class UDPSend {

	public static void main(String[] args) throws IOException {
	   //創建DatagramSocket對象,建立Socket服務,並指定本程序端口。
		DatagramSocket ds=new DatagramSocket(10000);
	   //將提供的數據封裝成數據報包,並在數據報包中指定要發送到的主機和端口。
		byte[]by="大河向東流,天上的星星參北斗".getBytes();
		DatagramPacket dp=new DatagramPacket(by,by.length,InetAddress.getLocalHost(),10001);
	   //通過send方法,將數據報包發送出去
		 ds.send(dp);
	   //關閉資源
		 ds.close();
	}
	
}
        (2)接收端:
             1.創建DatagramSocket對象,建立Socket服務,定義時要監聽一個端口,就是給應用程序定義數字標識,用來接收發送

給該應用程序的數據。  

               2. 定義數據報包,用來存儲接收到的數據,

              3.通過Socket服務中的receive方法,將接收到的數據存入數據報包中。

              4.利用DatagramPacket中的特有功能提取數據信息。

              5.關閉資源。

         具體如下:

 class UDPRec {

	public static void main(String[] args) throws IOException {
		//1,創建udp socket,定義一個監聽端口。
		DatagramSocket ds = new DatagramSocket(10001);
		//2,定義數據包。用於存儲數據。
		byte[] buf = new byte[1024];
		DatagramPacket dp = new DatagramPacket(buf,buf.length);
        //3,通過receive方法將收到數據存入數據包中。
		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+"::"+data+"::"+port);
        //5,關閉資源
	      ds.close();
	}

}
        要注意的是,發送端和接收端是兩個獨立的程序,定義發送端時,要在數據報包中明確接收數據的主機和端口;定義接收端時要定義一個監聽的端口。
       下面利用UDP傳輸協議的獲取鍵盤錄入,發送數據的程序。如下:

//發送端
class UDPSend {
        public static void main(String[] args) throws IOException  {
	   //創建DatagramSocket對象,建立Socket服務,並指定本程序端口。
		DatagramSocket ds=new DatagramSocket(10000);
	    //建立流對象,與鍵盤錄入相關聯,加入緩衝技術
		BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
		String line=null;
		while((line=br.readLine())!=null){
	       if("over".equals(line))
	        	 break;
	       byte[]by=line.getBytes();  
	     //將獲取的數據封裝成數據報包,並在數據報包中指定要發送到的主機和端口。
	       DatagramPacket dp=new DatagramPacket(by,by.length,InetAddress.getLocalHost(),10001);
	      //通過send方法,將數據報包發送出去
		   ds.send(dp);
		}
	   //關閉資源
		 ds.close();
	}
}
//接收端
class UDPRec {
       public static void main(String[] args) throws IOException {
		//1,創建udp socket,定義一個監聽端口。
		DatagramSocket ds = new DatagramSocket(10001);
		//2,定義數據包。用於存儲數據。
		while(true){
		   byte[] buf = new byte[1024];
		  DatagramPacket dp = new DatagramPacket(buf,buf.length);
          //3,通過receive方法將收到數據存入數據包中。
		  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+"::"+data+"::"+port);
		}
	}

}
       定義一個聊天程序,實現信息發送和收到的同時運行,這時就需要用到多線程來實現。如下:

class UDPTest {

 	public static void main(String[] args) throws IOException {
        //建立DatagramSocket對象
 		DatagramSocket ds1=new DatagramSocket();
 		DatagramSocket ds2=new DatagramSocket(10002);
 		//啓動程序
 		new Thread(new SendDemo(ds1)).start();
 		new Thread(new RecDemo(ds2)).start();
	}

}
//定義一個類,表示發送,實現Runnable接口
class  SendDemo implements Runnable{
    //定義DatagramSocket的引用
	private DatagramSocket ds;
	SendDemo(DatagramSocket ds){
	   this.ds=ds;
	}
	//複寫run方法,定義要運行的代碼
	public void run(){
		BufferedReader br=null;
		try {
			br=new BufferedReader(new InputStreamReader(System.in));
			String line=null;
			//獲取鍵盤錄入,封裝成數據報包,併發送出去
			while((line=br.readLine())!=null){
				if("88".equals(line))
					break;
				byte[]by=line.getBytes();
				DatagramPacket dp=new DatagramPacket(by,by.length,InetAddress.getLocalHost(),10002);
				ds.send(dp);
			}
			//關閉資源
			ds.close();
		} catch (Exception e) {
			throw new RuntimeException("獲取鍵盤錄入失敗");
		}
	}
}
//定義一個類表示接收,實現Runnable接口
class RecDemo implements Runnable{
	//定義DatagramSocket的引用
	private DatagramSocket ds;
	RecDemo(DatagramSocket ds){
	   this.ds=ds;
	}
	//複寫run方法,定義要運行的代碼
	public void run(){
		while(true){
			  byte[] buf = new byte[1024];
			  DatagramPacket dp = new DatagramPacket(buf,buf.length);
	          //通過receive方法將收到數據存入數據包中。
			  try {
				ds.receive(dp);
			} catch (IOException e) {
				throw new RuntimeException("數據接收失敗");
			}
			 //通過數據包的方法獲取其中的數據。
			  String ip=dp.getAddress().getHostAddress();
			  String data=new String(dp.getData(),0,dp.getLength());
			  if("88".equals(data))
				   break;
			  int port=dp.getPort();
			  System.out.println(ip+":"+data);

          }
       }
}

  3.TCP傳輸:

        使用TCP傳輸,要建立客戶端和服務器端,分別用Socket和ServerSocket的對象表示。建立連接後,通過Socket中的IO流進行數據傳輸。客戶端與服務器端是兩個獨立的應用程序。Socket對象建立時就可以連接指定的主機,因爲TCP是面向連接的,所以在建立Socket服務時,就要有服務器端存在,連接成功,形成通路後,就可以在該通道進行數據傳輸了。注意,空參數Socket對象可以通過connect方法連接服務器端。設置基本的TCP傳輸步驟如下:

         (1)服務器端:

                 1.創建SeverSocket對象,建立Socket服務,監聽一個端口。

                 2.通過accept方法,獲取鏈接過來的客戶端對象。這個方法是阻塞式的,沒有連接就會等。

                 3.客戶端如果過來數據,那麼服務端就使用對應的客戶端對象,並獲取到該客戶端對象的讀取流來讀取發過來的數據。

                 4.關閉服務器(可選操作)。

               如下:

class TCPServer {

	public static void main(String[] args) throws IOException {
		 //創建ServerSocket對象,建立Socket服務,指定監聽端口
		  ServerSocket  ss=new ServerSocket(10004);
		 while(true){
		  //獲取連接的客戶端對象。
		  Socket s=ss.accept();
		 //通過獲取客戶端對象的讀取流來讀取客戶端發送來的數據。
		  InputStream is=s.getInputStream();
		  byte[]by=new byte[1024];
		  int len=is.read(by);
		  System.out.println(new String(by,0,len));
		 }
	}

}
    (2)客戶端:

          1.建立Socket服務,指定要連接的主機和端口。

          2.獲取Socket服務中的輸出流,將數據寫入到流中,通過網絡發送個服務器端。

          3.如果服務器端有反饋信息,獲取Socket流中的輸入流,獲取服務器端反饋的數據信息。

          4.關閉資源。

         如下:

class TCPClient {

	public static void main(String[] args) throws Exception {
		 //建立Socket服務,指定要連接的主機和端口
		Socket s=new Socket(InetAddress.getLocalHost(),10004);
         //獲取輸出流,寫入數據。
		OutputStream os=s.getOutputStream();
		os.write("大河向東流,天上的星星參北斗".getBytes());
		//關閉資源
		s.close();
	}

}
   當客戶端進行數據傳輸給服務器端時,數據傳輸成功後,服務器端一般會有信息反饋給客戶端。程序如下:

//定義服務端
public class TCPServer {

	public static void main(String[] args) throws Exception {
		 //創建ServerSocket對象,建立Socket服務,指定監聽端口
		  ServerSocket  ss=new ServerSocket(10004);
		 while(true){
		  //獲取連接的客戶端對象。
		  Socket s=ss.accept();
		 //通過獲取客戶端對象的讀取流來讀取客戶端發送來的數據。
		  InputStream is=s.getInputStream();
		  byte[]by=new byte[1024];
		  int len=is.read(by);
		  System.out.println(new String(by,0,len));
		 //通過獲取客戶端對象的輸出流給客戶端發送反饋信息
		    OutputStream out = s.getOutputStream();
	        
			out.write("數據已接收".getBytes());
            s.close();
	     }
	}
}
//定義客戶端
class TCPClient {

	public static void main(String[] args) throws Exception {
		 //建立Socket服務,指定要連接的主機和端口
		Socket s=new Socket(InetAddress.getLocalHost(),10004);
         //獲取輸出流,寫入數據。
		OutputStream os=s.getOutputStream();
		os.write("大河向東流,天上的星星參北斗".getBytes());
		//獲取輸入流,讀取服務器端的反饋信息
		InputStream in = s.getInputStream();
        byte[] buf = new byte[1024];
        int len = in.read(buf);
        System.out.println(new String(buf,0,len));
		
    }

}

       下面是一個TCP傳輸的練習:

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

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

步驟
1,建立服務。
2,獲取鍵盤錄入。
3,將數據發給服務端。
4,服務端返回大寫數據。
5,關閉資源。

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


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

class  TransClient
{
	public static void main(String[] args) throws Exception
	{
		Socket s = new Socket(InetAddress.getLocalHost(),10005);


		//定義讀取鍵盤數據的流對象。
		BufferedReader bufr = 
			new BufferedReader(new InputStreamReader(System.in));


		//定義目的,將數據寫入到socket輸出流。發給服務端。
		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);

			String str =bufIn.readLine();
			System.out.println(str);
			
		}
                
		//關閉資源
		bufr.close();
		s.close();


	}
}
/*

服務端:
源:socket讀取流。
目的:socket輸出流。
都是文本,裝飾。



*/

class  TransServer
{
	public static void main(String[] args) throws Exception
	{
		ServerSocket ss = new ServerSocket(10005);

		Socket s = ss.accept();
		String ip = s.getInetAddress().getHostAddress();
		System.out.println(ip+"....connected");

		//讀取socket讀取流中的數據。
		BufferedReader bufIn =
			new BufferedReader(new InputStreamReader(s.getInputStream()));

		//目的。socket輸出流。將大寫數據寫入到socket輸出流,併發送給客戶端。
	
		PrintWriter out = new PrintWriter(s.getOutputStream(),true);

		String line = null;
		while((line=bufIn.readLine())!=null)
		{

		    System.out.println(line);
                    out.println(line.toUpperCase());
		}

		s.close();
		ss.close();

	}
}
      在這個程序中加入緩衝技術,提高了程序的運行效率。要注意的是read方法是阻塞式方法,當沒有讀到數據或沒有讀到結束標記時兩邊read方法都會等待。

       通過TCP傳輸,可以實現不同主機之間的文件拷貝,就是把客戶端的文件上傳到服務端上,一般上傳成功後,服務器端會提示上傳成功,如下面的這個程序:

/**
 客戶端向服務器上傳一個文件。上傳完畢後,服務器向客戶端反饋上傳成功。
 */
//定義客戶端
class  TextClient
{
	public static void main(String[] args) throws Exception
	{
		Socket s = new Socket(InetAddress.getLocalHost(),10006);
        //建立讀取流,和要上傳的文件相關連
		BufferedReader bufr = 
			new BufferedReader(new FileReader("F:\\123.txt"));
        //獲取輸出流。
        PrintWriter out = new PrintWriter(s.getOutputStream(),true);
        //實現上傳     
        String line = null;
		while((line=bufr.readLine())!=null)
		{
			out.println(line);
		}
		//關閉客戶端的輸出流。相當於給流中加入一個結束標記-1.
		s.shutdownOutput();
        //獲取讀取流,爲了提高效率,加入轉換流和緩衝技術
		BufferedReader bufIn = new BufferedReader(new InputStreamReader(s.getInputStream()));
        //獲取服務器端反饋信息
        String str = bufIn.readLine();
		System.out.println(str);
        //關閉資源
		bufr.close();
        s.close();
	}
}
//定義服務端
class  TextServer
{
	public static void main(String[] args) throws Exception
	{
		ServerSocket ss = new ServerSocket(10006);

		Socket s = ss.accept();
		String ip = s.getInetAddress().getHostAddress();
		System.out.println(ip+"....connected");
        //獲取連接對象的輸入流,加入轉換流和緩衝技術,提高效率
        BufferedReader bufIn = new BufferedReader(new InputStreamReader(s.getInputStream()));
         //定義輸出流,指定目的文件
        PrintWriter out  = new PrintWriter(new FileWriter("F:\\234.txt"),true);
        //接收並輸出數據到指定文件
		String line = null;
        while((line=bufIn.readLine())!=null)
                 out.println(line);
        //給客戶端發送反饋信息
		PrintWriter pw = new PrintWriter(s.getOutputStream(),true);
		pw.println("上傳成功");
        //關閉資源
        out.close();
		s.close();
		ss.close();

	}
}

       在這個程序中,要注意的是在客戶端的上傳數據上傳完畢後,要通過shutdownOutput()方法告訴服務器端,數據已全部上傳完畢。否則服務器端一直繼續等待客戶端上傳數據。shutdownOutput()方法,相當於給輸出流加了一個結束標記。

       這個程序是單線程的,當一個客戶端連接上服務器,被服務器獲取到後,其他的客戶端就必須等待,而在實際生活中每個服務器都有大量的客戶端進行連接,這時爲了實現客戶端的併發使用服務器,就需要使用多線程技術,將每一個客戶端對象都封裝在一個線程裏,實現客戶端的併發使用。下面的程序就是服務器端實現了多線程,客戶端可以併發使用,如下:

//將每一個客戶端對象封裝到線程裏
class PicThread implements Runnable
{

	private Socket s;
	PicThread(Socket s)
	{
		this.s = s;
	}
	public void run()
	{

		int count = 1;
		String ip  = s.getInetAddress().getHostAddress();
		try
		{
			System.out.println(ip+"....connected");

			InputStream in = s.getInputStream();

			File dir =  new File("d:\\pic");

			File file = new File(dir,ip+"("+(count)+")"+".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+"上傳失敗");
		}
	}
}



class  PicServer
{
	public static void main(String[] args) throws Exception
	{
		ServerSocket ss = new ServerSocket(10007);

		//多個客戶端可以同時訪問
                 while(true)
		{
			Socket s = ss.accept();

			new Thread(new PicThread(s)).start();

		
		}

	
	}
}

下面是一個登陸服務器端的程序:

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

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

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

最多就登錄三次。
*/
import java.io.*;
import java.net.*;

//定義客戶端
class  LoginClient
{
	public static void main(String[] args) throws Exception
	{
		Socket s = new Socket(InetAddress.getLocalHost(),10008);
        //建立流對象,獲取鍵盤錄入,加入緩衝
		BufferedReader bufr = 
			new BufferedReader(new InputStreamReader(System.in));
        //獲取Socket輸出流
		PrintWriter out = new PrintWriter(s.getOutputStream(),true);
        //獲取Socket輸入流,加入緩衝
		BufferedReader bufIn =
			new BufferedReader(new InputStreamReader(s.getInputStream()));

         //限定登陸次數
		for(int x=0; x<3; x++)
		{
			String line = bufr.readLine();
            //不再輸入,循環結束
			if(line==null)
				break;
			out.println(line);
            //獲取服務端反饋
			String info = bufIn.readLine();
			System.out.println("info:"+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();
		try  
		{   //限制登陸次數
			for(int x=0; x<3; x++)
			{    
				BufferedReader bufIn = new BufferedReader(new InputStreamReader(s.getInputStream()));
                //讀取用戶傳入的登陸名
				String name = bufIn.readLine();
				//用戶不再輸入,循環結束
				if(name==null)
					break;
                //建立流對象,與登錄名列表的文件相關聯
				BufferedReader bufr = new BufferedReader(new FileReader("F:\\list.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(10008);

		//客戶端可併發訪問
         while(true)
		  {
			Socket s = ss.accept();

			new Thread(new UserThread(s)).start();
		 }
	}
}

三、瀏覽器和Tomcat服務器

          瀏覽器就是最常見的客戶端,它能夠對html代碼進行解析。下面自定義一個服務器,通過瀏覽器對其進行訪問。程序如下:

public static void main(String[] args) throws Exception {
		//建立ServerSocket對象,創建Socket服務
         ServerSocket ss=new ServerSocket(10009);
        //獲取連接客戶端對象
         Socket s=ss.accept();
        //獲取Socket服務的輸出流。  
         PrintWriter pw=new PrintWriter(s.getOutputStream(),true);
        //輸出數據
         pw.println("<font color='black' size='8'>歡迎光臨服務器</font>");
        //關閉資源
         s.close();
         ss.close();
	}
   在瀏覽器輸入IP地址和端口號,就能看到服務端的輸出數據。效果如下:


         還可以通過瀏覽器訪問Tomcat服務器,Tomcat是常用的一種服務器,它內部封裝了ServerSocket對象,可以讀取自定義的資源。可以把自定義的資源存儲在Tomcat目錄中的myweb目錄裏。下面我們就通過定義程序,模擬瀏覽器,訪問Tomcat服務器,獲取數據。這時就需要通過向Tomcat發送http請求消息頭,來獲取資源。如下:

  public static void main(String[] args)throws Exception 
	{   
		//建立Socket對象,創建Socket服務
		Socket s = new Socket("192.168.1.113",8080);
		//獲取輸出流
		PrintWriter out = new PrintWriter(s.getOutputStream(),true);
        //發送請求消息頭
		out.println("GET /myweb/2.html HTTP/1.1");//請求獲取指定資源路徑下的資源
		out.println("Accept: */*");//可以接收所有數據類型
		out.println("Accept-Language: zh-cn");//接收語言
		out.println("Host: 192.168.1.113:8080");//明確IP地址和端口
		out.println("Connection: Keep-Alive");//連接狀態
        //請求消息頭必須和消息體有空行。
		out.println();
		out.println();
        //獲取輸入流
		BufferedReader bufr = new BufferedReader(new InputStreamReader(s.getInputStream()));

		String line = null;
          
		while((line=bufr.readLine())!=null)
		{
			System.out.println(line);
		}
        //關閉資源
		s.close();

      }
}

       要注意的請求消息頭和請求消息之間一定要用空行隔開。接下來我們再結合圖形化界面,創建一個簡易的瀏覽器,因爲要獲取輸入的指定IP地址、主機名、端口等,這時就要用到URL對象,URL代表一個統一資源定位符,內部帶有協議的封裝了Socket對象,通過其特有方法獲取I要連接的服務器端,通過openConnection方法與服務器端建立連接。下面就通過URL對象,實現瀏覽器的創建和功能實現。如下:

class  IEDemo
{
	//定義組件引用
        private Frame f;
	private TextField tf;
	private Button but;
	private TextArea ta;
	
	private Dialog d;
	private Label lab;
	private Button okBut;


	IEDemo()
	{
		init();
	}
	public void init()
	{
		//窗體的創建、基本設置
                f = new Frame("my window");
		f.setBounds(300,100,600,500);
		f.setLayout(new FlowLayout());

		tf = new TextField(60);

		but = new Button("轉到");

		ta = new TextArea(25,70);


		d = new Dialog(f,"提示信息-self",true);
		d.setBounds(400,200,240,150);
		d.setLayout(new FlowLayout());
		lab = new Label();
		okBut = new Button("確定");

		d.add(lab);
		d.add(okBut);



		f.add(tf);
		f.add(but);
		f.add(ta);

                //加載事件
		myEvent();
		f.setVisible(true);
	}
	private void  myEvent()
	{
                
		okBut.addActionListener(new ActionListener()
		{
			public void actionPerformed(ActionEvent e)
			{
				d.setVisible(false);
			}
		});
		d.addWindowListener(new WindowAdapter()
		{
			public void windowClosing(WindowEvent e)
			{
				d.setVisible(false);
			}
		});
                //文本框添加鍵盤監聽
		tf.addKeyListener(new KeyAdapter()
		{
			public void keyPressed(KeyEvent e)
			{
				try
				{
				if(e.getKeyCode()==KeyEvent.VK_ENTER)
					showDir();
				}
				catch (Exception ex)
				{
					throw new RuntimeException("操作失敗");
                                }
			
			}
		});

                //按鈕添加活動監聽
		but.addActionListener(new ActionListener()
		{
			public void actionPerformed(ActionEvent e)
			{
				try
				{
					showDir();
				}
				catch (Exception ex)
				{
				  throw new RuntimeException("操作失敗");
				}
				
				
			}
		});
                  
		f.addWindowListener(new WindowAdapter()
		{
			public void windowClosing(WindowEvent e)
			{
				System.exit(0);	
			}
		});
	}
        //定義連接客戶端的方法
	private void showDir()throws Exception
	{

		ta.setText("");
	         
                String urlPath = tf.getText();
		
		//建立URL對象
                URL url = new URL(urlPath);

		//實現連接
                URLConnection conn = url.openConnection();
		//獲取輸入流
		InputStream in = conn.getInputStream();

		byte[] buf = new byte[1024];

		int len = in.read(buf);
                //將數據寫入文本區
		ta.setText(new String(buf,0,len));

	}

	public static void main(String[] args) 
	{
		new IEDemo();
	}
}
    注意一般輸入網址,是不帶端口號的,此時通過URL的getPort()獲取。若通過網址返回的port爲-1,則分配一個默認的80端口

小知識點:

        1.當Socket對象中構造函數爲空時,可以通過connect方法連接服務器。

        2.ServerSocket(int port ,int backlog ),backlog表示隊列的最大長度,即最多有幾個客戶端。通過這個構造函數,對客戶端數量進行限制。

       3.我們上網時輸入的都是網址,而不是ip地址,是怎麼連接到指定主機的呢?這時就需要域名解析(DNS),將主機名變成IP地址。我們訪問一個網址的過程是這樣的:我們在進行訪問時,會現在本機的hosts文件中尋找對應映射,有的話就直接返回。沒有,就到DNS中去找對應的映射,找打後返回對應的IP地址,再通過IP地址找到對應的服務器。


-------------Java培訓、Android培訓、iOS培訓、.Net培訓、期待與您交流! -------

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