第一講??? ?概述?
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。
示例:
- 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、傳輸協議:
??????? 即通信規則,包含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、在接收端,要指定監聽的端口。
示例:
- /*?
- 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傳輸
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)關閉服務端(可選)。一般服務端是常開的,因為在實際應用中,隨時有客戶端在請求連接和服務。但這里需要定時關閉客戶端對象流,避免某一個客戶端長時間占用服務器端。
示例:
- /*?
- 需求:客戶端給服務端發送數據,服務端收到后,給客戶端反饋信息。?
- */??
- //客戶端??
- 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();??
- ????}??
- }??
?
四、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();//獲取輸出流
示例:
- /*?
- 自定義瀏覽器,顯示網頁信息?
- */??
- ??
- 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();??
- ????}??
- }??
?
小知識點
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,就不會將請求發送出去了。
?