java網絡編程之Socket編程

概念

網絡編程分爲BIO(傳統IO)、NIO、AIO。Socket編程屬於BIO這種傳統IO。java

InetAddress

  java.net.InetAddress是JAVA中管理IP地址的類,經常使用編程

  

  

   public static void main(String[] args) throws UnknownHostException {
        InetAdressDemo.getLocalHost();
        System.out.println("---------------------------");
        getHostByName("Lenovo-Autumn");
    }    /**
     * 獲取主機ip和主機名
     * @throws UnknownHostException     */
    public static void getLocalHost() throws UnknownHostException {        //根據InetAddress獲取主機名和主機ip
        InetAddress localHost = InetAddress.getLocalHost();
        System.out.println(localHost);   //打印:Lenovo-Autumn/192.168.56.1        //根據getLocalHost()返回的值獲取ip和主機名
        String hostName = localHost.getHostName();
        String hostAddress = localHost.getHostAddress();
        System.out.println(hostName); //打印 Lenovo-Autumn
        System.out.println(hostAddress); //打印 192.168.56.1        //根據切割獲取主機名和ip
        String[] str = localHost.toString().split("/");
        System.out.println(str[0]); //打印 Lenovo-Autumn
        System.out.println(str[1]); //打印 192.168.56.1    }    /**
     * 根據主機名稱獲取ip地址
     * @param otherName  主機名(能夠是局域網中的機器名或者是域名或者是ip)
     * @throws UnknownHostException     */
    public static void getHostByName(String otherName) throws UnknownHostException {
        InetAddress otherHost = InetAddress.getByName(otherName);
        String hostName = otherHost.getHostName();
        String hostAddress = otherHost.getHostAddress();
        System.out.println(hostName); //打印 Lenovo-Autumn
        System.out.println(hostAddress); //打印 192.168.56.1
        System.out.println(otherHost);   //打印:Lenovo-Autumn/192.168.56.1
    }

  code數組

UDP

  發送數據時必須指定接收端的IP地址和端口號,就好像發送貨物的集裝箱上面必須標明接收人的地址同樣。服務器

  接收端不須要明確知道數據的來源,只須要接收到數據便可。網絡

  java.net.DatagramPackage

構造函數:併發

第一種是用來接受的數據包,不須要指定IP和端口,第二種是用來發送的數據包須要指定ip和端口異步

方法:socket

獲取ip地址,獲取服務器ip或者客戶端ipide

  

  返回端口號,獲取發送方或者函數

  返回數據緩衝區

  

  返回要發送或接受的數據包大小

  java.net.DatagramSocket

  DatagramPacket數據包的做用就如同是「集裝箱」,能夠將發送端或者接收端的數據封裝起來。然而運輸貨物只有「集裝箱」是不夠的,還須要有碼頭。在程序中須要實現通訊只有DatagramPacket數據包也一樣不行,爲此JDK中提供的一個DatagramSocket類。DatagramSocket類的做用就相似於碼頭,使用這個類的實例對象就能夠發送和接收DatagramPacket數據包,發送數據的過程以下圖所示。

構造函數:

用來建立發送端的DatagramSocket對象

用來建立接收端的DatagramSocket對象

  方法:

發送數據報包

接收數據報包

發送端

1,  建立DatagramSocket對象

DatagramSocket()

2,建立DatagramPacket對象,封裝數據,並指定ip和端口。

    DatagramPacket(byte[] buf, int length, InetAddress address, int port)  

3,發送數據

    socket.send(DatagramPacket dp)

4,釋放流資源

ds.close();

接收端

1,建立DatagramSocket對象,只須要指定端口

    DatagramSocket(port)

2,建立DatagramPacket對象

    DatagramPacket(byte[] data, int length)

3,接收數據存儲到DatagramPacket對象中 

    receive(DatagramPackage dp)

4,獲取DatagramPacket對象的內容   

    new String(data,0,dp.getLength());

5,釋放流資源

    ds.close();

/**
 * 實現udp發送端
 *  用java.net.DatagramPackage封裝數據
 *  用java.net.DatagramSocket發送數據
 *
 * 實現步驟
 *  1.用DatagramPackage對象,封裝數據,接受的地址和端口
 *  2.建立DatagramSocket
 *  3.調用DatagramSocket對象send方法,發送數據
 *  4.關閉資源
 *
 *  DatagramPackage構造函數
 *    DatagramPacket(byte[] buf, int length, InetAddress address, int port)
 *  DatagramSocket構造函數
 *    DatagramSocket()
 *    方法:send(DatagramPacket d)
 *  Created by Autumn on 2018/2/5. */public class UdpSend {    public static void main(String[] args) throws Exception {
        Scanner scanner = new Scanner(System.in);        //獲取地址
        InetAddress inet = InetAddress.getByName("127.0.0.1");        //建立DatagramSocket,負責接受和發送數據
        DatagramSocket ds = new DatagramSocket();        while(true){
            String msg = scanner.nextLine();            //建立數據包對象對象
            byte[] data = msg.getBytes();            //封裝數據,接受的地址和端口
            DatagramPacket dp = new DatagramPacket(data,data.length,inet,6000);            //發送數據包            ds.send(dp);            if(msg.equals("exit")){                break;
            }
        }        //關閉        ds.close();
    }
}/**
 *  實現udp接收端
 *  用java.net.DatagramPackage 接受數據
 *  用java.net.DatagramSocket 接受數據包
 *
 *  步驟
 *  1.建立DatagramSocket對象,綁定端口號(要和發送端端口一致)
 *  2.建立字節數組用來接受數據
 *  3.建立數據對象包DatagramPackage
 *  4.建立DatagramSocket
 *    receive(DatagramPackage dp)接受數據,將數據封裝如dp中
 *  5.拆包
 *    發送端的ip地址(DatagramPackage.get)
 *    接受到的字節數組
 *    發送的端口號
 *  6.關閉資源
 * Created by Autumn on 2018/2/5. */public class UdpReceive {    public static void main(String[] args) throws IOException {        //建立數據包傳輸的對象,並綁定端口號
        DatagramSocket ds = new DatagramSocket(6000);        //建立字節數組
        byte[] data = new byte[1024];        while(true){            //建立數據包對象,傳遞字節數組
            DatagramPacket dp = new DatagramPacket(data,data.length);            //調用ds對象的receive接受數據包,receive()有線程阻塞效果會一直等待接受數據            ds.receive(dp);            //獲取數據包大小
            int len = dp.getLength();            //獲取發送端的ip地址
            InetAddress sendAddress = dp.getAddress();
            String sendHostAddress = sendAddress.getHostAddress();            //System.out.println(sendHostAddress);            //獲取發送端端口號
            int port = dp.getPort();            //System.out.println(port);            //System.out.println(new String(data));   //直接打印1024個字節的字符串,有不少空格
            System.out.println(sendHostAddress+":"+port+"    "+new String(data,0,len));   //這樣打印沒有多餘的空格

            if(new String(data,0,len).equals("exit")){                break;
            }
        }        //關閉        ds.close();
    }
}

  code

TCP

ServerSocket類,用於表示服務器端,Socket類,用於表示客戶端。創建鏈接後用流進行輸入和輸出

 

ServerSocket

   實例化一個ServerSocket類,指定端口號

  監聽並接受此套接字的鏈接

  返回此服務器套接字的本地地址

  Socket

  實例化一個Socket並指定ip和端口

  

返回一個輸出流,用於客戶端發送數據

  返回一個輸入流,用於服務器端接受數據

  

  返回ip地址(服務器端的地址)

  返回端口號

客戶端

1,建立客戶端的Socket對象,指定服務器IP和端口號

  Socket(String host, int port)      

2,獲取Socket的輸出流對象

  getOutputStream(); 

3,寫數據給服務器

  out.write("服務器數據".getBytes());

4,關閉流資源

  socket.close();

服務器端

1,建立服務器端ServerSocket對象,指定服務器端端口號

  ServerSocket(int port)

2,開啓服務器,等待着客戶端Socket對象的鏈接,若有客戶端鏈接,返回客戶端的Socket對象

  accept()

3,經過客戶端的Socket對象,獲取客戶端的輸入流,爲了實現獲取客戶端發來的數據

  socket.getInputStream();

4,經過客戶端的輸入流,獲取流中的數據

byte[] data = new byte[1024];
int len = inputStream.read(data);
System.out.println(new String(data,0,len));

5,經過客戶端的Socket對象,獲取客戶端的輸出流,爲了實現給客戶端反饋信息

6,經過客戶端的輸出流,寫數據到流中

7,關閉流資源

socket.close();
serverSocket.close();

/**
 * 實現TCP服務器程序
 * 表示服務器程序的類java.net.ServerSocket
 * 構造方法:
 *      ServerSocket(int port);   傳遞端口號
 *
 * Important:必須得到客戶端的套接字(Socket)
 *     方法:Socket accept()
 *     服務器能夠獲取到客戶端的套接字
 * Created by Autumn on 2018/2/5. */public class TCPServer {    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(8888);        //調用服務器套接字對象accept()獲取客戶端套接字,具備線程等待效果
        Socket socket = serverSocket.accept();    //這裏會阻塞等待鏈接接入    cmd中telnet 127.0.0.1 8888便可鏈接        //根據得到的客戶端的socket獲取輸入流
        InputStream inputStream = socket.getInputStream();        //根據輸入流將數據讀入到data中
        byte[] data = new byte[1024];        int len = inputStream.read(data);   //這裏會阻塞,等待數據   cmd中ctrl+]進入到telnet操做模式send value發送數據
        System.out.println(new String(data,0,len));
        socket.close();
        serverSocket.close();
    }
}/**
 * 實現TCP客戶端,鏈接到服務器
 * 和服務器實現數據交換
 * 實現TCP客戶端程序的類 java.net.Socket
 *
 * 構造方法:
 *      Socket(String host, int port)傳遞服務器IP和端口號
 *      注意:構造方法只要運行,就會和服務器進行鏈接,鏈接時報,拋出異常
 *
 *      OutputStream    getOutputStream()  返回套接字的輸出流
 *          做用:將數據輸出,輸出到服務器
 *      InputStream    getInputStream()  返回套接字的輸入流
 *          做用:從服務器端讀取數據
 *
 *      客戶端服務器數據交換,必須使用套接字對象Socket中的獲取的IO劉,本身new的流不行
 *
 * Created by Autumn on 2018/2/5. */public class TCPClient {    public static void main(String[] args) throws IOException {        //建立Socket對象,鏈接服務器
        Socket socket = new Socket("127.0.0.1",8888);        //經過客戶端的套接字對象Socket方法,獲取字節輸出流,將數據寫向服務器
        OutputStream out = socket.getOutputStream();
        out.write("這是一條來客戶端的數據".getBytes());
        socket.close();
    }
}

用cmd命令telnet實現和SocketServer互動

import java.io.IOException;import java.io.InputStream;import java.net.ServerSocket;import java.net.Socket;public class TCPServer {    /*同步阻塞*/
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(8888);
        System.out.println("服務端啓動成功...");        while(true){            /*一次只能處理一個鏈接,在一個鏈接沒關閉前沒法接收第二個鏈接,在這裏開一個框發送數無問題,開放兩個框時會出現第二個無反應*/
            Socket socket = serverSocket.accept();   //這裏會阻塞等待鏈接接入    cmd中telnet 127.0.0.1 8888即表明鏈接
            System.out.println("新客戶端鏈接成功....");
            InputStream inputStream = socket.getInputStream();            while(true) {                byte[] data = new byte[1024];
                System.out.println("正在等待數據...");                int len = inputStream.read(data);    //這裏會阻塞,等待數據,若是直接關閉cmd窗口會由於關閉socket通道致使len返回-1   cmd中ctrl+]進入到telnet操做模式send value發送數據
                if (len != -1){
                    System.out.println(new String(data, 0,len, "GBK"));   //用GBK是由於CMD窗口命令發送的數據是GBK編碼
                }else{                    break;
                }
            }
        }        //socket.close();        //serverSocket.close();        //System.out.println("服務器端關閉....");    }
}

鏈接服務端 

向服務端發送數據

再開一個cmd而後telnet發送數據,發現無反應。必須關閉第一個cmd才能鏈接成功。

線程池改進,能併發訪問

用ExecutorService線程池實現每個鏈接建立一個新的線程

import java.io.IOException;import java.io.InputStream;import java.io.UnsupportedEncodingException;import java.net.ServerSocket;import java.net.Socket;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;public class TCPServer {    /*異步阻塞*/
    public static void main(String[] args) throws IOException {
        ExecutorService threadPool = Executors.newCachedThreadPool();   //線程池
        ServerSocket serverSocket = new ServerSocket(8888);
        System.out.println("服務端啓動成功...");        while(true){            /*一次只能處理一個鏈接,在一個鏈接沒關閉前沒法接收第二個鏈接,在這裏開一個框發送數無問題,開放兩個框時會出現第二個無反應*/
            System.out.println("等待客戶端鏈接...");            final Socket socket = serverSocket.accept();   //這裏會阻塞等待鏈接接入    cmd中telnet 127.0.0.1 8888即表明鏈接
            threadPool.execute(new Runnable() {   //啓動一個線程
                public void run() {                    try {
                        System.out.println("新客戶端鏈接成功....");
                        InputStream inputStream = socket.getInputStream();                        while(true) {                            byte[] data = new byte[1024];
                            System.out.println("正在等待數據...");                            int len = inputStream.read(data);    //這裏會阻塞,等待數據,若是直接關閉cmd窗口會由於關閉socket通道致使len返回-1   cmd中ctrl+]進入到telnet操做模式send value發送數據
                            if (len != -1){
                                    System.out.println(Thread.currentThread()+new String(data, 0,len, "GBK"));   //用GBK是由於CMD窗口命令發送的數據是GBK編碼
                            }else{
                                System.out.println("break循環");                                break;
                            }
                        }
                        System.out.println("一次Socket鏈接關閉");
                    } catch (UnsupportedEncodingException e) {
                        e.printStackTrace();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            });

        }        //socket.close();        //serverSocket.close();        //System.out.println("服務器端關閉....");    }
}

優勢:傳輸質量好(因此BIO適合傳輸鏈接少可是數據量大的請求)

缺點:每個鏈接都佔用一個線程,很佔用系統資源。

tip:全部的調優都關聯到系統資源(IO、存儲、內存、CPU)

code

相關文章
相關標籤/搜索