概述
1、網絡模型:OSI參考模型和TCP/IP參考模型
圖示:
一般來說開發處於傳輸層和網際層,應用層爲:FTP和HTTP協議等,傳輸層爲:UDP和TCP等,網際層爲:IP。
通常用戶操作的是應用層,而編程人員需要做的是傳輸層和網際層,用戶在應用層操作的數據,經過逐層封包,最後到物理層發送到另一個模型中,再進行逐層解包,圖示爲:
2、網絡通信三要素:IP地址,端口號,傳輸協議
A、IP地址
a、它是網絡中的設備標識
b、不易記憶,可用主機名錶示,兩者存在映射關係
c、本機迴環地址:127.0.0.1,主機名爲:localhost。
IP地址:java中對應的是InetAddress類,存在於java.net包中。
InetAddress類:
(一)無構造函數,可通過getLocalHost()方法獲取InetAddress對象,此方法是靜態的,返回本類對象。
InetAddress i = InetAddress.getLocalHost();
(二)方法:
1)static InetAddress getByName(String host):獲取指定主機的IP和主機名。(最好用ip地址去獲取,主機名需要解析)
2)static InetAddress[] getAllByName(String host):在給定主機名的情況下,根據系統上配置的名稱服務返回IP地址所組成的數組。返回對象不唯一時,用此方法。
3)String getHostAddress():返回IP地址字符串文本形式,以IP地址爲主。
4)String getHostName():返回IP地址主機名。
(三)如何獲取任意一臺主機的IP地址對象:
1)功能:返回InetAddress對象
2)對於任意主機,需要指定傳入主機名的參數
注意:如果IP地址和對應的主機名,這種映射關係沒有在網絡上,就不會解析成功,返回的還是指定的IP。
示例:
<span style="font-size:14px;">/*InetAddress練習*/ import java.net.*; class InetAddressDemo { public static void main(String[] args) { try { //練習一、獲取本機信息 //通過getLocalHost("String");獲取本地主機 InetAddress inet = InetAddress.getLocalHost(); //如果找不到 host 的任何 IP 地址。拋出UnknownHostException異常 //通過InetAddress對象獲取本地主機IP sop(inet.getHostAddress()); //通過InetAddress對象獲取本地主機名 sop(inet.getHostName()); //練習二、指定IP獲取信息 /* static InetAddress getByName(String host) 在給定主機名的情況下確定主機的 IP 地址。 */ /* InetAddress inet1 = InetAddress.getByName("196.168.1.254"); sop("inet1="+inet1.getHostAddress()); sop("inet1="+inet1.getHostName()); */ InetAddress inet1 = InetAddress.getByName("www.baidu.com"); sop("inet1="+inet1.getHostAddress()); sop("inet1="+inet1.getHostName()); //練習三、指定IP獲取信息 /* static InetAddress[] getAllByName(String host) 在給定主機名的情況下,根據系統上配置的名稱服務返回其 IP 地址 所組成的數組。 */ InetAddress [] inet2 = InetAddress.getAllByName("www.baidu.com"); for(InetAddress in:inet2){ sop("inet2="+in.getHostAddress()); sop("inet2="+in.getHostName()); } } catch (UnknownHostException e) { } } public static void sop(Object obj){ System.out.println(obj); } }</span>
B、端口號:
a、用於標識進程的邏輯地址,不用進程的標識。
b、有效端口:0 ~65535,系統使用或保留的端口是:0~ 1024。
C、傳輸協議:
即通信規則,包含TCP和UDP協議
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、在接收端,要指定監聽的端口。
示例:練習一、接收端接收發送端鍵盤錄入內容,並打印<span style="font-size:14px;"><span style="font-size:14px;">/*UDP傳輸 */ /*發送端 步驟:1、建立UDP的socket服務,也就是端點 2、將要提供的數據封裝到數據包中 3、通過socket服務的發送功能將數據發出 4、關閉資源 */ import java.net.*; class DatagramSocketSend { public static void main(String[] args) throws Exception { //創建UDP的socket服務 DatagramSocket out = new DatagramSocket(); //要發送的數據 byte [] data = "UDP come to Be".getBytes(); //建立數據包,實現無線投遞服務 DatagramPacket pack = new DatagramPacket(data,data.length,InetAddress.getByName("192.168.1.255"),10010); //通過socket服務的sent方法將數據發出 out.send(pack); //關閉資源 out.close(); } } /*接收端 步驟:1、創建UDP接收端服務,並指定端口號 2、定義一個數據包,用來存儲接收到的字節數據, 因爲數據包對象中有更多功能可以提取字節數據中的不同數據信息 3、通過Socket服務的receive方法將接收到的數據存入已定義好的數據包中 4、使用數據包的特有功能,對這些不同的數據取出,並打印到控制檯 5、關閉資源 */ class DatagramSocketReceive { public static void main(String[] args) throws Exception { //創建socket服務,並指定端口號爲10000,方便測試 DatagramSocket ds = new DatagramSocket(10010); while(true){ //定義一個數據包,用於存儲接收到的數據 byte [] buff = new byte[1024]; DatagramPacket pack = new DatagramPacket(buff,buff.length); //通過socket服務的receive方法將接收到的數據存入數據包 ds.receive(pack); //該方法是阻塞狀態 //獲取信息 //獲取發送端IP System.out.println(pack.getAddress().getHostAddress()); //獲取發送端端口號 System.out.println(pack.getPort()); //獲取發送端發送的數據的長度 System.out.println(pack.getLength()); //獲取發送端發送的數據 System.out.println(new String(pack.getData(),0,pack.getLength())); } } }</span></span>
練習二、<span style="font-size:14px;"><span style="font-size:14px;">/* 需求:把鍵盤輸入的數據,在接收端顯示 */ /* 發送端: 步驟: 1、創建UDP的socket服務 2、定義字符輸出流,讀取鍵盤輸入的數據 3、定義數組保存鍵盤輸入數據 4、使用socket服務的send方法將數據發出 5、關閉資源 */ import java.io.*; import java.net.*; class DatagramSystemSend { public static void main(String[] args) throws Exception { //1、 DatagramSocket out = new DatagramSocket(); //2、 BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in)); //3、 String line = ""; while((line=bufr.readLine())!=null){ if("over".equals(line)){ line = "發送端發送完畢!!!"; //4 send(line,out); break; } else send(line,out); } //5 out.close(); bufr.close(); } public static void send(String line,DatagramSocket out)throws IOException{ byte [] data = line.getBytes(); DatagramPacket pack = new DatagramPacket(data,data.length,InetAddress.getByName("192.168.1.255"),10005); out.send(pack); } } /* 接收端: 步驟: 1、創建UDP的socket服務 2、定義數據包用於保存從發送端讀取到的數據 3、使用socket的receive方法將讀取到的數據保存到數據包 4、使用數據包的功能取出數據,並打印在控制檯 5、關閉資源 */ class DatagramSystemReceive { public static void main(String[] args) throws Exception { //1、 DatagramSocket in = new DatagramSocket(10005); while(true){ //2、 byte [] data = new byte[1024]; DatagramPacket pack = new DatagramPacket(data,data.length); //3、 in.receive(pack); System.out.println(data.length); //4、 //獲取發送端發送數據的長度 int length = pack.getLength(); //獲取發送端IP String address = pack.getAddress().getHostAddress(); //獲取發送端端口號 int port = pack.getPort(); //獲取發送端數據 String str = new String(pack.getData(),0,length); System.out.println(address+"......."+port+"。。。。。"+length); System.out.println(str); if(str.equals("發送端發送完畢!!!")) break; } in.close(); } }</span></span>
<span style="font-size:14px;">/* 編寫一個聊天程序。 有收數據的部分,和發數據的部分。 這兩部分需要同時執行。 那就需要用到多線程技術。 一個線程控制收,一個線程控制發。 因爲收和發動作是不一致的,所以要定義兩個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(); } }</span>
三、TCP傳輸
1、TCP分客戶端和服務端。客戶端對應的對象是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():返回此套接字的輸入流,Socket對象調用
7)OutputStream 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)獲取連接過來的客戶對象,通過ServerSocket的accept()方法,此方法是阻塞式的,如果服務端沒有連接到就會一直等待
3)客戶端如果發過來數據,則服務端要使用對應的客戶端對象,並獲取到該客戶端對象的讀取流讀取發過來的數據,並輸出到指定目的地。
4)關閉服務端(可選)。一般服務端是常開的,因爲在實際應用中,隨時有客戶端在請求連接和服務。但這裏需要定時關閉客戶端對象流,避免某一個客戶端長時間佔用服務器端。
示例:
練習二、<span style="font-size:14px;">/*TCP傳輸*/ /* 客戶端: 1、創建Socket服務,並指定要連接的服務器的Ip和端口號 2、向服務端發送數據 3、關閉資源 */ import java.net.*; import java.io.*; class SocketDemo { public static void main(String[] args) throws Exception { Socket s = new Socket("127.0.0.1",10000); OutputStream out = s.getOutputStream(); out.write("TCP come by me".getBytes()); s.close(); } } /* 服務端: 1、創建Socket服務,並指定端口號 2、使用accept方法得到Socket連接過來的客戶端對象 3、通過客戶端對象獲取客戶端發送過來的數據,並打印在控制檯 4、關閉資源(可選) */ class ServerSocketDemo { public static void main(String[] args) throws Exception { ServerSocket ss = new ServerSocket(10000); Socket s = ss.accept(); InputStream in = s.getInputStream(); byte [] data = new byte[1024]; int len = in.read(data); //打印客戶端發送的數據 System.out.println(new String(data,0,len)); //打印客戶端信息IP System.out.println(s.getInetAddress().getHostAddress()); } } </span>
練習三、<span style="font-size:14px;">/*TCP傳輸*/ /* 客戶端: 1、創建Socket服務,並指定要連接的服務器的Ip和端口號 2、向服務端發送數據 3、關閉資源 */ import java.net.*; import java.io.*; class SocketDemo { public static void main(String[] args) throws Exception { //創建socket服務,並指定要連接的服務器的IP和端口號 Socket s = new Socket("127.0.0.1",10000); //通過socket服務的getOutputStream方法獲取輸出流 OutputStream out = s.getOutputStream(); //向服務端傳輸數據 out.write("TCP 我來了!!!".getBytes()); //通過socket服務的getInputStream方法獲取讀取流,得到服務端反饋的信息 InputStream in = s.getInputStream(); byte [] data = new byte[1024]; //讀取服務端反饋的信息 int len = in.read(data); System.out.println(new String(data,0,len)); s.close(); } } /* 服務端: 1、創建Socket服務,並指定端口號 2、使用accept方法得到Socket連接過來的客戶端對象 3、通過客戶端對象獲取客戶端發送過來的數據,並打印在控制檯 4、關閉資源(可選) */ class ServerSocketDemo { public static void main(String[] args) throws Exception { //創建socket服務,並指定端口號 ServerSocket ss = new ServerSocket(10000); //通過accept方法獲取socket連接的客戶端對象 Socket s = ss.accept(); //通過獲取讀取流得到客戶端傳輸過來的數據 InputStream in = s.getInputStream(); byte [] data = new byte[1024]; //讀取客戶端傳輸的數據 int len = in.read(data); //打印客戶端發送的數據 System.out.println(new String(data,0,len)); //打印客戶端信息IP System.out.println(s.getInetAddress().getHostAddress()); //向客戶端反饋信息 OutputStream out = s.getOutputStream(); out.write("已收到!!!".getBytes()); } } </span>
練習四、<span style="font-size:14px;">/* 練習 需求:建立一個文本轉換服務器 客戶端給服務端發送文本,服務端會將文本轉成大寫再返回給客戶端。 而且客戶端可以不斷的進行文本轉換。當客戶端輸入over時,轉換結束。 分析: 客戶端: 既然是操作設備上的數據,那麼就可以使用io技術,並按照io的操作規律來思考。 源:鍵盤錄入 目的:網絡設備,網絡輸出流。 而且操作的是文本數據。可以選擇字符流。 步驟: 1、建立服務 2、獲取鍵盤錄入 3、將數據發給服務端 4、獲取服務端返回的大寫數據 5、結束,管資源。 都是文本數據,可以使用字符流進行操作,同時提高效率,加入緩衝。 此練習出現的問題: 現象:客戶端和服務端都在莫名的等待。 原因:因爲客戶端和服務端都有阻塞式方法。這些方法沒有讀到結束標記。那麼就一直等。而導致兩端都在等待。 解決:需要用到刷新和換行的方式將寫入和讀取的數據從流中刷新到內存中 方式一:可用高效緩衝區類的newLine()換行作爲結束標記,並用flush()進行刷新。 方式二:可用PrintWriter(s.getOutputStrean(),true)創建輸出流對象,true作用是刷新,通過打印方法println()換行,“ln”表示換行。 */ import java.io.*; import java.net.*; class TransSocket { public static void main(String[] args) throws Exception { //創建Socket服務,並指定要連接的主機的IP和端口號 Socket s = new Socket("127.0.0.1",10002); //創建讀取流對象,用於讀取鍵盤錄入,這裏使用字符讀取流 BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in)); //定義目的,將數據寫入到Socket輸出流。發給服務端 // BufferedWriter bufw = // new BufferedWriter(new OutputStreamWriter(s.getOutputStream())); //------------使用PrintWriter替代BufferedWriter-------- PrintWriter print = new PrintWriter(new BufferedWriter( new OutputStreamWriter(s.getOutputStream())),true); //定義讀取流,讀取服務端反饋的信息 BufferedReader readerServer = new BufferedReader(new InputStreamReader(s.getInputStream())); //讀取鍵盤錄入 String data = null; while((data=bufr.readLine())!=null){ //如果輸入over轉換結束 if("over".equals(data)) break; //-----使用打印流的特有方法替代緩衝------ // bufw.write(data); // bufw.newLine(); // bufw.flush(); print.println(data); //每發送一次,獲取一次服務端反饋回來的信息 System.out.println("服務端反饋信息:"+readerServer.readLine()); } s.close(); bufr.close(); } } class TransServer { public static void main(String [] args) throws Exception { //創建ServerSocket服務,並指定端口號 ServerSocket ss = new ServerSocket(10002); //通過accept方法獲取客戶端對象 Socket s = ss.accept(); //顯示客戶端IP信息 System.out.println(s.getInetAddress().getHostAddress()); //獲取客戶端傳輸的數據 BufferedReader bufr = new BufferedReader(new InputStreamReader(s.getInputStream())); //將客戶端發送的文本轉換爲大寫,反饋給客戶端 // BufferedWriter bufw = // new BufferedWriter(new OutputStreamWriter(s.getOutputStream())); //--------------使用打印流 替換 BufferedWriter-------- PrintWriter print = new PrintWriter(new BufferedWriter( new OutputStreamWriter(s.getOutputStream())),true); String line = null; while((line=bufr.readLine())!=null){ // bufw.write(line.toUpperCase()); // bufw.newLine(); // bufw.flush(); //--------------使用打印流的特有方法 替換 BufferedWriter的方法-------- print.println(line.toUpperCase()); } } } </span>
<span style="font-size:14px;">/* 需求:向服務器上傳一個文件,服務返回一條信息 1、客戶端: 源:硬盤上的文件;目的:網絡設備,即網絡輸出流。 若操作的是文本數據,可選字符流,並加入高效緩衝區。若是媒體文件,用字節流。 2、服務端: 源:socket讀取流;目的:socket輸出流。 3、出現的問題: 現象: a、文件已經上傳成功了,但是沒有得到服務端的反饋信息。 b、即使得到反饋信息,但得到的是null,而不是“上傳成功”的信息 原因: a、因爲客戶端將數據發送完畢後,服務端仍然在等待着讀取數據,並沒有收到結束標記,就會一直等待讀取。 b、上個問題解決後,收到的不是指定信息而是null,是因爲服務端寫入數據後,需要刷新,才能將信息反饋給客服端。 解決: 方法一:定義結束標記,先將結束標記發送給服務端,讓服務端接收到結束標記,然後再發送上傳的數據。但是這樣定義可能會發生定義的標記和文件中的數據重複,而導致提前結束。 方法二:定義時間戳,由於時間是唯一的,在發送數據前,先獲取時間,發送完後在結尾處寫上相同的時間戳,在服務端,接收數據前先接收一個時間戳,然後在循環中判斷時間戳以結束標記。 方法三:通過socket方法中的shutdownOutput(),關閉輸入流資源,從而結束傳輸流,以給定結束標記。通常用這個方法。 */ import java.io.*; import java.net.*; class TextSocket { public static void main(String[] args) throws Exception { //創建socket服務 Socket s = new Socket("127.0.0.1",10003); //創建文件對象 File file = new File("D:\\file.txt"); //判斷文件是否存在 if(file.exists()){ //創建本地讀取文件流 BufferedReader bufr = new BufferedReader(new FileReader(file)); //定義目的,將數據寫入到socket流,並傳輸個服務端 PrintWriter print = new PrintWriter(new BufferedWriter( new OutputStreamWriter(s.getOutputStream())),true); //定義讀取流,讀取服務端反饋信息 BufferedReader readerSer = new BufferedReader(new InputStreamReader(s.getInputStream())); String data = null; while((data=bufr.readLine())!=null){ //向服務端輸出信息 print.println(data); } //使用socket服務的shutdownOutput方法,標識客戶端數據傳輸已結束 s.shutdownOutput(); //打印服務端反饋信息 System.out.println(readerSer.readLine()); bufr.close(); s.close(); } else { System.out.println("該文件不存在"); s.close(); } } } class TextServer { public static void main(String [] args)throws Exception { //創建服務 ServerSocket ss = new ServerSocket(10003); //獲取客戶端對象 Socket s = ss.accept(); if(s!=null){ //打印客戶端IP System.out.println("IP="+s.getInetAddress().getHostAddress()); //創建流對象,衝socket流中讀取客戶端傳輸的數據 BufferedReader bufr = new BufferedReader(new InputStreamReader(s.getInputStream())); //創建輸出流,用於給客戶端反饋信息 PrintWriter printSocket = new PrintWriter(new BufferedWriter( new OutputStreamWriter(s.getOutputStream())),true); //創建輸出流,將客戶端傳輸的數據打印到本地硬盤 PrintWriter printText = new PrintWriter(new BufferedWriter( new FileWriter("d:\\server.txt")),true); //讀取客戶端傳輸的數據 String data = null; while((data=bufr.readLine())!=null){ printText.println(data); } printSocket.println("上傳成功!"); printText.close(); bufr.close(); ss.close(); } else{ System.out.println("沒有客戶端訪問!!!"); ss.close(); } } }</span>
重點
一、用TCP客戶端併發上傳圖片
1、一對一(單線程)上傳的思路:
客戶端
a、服務端點。
b、讀取客戶端已有的圖片數據
c、通過Socket輸出流將數據發給服務端
d、讀取服務端反饋信息。
e、關閉
服務端
a、服務端服務,並監聽窗口
b、獲取客戶端對象,並獲取客戶ip
c、讀取客戶端輸入流數據
d、寫入文件
e、用客戶端輸出流反饋信息
f、關流
2、單線程的服務端有個侷限性。當A客戶端連接上以後,被服務端獲取到。服務端執行具體流程。這時B客戶端連接,只能等待。因爲服務端還沒有處理完A客戶端的請求。還沒有循環回來執行下一次accept方法。所以,暫時獲取不到B客戶端對象。
那麼爲了可以讓多個客戶端同時併發訪問服務端。服務端最好就是將每個客戶端封裝到一個單獨的線程中,這樣,就可以同時處理多個客戶端請求。
如何定義線程呢?
只要明確了每一個客戶端要在服務端執行的代碼,將該代碼存入run方法即可。
代碼:
<span style="font-size:14px;">/*
需求:併發上傳圖片
*/
import java.io.*;
import java.net.*;
class PicSocket
{
public static void main(String[] args) throws Exception
{
if(args.length!=1){
System.out.println("請選擇單個文件上傳");
return;
}
//創建socket服務
Socket s = new Socket("127.0.0.1",1005);
//從控制檯接收一個文件路徑
File file = new File(args[0]);
if(!(file.exists()&&file.isFile)){
System.out.println("您上傳的文件可能不存在");
return;
}
if(file.length=>1024*1024*5){
System.out.println("您上傳到文件不能超過5M");
return;
}
if(!(file.getName().endsWith(".jpg"))){
System.out.println("您只能上傳.jpg文件');
}
//創建本地讀取流,關聯文件
BufferedInputStream bufi =
new BufferedInputStream(new FileInputStream(file));
//定義目的,將數據寫入socket流,並傳輸到客戶端
BufferedOutputStream bufo =
new BufferedOutputStream(s.getOutputStream());
//定義讀取服務端反饋信息流
BufferedReader bufr =
new BufferedReader(new InputStreamReader(s.getInputStream()));
//開始讀取數據
int len = 0;
while((len=bufi.read())!=-1){
bufo.write(len);
}
s.shutdownOutput();
System.out.println(bufr.readLine());
s.close();
bufi.close();
}
}
class ServerThread implements Runnable
{
//ServetSocket服務對象
private Socket s;
public ServerThread(Socket s){
this.s = s;
}
public void run(){
int count =1;
try
{
//獲取客戶端對象
String ip = s.getInetAddress().getHostAddress();
File file = new File("d:\\pic");
if(!file.exists())
file.mkdir();
File dir = new File(file,ip+"("+(count)+")"+".jpg");
//創建讀取流,從socket流中讀取數據
BufferedInputStream bufr =
new BufferedInputStream(s.getInputStream());
//判斷文件是否存在,存在更改文件名
while(dir.exists()){
dir = new File(file,ip+"("+(count++)+")"+".jpg");
}
//創建輸出流對象,將客戶端傳輸的數據保存到本地硬盤
BufferedOutputStream bufo =
new BufferedOutputStream(new FileOutputStream(dir));
int len = 0;
while((len=bufr.read())!=-1){
bufo.write(len);
}
//創建反饋信息流
PrintWriter print =
new PrintWriter(s.getOutputStream(),true);
print.println("上傳成功!!!");
bufo.close();
}
catch (Exception e)
{
}
}
}
class PicServer
{
public static void main(String [] args) throws Exception
{
ServerSocket ss = new ServerSocket(1005);
while(true){
Socket s = ss.accept();
new Thread(new ServerThread(s)).start();
}
}
}
</span>
二、客戶端併發登錄
客戶端通過鍵盤錄入用戶名,服務端對這個用戶名進行校驗。
如果該用戶存在,在服務端顯示xxx,已登陸;並在客戶端顯示xxx,歡迎光臨。
如果用戶不存在,在服務端顯示xxx,嘗試登陸;並在客戶端顯示xxx,該用戶不存在。
最多就登錄三次。
/* 需求:客戶端併發登錄 客戶端通過鍵盤錄入用戶名,服務端對這個用戶名進行校驗。 如果該用戶存在,在服務端顯示xxx,已登陸;並在客戶端顯示xxx,歡迎光臨。 如果用戶不存在,在服務端顯示xxx,嘗試登陸;並在客戶端顯示xxx,該用戶不存在。 最多就登錄三次。 */ import java.io.*; import java.net.*; class UserSocket { public static void main(String[] args) throws Exception { //創建Socket服務 Socket s = new Socket("127.0.0.1",1006); //創建讀取流,從鍵盤讀取錄入數據 BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in)); //定義目的,將數據寫入socket流,並傳輸到服務端 PrintWriter print = new PrintWriter(new BufferedWriter( new OutputStreamWriter(s.getOutputStream())),true); //創建讀取服務端反饋信息流 BufferedReader readSer = new BufferedReader(new InputStreamReader(s.getInputStream())); String data = null; //控制用戶輸入次數 for(int i=0;i<3;i++){ data = bufr.readLine(); print.println(data); //打印服務端反饋信息 String str = readSer.readLine(); System.out.println(str); if(str.contains("歡迎")) break; } bufr.close(); s.close(); } } class User implements Runnable { private Socket s; public User(Socket s){ this.s = s; } public void run(){ try { System.out.println("IP"+(s.getInetAddress().getHostAddress())); System.out.println("22222222222"); //創建讀取流,從socket流中讀取客戶端傳輸的數據 BufferedReader bufr = new BufferedReader(new InputStreamReader(s.getInputStream())); //創建讀取流,從本地硬盤讀取用戶信息 BufferedReader bufUser = new BufferedReader(new FileReader("D:\\userInfo.txt")); //定義打印流,輸出服務端反饋信息 PrintWriter print = new PrintWriter(s.getOutputStream(),true); //這個for循環很重要,沒有它,數據只能傳輸一次 /*這裏一定要放到循環中來,因爲這是一個線程,如果沒有循環,服務端接收完 一次客戶端傳輸的數據後,該線程就結束了,和之前的控制檯轉換大寫是一個道理 */ for(int i=0;i<3;i++){ String name = bufr.readLine(); //定義標記,用於判斷用戶是否存在 boolean flag = false; String line = null; while((line=bufUser.readLine())!=null){ if(line.equals(name)){ flag = true; break; } } if(flag==true){ print.println(name+"歡迎光臨!"); System.out.println(name+"已登陸"); break; } else{ print.println(name+"該用戶不存在"); System.out.println(name+"嘗試登陸"); } } } catch (Exception e) { } } } class UserServer { public static void main(String [] args)throws Exception { ServerSocket ss = new ServerSocket(1006); while(true){ Socket s = ss.accept(); System.out.println("1111111111111"); new Thread(new User(s)).start(); } } }
瀏覽器是一個標準的客戶端,它可以對服務端傳送過來的數據消息進行解析,把符合應用層協議的消息部分解析後,將頭信息拆包掉,傳送到應用層,只保留了正確的正文主題部分顯示在主體部分上。
而由於使用java編譯是在傳輸層和網際層處理的,所以,會接受到全部的消息,包含了頭消息。而瀏覽器處於應用層,已將發送來的頭消息去除,只留下了主體信息。
示例:
自定義服務器,用瀏覽器訪問:
<span style="font-size:14px;">/* 客戶端爲瀏覽器 向瀏覽器反饋信息 注意:使用瀏覽器作爲客戶端訪問服務器時,客戶端會給服務端發送“消息頭” 只需要通過socket服務的getInputStream()方法即可獲取: 如:在客戶端輸入http://192.168.1.254:11000/myweb/demo.html HTTP/1.1 則客戶端向服務端發送的消息頭爲: GET /myweb/demo.html HTTP/1.1 //這是客戶端在告訴服務端要訪問的資源路徑 Accept: application/x-shockwave-flash, image/gif, image/x-xbitmap, image/jpeg, i mage/pjpeg, application/vnd.ms-excel, application/vnd.ms-powerpoint, application /msword, application/QVOD, application/QVOD,//這是在告訴服務端客戶端支持的類型 Accept-Language: zh-cn //這是在告訴服務端客戶端支持的語言 Accept-Encoding: gzip, deflate //這是在告訴服務端客戶端支持的壓縮格式 User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0 .50727) Host: 192.168.1.254:11000 //這是告訴服務端客戶端要訪問的192.168.1.254主機的11000端口號 Connection: Keep-Alive //這是告訴服務端要保持連接 */ import java.net.*; import java.io.*; class ServerToIE { //在客戶端輸入http://127.0.0.1訪問 public static void main(String[] args) throws Exception { //創建socket服務 ServerSocket ss = new ServerSocket(10086); //獲取客戶端對象 Socket s = ss.accept(); //獲取客戶端發送的數據(包含消息頭) InputStream in = s.getInputStream(); byte [] data = new byte[1024]; int len = in.read(data); System.out.println(new String(data,0,len)); /*服務端反饋信息(包含應答消息頭),不在瀏覽器上顯示, 因爲瀏覽器是應用層,被解析了,所以我們在瀏覽器上只能看到“哥們,收到!!”*/ PrintWriter print = new PrintWriter(s.getOutputStream(),true); print.println("哥們,收到!!!"); s.close(); } } //向服務端z(Tomcat服務器)發送自定義消息頭,並打印應答消息頭 class SocketToServer { public static void main(String [] args)throws Exception { //創建socket服務 Socket s = new Socket("127.0.0.1",10086); //定義目的,將數據寫入Socket流,並傳輸給服務端 PrintWriter print = new PrintWriter(s.getOutputStream(),true); print.println("GET / HTTP/1.1"); /*這裏我們是直接訪問10086端口,要想指定資源路徑, 可以使用Tomcat服務器,把資源放到Tomcat的webapps目錄下即可*/ print.println("Accept: */*"); // */*代表所支持的類型及格式 如圖片,視頻及其格式 print.println("Accept-Language: zh-cn"); //支持的語言 print.println("Host: 127.0.0.1:10086 ");//要訪問的主機的端口 print.println("Connection: closed"); //這裏一般我們自己測試是使用closed //注意:這裏一定要輸出空行 print.println(); print.println(); //獲取服務端反饋信息(包含應答消息頭) BufferedReader bufr = new BufferedReader(new InputStreamReader(s.getInputStream())); String line = null; while((line=bufr.readLine())!=null){ System.out.println(line); } s.close(); } } </span>
四、URL和URLConnection
1、URL:
URI:範圍更大,條形碼也包含於此範圍
URL:範圍較小,即域名
方法:
1)構造函數:URL(String protocol,String host,int port,String file);//根據指定 protocol、host、port號和 file創建 URL對象。
2)String getProtocol();//獲取協議名稱
3)String getHost();//獲取主機名
4)int getPort();//獲取端口號
5)String getFile();//獲取URL文件名
6)String getPath();//獲取此URL的路徑部分
7)String getQuery();//獲取此URL的查詢部,客戶端傳輸的特定信息
注:一般輸入網址,是不帶端口號的,此時可進行獲取,通過獲取網址返回的port,若port爲-1,則分配一個默認的80端口,如
int port = getPort();
if(port == -1)
port = 80;
2、URLConnection
方法:
1)URLConnection openConnection();//用URL調用此方法,返回一個 URLConnection 對象,它表示到 URL 所引用的遠程對象的連接。
2)InputStream getInputStream();//獲取輸入流
3)OutputStream getOutputStream();//獲取輸出流
示例:
<span style="font-size:14px;"> /*
自定義瀏覽器,顯示網頁信息
*/
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是一個抽象類,裏面包含getInputStream方法和getOutputStream方法
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();
}
} </span>
補充
1、Socket類的構造函數中,有一個空參數的構造函數:
Socket()//通過系統默認類型的 SocketImpl創建未連接套接字對象
可以通過connect(SocketAddress endpoint)方法來連接服務器。而SocketAddress是一個抽象類,它的子類InetSocketAddress實現了IP套接字地址(IP地址+端口號)。所以就可以連接到服務器了。
2、ServerSocket對象中的構造函數:
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,就不會將請求發送出去了。