Java编程之路——网络编程篇
網(wǎng)絡(luò)編程
- 關(guān)注的是底層數(shù)據(jù)的傳輸;
- 與網(wǎng)頁編程不同,網(wǎng)頁編程是關(guān)注與用戶的數(shù)據(jù)交互。
概念:
網(wǎng)絡(luò):
將不同區(qū)域的計(jì)算機(jī)連接到一起;區(qū)域(局域網(wǎng),城域網(wǎng),互聯(lián)網(wǎng))
地址:
IP地址:
確定網(wǎng)絡(luò)上一個絕對地址,位置;——》房子的地址
端口號:
區(qū)分計(jì)算機(jī)的軟件——》房門 2個字節(jié) 0-65535 共65536個
1、在同一個協(xié)議下 端口號不能重復(fù);不同協(xié)議下可以重復(fù)
2、1024以下的不要使用,給知名廠商預(yù)留的,如80——》 http;21——》ftp 盡量往大了指定
資源定位:
URL統(tǒng)一資源定位符;URI:統(tǒng)一資源。
數(shù)據(jù)的傳輸
1.協(xié)議:
TCP和UDP兩種協(xié)議
1)TCP:面向連接,安全可靠;電話 類似于三次握手;效率低
2)UDP:非面向連接,效率高;短信
2.傳輸數(shù)據(jù):
1)先封裝
2)后拆封
在Java中所涉及的類主要包括:=========》類
Java中相關(guān)類的學(xué)習(xí)
網(wǎng)絡(luò)編程相關(guān)的類與接口位于java.net包中
一、地址與端口
InetAddress類,封裝計(jì)算機(jī)的IP地址和域名解析DNS,但是沒有端口
需掌握的有:
該類沒有公開的構(gòu)造器,所以構(gòu)造對象應(yīng)使用本身提供的靜態(tài)方法。
1)靜態(tài)方法獲取對象:
InetAddress.getLocalHost();產(chǎn)生一個本機(jī)的InetAddress對象
InetAddress.getByName(“IP地址或者域名”);通過IP或域名產(chǎn)生一個InetAddress對象
2)調(diào)用對象的方法:
getHostAddress();返回對象的IP地址
getHostName();返回對象的域名,若無或者不能解析,則返回IP地址
InetSocketAddress類,封裝端口,在InetAddress類基礎(chǔ)上 + 端口
1)含有對外構(gòu)造器,故用構(gòu)造方法產(chǎn)生對象:
InetSocketAddress(String hostname, int port);主機(jī)名與端口號構(gòu)造對象
InetSocketAddress(InetAddress addr, int port);IP地址與端口號構(gòu)造對象
2)調(diào)用對象的方法:
getAddress();返回IP地址IntAddress;
getHostName();返回域名hostname
getPort();返回端口號
構(gòu)造端口的例子:
1.InetSocketAddress address = new InetSocketAddress(“127.0.0.1”,9999);
等價于:
2. Address = new InetSocketAddress(InetAddress.getByName(“127.0.0.1”),9999);
因?yàn)榈谝环N方法的源碼中就存在著將字符轉(zhuǎn)化成為IP地址InetAddress的代碼;故這兩種方法是等價的。
二、 資源定位
URL包含四部分:協(xié)議+域名+端口號+資源文件名(/之后開始算,是相對路徑)
URL類:
表示指向互聯(lián)網(wǎng)資源的指針
1)創(chuàng)建對象:
URL(String spec);絕對路徑構(gòu)建
URL(URL context, String spec);相對路徑構(gòu)建
2)方法:
getProtocol();返回協(xié)議
getHost();返回域名
getPort();返回端口號
getFile();返回資源
getPath();獲取相對路徑
getRef();返回錨點(diǎn)
getQuery();返回參數(shù);在有錨點(diǎn)情況下返回null;無錨點(diǎn)則返回正確
3)資源流
openStream();返回一個輸入流InputStream
構(gòu)建URL與使用方法的一個例子:
1.絕對URL
2.相對URL
url = new URL(“http://www.baidu.com:80/a/”); url = URL(url, “b.txt”); System.out.println(url.toString()); //http://www.baidu.com:80/a/b.txt 對第一種構(gòu)建對象使用方法: System.out.println(“協(xié)議:”+ url.getProtocol()); //協(xié)議:http System.out.println(“域名:”+ url.getHost()); //域名:www.baidu.com System.out.println(“端口:”+ url.getPort()); //端口:80 System.out.println(“資源:”+url.getFile());//資源:index.html#aa?uname=bjsxt System.out.println(“相對路徑:”+url.getPath()); //相對路徑:index.html System.out.println(“錨點(diǎn):”+url.getRef()); // 錨點(diǎn):aa?uname=bjsxt System.out.println(“參數(shù):”+url.getQuery()); //參數(shù):有錨點(diǎn)返回null,若無錨點(diǎn)則返回uname=bjsxt爬蟲原理的一個例子:(爬蟲第一步,先獲取網(wǎng)頁數(shù)據(jù))
public static void main(String[] args){URL url = new URL(“http://www.baidu.com”); //主頁,默認(rèn)資源//獲取資源,網(wǎng)絡(luò)流/* //直接這樣讀取的主頁數(shù)據(jù)會存在亂碼,主要是由于編碼與解碼的字符集不統(tǒng)一造成的,所以可以用轉(zhuǎn)換流InputStream is = url.openStream();byte[] flush = new byte[1024];int len = 0;while((len=is.read(flush))!= -1){System.out.println(new String(flush,0,len));}is.close();*///使用轉(zhuǎn)換流BufferedReader br = new BufferedReader(new InputStreamReader(url.openStream(),”utf-8”));//也可以再定義寫入流將讀取到的內(nèi)容直接寫到文件中BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(baidu.html),”utf-8”));String msg = null;while((msg=br.readLine())!=null){System.out.println(msg); //直接打印出來內(nèi)容bw.append(msg);bw.newLine();}bw.flush();bw.close();br.close(); }三、UDP傳輸數(shù)據(jù)
UDP是以數(shù)據(jù)為中心的,非面向連接的,不安全,但效率高
根據(jù)數(shù)據(jù)傳輸?shù)膶ο罂煞譃榭蛻舳伺c服務(wù)器端;
在java種主要涉及到***DatagramSocket類***以及***DatagramPacket類***,其傳輸步驟分別為:
1.客戶端
1)創(chuàng)建客戶端對象,DatagramSocket類 + 指定端口
2)準(zhǔn)備數(shù)據(jù) 字節(jié)數(shù)組
3)打包 DatagramPacket類 + 服務(wù)器地址及端口
4)發(fā)送數(shù)據(jù)
5)釋放資源
2.服務(wù)器端
1)創(chuàng)建服務(wù)器對象,DatagramSocket類 + 指定端口
2)準(zhǔn)備接收容器 字節(jié)數(shù)組 封裝 DatagramPacket類
3)包 接收數(shù)據(jù)
4)分析數(shù)據(jù)
5)釋放資源
服務(wù)器端與客戶端使用的類是相同的,不過使用的方法有些不同:
DatagramSocket類 發(fā)送和接收數(shù)據(jù)報數(shù)據(jù)包的套接字。
1)創(chuàng)建對象 均使用DatagramSocket(int port)
2)方法
send(DatagramPacket p) 通過數(shù)據(jù)包p發(fā)送數(shù)據(jù)
receive(DatagramPacket p)從數(shù)據(jù)包p接收數(shù)據(jù)
close()關(guān)閉套接字,釋放資源
DatagramPacket類 該類表示數(shù)據(jù)報包
數(shù)據(jù)封裝與拆封操作均是DatagramPacket類,
客戶端打包數(shù)據(jù):
服務(wù)端準(zhǔn)備包接收數(shù)據(jù):
DatagramPacket(byte[] buf, int length)//主需要準(zhǔn)備一個byte接收數(shù)組即可,不需要知道發(fā)送方的信息。一般先寫服務(wù)端,再寫客戶端:
服務(wù)端保存為:MyServer.java
客戶端保存為:MyClient.java
public static void main(String[] args){//1、創(chuàng)建客戶端 + 端口DatagramSocket client = new DatagramSocket(6666);//2、準(zhǔn)備數(shù)據(jù)String msg = “UDP編程”;byte[] data = msg.getBytes();//3、封裝成包(指定發(fā)送地點(diǎn)及端口)DatagramPacket(byte[] buf, int length, SocketAddress address)DatagramPacket packet = new DatagramPacket(data,data.length,new InetSocketAddress(“l(fā)ocalHost”,8888));//4、發(fā)送數(shù)據(jù)client.send(packet);//5、釋放資源client.close(); }上述實(shí)現(xiàn)的功能是將字符數(shù)組從客戶端發(fā)送到服務(wù)端;而如何將其他類型的數(shù)據(jù)發(fā)送呢?
由于數(shù)據(jù)的傳輸只能一字節(jié)數(shù)組的形式,所以數(shù)據(jù)在打包和拆封時均需要進(jìn)行相對應(yīng)的轉(zhuǎn)換;上述其實(shí)已經(jīng)存在String——》byte[]的轉(zhuǎn)換:byte[] data = msg.getBytes();以及相對應(yīng)的byte[]——》String轉(zhuǎn)換:new String(data,0,len)。
所以對于其他類型的數(shù)據(jù),我們發(fā)送前也先要將其轉(zhuǎn)換為byte[]形式,接收后也需要將其轉(zhuǎn)化為原來的形式。
客戶端的轉(zhuǎn)換
轉(zhuǎn)換方式double——》byte[] 字節(jié)數(shù)組 數(shù)據(jù)源 + Data輸出流
類型轉(zhuǎn)換結(jié)束后再將客戶端的步驟2修改為:
double num = 89.12;byte[] data = convert(num);服務(wù)端的轉(zhuǎn)換
轉(zhuǎn)換方式 字節(jié)數(shù)組——》double 字節(jié)數(shù)組+ Data輸入流
完成轉(zhuǎn)換后再將服務(wù)端的分析數(shù)據(jù)步驟5修改為:
double data = convert(packet.getData());四、TCP數(shù)據(jù)傳輸
TCP是面向連接的,安全,可靠,效率低;這個連接對服務(wù)端與客戶端是同一個Socket。
1.面向連接Request—Response。
2.Socket編程
服務(wù)端ServerSocket類
1)構(gòu)建對象:ServerSocket(int port)需指明端口
2)接收客戶端連接:accept()阻塞式接收
3)發(fā)送數(shù)據(jù);一個連接Socket相當(dāng)于雙向流,所以可以由Socket中g(shù)etOutputStream()方法獲得輸出流OutputStream;
客戶端Socket類
1)創(chuàng)建對象:Socket(String host, int port)指明服務(wù)器IP及端口;客戶端本身不需要指明端口,由客戶端自動分配。
客戶端建立成功即表示建立了連接,一個接收accept()只能連接一次
2)接收數(shù)據(jù):先使用Socket中g(shù)etInputStream()方法獲得輸入流InputStream由于服務(wù)端與客戶端的連接是同一個,所以兩端對流進(jìn)行包裝時需要用同樣的種類,即若服務(wù)端用BufferedWriter,則客戶端要用BufferedReader;同樣若服務(wù)端用DataOutputStream,則客戶端也要用DataInputStream
如何在一個服務(wù)端創(chuàng)建多個客戶端的連接?
若直接在服務(wù)端的接受連接accept()處建立循環(huán),則實(shí)現(xiàn)的功能實(shí)際上是服務(wù)器先與一個客戶端 建立連接,然后與之通信完畢后再與其他客戶端建立連接。這樣每個客戶端之間并不是獨(dú)立的,若一個客戶端的連接出現(xiàn)阻塞式服務(wù),則所有客戶端均需等待。
實(shí)際上多個客戶端連接需要的是每個客戶端都能與服務(wù)器獨(dú)立的發(fā)送,接收數(shù)據(jù);并且客戶端之間也是獨(dú)立的;所以需要服務(wù)器為每一個客戶端的連接創(chuàng)建一個線程,且需要為客戶端的發(fā)送與接收分別創(chuàng)建一個線程。
簡易聊天室原理:
先寫服務(wù)器:Server01.java
public static void main(String[] args){//創(chuàng)建服務(wù)器ServerSocket server = new ServerSocket(9999);//接收服務(wù)端連接Socket client = server.accept();//寫出數(shù)據(jù)//輸入流DataInputStream dis = new DataInputStream(client.getInputStream());String msg = dis.readUTF();//輸出流DataOutputStream dos = new DataOutputStream(client.getOutputStream());dos.writeUTF(“服務(wù)器——>” + msg);dos.flush(); }再寫客戶端:Client01.java
public static void main(String[] args){//創(chuàng)建客戶端Socket client = new Socket(“l(fā)ocalhost”,9999);//創(chuàng)建控制臺輸入流BufferedReader console = new BufferedReader(new InputStreamReader(System.in));String info = console.readLine();//輸出流DataOutputStream dos = new DataOutputStream(client.getOutputStream());dos.writeUTF(info);//輸入流DataInputStream dis = new DataInputStream(client.getInputStream());String msg = dis.readUTF();System.out.println(msg);}在Client01.java中,客戶端只能單次讀入,為了能多次讀入信息并發(fā)送,需要進(jìn)行循環(huán),在循環(huán)中不需要重復(fù)建立流,所以可以將流單獨(dú)出來,將其處理進(jìn)行循環(huán)即可。
針對上述聊天室,客戶端只能先讀取控制臺數(shù)據(jù),然后發(fā)送數(shù)據(jù),最后接收數(shù)據(jù);服務(wù)端只能先接收數(shù)據(jù),然后轉(zhuǎn)發(fā)數(shù)據(jù);即數(shù)據(jù)的收發(fā)并不是獨(dú)立的,所以需要建立線程單獨(dú)控制,如下:
先建立發(fā)送線程Send.java
public class Send implements Runnable{//控制臺輸入流private BufferedReader console;//輸出流private DataOutputStream dos;//線程標(biāo)識private boolean isRunning = true;//構(gòu)造器public Send(){console = new BufferedReader(new InputStreamReader(System.in));}public Send(Socket client){this.Send();try{dos = new DataOutputStream(client.getOutputStream());}catch(IOException e){isRunning = false;CloseUtil.closeAll(dos, console);}}//1.從控制臺接收數(shù)據(jù)public String getMsgFromConsole(){try{return console.readLine();}catch(IOException e){isRunning = false;CloseUtil.closeAll(dos, console);}return “”;}//將接收的數(shù)據(jù)發(fā)送出去public void send(){String msg = getMsgFromConsole();try{if(msg != null && !msg.equals(“”)){dos.writeUTF(msg);dos.flush();}}catch(IOException e){isRunning = false;CloseUtil.closeAll(dos, console);}}public void run(){//線程主體while(isRunning){send();}}}然后在客戶端處連接后直接建立新的線程即可:
new thread(new Send(client)).start(); //一條路徑類似的建立接收線程Receive.java
public class Receive implements Runnable{//輸入流private DataInputStream dis;//線程標(biāo)識private boolean isRunning = true;//構(gòu)造器public Receive(){}public Receive(Socket client){try{dis = new DataInputStream(client.getInputStream());}catch(IOException e){isRunning = false;CloseUtil.closeAll(dis);}}//接收數(shù)據(jù)public String receive(){String msg = “”;try{msg = dis.readUTF();}catch(IOException e ){isRunning = false;CloseUtil.closeAll(dis);}return msg;}public void run(){//線程主體while(isRunning){System.out.println(receive());}}}將關(guān)閉流的方法進(jìn)行封裝:CloseUtil.java
public class CloseUtil{public static void closeAll(Closeable… io){for(Closeable temp:io){try{if(temp !=null){temp.close();}}catch(Exception e){}}} }線程封裝完畢后再將客戶端進(jìn)行修改Client02.java
public static void main(String[] args){//創(chuàng)建客戶端Socket client = new Socket(“l(fā)ocalhost”,9999);//創(chuàng)建輸入線程new Thread(new Send(client)).start();//創(chuàng)建輸入線程new Thread(new Receive(client)).start(); }這樣客戶端在連接之后便可以任意發(fā)送與接收數(shù)據(jù)了,但是服務(wù)端目前還是只接受與轉(zhuǎn)發(fā)數(shù)據(jù)一次,所以需要將服務(wù)端加一個循環(huán)。Server02.java
public static void main(String[] args){//創(chuàng)建服務(wù)器ServerSocket server = new ServerSocket(9999);//接收服務(wù)端連接Socket client = server.accept();//寫出數(shù)據(jù)//輸入流DataInputStream dis = new DataInputStream(client.getInputStream());//輸出流DataOutputStream dos = new DataOutputStream(client.getOutputStream());while(true){String msg = dis.readUTF();dos.writeUTF(“服務(wù)器——>” + msg);dos.flush();} }這樣便可以實(shí)現(xiàn)一個客戶端與服務(wù)端任意的收發(fā)數(shù)據(jù)了,但是還不能實(shí)現(xiàn)多個客戶端的連接。主要是因?yàn)榉?wù)端并沒有為每個客戶端的連接創(chuàng)建單獨(dú)的線程,由于這個服務(wù)線程只有服務(wù)端使用,所以可以考慮在服務(wù)端建立內(nèi)部類,創(chuàng)建如下:
服務(wù)端Server03.java
public class Server03{//為每個客戶端創(chuàng)建一個容器private List< MyChannel > all = new ArrayList< MyChannel >();public static void main(String[] args){new Server03. channel();}//接收客戶端線程服務(wù)public void channel(){//創(chuàng)建服務(wù)器ServerSocket server = new ServerSocket(9999);//接收服務(wù)端連接while(true){Socket client = server.accept();MyChannel channel = new MyChannel(client);all.add(channel); //將一條道路加入容器new Thread(channel).start();}}//內(nèi)部類,處理客戶端的線程private class MyChannel implements Runnable(){private DataInputStream dis;private DataOutputStream dos;private boolean isRunning = true;//構(gòu)造器public MyChannel(){}public MyChannel(Socket client){try{dis = new DataInputStream(client.getInputStream());dos = new DataOutputStream(client.getOutputStream());}catch(IOException e){isRunning = false;CloseUtil.closeAll(dis,dos);all.remove(this): //移除自身}}//接收數(shù)據(jù)pivate String receive(){String msg = “”;try{msg = dis.readUTF();}catch(IOException e){isRunning = false;CloseUtil.closeAll(dis);all.remove(this): //移除自身}return msg;}//發(fā)送數(shù)據(jù)private void send(String msg){try{if(msg!=null && !msg.equals(“”)){dos.writeUTF(msg);dos.flush();}}catch(IOException e){isRunning = false;CloseUtil.closeAll(dos);all.remove(this): //移除自身}}//發(fā)送給其他客戶端private void sendOthers(){String msg = receive();for(MyChannel other : all){if(other == this){continue;}other.send(msg)}}public void run(){//線程主體while(isRunning){sendOthers();}}}}這樣便實(shí)現(xiàn)了客戶端與服務(wù)器端的獨(dú)立通信。
總結(jié)
以上是生活随笔為你收集整理的Java编程之路——网络编程篇的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 关于苹果的开发者账号的唠嗑
- 下一篇: 智能汽车-ICALL、BCALL、ECA