Socket編程
一、網絡基礎知識
兩臺計算機要通過網絡進行通信,必須具備:
a、唯一的標識(IP地址);
b、需要共同的語言(協(xié)議);
c、辨別不同應用程序(端口號)。
1、IP地址:
每臺計算機的唯一標識,用來區(qū)分網絡中的不同主機,是兩臺主機進行網絡通信必不可少的。IPv4
2、協(xié)議:
a、TCP/IP協(xié)議:目前世界上應用最為廣泛的協(xié)議。是以TCP和IP為基礎的不同層次上多個協(xié)議的集合。也稱為:TCP/IP協(xié)議族 或者 TCP/IP協(xié)議棧。
b、TCP: Transmission Control Protocol 傳輸控制協(xié)議
c、IP :Internet Protocol 互聯(lián)網協(xié)議
d、TCP/IP模型(網絡分層):
1、物理層;網線。。。
2、數(shù)據(jù)鏈路層
3、網絡層
4、傳輸層:TCP/IP協(xié)議
5、應用層:HTTP超文本傳輸協(xié)議、FTP文件傳輸協(xié)議、SMTP簡單郵件傳送協(xié)議、Telnet遠程登錄服務。
3、端口號:
a、用于區(qū)分不同的應用程序;
b、端口號范圍為0-65535,其中0-1023為系統(tǒng)所保留;
c、IP地址和端口號組成了所謂的Socket,Socket是網絡上運行的程序之間雙向通信鏈路的終結點,是TCP和UDP的基礎。
d、常用端口號-- http: 80; ftp; 21; telnet; 23。
JAVA中得網絡支持
針對網絡通信的不同層次,Java提供的網絡功能有四大類:
1、InetAddress:用于標識網絡上的硬件資源。(IP地址)
2、URL:統(tǒng)一資源定位符---通過URL可以直接讀取或寫入網絡上得數(shù)據(jù)。
3、Sockets:使用TCP協(xié)議實現(xiàn)網絡通信的Socket相關的類。
4、Datagram:使用UDP協(xié)議,將數(shù)據(jù)保存在數(shù)據(jù)報中,通過網絡進行通信
1.InetAddress類沒有構造方法,所以不能直接new出一個對象;
可以通過InetAddress類的靜態(tài)方法獲得InetAddress的對象;
InetAddress.getLocalHost();
InetAddress.getByName("");
2.類主要方法:
String - address.getHostName();
String - address.getHostAddress();
public static InetAddress getByName(String host) throws UnknownHostException
在給定主機名的情況下確定主機的 IP 地址。
主機名可以是機器名(如 "java.sun.com"),也可以是其 IP 地址的文本表示形式
// 獲取本機的InetAdress實例
InetAddress address = InetAddress.getLocalHost();
輸出:hostName/hostAdress
// 根據(jù)主機名稱獲取InetAdress實例
InetAddress address2 = InetAddress.getByName("hostName");
// 根據(jù)IP地址獲取實例
InetAddress address2 = InetAddress.getByName("ipAdress");
URL
url:統(tǒng)一資源定位符:表示internet上的網絡資源<br>
協(xié)議+資源名稱<br>
url常用方法:<br>
存在java.net包中,提供創(chuàng)建url/子url,獲取url等方法<br>
第一步:創(chuàng)建實例<br>
URL imooc=new URL("http://www.imooc.com");<br>
//在原有url下再創(chuàng)建url<br>
URL url=new URL(imooc,"/index.html?username=tom#test")<br>
//獲取url的信息<br>
url.getProtocol();//獲取協(xié)議http<br>
url.getHost();//獲取主機www.imooc.com<br>
url.getPort();//獲取端口號:-1<br>
url.getPath();//獲取文件路徑/index.html<br>
url.getFile();//獲取文件名/index.html?username=tom<br>
url.getRef();//獲取相對路徑test<br>
url.getQuery();//查詢字符串username=tom<br>
注:創(chuàng)建url時沒有指定端口號則getPort方法返回-1,協(xié)議不同會使用默認端口
2url讀取網頁內容:<br>
URL url=new URL("http://www.baidu.com");<br>
//獲取輸入流通過openStream方法<br>
InputStream is=url.openStream();<br>
//轉化成字符輸入流<br>
InputStream isr=new InputStreamReader(is);<br>
//加緩沖提高讀取效率<br>
BufferedReader br=new BufferedReader();<br>
String date=br.teadline();
while(date.next()){System.out.print(date);
date=br.readLine();
}
完成后要關閉資源相關資源:br,isr字符輸入流,is字節(jié)輸入流
注:如果輸出是亂碼則要在is字節(jié)輸入流中規(guī)定編碼為
InputStream isr=new InputStreamReader(is,"utf8");<br>
二、通信過程(Socket通信模型):
1、在服務端建立一個ServerSocket,綁定相應的端口,并且在指定的端口進行偵聽,等待客戶端的連接。
2、當客戶端創(chuàng)建連接Socket并且向服務端發(fā)送請求。
3、服務器收到請求,并且接受客戶端的請求信息。一旦接收到客戶端的連接請求后,會創(chuàng)建一個鏈接socket,用來與客戶端的socket進行通信。通過相應的輸入/輸出流進行數(shù)據(jù)的交換,數(shù)據(jù)的發(fā)送接收以及數(shù)據(jù)的響應等等。
4、當客戶端和服務端通信完畢后,需要分別關閉socket,結束通信。
ServerSocket常用方法:
ServerSocket(int port)——創(chuàng)建并綁定到特定端口的服務器套接字
accept()——偵聽并接受到此套接字的連接
close()——關閉此套接字
getInetAddress()——得到ServerSocket對象綁定的IP地址。如果ServerSocket對象未綁定IP地址,返回0.0.0.0。
getLocalPort()——返回此套接字在其上偵聽的端口
Socket常用方法:
Socket(InetAddress address, int port)——創(chuàng)建一個套接字并將其連接到指定ip地址的指定端口號
Socket(String host, int port)——創(chuàng)建一個套接字并將其連接到指定主機上的指定端口號
close()——關閉此套接字
getInetAddress()——返回套接字連接的地址
getInputStream()——返回此套接字的輸入流
getOutputStream——返回此套接字的輸出流
實例
步驟:
(1)創(chuàng)建ServerSocket和Socket
(2)打開連接到Socket的輸入/輸出操作
(3)按照協(xié)議對Socket進行讀/寫操作
(4)關閉輸入輸出流,關閉Socket
服務器端:
(1)創(chuàng)建ServerSocket對象,綁定監(jiān)聽器
(2)通過accept()方法監(jiān)聽客戶端請求
(3)連接建立以后通過讀取客戶端發(fā)送請求消息
(4)通過輸出流向客戶端發(fā)送響應信息
(5)關閉資源
客戶端:
(1)創(chuàng)建Socket對象,指明需要連接的服務器地址和端口號(1023以后的端口)
(2)連接建立后,通過輸出流向服務器端請求
(3)通過輸入流獲取服務器響應信息
(4)關閉資源
常用I/O操作
InputStream is = socket.getInputStream();//字節(jié)輸入流
InputStreamReader isr = new InputStreamReader(is)//將字節(jié)輸入流轉換為字符輸入流
BufferedReader br = new BufferedReader(isr);//為輸入流添加緩沖
br.readLine()按行讀取
flush()刷新緩存
使用多線程實現(xiàn)多客戶端的
主線程負責創(chuàng)建socket
* 一個線程用來讀取
* 一個線程用來寫入,用兩個內部類
* 要用多線程實現(xiàn),send線程和recived線程同時訪問buff數(shù)據(jù)區(qū)。
* 發(fā)送完成后notify,接收線程。接收線程自身wait
* 接收的說,我先wait一下,可能緩存中還沒有數(shù)據(jù),我會拿到空值得。
* 發(fā)送的說Ok,我先寫,寫完了我notifyall你。我就一直鎖著,這樣默契的合作了。變成并行處理了
每個客戶端連接服務端都會產生一個新的socket。
UDP編程
1、UDP協(xié)議(用戶數(shù)據(jù)報協(xié)議)是無連接、不可靠、無序的。
2、UDP協(xié)議以數(shù)據(jù)報作為數(shù)據(jù)傳輸?shù)妮d體。
3、使用UDP進行數(shù)據(jù)傳輸時,首先需要將要傳輸?shù)臄?shù)據(jù)定義成數(shù)據(jù)報(Datagram),在數(shù)據(jù)報中指明所要達到的Socket(主機地址和端口號),然后在將數(shù)據(jù)報發(fā)生出去。
4、相關操作類:DatagramPacket:表示數(shù)據(jù)報包
DatagramSocket:進行端到端通信的類
DatagramPacket類構造方法:
1、DatagramPacket(byte[] buf,int length)//接受長度為length的數(shù)據(jù)包
2、DatagramPacket(byte[] buf,int length,InetAddress address,int port)//將指定長度的字節(jié)發(fā)生到指定主機的指定端口
DatagramSocket類
1、構造方法:DatagramSocket();
DatagramSocket(int port,InetAddress laddr);
2、close();//關閉DatagramSocket
3、getInetAddress();//獲取地址
4、getPort();//獲取端口號
5、send(DatagramPacket p);//從此套接字發(fā)送數(shù)據(jù)包
recrive(DatagramPacket p);//從此套接字接收數(shù)據(jù)包
服務器端實現(xiàn)步驟:
1、創(chuàng)建DatagramSocket,指定端口號
2、創(chuàng)建DatagramPacket
3、接收客戶端發(fā)送的數(shù)據(jù)信息
4、讀取數(shù)據(jù)
客戶端:
1、定義發(fā)送信息
2、創(chuàng)建DatagramPacket:包含將要發(fā)送信息
3、創(chuàng)建DatagramSocket
4、發(fā)送數(shù)據(jù)
服務端具體代碼:
1、創(chuàng)建服務器端DatagramSocket
DatagramSocket socket=DatagramSocket(8800);
2、創(chuàng)建數(shù)據(jù)報,用戶接收客戶端發(fā)送的數(shù)據(jù)
byte[] data=new byte[1024];
DatagramPacket packet=new datagramPacket(data,data.length);
3、接收客戶端發(fā)送的數(shù)據(jù)
socket.receive(packet);//會處于阻塞,直到接收到數(shù)據(jù)報
4、讀取數(shù)據(jù)
String info=new String(data,0,packet.getLength());
System.out.println("客戶端說"+info)
UDP編程-服務器向客戶端響應數(shù)據(jù)(與客戶端向服務器發(fā)送數(shù)據(jù)類似)
1、定義客戶端的地址、端口號、數(shù)據(jù)。通過DatagramPacket的.getAddress()方法獲取客戶端的地址,通過.getPort()方法獲取端口號。
2、創(chuàng)建數(shù)據(jù)報DatagramPacket,包含響應的數(shù)據(jù)信息。
3、響應客戶端。調用DatagramSocket的.send()方法。
4、關閉資源
UDP編程-客戶端接受服務器端響應的數(shù)據(jù)
1、創(chuàng)建數(shù)據(jù)報DatagramPacket,用于接受服務器端響應的數(shù)據(jù)。首先創(chuàng)建一個字節(jié)數(shù)組。
2、接受服務器端響應的數(shù)據(jù)。調用DatagramPacket的.receive()方法。
3、讀取數(shù)據(jù)。將字節(jié)數(shù)組轉變?yōu)樽址?4、關閉資源。
同步模式:
同步模式的特點是在通過Socket進行連接、接收、發(fā)送數(shù)據(jù)時,客戶機和服務器在接收到對方響應前會處于阻塞狀態(tài),即一直等到收到對方請求進才繼續(xù)執(zhí)行下面的語句。可見,同步模式只適用于數(shù)據(jù)處理不太多的場合。當程序執(zhí)行的任務很多時,長時間的等待可能會讓用戶無法忍受。
異步模式:
異步模式的特點是在通過Socket進行連接、接收、發(fā)送操作時,客戶機或服務器不會處于阻塞方式,而是利用callback機制進行連接、接收、發(fā)送處理,這樣就可以在調用發(fā)送或接收的方法后直接返回,并繼續(xù)執(zhí)行下面的程序。可見,異步套接字特別適用于進行大量數(shù)據(jù)處理的場合。
Socket 總結
1、多線程的優(yōu)先級(死循環(huán)中注意設置優(yōu)先級問題。)建議降低優(yōu)先級。
2、關閉socket流,而不提倡關閉輸入輸出流。
3、使用tcp通信傳輸對象更符合面向對象編程的思想。
4、通過socket編程傳輸文件的功能模塊是:通過io流讀取文件字符流進行傳輸。
深入淺出Java多線程
概念
1.進程:是程序或任務的執(zhí)行的過程,具有動態(tài)性,它持有資源(共享內存,共享文件)和線程
2.線程:系統(tǒng)中最小的執(zhí)行單元。 比如一個軟件里邊的各種任務就是線程。同一進程中有多個線程,線程共享進程的資源
3.線程交互:即線程通信
4.線程之間存在同步和互斥
線程同步互斥的4種方式
臨界區(qū)(Critical Section)、互斥量(Mutex)、信號量(Semaphore)、事件(Event)的區(qū)別
1、臨界區(qū):通過對多線程的串行化來訪問公共資源或一段代碼,速度快,適合控制數(shù)據(jù)訪問。在任意時刻只允許一個線程
對共享資源進行訪問,如果有多個線程試圖訪問公共資源,那么在有一個線程進入后,其他試圖訪問公共資源的線程將被
掛起,并一直等到進入臨界區(qū)的線程離開,臨界區(qū)在被釋放后,其他線程才可以搶占。
2、互斥量:采用互斥對象機制。 只有擁有互斥對象的線程才有訪問公共資源的權限,因為互斥對象只有一個,所以能保
證公共資源不會同時被多個線程訪問。互斥不僅能實現(xiàn)同一應用程序的公共資源安全共享,還能實現(xiàn)不同應用程序的公共
資源安全共享
3、信號量:它允許多個線程在同一時刻訪問同一資源,但是需要限制在同一時刻訪問此資源的最大線程數(shù)目
4、事 件: 通過通知操作的方式來保持線程的同步,還可以方便實現(xiàn)對多個線程的優(yōu)先級比較的操作
Thread常用方法
sleep(long millis, int nanos) 線程休眠 millis休眠的時間,單位是毫秒,可以精確到納秒
join(long millis, int nanos) 調用線程 可以讓其它線程等待自己運行,直到結束
static void yield() 當前運行線程釋放處理器資源并且重新去競爭處理器資源
static Thread currentThread() 返回當前正在處理器上運行的線程的引用
重載的幾個形式
1.沒有參數(shù),指明了其它的線程一定要等待正在執(zhí)行的線程執(zhí)行完畢之后,都會獲得運行的機會
2.nanos是要把精確度改變,可改成納秒
兩種方法實現(xiàn)線程:
1、繼承 Thread 類
class MyThread extends Thread{};
Thread myThread = new MyThread();
myThread.start();
2、實現(xiàn)Runnable類
class MyRunnable implements Runnable{}
Thread myRunnable = new Thread(new MyRunnable);
myRunnable.start();
3、Thread啟動后執(zhí)行run()方法
4、若實現(xiàn)接口通過Thread.currentThread().getName()方法獲取當前線程名稱,繼承Thread則getName()方法獲取當前線程。
1.加入join是為了讓舞臺線程最后停止,如果不加有可能舞臺線程結束,軍隊線程還未停止,就好比導演喊停,演員還在
演!可以在join后面加入測試語句System.out.println("舞臺結束!");,然后去掉或者保留join觀察效果。
2.volatile 關鍵字 保證了線程可以正確地讀取其他線程寫入的值,如果不寫成volatile,由于可見性的問題,當前線程有可能
不能讀到這個值//可見性JMM(JAVA內存模型)happens-before原則、可見性原則
用volatile修飾的變量,線程在每次使用變量的時候,都會讀取變量修改后的值
3.Thread.yield();//讓出處理器時間,公平競爭
線程執(zhí)行過程中幾個重要的方法
sleep(); 讓線程休眠一段時間,
yield(); 讓出當前線程的執(zhí)行權限,讓線程調度重新選擇線程進行執(zhí)行;
join(); 讓其他線程都停止,等待當前線程執(zhí)行完畢。
當某個線程使用join()方法加入到另一個線程時,另一個線程會等待該線程執(zhí)行完畢后再繼續(xù)執(zhí)行。
stop方法使得線程戛然而止,完成了什么工作,哪些工作還沒有做,都不知道,且清理工作也沒有做,所以不是正確的停止線程方法
正確的停止線程方法是,在線程執(zhí)行中設置狀態(tài)標識,通過控制標識來控制線程正常完整的執(zhí)行結束線程
volatile是保證所有子線程里的變量都能同步到主內存里變量的值
不要用stop()方法結束線程
如何正確停止線程?
--使用退出標志
如本文:volatile boolean keepRunning=true;
這樣做的好處是:使得線程有機會使得一個完整的業(yè)務步驟被完整地執(zhí)行,在執(zhí)行完業(yè)務步驟后有充分的時間去做代碼的清理工作,使得線程代碼在實際中更安全
@Java線程——如何正確停止線程
一、錯誤一:stop()方法
1、not stop:stop()方法會使線程戛然而止
2、使程序突然中止,無法完成完整的業(yè)務步驟,也無法進行清理工作
二、錯誤二:interrupt()方法
1、interrupt()方法只能設置interrupt標志位(且在線程阻塞情況下,標志位會被清除,更無法設置中斷標志位),無法停止線程
三、正確方法:設置退出標志
1、使用退出標志位來停止while循環(huán)
2、完成最后一次業(yè)務后跳出while循環(huán)后,之后進行一些清理工作
線程停止:
1、調用stop()方法會使線程戛然停止,而無法知道線程任務完成情況,官方已經不推薦使用。
2、interrupt()方法設置線程的標識位,并在線程中判斷標志位的狀態(tài),從而結束線程,但是當在線程中開啟了另外的線程
時,比如在線程中Tread.sleep(),這時候調用interrupt()方法設置標志位可能設置的是你想要停止的線程,也可能是想要停
止的線程中的線程的標志位,因此interrupt()方法也并不能很好的結束線程。
3、第三種方法,在線程的類聲明一個volatile變量來記錄線程的狀態(tài),相當于interrupt()方法那樣,volatile關鍵字表示線程
中的變量可以接受外部其他線程改變。因此可以在需要停止的地方設置volatile聲明的變量的值設置為狀態(tài),并在執(zhí)行run()
函數(shù)里判斷是否結束。
線程交互
@Java線程——線程交互——爭用條件
1、當多個線程同時共享訪問同一數(shù)據(jù)(內存區(qū)域)時,每個線程都嘗試操作該數(shù)據(jù),從而導致數(shù)據(jù)被破壞(corrupted),這種現(xiàn)象稱為爭用條件
2、原因是,每個線程在操作數(shù)據(jù)時,會先將數(shù)據(jù)初值讀【取到自己獲得的內存中】,然后在內存中進行運算后,重新賦值到數(shù)據(jù)。
3、爭用條件:線程1在還【未重新將值賦回去時】,線程1阻塞,線程2開始訪問該數(shù)據(jù),然后進行了修改,之后被阻塞的線程1再獲得資源,而將之前計算的值覆蓋掉線程2所修改的值,就出現(xiàn)了數(shù)據(jù)丟失情況
@Java線程——線程交互——互斥與同步
一、互斥
1、同一時間,只能有一個線程訪問數(shù)據(jù)
二、同步
1、是一種通信機制,一個線程操作完成后,以某種方式通知其他線程
三、實現(xiàn)方法
1、【互斥】構建鎖對象(Object objLock),通過synchronized(lockObj){ 互斥的代碼塊 }
2、加鎖操作會開銷系統(tǒng)資源,降低效率。
3、在某線程的條件不滿足任務時,使用lockObj.wait()對線程進行阻擋,防止其繼續(xù)競爭CPU資源,滯留在wait set中,等待喚醒,【喚醒后繼續(xù)完成業(yè)務】
4、【同步】在某一代碼正確執(zhí)行完業(yè)務后,通過lockObj.notifyAll()喚醒所有在lockObj對象等待的線程
同步是兩個線程之間的一種交互的操作(一個線程發(fā)出消息另外一個線程響應)。
同步的實現(xiàn):wait();notify();notifyAll();這三個方法都是屬于Java中的Object對象的成員函數(shù)。
調用wait();和notifyAll();方法使線程進入等待或者喚醒不是在同一個線程的同一次操作中執(zhí)行的,當操作結束,喚醒了所有的等待線程之后,線程又將有著公平的機會競爭CPU資源。
注意:notify();方法喚醒wait set 中的一條線程使其具有競爭CPU的機會,具體喚醒那一條線程是隨機的由Java的底層算法決定,我們不能去控制。
通過synchronized關鍵字為臨界區(qū)(critical)加鎖,這樣在線程競爭資源時,當某一條線程獲得鎖進入臨界區(qū)后,其他線程將無法再次獲取鎖進入臨界區(qū)(critical),直到獲得鎖的線程退出臨界區(qū)(critical),釋放鎖資源。Java的語法保證了同一時間只能有一條線程可以獲得lockObject。
重點回顧:
1,創(chuàng)建線程的方法--繼承Thread--實現(xiàn)Runnable接口----線程的基本操作
2,可見性--volatile關鍵字
3,征用條件
4,互斥與同步-----synchronized----wait/notify/notifyAll