天天看點

黑馬程式員-Socket網絡程式設計

---------------------- ASP.Net+Android+IOS開發、.Net教育訓練、期待與您交流! ----------------------

網絡通信的三要素:

(1):IP位址

(2):通信端口

(3):通信協定

Ip位址:ip位址實際上是有4個位元組共32位的二進制來表示的,但是這樣不便于記憶,那麼把這32位分割成4分,每一位元組8位,再轉成十進制表示。2^8=256-1,範圍在0~255,255是廣播位址

分A,B,C類IP位址

子網路遮罩的作用:為了辨別網絡号

示例:255.255.255.0

A類IP位址:1個網絡号+3個主機号

B類IP位址:2個網絡号+2個主機号

C類IP位址:3個網絡号+1個主機号

類 InetAddress

此類表示網際網路協定 (IP) 位址

static InetAddress getLocalHost():擷取本機的IP位址對象,傳回一個InetAddress對象

String getHostAddress():擷取 IP 位址字元串,傳回String類型

String getHostName():擷取此 IP 位址的主機名,傳回String類型

static InetAddress getByName(String host):在給定主機名的情況下确定主機的 IP 位址。主機名可以是機器名(如 "

java.sun.com

"),也可以是其 IP 位址的文本表示形式(如"192.168.10.10")

注意:主機名有可能重複,但IP位址是唯一的(在同一網内),是以此方法推薦使用IP位址

boolean isReachable(int timeout): 測試是否可以達到該位址(能否在指定時間内連接配接上該位址),timeout 為調用中止前的時間(以毫秒為機關)

static InetAddress[] getAllByName(String host):在給定主機名的情況下,根據系統上配置的名稱服務傳回其 IP 位址所組成的數組。

用法:getAllByName(“www.baidu.com”),一個域名可以對應多個主機,百度域名可能有多個(主機)伺服器,是以這個方法是得到百度的所有伺服器的IP位址對象

網絡通訊也叫Socket通訊

不同的協定用不同的socket

UDP協定: DatagramSocket(發送與接收), DatagramPacket(資料包)

         UDP沒有用戶端與服務端之分

發送端與接收端都用DatagramSocket

TCP協定:Socket(用戶端), ServerSocket(服務端)

         TCP分用戶端,服務端,兩者有各自的Socket

UDP協定與TCP協定的差別

UDP

将資料及源和目的封裝成資料包中,不需要建立連接配接

每個資料報的大小在限制在64k内

因無連接配接,是不可靠協定,會出現丢包(帶寬或CPU能力不足)

不需要建立連接配接,速度快(效率比tcp高)

TCP

建立連接配接,形成傳輸資料的通道。

在連接配接中進行大資料量傳輸,資料沒有大小限制

通過三次握手完成連接配接,是可靠協定

必須先建立連接配接,效率會稍低

建立UDP發送端

思路與步驟:

1:定義Socket服務(DatagramSocket)

         DatagramSocket(int port) //如果沒有指定端口,那麼由CPU指定

         建立資料報套接字并将其綁定到本地主機上的指定端口。

2:定義一個資料包,封裝需要發送的資料

         DatagramPacket(byte[] buf, intlength, InetAddress address, int port)

         構造資料報包,用來将長度為length 的包發送到指定主機上的指定端口号。

3:通過Socket服務發送資訊

         send(DatagramPacket p)

    從此套接字發送資料報包。

4:關閉資源

         close()

核心代碼:

DatagramSocket ds = new DatagramSocket(8888);

byte[] buf = "我的資訊".getBytes();
DatagramPacket dp =
 new DatagramPacket(buf,buf.length,InetAddress.getByName("192.168.10.10"),10000);
		ds.send(dp);
		ds.close();
           

建立一個UDP接收端

思路與步驟

1:建立一個Socket服務

         DatagramSocket(int port)

         建立資料報套接字并将其綁定到要監聽(要接收資料的)的指定端口。

2:定義一個資料包,用于存儲到接收到的資料

         DatagramPacket(byte[] buf, intlength)

    構造DatagramPacket,用來接收長度為 length 的資料包。

3:通過Socket服務的receive()将接收到的資料存放到資料包中

         receive(DatagramPacket p)

    從此套接字接收資料報包。

4:通過資料包特有的方法擷取接收到的資料

5:關閉資源

核心代碼:

DatagramSocket ds = new DatagramSocket(10000);

		byte[] buf = new byte[1024];
		DatagramPacket dp = new DatagramPacket(buf,buf.length);

		ds.receive(dp);

		String ip = dp.getAddress().getHostAddress();
		String data = new String(dp.getData(),0,dp.getLength());
		int port = dp.getPort();
		System.out.println(ip+"::"+data+"::"+port);

		ds.close();
           

建立一個TCP的用戶端

思路與步驟:

1:建立用戶端Socket服務,指定要連接配接的主機位址和服務端口

2:為了發送資料,應該擷取Socket流中的輸出流

3:往輸出流寫資料(與UDP這一步驟有差別,不需要寫發送資料的語句,隻需把資料寫入流即可)

4:關閉Socket流對象

核心代碼

Socket s = new Socket("192.168.1.254",10003);
		//建立用戶端Socket服務,指定要連接配接的主機位址和服務端口
		OutputStream out = s.getOutputStream();
		//為了發送資料,應該擷取Socket流中的輸出流
		out.write("我是服務端".getBytes());
		//往輸出流寫資料
		s.close();
		//關閉流
           

建立一個TCP服務端

思路與步驟:

1:建立一個服務端socket的服務,并監聽一個端口

2:通過accept方法擷取連接配接過來的用戶端的對象

3:擷取用戶端發過來的資料,要使用用戶端對象的讀取流來讀取

4:斷開連着的用戶端,免得占用資源

核心代碼

ServerSocket ss = new ServerSocket(10003);
//通過accept方法擷取連接配接過來的用戶端的對象
Socket s = ss.accept();//accept()傳回的是一個Socket類型的對象
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));
		
s.close();//斷開連着的用戶端,免得占用資源
           

 建立TCP用戶端與服務端互訪:

用戶端發送資料到服務端,服務端接收到資料後,傳回自定義資料

用戶端代碼:

class  TransClient
{
	public static void main(String[] args) throws Exception
	{
		Socket s = new Socket("192.168.1.254",10005);
		//定義讀取鍵盤資料的流對象。
		BufferedReader bufr = 
			new BufferedReader(new InputStreamReader(System.in));
		//定義目的,将資料寫入到socket輸出流。發給服務端。
		BufferedWriter bufOut = 
			new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
		//定義一個socket讀取流,讀取服務端傳回的大寫資訊。
		BufferedReader bufIn = 
			new BufferedReader(new InputStreamReader(s.getInputStream()));
		String line = null;
		
		while((line=bufr.readLine())!=null)
		{
			if("over".equals(line))
				break;		
			bufOut.write(line);
			bufOut.newLine();
			bufOut.flush();
			String str =bufIn.readLine();
			System.out.println("server:"+str);
		}
		bufr.close();
		s.close();
	}
}
           

服務端代碼:

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輸出流,并發送給用戶端。
		BufferedWriter bufOut = 
			new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
		String line = null;
		while((line=bufIn.readLine())!=null)
		{
			System.out.println(line);
			bufOut.write(line.toUpperCase());
			bufOut.newLine();
			bufOut.flush();
		}
		s.close();
		ss.close();
	}
}
           

在建立TCP用戶端與服務端互訪的時候,有如下細節需要注意

(1)      用戶端在寫入資料之後,需要重新整理,才能被發送

(2)      服務端在讀資料的時候,使用的是緩沖讀取流,readLine()判斷資料結束的标記是回車符\r\n,是以要在用戶端發送資料後再添加一個換行:newLine();

(3)      同理,服務端發送傳回資料的時候,也要重新整理和換行:newLine();

介紹一個新的帶自動重新整理的輸出流對象:PrintWrite

并且PrintWrite的特有輸出方法println()能夠自動加上換行符

那麼上述代碼核心部分

BufferedWriterbufOut =

                            new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));

修改為:PrintWriter out = newPrintWriter(s.getOutputStream(),true);

bufOut.write(line.toUpperCase());

                            bufOut.newLine();

                            bufOut.flush();

修改為:out.println(line.toUpperCase());

用戶端從服務端下載下傳檔案

服務端支援多台主機同時連接配接下載下傳,并統計被下載下傳多少次,同一IP位址下載下傳多次算一次

服務端代碼示例:

public class MyServer {
	public static void main(String[] args) throws Exception {
		@SuppressWarnings("resource")
		ServerSocket server = new ServerSocket(10000);
		ClientThread ct = null;
		while (true) {
			Socket client = server.accept();
//accept()是阻塞式方法,如果有連接配接,才會往下執行
			ct = new ClientThread(client);//注意多線程的寫法
			Thread t = new Thread(ct);
			t.start();
		}
	}
}

class ClientThread extends Thread {
	private Socket client;
	public static int num = 0;

	public ClientThread(Socket client) {
		this.client = client;
	}

	HashSet<InetAddress> hashset = new HashSet<InetAddress>();

	@Override
	public void run() {
		FileInputStream in = null;
		try {
			OutputStream out = client.getOutputStream();
			in = new FileInputStream(new File("d:\\test\\IMG_0309.JPG"));

			byte[] buf = new byte[1024];
			int len = 0;
			while ((len = in.read(buf)) != -1) {
				out.write(buf, 0, len);
				out.flush();
			}
			if (!(hashset.contains(client.getInetAddress()))) {
				num++;
			}
			System.out.println("下載下傳了" + num + "次");
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				in.close();
				client.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}
           

用戶端代碼:

public class DownloadPic {
	public static void main(String[] args) throws Exception {
		// TODO Auto-generated method stub
		Socket socket = new Socket("192.168.10.10", 10000);
		InputStream in = socket.getInputStream();
		FileOutputStream fos = new FileOutputStream(new File(
				"d:\\test\\我下載下傳的pic.jpg"));
		byte[] buf = new byte[1024];
		int length = 0;
		while ((length = in.read(buf)) != -1) {
			fos.write(buf, 0, length);
			fos.flush();
		}
		fos.close();
		socket.close();
	}
}
           

用戶端往服務端上傳檔案

要注意的地方:要注意結束标記,服務端讀取的結束标記,否則會一直阻塞

可以用Socket的方法,shutDownOutput()關閉用戶端的輸出流,執行此方法是通知服務端,檔案讀完了,可以結束服務端讀取的循環

問題:為什麼用戶端從服務端下載下傳檔案的時候,不需要shutDownOutput()也可以正常執行?

答:

下載下傳:服務端使用自定義輸入流讀取需要被下載下傳的檔案,然後交給socket輸出流,當輸入流讀到流末尾的時候會自動結束讀取循環,socket輸出流沒有寫入結束符号。但是服務端會繼續往下執行,會關閉socket對象,如果socket被關閉,用戶端會收到通知,會自動結束用戶端的socket流

上傳:因為用戶端用自定義的輸入流中讀取需要被上傳的檔案,交給socket的輸出流,自定義的輸入流讀完資料會結束讀取,但是不會寫入結束符号,會結束while循環,繼續往下執行,會關閉socket,服務端從socket讀取資料的時候,也不會讀到結束符号,是以服務端會一直等待,這時候必須要用戶端發送一個結束的标記,提示服務端結束讀取。

URLConnection(重點)屬于應用層

URL url = new URL(www.baidu.com:8080/web/demo.html);

URLConnection conn= url.openConnection();

//此時openConnection()方法會傳回一個URLConnection類型的對象

該對象的作用是把Socket封裝在了内部,起的是Socket的連接配接作用

---------------------- ASP.Net+Android+IOS開發、.Net教育訓練、期待與您交流! ----------------------

詳細請檢視:http://edu.csdn.net

繼續閱讀