網絡通信協議:位於同一個網絡中的計算機在進行連接和通信時需要遵守一定的規則,它對數據的傳輸格式、傳輸速率、傳輸步驟等做了統一規定
TCP/IP協議:它定義了計算機如何連入因特網,以及數據如何在它們之間傳輸的標準。它的內部包含一系列的用於處理數據通信的協議,每一層都呼叫它的下一層所提供的協議來完成自己的需求
-
-
表示層:數據的表示、安全、壓縮。格式有:JPEG、ASCll、DECOIC、加密格式等。
-
會話層:建立、管理、終止會話。對應主機進程,指本地主機與遠程主機正在進行的會話
-
傳輸層:定義傳輸數據的協議端口號,以及流控和差錯校驗。協議有:TCP、UDP。
-
網絡層:進行邏輯地址尋址,實現不同網絡之間的路徑選擇。協議有:ICMP、IGMP、IP(IPV4 IPV6)、ARP、RARP。
-
數據鏈路層:建立邏輯連接、進行硬件地址尋址、差錯校驗等功能。將比特組合成字節進而組合成幀,用MAC地址訪問介質,錯誤發現但不能糾正。
-
物理層:建立、維護、斷開物理連接。
IP(internet protocal)又稱爲互聯網協議。IP的責任就是把數據從源傳送到目的地。它在源地址和目的地址之間傳送一種稱之爲數據包的東西,它還提供對數據大小的重新組裝功能,以適應不同網絡對包大小的要求。經常與IP協議放在一起的還有TCP(Transmission Control Protocol)協議
- 四次揮手
IP地址用來給一個網絡中的計算機設備做唯一的編號,
IPv4:32位整數,8位一組最多可以表示42億個
IPv6:採用128位地址長度,每16個字節一組,分成8組十六進制數
公網地址( 萬維網使用)和 私有地址( 局域網使用)。192.168.開頭的就是私有址址,範圍即爲192.168.0.0--192.168.255.255,專門爲組織機構內部使用
特殊的IP地址:
-
-
主機名(hostName):
localhost
域名:域名服務器(DNS)負責將域名轉化成IP地址,方便記憶。
如果端口號被另外一個服務或應用所佔用,會導致當前程序啓動失敗。
利用協議
+IP地址
+端口號
三元組合,就可以標識網絡中的進程了,那麼進程間的通信就可以利用這個標識與其它進程進行交互。
InetAddress類主要表示IP地址,兩個子類:Inet4Address、Inet6Address。
lInetAddress 類沒有提供公共的構造器,而是提供 了 如下幾個 靜態方法來獲取InetAddress 實例
-
public static InetAddress getLocalHost() 【返回本地主機】
-
public static InetAddress getByName(String host) 【在給定主機名的情況下確定主機的 IP 地址】
-
public static InetAddress getByAddress(byte[] addr) 【在給定原始 IP 地址的情況下,返回
InetAddress
對象】 - 例:byte[] addr = {(byte)192,(byte)168,24,56}; 其內部用一個int存儲
InetAddress 提供瞭如下幾個常用的方法
-
public String getHostAddress() : 【返回 IP 地址字符串(以文本表現形式)】
-
public String getHostName() : 【獲取此 IP 地址的主機名】
- public String getCanonicalHostName(): 【獲取此 IP 地址的完全限定域名】
- boolean isReachable(int timeout) 【測試是否可以達到該地址。】
socket是兩個主機通信的關鍵,先理解IO流的工作流程有助於理解網絡間的通信,socket數據的發送與接收也可簡單的理解爲:
客戶端將要發送的數據通過send()發送給客戶端的tcp/udp協議的緩衝區,由客戶端協議發送給服務端的tcp/udp協議,服務端的receive()會讀取服務端的協議緩衝區接收到的數據,
如需返回數據再經服務端的send()發送給服務端的協議緩衝區,服務端的協議再發送給客戶端的協議,客戶端的receive()會讀取客戶端協議緩衝區中的數據如此循環,直到close()
-
-
Socket:此類實現客戶端套接字(也可以就叫“套接字”)。套接字是兩臺機器間通信的端點。流套接字
- DatagramSocket:此類表示用來發送和接收UDP數據報包的套接字。數據報套接字
-
-
調用 accept() :監聽連接請求,如果客戶端請求連接,則接受連接,返回通信套接字對象。
-
調用 該Socket 類對象的 getOutputStream() 和 getInputStream () :獲取輸出流和輸入流,開始網絡數據的發送和接收。
-
關閉Socket 對象:客戶端訪問結束,關閉通信套接字。
-
-
打開連接到 Socket 的輸入/出流: 使用 getInputStream()方法獲得輸入流,使用getOutputStream()方法獲得輸出流,進行數據傳輸
-
按照一定的協議對 Socket 進行讀/ 寫操作:通過輸入流讀取服務器放入線路的信息(但不能讀取自己放入線路的信息),通過輸出流將信息寫入線路。
-
關閉 Socket:斷開客戶端到服務器的連接,釋放線路
ServerSocket類的構造方法:
-
ServerSocket(int port) : 【創建綁定到特定端口的服務器套接字】
ServerSocket類的常用方法:
-
- InetAddress getInetAddress() 【返回此服務器套接字的本地地址。】
- int getLocalPort() 【返回此套接字在其上偵聽的端口。】
- void close(): 【關閉此套接字。】
Socket類的常用構造方法:
-
public Socket(InetAddress address,int port): 【創建一個流套接字並將其連接到指定 IP 地址的指定端口號】
-
Socket類的常用方法:
-
public InputStream getInputStream(): 【返回此套接字的輸入流,可以用於接收消息】
-
public OutputStream getOutputStream(): 【返回此套接字的輸出流,可以用於發送消息】
-
public InetAddress getInetAddress(): 【返回此套接字連接到的遠程 IP 地址;如果套接字是未連接的,則返回 null】
-
public InetAddress getLocalAddress(): 【獲取套接字綁定的本地地址】
-
public int getPort(): 【返回此套接字連接到的遠程端口號;如果尚未連接套接字,則返回 0】
-
public int getLocalPort(): 【返回此套接字綁定到的本地端口。如果尚未綁定套接字,則返回 -1】
-
public void close(): 【關閉套接字(即無法重新連接或重新綁定) 同時也將會關閉該套接字的 InputStream 和 OutputStream】
-
public void shutdownInput():
如果在套接字上調用 shutdownInput() 後從套接字輸入流讀取內容,則流將返回 EOF(文件結束符)。 即不能在從此套接字的輸入流中接收任何數據。關閉輸入流
-
public void shutdownOutput():
禁用此套接字的輸出流。對於 TCP 套接字,任何以前寫入的數據都將被髮送,並且後跟 TCP 的正常連接終止序列。 如果在套接字上調用 shutdownOutput() 後寫入套接字輸出流,則該流將拋出 IOException。 即不能通過此套接字的輸出流發送任何數據。關閉輸出流
- boolean isInputShutdown() : 【返回是否關閉套接字連接的半讀狀態 (read-half)】
- boolean isOutputShutdown() : 【返回是否關閉套接字連接的半寫狀態 (write-half)】
注意:先後調用Socket的shutdownInput()和shutdownOutput()方法,僅僅關閉了輸入流和輸出流,並不等於調用Socket的close()方法。在通信結束後,仍然要調用Scoket的close()方法,因爲只有該方法纔會釋放Socket佔用的資源,比如佔用的本地端口號等。
-
public DatagramSocket(int port)
創建數據報套接字並將其綁定到本地主機上的指定端口。套接字將被綁定到通配符地址,IP 地址由內核來選擇。
-
public DatagramSocket(int port,InetAddress laddr)
創建數據報套接字,將其綁定到指定的本地地址。本地端口必須在 0 到 65535 之間(包括兩者)。如果 IP 地址爲 0.0.0.0,套接字將被綁定到通配符地址,IP 地址由內核選擇。
-
public void send(DatagramPacket p)
從此套接字發送數據報包。DatagramPacket 包含的信息指示:將要發送的數據、其長度、遠程主機的 IP 地址和遠程主機的端口號。
-
public void receive(DatagramPacket p)
從此套接字接收數據報包。當此方法返回時,DatagramPacket 的緩衝區填充了接收的數據。數據報包也包含發送方的 IP 地址和發送方機器上的端口號。 此方法在接收到數據報前一直阻塞。數據報包對象的 length 字段包含所接收信息的長度。如果信息比包的長度長,該信息將被截短。
- public void close() 【關閉此數據報套接字。】
DatagramPacket類的常用方法:
-
public DatagramPacket(byte[] buf,int length)
構造 DatagramPacket,用來接收長度爲 length 的數據包。 length 參數必須小於等於 buf.length。
-
public DatagramPacket(byte[] buf,int length,InetAddress address,int port)
構造數據報包,用來將長度爲 length 的包發送到指定主機上的指定端口號。length 參數必須小於等於 buf.length。
-
public int getLength() 【返回將要發送或接收到的數據的長度】
多點廣播
Datagram只允許數據報發送給指定的目標地址,而MulticastSocket可以將數據報以廣播方式發送到數量不等的多個客戶端。
IP協議爲多點廣播提供了這批特殊的IP地址,這些IP地址的範圍是224.0.0.0至239.255.255.255。
MulticastSocket常用的方法:
-
MulticastSocket(int port) :
創建多播套接字並將其綁定到特定端口。創建一個MulticastSocket對象後,還需要將該MulticastSocket加入到指定的多點廣播地址,如果結束也需要脫離多點廣播地址。
-
void joinGroup(InetAddress mcastaddr) :【加入多播組。】
-
void leaveGroup(InetAddress mcastaddr) :【離開多播組。】
-
void setLoopbackMode(boolean disable) :【啓用/禁用多播數據報的本地回送。true 表示禁用LoopbackMode。】
public class Client { public static void main(String[] args) throws UnknownHostException, IOException { // 1、連接服務器 Socket socket = new Socket("127.0.0.1", 9999); // 2、開啓兩個線程,一個收消息,一個發消息 SendThread st = new SendThread(socket); ReceiveThread rt = new ReceiveThread(socket);
st.start(); rt.start(); // 等發送線程停下來再往下走 try { st.join(); } catch (InterruptedException e) { e.printStackTrace(); } // 等接收線程停下來,再往下走,斷開連接 try { rt.join(); } catch (InterruptedException e) { e.printStackTrace(); } socket.close(); } static class SendThread extends Thread { private Socket socket; public SendThread(Socket socket) { super(); this.socket = socket; } public void run() { try { // 鍵盤輸入 Scanner input = new Scanner(System.in); OutputStream out = socket.getOutputStream(); PrintStream ps = new PrintStream(out); while (true) { // 從鍵盤輸入 System.out.print("請輸入要發送的消息:"); String content = input.nextLine(); // 給服務器發送 ps.println(content); // 如果bye,就結束髮送 if ("bye".equals(content)) { break; } } input.close(); } catch (IOException e) { e.printStackTrace(); } } } static class ReceiveThread extends Thread { private Socket socket; public ReceiveThread(Socket socket) { super(); this.socket = socket; } public void run() { try { InputStream in = socket.getInputStream(); InputStreamReader isr = new InputStreamReader(in); BufferedReader br = new BufferedReader(isr); while (true) { String line = br.readLine(); if("bye".equals(line)){ break; } System.out.println(line); } } catch (IOException e) { e.printStackTrace(); } } } }
public class Server { private static ArrayList<Socket> online = new ArrayList<Socket>(); public static void main(String[] args) throws IOException { //1、開啓服務器 ServerSocket server = new ServerSocket(9999); while(true){ //2、接收客戶端的連接 Socket socket = server.accept(); //把這個客戶端加入到online中 online.add(socket); //每一個客戶端獨立的線程 MessageHandler mh = new MessageHandler(socket); mh.start(); } } private static class MessageHandler extends Thread{ private Socket socket; private String ip; public MessageHandler(Socket socket) { super(); this.socket = socket; this.ip = socket.getInetAddress().getHostAddress(); } public void run(){ //這個客戶端的一連接成功,線程一啓動,就可以告訴其他人我上線了 sendToOthers(ip+"上線了"); //(1)接收當前的客戶端發送的消息 try { InputStream in = socket.getInputStream(); InputStreamReader isr = new InputStreamReader(in); BufferedReader br = new BufferedReader(isr); String content; while((content = br.readLine()) !=null){ //收到一句,轉發一句 sendToOthers(ip+"說:" + content); if("bye".equals(content)){ //給自己發一句bye OutputStream out = socket.getOutputStream(); PrintStream ps = new PrintStream(out); ps.println("bye"); break; } } sendToOthers(ip+"下線了"); } catch (IOException e) { sendToOthers(ip+"掉線了"); } } //因爲轉發的代碼也很長,獨立爲一個方法 public void sendToOthers(String str){ //遍歷所有online的客戶端 Iterator<Socket> iterator = online.iterator(); while(iterator.hasNext()){ Socket on = iterator.next(); if(!on.equals(socket)){//只給其他客戶端轉發 try { OutputStream out = on.getOutputStream(); PrintStream ps = new PrintStream(out); ps.println(str); } catch (IOException e) { //說明on這個客戶端要麼下線了,要麼掉線了 iterator.remove(); } } } } } }