1. 程式人生 > >Java基礎知識網路程式設計

Java基礎知識網路程式設計

概述

java除了可以完成本地的操作,也可以完成網路通訊。比如想從自己的電腦上傳送一個資訊到張三的電腦上,張三收到資訊之後再給我返回一個資訊,利用java實現兩個機器之間的資料的通訊。資料通訊的原理就是資料傳輸的過程,與本機的區別就是涉及到網路。
網路通訊要具備的要素和模型:
比如和張三通訊
1、首先要找到張三的主機,張三主機的標識就是IP地址(也就是主機的名字,IP地址由4個位元組表示,可以表示很多主機,避免衝突)。
2、和張三通訊的方式有很多種,可以是QQ,也可以是微信。兩個機器都要裝有通訊的軟體QQ或者微信。資料要傳送到對方指定的應用程式上,為了標識這些應用程式,所以給這些網路應用程式都用數字進行標識,比如QQ用4000標識,微信用2900標識,為了方便稱呼這個數字,叫做埠 ,這是邏輯埠,不是物理介面,也不是網線介面。
這裡寫圖片描述


3、兩個機器想要進行通訊,還要定義通訊規則,使兩方都能聽懂對方的內容,這個通訊規則就是協議。網際網路的成千上萬的機器都能進行通訊,因為國際組織定義了一個通用的協議TCP/IP,現在的作業系統裡面都安裝了這個協議 ,適用於廣域網和區域網。
這裡寫圖片描述
可以新增新的協議。也可以解除安裝TCP/IP協議,但是一般的機器解除安裝不了,因為已經固化到系統裡面了。
但是某些特有的單位和組織,為了安全,他們的通訊方式和我們不一樣,他們有自己特有的協議進行通訊,所以外界不能與之通訊,入侵不了。
IP地址介紹:

  • IP地址是由4段組成,每段是個位元組,最大值是255
  • IP地址分成很多段,A/B/C等
  • 有個IP很特殊127.0.0.1,這是本地迴環地址,當本機沒有配地址時,本機預設的地址就是127.0.0.1,其中一個用處是用來測試網絡卡,ping 127.0.0.1 如果成功說明網絡卡正常。
  • 有些IP地址被保留不用於公網,用於區域網中。不同區域網中可以有相同的IP地址。192.168.. 是最常用的保留地址段,還有其他保留地址段。
  • 子網掩碼的出現是為了解決電腦數量增多,IP地址不夠用的問題。電信廠商們給某個區域只提供一個公網IP,通過子網掩碼將這個區域內劃分為區域網,整個區域走同一個公網IP。
  • 後來四段IP地址不夠用了,便出現了六段IP地址,而且出現字母,數量多得多。

埠介紹
埠的大小:0-65535
其中0-1024埠被系統使用了,自己的程式也可以用,但是可能出現衝突現象。
幾個預設埠:web服務:80、tomcat伺服器:8080、MySql資料庫:3306。可以自己定義埠。

網路模型

這裡寫圖片描述
ISO網路模型中傳輸過程簡單描述:
以QQ傳送訊息為例解釋。
QQ軟體在應用層上,首先將資料按照應用層的封裝規則對資料進行封裝。
然後傳到表示層,再按照表示層的規則進行封裝,然後進行會話層封裝,傳輸層封裝,網路層封裝(加上IP地址),資料鏈路層封裝,最後到達物理層,物理層就是網線、光纖、無線 等。這個過程叫做資料封包過程,根據每一層的協議加上每層的資訊。資料封包之後,最後經過物理層傳輸到目的地址。
在目的地址會按照每層協議進行資料拆包,最終到達應用層,在應用層根據埠號確定將資料傳給QQ軟體。
TCP/IP模型
由於ISO的七層模型理解起來比較麻煩,後來出現了TCP/IP模型,將7層模型簡化成了4層模型。將應用層、表示層、會話層歸為應用層;將資料鏈路層和物理層歸為主機至網路層,在加上傳輸層和網路層一共是4層。
我們進行Java網路程式設計就是在傳輸層和網際層,而Java Web開發,是在應用層,將底層的東西進行了封裝。
傳輸層協議常見的有TCP、UDP
網際層最常見的協議IP
應用層的協議有很多比如HTTP和FTP等。

網路通訊要素

IP地址、埠號、傳輸協議。
Java語言進行網路通訊時,這三個要素是怎麼體現的呢?
Java提供三個物件來操作三個要素。
IP地址:類 InetAddress 此類表示網際網路協議 (IP) 地址。
IP 地址是 IP 使用的 32 位或 128 位無符號數字,它是一種低階協議,UDP 和 TCP 協議都是在它的基礎上構建的。
沒有建構函式。
獲得物件的方式:static InetAddress getLocalHost() 返回本地主機
主要方法: String getHostAddress()
返回 IP 地址字串(以文字表現形式)。
String getHostName()
獲取此 IP 地址的主機名。
static InetAddress getByName(String host)
在給定主機名的情況下確定主機的 IP 地址。
埠號:用於標識程序的邏輯地址,不同程序的標識。有效埠號0~65535.其中0~1024系統使用或者保留埠。
傳輸協議:
兩種傳輸協議比較常見:TCP和UDP,區別是什麼?
UDP(面向無連線,傳送資料之前雙方不需要建立連線,類似郵局寄包裹):
- 將資料及源和目的封裝成資料包中,不需要建立連線
- 每個資料包的大小限制在64K
- 因無連線,是不可靠協議
- 不需要建立連線,速度快
聊天通訊使用的就是UDP。網路視訊也是UDP。對傳輸速度要求高,可靠性要求低的網路傳輸一般是UDP。
TCP(面向連線,雙方必須都在(三次握手確定),相當於打電話):
- 建立連線,形成傳輸資料
- 在連線中進行大資料量傳輸 (不需要封裝包)
- 通過三次握手完成連線,是可靠協議
- 必須建立連線,效率稍低 ,消耗資源
檔案下載使用的就是TCP,可靠性要求高

Socket

Java網路程式設計指的就是Socket程式設計。Socket是插座(也稱套接字)的意思。
Socket為網路服務提供一種機制。
兩個主機如果想要通訊,需要有物理層的連線,比如網線,主機上都有一個網線的插口,連線兩個主機。每個應用程式都有一個類似的插口,使兩個主機上的同種應用程式之間可以網路通訊。這個插口就是Socket的概念。
所以,通訊的兩端都有Socket,網路通訊其實就是Socket間的通訊,資料在兩個Socket之間通過IO傳輸。
每種傳輸協議對應的建立Socket端點的方式,就有了UDP傳輸方式中Socket服務建立方式和TCP傳輸方式中Socket服務建立方式。

UDP傳輸方式

DatagramSocket類 此類表示用來發送和接收資料報包的套接字(插座)。
構造方法:
DatagramSocket()
構造資料報套接字並將其繫結到本地主機上任何可用的埠(隨機)。
DatagramSocket(int port)
建立資料報套接字並將其繫結到本地主機上的指定埠(接收端和傳送端都可以指定繫結的埠號)。
主要方法:
void receive(DatagramPacket p)
從此套接字接收資料報包。
void send(DatagramPacket p)
從此套接字傳送資料報包。
void close()
關閉此資料報套接字。

DatagramPacket類 此類表示資料報包。每條報文僅根據該包中包含的資訊從一臺機器路由到另一臺機器。從一臺機器傳送到另一臺機器的多個包可能選擇不同的路由,也可能按不同的順序到達。不對包投遞做出保證。
構造方法:
DatagramPacket(byte[] buf, int length)
構造 DatagramPacket,用來接收長度為 length 的資料包。
部分方法:
InetAddress getAddress()
返回某臺機器的 IP 地址,此資料報將要發往該機器或者是從該機器接收到的。
byte[] getData()
返回資料緩衝區。

小例項:

需求:建立兩個應用程式,一個用於傳送資料,一個用於接收資料,接收資料的埠設為10000。
思路:
傳送端
1,建立udpsocket服務
2. 提供資料,並將資料封裝到資料包中
3. 通過socket服務的傳送功能,將資料包傳送出去
4.關閉資源
接收端:
1,建立udpsocket服務,通常會監聽一個埠,其實就是給這個接收網路應用程式定義數字標識, 方便於明確哪些資料過來,該應用程式可以處理。
2. 定義一個數據包,因為要儲存接收到的位元組資料,且資料包物件中有更多功能可以
提取位元組資料中的不同資料資訊
3. 通過socket服務的接收功能,將資料包存入已經定義好的資料包中。
4. 通過資料包物件的特有功能,將這些不同的資料取出,列印在控制檯上
5. 關閉資源

import java.net.*;
class UdpSendDemo
{
    public static void main(String[] args) throws Exception
    {
        DatagramSocket ds=new DatagramSocket();

        byte[] data="udp is coming".getBytes();
        DatagramPacket dp=new DatagramPacket(data,data.length,InetAddress.getByName("127.0.0.1"),10000);
         ds.send (dp);
         ds.close();
    }
}
import java.net.*;
class UdpReceiveDemo 
{
    public static void main(String[] args) throws Exception
    {
        DatagramSocket ds=new DatagramSocket(10000);
        byte[] data=new byte[1024];
        DatagramPacket dp=new DatagramPacket(data,data.length);
        ds.receive(dp);//阻塞式方法,沒有接收到資料就等
        InetAddress ip=dp.getAddress();
        System.out.println("主機名"+ip.getHostName());
        System.out.println("IP"+ip.getHostAddress());
        System.out.println("資料:"+new String(dp.getData(),0,dp.getLength()));

    }
}

使用UDP方式編寫簡單的聊天程式

要求:
編寫一個聊天程式。
有收資料的部分,和發資料的部分。
這兩部分需要同時執行。
那就需要用到多執行緒技術。
一個執行緒控制收,一個執行緒控制發。
因為收和發動作是不一致的,所以要定義兩個run方法。
而且這兩個方法要封裝到不同的類中。

傳送類:

class SendThread implements Runnable
{
    private DatagramSocket ds=null;
    public  SendThread(DatagramSocket ds)
    {
        this.ds=ds;
    }

    public void run()
    {
        try
        {
            BufferedReader bur=new BufferedReader(new InputStreamReader(System.in));
            String line=null;
            while((line=bur.readLine())!=null)
            {
                byte[] senddata=line.getBytes();
                DatagramPacket dp=new DatagramPacket(senddata,senddata.length,InetAddress.getByName("192.168.1.114"),10001);
                ds.send(dp);
                //System.out.println("IP:"+InetAddress.getLocalHost().getHostName()+":"+line);
            }
        }
        catch (Exception e)
        {
            throw new RuntimeException("傳送失敗");
        }


    }
}

接收類:

class ReceiveThread implements Runnable
{
    private DatagramSocket ds=null;
    public  ReceiveThread(DatagramSocket ds)
    {
        this.ds=ds;
    }

    public void run()
    {
        try
        {
            while(true)
            {
                byte[] data=new byte[1024];
                DatagramPacket dp=new DatagramPacket(data,data.length);
                ds.receive(dp);//阻塞式方法,沒有接收到資料就等
                InetAddress ip=dp.getAddress();
                String receiveData=new String(dp.getData(),0,dp.getLength());
                System.out.println("IP:"+ip.getHostAddress()+":"+receiveData);
            }
        }
        catch (Exception e)
        {
            throw new RuntimeException("接收失敗");
        }

    }
}

聊天類:

{
    public static void main(String[] args) throws Exception
    {
        DatagramSocket sds=new DatagramSocket();
        DatagramSocket rds=new DatagramSocket(10002);

        new Thread(new SendThread(sds)).start();
        new Thread(new ReceiveThread(rds)).start();
    }
}

因為聊天是兩個主機之間的通訊,為了簡單的測試程式,在同一主機上開啟兩個dos視窗測試。並且上面的程式碼只是通訊的一方的程式碼,另外一方的程式碼沒有差別,只是DatagramSocket和DatagramPacket繫結的埠不同:比如A的接收Socket服務物件的埠繫結為10002埠,傳送的DatagramPacket繫結為對方的10001埠,而B的接收Socket服務物件的埠繫結為10001埠,傳送的DatagramPacket繫結為對方的10002埠.兩者的傳送Socket服務不用繫結埠,讓系統隨機分配就好。聽的好暈,看看程式碼就好了。簡言之,傳送資料時,用DatagramPacket確定接收者的埠,接收資料時,用DatagramSocket確定自身繫結的埠,這兩個埠保持一致,就可以接收到資料啦。
這裡寫圖片描述

TCP傳輸方式

UDP分的是傳送端和接收端,而TCP分的是客戶端和伺服器端,分別對應兩個物件:Socket和ServerSocket
客戶端:
Socket類:此類實現客戶端套接字(也可以就叫“套接字”)。
建構函式:
Socket(InetAddress address, int port)
建立一個流套接字並將其連線到指定 IP 地址的指定埠號。
Socket(String host, int port)
建立一個流套接字並將其連線到指定主機上的指定埠號。
通過查閱Socket物件,發現在該物件建立時,就可以連線指定的主機。因為TCP是面向連線的,所以在建立Socket服務時,就要有服務端存在,並且連線成功。形成通路後,在該通道進行資料的傳輸。也有無引數的建構函式,此時用connect()方法確定目的端並連線。
Socket物件一旦建立成功,說明通訊的通道已建立成功,便產生了Socket流也就是網路流。
Socket中封裝了網路流,既有輸入流也有輸出流。可以用方法獲得這兩個流。
InputStream getInputStream()
返回此套接字的輸入流。
OutputStream getOutputStream()
返回此套接字的輸出流。 通過網路傳送到對方主機上。
伺服器端:
SocketServer類:此類實現伺服器套接字。伺服器套接字等待請求通過網路傳入。
構造方法:
ServerSocket()
建立非繫結伺服器套接字。
ServerSocket(int port)
建立繫結到特定埠的伺服器套接字。
方法:
Socket accept()
偵聽並接受到此套接字的連線。 這也是一個阻塞式方法。返回一個Socket物件。
多個客戶端往服務端傳送資料,服務端返回資料時,是如何做到那個客戶端發來的,返回給哪個客戶端,而不發生錯誤呢?
答:實際上,ServerSocket用accept()方法獲得請求的客戶端的Socket物件,利用該物件的輸入流和輸出流與該客戶端通訊,這樣便不會發生髮錯物件的情況。而且,服務端的輸入流和客戶端的輸出流對應,服務端的輸出流和客戶端的輸入流對應。

TCP演示示例1:

客戶端:
需求:給服務端傳送一個文字資料

import java.io.*;
import java.net.*;

class TcpClient
{
    public static void main(String[] args) throws Exception
    {
        //建立客戶端的socket服務,指定目的主機和埠,
        //一旦成功,通路建立,便有了Socket流即網路流,Socket流裡面既有輸入流也有輸出流。
        Socket s=new Socket("192.168.1.114",10003);
        //為了傳送資料,應該獲取Socket流中的輸出流
        OutputStream os=s.getOutputStream();
        os.write("tcp is coming".getBytes());

        s.close();
    }
}

服務端:
需求:定義端點接收資料並列印在控制檯上。

服務端:
1,建立服務端的socket服務。ServerSocket();
並監聽一個埠。
2,獲取連線過來的客戶端物件。
通過ServerSokcet的 accept方法。沒有連線就會等,所以這個方法阻塞式的。
3,客戶端如果發過來資料,那麼服務端要使用對應的客戶端物件,並獲取到該客戶端物件的讀取流來讀取發過來的資料。
並列印在控制檯。

4,關閉客戶端(服務端有可能不關閉自己,但會關閉客戶端,節省資源)
5、關閉服務端。(可選)

class  TcpServer
{
    public static void main(String[] args) throws Exception
    {
        //建立服務端socket服務。並監聽一個埠。
        ServerSocket ss = new ServerSocket(10003);

        //通過accept方法獲取連線過來的客戶端物件。
        while(true)
        {
        Socket s = ss.accept();

        String ip = s.getInetAddress().getHostAddress();
        System.out.println(ip+".....connected");

        //獲取客戶端傳送過來的資料,那麼要使用客戶端物件的讀取流來讀取資料。
        InputStream in = s.getInputStream();

        byte[] buf = new byte[1024];
        int len = in.read(buf);

        System.out.println(new String(buf,0,len));

        s.close();//關閉客戶端.
        }
        //ss.close();
    }
}

這裡寫圖片描述
TCP演示示例2:
需求:客戶端給服務端傳送資料,服務端收到後,給客戶端反饋資訊。
思路:
程式基於示例1的程式進行簡單修改即可。
客戶端Socket物件的輸出流寫完資料之後,獲得Socket的輸入流,執行read()方法,這也是個阻塞式方法,若沒讀到資料,等待。
服務端讀完資料之後,獲得socket的輸出流,寫入反饋的內容即可。

class TcpClient2 
{
    public static void main(String[] args)throws Exception 
    {
        Socket s = new Socket("192.168.1.254",10004);

        OutputStream out = s.getOutputStream();

        out.write("服務端,你好".getBytes());


        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 TcpServer2
{
    public static void main(String[] args) throws Exception
    {
        ServerSocket ss = new ServerSocket(10004);

        Socket s = ss.accept();

        String ip = s.getInetAddress().getHostAddress();
        System.out.println(ip+"....connected");
        InputStream in = s.getInputStream();

        byte[] buf = new byte[1024];

        int len = in.read(buf);

        System.out.println(new String(buf,0,len));


        OutputStream out = s.getOutputStream();


        Thread.sleep(10000);
        out.write("哥們收到,你也好".getBytes());

        s.close();

        ss.close();
    }
}

這裡寫圖片描述

TCP練習

要求:
建立一個文字轉換伺服器。
客戶端給服務端傳送文字,服務斷會將文字轉成大寫再返回給客戶端。
而且客戶端可以不斷的進行文字轉換。當客戶端輸入over時,轉換結束。
分析:
客戶端:
既然是操作裝置上的資料,那麼就可以使用io技術,並按照io的操作規律來思考。
源:鍵盤錄入。
目的:網路裝置,網路輸出流。
而且操作的是文字資料。可以選擇字元流。

步驟
1,建立服務。
2,獲取鍵盤錄入。
3,將資料發給服務端。
4,獲取服務端返回的大寫資料。
5,結束,關資源。

都是文字資料,可以使用字元流進行操作,同時提高效率,加入緩衝。

服務端:
源:socket讀取流。
目的:socket輸出流。
都是文字,裝飾。

import java.io.*;
import java.net.*;

class TranseClient
{
    public static void main(String[] args) throws Exception
    {
        Socket s=new Socket("192.168.1.6",10003);
        BufferedReader bur=new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter buw=new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
        BufferedReader burIn=new BufferedReader(new InputStreamReader(s.getInputStream())); 
        String line=null;
        while((line=bur.readLine())!=null)
        {
            System.out.println("想要轉換的文字為"+line);
            if(line.equals("over"))
                break;
            buw.write(line);
            buw.newLine();
            buw.flush();

            String str=burIn.readLine();
            System.out.println("轉換後的文字為"+str);

        }
        bur.close();
        s.close();

    }
}


class TranseServer
{
    public static void main(String[] args) throws Exception
    {
        ServerSocket ss=new ServerSocket(10003);
            Socket s=ss.accept();
            BufferedReader bur=new BufferedReader(new InputStreamReader(s.getInputStream()));
            BufferedWriter buw=new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
            String line=null;
            while((line=bur.readLine())!=null)
            {
                System.out.println("要轉換的文字為:"+line);
                if(line.equals("over"))
                    break;
                buw.write(line.toUpperCase());
                buw.newLine();
                buw.flush();
            }
            s.close();


           ss.close();

    }
}

這裡寫圖片描述
這個練習容易出現的問題。
現象:客戶端和服務端都在莫名的等待。
為什麼呢?
因為客戶端和服務端都有阻塞式方法。這些方法麼沒有讀到結束標記。那麼就一直等
而導致兩端,都在等待。
readLine()方法讀取一個文字行。通過下列字元之一即可認為某行已終止:換行 (‘\n’)、回車 (‘\r’) 或回車後直接跟著換行,如果沒有遇到換行符,那麼會一直等待。所以當源位元組流是鍵盤錄入時,要在程式中手動寫入換行符,或者呼叫BufferedWriter的newLine()方法。

還有一個地方比較特殊,當客戶端close(),相當於在輸出流的末尾寫入-1,服務端讀取到-1,根據程式關閉或者不關閉。