Java最初是作為網(wǎng)絡(luò)編程語言出現(xiàn)的,其對網(wǎng)絡(luò)提供了高度的支持,使得客戶端和服務(wù)器的溝通變成了現(xiàn)實(shí)置尔,而在網(wǎng)絡(luò)編程中,使用最多的就是Socket氢伟。像大家熟悉的QQ榜轿、MSN都使用了Socket相關(guān)的技術(shù)篮愉。
網(wǎng)絡(luò)基礎(chǔ)
首先看下網(wǎng)絡(luò)的分層結(jié)構(gòu)和一些基本協(xié)議:
基于TCP/IP協(xié)議族的網(wǎng)絡(luò),被分為四層差导,分別為應(yīng)用層,傳輸層猪勇,網(wǎng)際層以及網(wǎng)絡(luò)接口層设褐,每一層都設(shè)計(jì)了相應(yīng)的協(xié)議,我們常用的一些協(xié)議(如HTTP泣刹,F(xiàn)TP等)助析,都是屬于應(yīng)用層協(xié)議,他們大多是基于傳輸層的TCP椅您,UDP設(shè)計(jì)出來的外冀。
1、兩臺(tái)計(jì)算機(jī)間進(jìn)行通訊需要以下三個(gè)條件:
IP地址掀泳、協(xié)議雪隧、端口號(hào)
2、TCP/IP協(xié)議:
是目前世界上應(yīng)用最為廣泛的協(xié)議员舵,是以TCP和IP為基礎(chǔ)的不同層次上多個(gè)協(xié)議的集合脑沿,也成TCP/IP協(xié)議族、或TCP/IP協(xié)議棧
TCP:Transmission Control Protocol 傳輸控制協(xié)議
IP:Internet Protocol 互聯(lián)網(wǎng)協(xié)議
3马僻、TCP/IP五層模型
應(yīng)用層:HTTP庄拇、FTP、SMTP韭邓、Telnet等
傳輸層:TCP/IP
網(wǎng)絡(luò)層:
數(shù)據(jù)鏈路層:
物理層:網(wǎng)線措近、雙絞線、網(wǎng)卡等
4女淑、IP地址
為實(shí)現(xiàn)網(wǎng)絡(luò)中不同計(jì)算機(jī)之間的通信瞭郑,每臺(tái)計(jì)算機(jī)都必須有一個(gè)唯一的標(biāo)識(shí)---IP地址。
32位二進(jìn)制
5鸭你、端口
區(qū)分一臺(tái)主機(jī)的多個(gè)不同應(yīng)用程序凰浮,端口號(hào)范圍為0-65535,其中0-1023位為系統(tǒng)保留苇本。
如:HTTP:80 FTP:21 Telnet:23
IP地址+端口號(hào)組成了所謂的Socket袜茧,Socket是網(wǎng)絡(luò)上運(yùn)行的程序之間雙向通信鏈路的終結(jié)點(diǎn),是TCP和UDP的基礎(chǔ)
6瓣窄、Socket套接字:
網(wǎng)絡(luò)上具有唯一標(biāo)識(shí)的IP地址和端口組合在一起才能構(gòu)成唯一能識(shí)別的標(biāo)識(shí)符套接字笛厦。
Socket原理機(jī)制:
通信的兩端都有Socket
網(wǎng)絡(luò)通信其實(shí)就是Socket間的通信
數(shù)據(jù)在兩個(gè)Socket間通過IO傳輸
7、Java中的網(wǎng)絡(luò)支持
針對網(wǎng)絡(luò)通信的不同層次俺夕,Java提供了不同的API裳凸,其提供的網(wǎng)絡(luò)功能有四大類:
InetAddress:用于標(biāo)識(shí)網(wǎng)絡(luò)上的硬件資源贱鄙,主要是IP地址
URL:統(tǒng)一資源定位符,通過URL可以直接讀取或?qū)懭刖W(wǎng)絡(luò)上的數(shù)據(jù)
Sockets:使用TCP協(xié)議實(shí)現(xiàn)的網(wǎng)絡(luò)通信Socket相關(guān)的類
Datagram:使用UDP協(xié)議姨谷,將數(shù)據(jù)保存在用戶數(shù)據(jù)報(bào)中逗宁,通過網(wǎng)絡(luò)進(jìn)行通信。
InetAddress類
InetAddress類用于標(biāo)識(shí)網(wǎng)絡(luò)上的硬件資源梦湘,標(biāo)識(shí)互聯(lián)網(wǎng)協(xié)議(IP)地址瞎颗。
該類沒有構(gòu)造方法
//獲取本機(jī)的InetAddress實(shí)例
InetAddress address =InetAddress.getLocalHost();
address.getHostName();//獲取計(jì)算機(jī)名
address.getHostAddress();//獲取IP地址
byte[] bytes = address.getAddress();//獲取字節(jié)數(shù)組形式的IP地址,以點(diǎn)分隔的四部分
//獲取其他主機(jī)的InetAddress實(shí)例
InetAddress address2 =InetAddress.getByName("其他主機(jī)名");
InetAddress address3 =InetAddress.getByName("IP地址");
URL類
1、URL(Uniform Resource Locator)統(tǒng)一資源定位符
表示Internet上某一資源的地址捌议,協(xié)議名:資源名稱
//創(chuàng)建一個(gè)URL的實(shí)例
URL baidu =new URL("http://www.baidu.com");
URL url =new URL(baidu,"/index.html?username=tom#test");//哼拔?表示參數(shù),#表示錨點(diǎn)
url.getProtocol();//獲取協(xié)議
url.getHost();//獲取主機(jī)
url.getPort();//如果沒有指定端口號(hào)瓣颅,根據(jù)協(xié)議不同使用默認(rèn)端口倦逐。此時(shí)getPort()方法的返回值為 -1
url.getPath();//獲取文件路徑
url.getFile();//文件名,包括文件路徑+參數(shù)
url.getRef();//相對路徑宫补,就是錨點(diǎn)檬姥,即#號(hào)后面的內(nèi)容
url.getQuery();//查詢字符串,即參數(shù)
2粉怕、使用URL讀取網(wǎng)頁內(nèi)容
通過URL對象的openStream()方法可以得到指定資源的輸入流穿铆,通過流能夠讀取或訪問網(wǎng)頁上的資源
//使用URL讀取網(wǎng)頁內(nèi)容
//創(chuàng)建一個(gè)URL實(shí)例
URL url =new URL("http://www.baidu.com");
InputStream is = url.openStream();//通過openStream方法獲取資源的字節(jié)輸入流
InputStreamReader isr =new InputStreamReader(is,"UTF-8");//將字節(jié)輸入流轉(zhuǎn)換為字符輸入流,如果不指定編碼,中文可能會(huì)出現(xiàn)亂碼
BufferedReader br =new BufferedReader(isr);//為字符輸入流添加緩沖斋荞,提高讀取效率
String data = br.readLine();//讀取數(shù)據(jù)
while(data!=null){
System.out.println(data);//輸出數(shù)據(jù)
data = br.readerLine();
}
br.close();
isr.colose();
is.close();
TCP編程
1荞雏、TCP協(xié)議是面向連接的、可靠的平酿、有序的凤优、以字節(jié)流的方式發(fā)送數(shù)據(jù),通過三次握手方式建立連接蜈彼,形成傳輸數(shù)據(jù)的通道筑辨,在連接中進(jìn)行大量數(shù)據(jù)的傳輸,效率會(huì)稍低
2幸逆、Java中基于TCP協(xié)議實(shí)現(xiàn)網(wǎng)絡(luò)通信的類
- 客戶端的Socket類
-
服務(wù)器端的ServerSocket類
Socket通信模型.jpg
3棍辕、Socket通信的步驟
① 創(chuàng)建ServerSocket和Socket
② 打開連接到Socket的輸入/輸出流
③ 按照協(xié)議對Socket進(jìn)行讀/寫操作
④ 關(guān)閉輸入輸出流、關(guān)閉Socket
4还绘、服務(wù)器端:
① 創(chuàng)建ServerSocket對象楚昭,綁定監(jiān)聽端口
② 通過accept()方法監(jiān)聽客戶端請求
③ 連接建立后,通過輸入流讀取客戶端發(fā)送的請求信息
④ 通過輸出流向客戶端發(fā)送鄉(xiāng)音信息
⑤ 關(guān)閉相關(guān)資源
/**
* 基于TCP協(xié)議的Socket通信拍顷,實(shí)現(xiàn)用戶登錄抚太,服務(wù)端
*/
//1、創(chuàng)建一個(gè)服務(wù)器端Socket,即ServerSocket尿贫,指定綁定的端口电媳,并監(jiān)聽此端口
ServerSocket serverSocket =newServerSocket(10086);//1024-65535的某個(gè)端口
//2、調(diào)用accept()方法開始監(jiān)聽庆亡,等待客戶端的連接
Socket socket = serverSocket.accept();
//3匾乓、獲取輸入流,并讀取客戶端信息
InputStream is = socket.getInputStream();
InputStreamReader isr =new InputStreamReader(is);
BufferedReader br =new BufferedReader(isr);
String info =null;
while((info=br.readLine())!=null){
System.out.println("我是服務(wù)器又谋,客戶端說:"+info)拼缝;
}
socket.shutdownInput();//關(guān)閉輸入流
//4、獲取輸出流搂根,響應(yīng)客戶端的請求
OutputStream os = socket.getOutputStream();
PrintWriter pw = new PrintWriter(os);
pw.write("歡迎您!");
pw.flush();
//5铃辖、關(guān)閉資源
pw.close();
os.close();
br.close();
isr.close();
is.close();
socket.close();
serverSocket.close();
5剩愧、客戶端:
① 創(chuàng)建Socket對象,指明需要連接的服務(wù)器的地址和端口號(hào)
② 連接建立后娇斩,通過輸出流想服務(wù)器端發(fā)送請求信息
③ 通過輸入流獲取服務(wù)器響應(yīng)的信息
④ 關(guān)閉響應(yīng)資源
//客戶端
//1仁卷、創(chuàng)建客戶端Socket,指定服務(wù)器地址和端口
Socket socket =new Socket("localhost",10086);
//2犬第、獲取輸出流锦积,向服務(wù)器端發(fā)送信息
OutputStream os = socket.getOutputStream();//字節(jié)輸出流
PrintWriter pw =new PrintWriter(os);//將輸出流包裝成打印流
pw.write("用戶名:admin;密碼:123");
pw.flush();
socket.shutdownOutput();
//3歉嗓、獲取輸入流丰介,并讀取服務(wù)器端的響應(yīng)信息
InputStream is = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String info = null;
while((info=br.readLine())!null){
System.out.println("我是客戶端,服務(wù)器說:"+info);
}
//4鉴分、關(guān)閉資源
br.close();
is.close();
pw.close();
os.close();
socket.close();
6哮幢、應(yīng)用多線程實(shí)現(xiàn)服務(wù)器與多客戶端之間的通信
① 服務(wù)器端創(chuàng)建ServerSocket,循環(huán)調(diào)用accept()等待客戶端連接
② 客戶端創(chuàng)建一個(gè)socket并請求和服務(wù)器端連接
③ 服務(wù)器端接受苦讀段請求志珍,創(chuàng)建socket與該客戶建立專線連接
④ 建立連接的兩個(gè)socket在一個(gè)單獨(dú)的線程上對話
⑤ 服務(wù)器端繼續(xù)等待新的連接
//服務(wù)器線程處理
//和本線程相關(guān)的socket
Socket socket =null;
//
public serverThread(Socket socket){
this.socket = socket;
}
publicvoid run(){
//服務(wù)器處理代碼
}
//============================================
//服務(wù)器代碼
ServerSocket serverSocket =new ServerSocket(10086);
Socket socket =null;
int count =0;//記錄客戶端的數(shù)量
while(true){
socket = serverScoket.accept();
ServerThread serverThread =new ServerThread(socket);
serverThread.start();
count++;
System.out.println("客戶端連接的數(shù)量:"+count);
}
UDP編程
UDP協(xié)議(用戶數(shù)據(jù)報(bào)協(xié)議)是無連接的橙垢、不可靠的、無序的,速度快
進(jìn)行數(shù)據(jù)傳輸時(shí)伦糯,首先將要傳輸?shù)臄?shù)據(jù)定義成數(shù)據(jù)報(bào)(Datagram)柜某,大小限制在64k,在數(shù)據(jù)報(bào)中指明數(shù)據(jù)索要達(dá)到的Socket(主機(jī)地址和端口號(hào))敛纲,然后再將數(shù)據(jù)報(bào)發(fā)送出去
DatagramPacket類:表示數(shù)據(jù)報(bào)包
DatagramSocket類:進(jìn)行端到端通信的類
1喂击、服務(wù)器端實(shí)現(xiàn)步驟
① 創(chuàng)建DatagramSocket,指定端口號(hào)
② 創(chuàng)建DatagramPacket
③ 接受客戶端發(fā)送的數(shù)據(jù)信息
④ 讀取數(shù)據(jù)
//服務(wù)器端淤翔,實(shí)現(xiàn)基于UDP的用戶登錄
//1惭等、創(chuàng)建服務(wù)器端DatagramSocket,指定端口
DatagramSocket socket =new datagramSocket(10010);
//2办铡、創(chuàng)建數(shù)據(jù)報(bào)辞做,用于接受客戶端發(fā)送的數(shù)據(jù)
byte[] data =newbyte[1024];//
DatagramPacket packet =new DatagramPacket(data,data.length);
//3琳要、接受客戶端發(fā)送的數(shù)據(jù)
socket.receive(packet);//此方法在接受數(shù)據(jù)報(bào)之前會(huì)一致阻塞
//4、讀取數(shù)據(jù)
String info =new String(data,o,data.length);
System.out.println("我是服務(wù)器秤茅,客戶端告訴我"+info);
//=========================================================
//向客戶端響應(yīng)數(shù)據(jù)
//1稚补、定義客戶端的地址、端口號(hào)框喳、數(shù)據(jù)
InetAddress address = packet.getAddress();
int port = packet.getPort();
byte[] data2 = "歡迎您课幕!".geyBytes();
//2、創(chuàng)建數(shù)據(jù)報(bào)五垮,包含響應(yīng)的數(shù)據(jù)信息
DatagramPacket packet2 = new DatagramPacket(data2,data2.length,address,port);
//3乍惊、響應(yīng)客戶端
socket.send(packet2);
//4、關(guān)閉資源
socket.close();
2放仗、客戶端實(shí)現(xiàn)步驟
① 定義發(fā)送信息
② 創(chuàng)建DatagramPacket润绎,包含將要發(fā)送的信息
③ 創(chuàng)建DatagramSocket
④ 發(fā)送數(shù)據(jù)
//客戶端
//1、定義服務(wù)器的地址诞挨、端口號(hào)莉撇、數(shù)據(jù)
InetAddress address =InetAddress.getByName("localhost");
int port =10010;
byte[] data ="用戶名:admin;密碼:123".getBytes();
//2、創(chuàng)建數(shù)據(jù)報(bào)惶傻,包含發(fā)送的數(shù)據(jù)信息
DatagramPacket packet = new DatagramPacket(data,data,length,address,port);
//3棍郎、創(chuàng)建DatagramSocket對象
DatagramSocket socket =new DatagramSocket();
//4、向服務(wù)器發(fā)送數(shù)據(jù)
socket.send(packet);
//接受服務(wù)器端響應(yīng)數(shù)據(jù)
//======================================
//1银室、創(chuàng)建數(shù)據(jù)報(bào)涂佃,用于接受服務(wù)器端響應(yīng)數(shù)據(jù)
byte[] data2 = new byte[1024];
DatagramPacket packet2 = new DatagramPacket(data2,data2.length);
//2、接受服務(wù)器響應(yīng)的數(shù)據(jù)
socket.receive(packet2);
String raply = new String(data2,0,packet2.getLenth());
System.out.println("我是客戶端蜈敢,服務(wù)器說:"+reply);
//4巡李、關(guān)閉資源
socket.close();
注意問題:
1、多線程的優(yōu)先級(jí)問題:
根據(jù)實(shí)際的經(jīng)驗(yàn)扶认,適當(dāng)?shù)慕档蛢?yōu)先級(jí)侨拦,否側(cè)可能會(huì)有程序運(yùn)行效率低的情況
2、是否關(guān)閉輸出流和輸入流:
對于同一個(gè)socket辐宾,如果關(guān)閉了輸出流狱从,則與該輸出流關(guān)聯(lián)的socket也會(huì)被關(guān)閉,所以一般不用關(guān)閉流叠纹,直接關(guān)閉socket即可
3季研、使用TCP通信傳輸對象,IO中序列化部分
4誉察、socket編程傳遞文件与涡,IO流部分