Java網絡編程

基本概念:
一、網絡:將不同區域的計算機連接到一起就組成了一個網絡,根據區域大小可以將其分爲局域網、廣域網、城域網等。
二、地址:IP地址,確定網絡上的一個絕對位置  ,相當於房子的地址
三、端口號:區分計算機軟件的 ,每一個應用都需要綁定一個端口,在這個端口下運行。相當於房子的房門,包含兩個字節  0~65535 一共65535個端口
1、在同一個協議下,端口號不能重複。
2、1024以下的端口不要使用。操作系統預留的端口,供知名廠商使用。如80 -->http 21 -->ftp
四、資源定位:URL統一資源定位符  URI:統一資源
五、數據的傳輸
1、協議:TCP和UDP協議
(1)TCP:電話 類似於三次握手,面向連接  安全可靠  效率低下
(2)UDP:非面向連接  效率高 。類似短信 
2、數據傳輸
1)先封裝
2)後拆分

關注的重點:傳輸層+網絡層

我們在進行網絡編程的時候,按照步驟一步步來,其實是比較簡單的。

在用java進行網絡編程時,常用的的傳輸協議其實只有兩個,TCP協議和UDP協議。

包含的類有

1、InetAddress、InetSocketAddress

2、URL

3、TCP

4、UDP

InetAddress :此類表示互聯網協議(ip)地址。InetAddress的實例包含IP地址,還可能包含相應的 主機名稱取決於它是否用主機名構造或者是否已執行反向主機名解析),InetAddress不能訪問構造器,只能獲取靜態實例,即InetAddress.方法

InetAddress inetAddress=InetAddress.getLocalHost();
System.out.println(inetAddress.getHostAddress());
InetAddress inetAddress1= InetAddress.getByName("www.google.com");
System.out.println(inetAddress1.getHostName());
System.out.println(inetAddress1.getHostAddress());

上述代碼可以獲取主機名稱和主機地址,獲取谷歌的主機名稱和主機地址。

方法:InetAddress.getLocalHost()獲得本機地址
getHostName()返回域名,本機爲計算機名
getHostAddress()返回ip地址
InetAddress.getByName("ip地址|域名")這裏注意下,在獲得網絡上的ip地址時,若ip地址dns可以解析,則會返回解析之後的域名,如果dns解析不了,則只能返回ip地址。

2、InetSocketAddress address = new InetSocketAddress("127.0.0.1",999);


介紹了基礎的,然後我們首先看一下UDP協議。。。

UDP:以數據爲中心,非面向連接、不安全,效率高。我們在使用UDP連接的時候,服務器和客戶端之間不需要連接也可以發送消息。

使用UDP連接的步驟:

1、客戶端:(1)創建客戶端,準備連接。建立Socket+指定端口(2)準備數據  數據爲字節數組(3)打包數據DatagramPacket+服務器端口及地址(4)發送(5)釋放資源

簡單代碼如下:

//注意:服務器無需開啓也可以運行。這是和TCP不同的一點,TCP協議必須是服務器和客戶端兩者連接起來才能運行 
public static void main(String[] args) throws IOException {
        //            創建客戶端,建立Socket類+端口
        DatagramSocket client  = new DatagramSocket(999);
        try {
        //準備數據  字節數組
            while (true){
                System.out.println("請輸入要發送的信息:");
                BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
                String msg = br.readLine();
                byte[] data= msg.getBytes();

//            打包
                DatagramPacket datagramPacket =new DatagramPacket(data,data.length,new InetSocketAddress("localhost",9000));
//           發送
                client.send(datagramPacket);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            Utils.close();
        }
    }

2、服務器端

(1)創建服務器,DatagramSocket類+指定端口(2)準備容器接收從客戶端傳來的數據,字節數組,封裝DatagramPacket(3)封裝成包,接收數據  (4)對數據進行分析 (5)釋放資源。

簡單代碼如下:

 public static void main(String[] args) throws IOException {
//        1)創建服務器端,DatagramSocket 類+指定端口
        DatagramSocket server = null;
        try {
            server = new DatagramSocket(9000);
            //        2)準備接收容器 字節數組  封裝 DatagramPacket
            while (true){
                byte[] data = new byte[100];
//        3)封裝成包 接收數據
                DatagramPacket packet = new DatagramPacket(data,data.length);
                server.receive(packet);
//      4)  分析數據
                byte[] res= packet.getData();
                System.out.println(new String(res,0,res.length));
            }
        } catch (SocketException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            Utils.close(server);
        }
    }

TCP:Socket通信,面向連接  特點是安全、可靠、效率低 ,類似於打電話

採用套接字編程。必須要等服務器開啓才能開啓客戶端,且接收數據之後,兩者之間可以相互通信,其應用有聊天室,私聊等。

比如說我們打電話給10086,10086就相當於一個服務器,服務器接收到請求之後,會把我們的請求交給一個客服處理,與此同時又有另一個客戶打電話,服務器轉發到另一個客服進行處理。這個都是通過多線程實現的,各個線程之間彼此獨立,互不干擾。

由於我們的Java底層都封裝好了,因此我們直接用Socket連接,監聽端口即可實現網絡編程。

步驟:

1、服務器端(1)首先建立Socket連接,監聽端口。(2)當有客戶端連接之後,獲得客戶端的ip地址和其他信息。(3)和客戶端交換數據(發送/接收消息)等   (4)關閉連接

2、客戶端  客戶端的步驟也是一樣的。

簡單示例:(單線程、一個客戶一個服務器)

客戶端:

/**
 * 1、創建客戶端,必須指定服務器和端口  此時就在連接
 * 2、接收服務器端連接
 * 3、發送數據+接收數據
 */
public class MyClient {
    public static void main(String[] args) throws IOException {
        Socket socket =null;
        DataInputStream dis =null;
        DataOutputStream dos=null;

       while (true){
           //創建socket連接
           socket = new Socket("localhost",9001);
//            System.out.println("請輸入姓名:");
           //接收服務器消息

//             br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
//            String msg = br.readLine();//注意,用這個方法需要有行結束符
//            System.out.println(msg);
           dis = new DataInputStream(socket.getInputStream());
           String msg = dis.readUTF();
           System.out.println(msg);
           //發送消息給服務器
           Scanner sc = new Scanner(System.in);
           //創建輸出管道
           dos = new DataOutputStream(socket.getOutputStream());
           dos.writeUTF(sc.nextLine());
           dos.flush();
           Utils.close(dos,dis,socket);
       }

    }
}

服務器端:

public static void main(String[] args) throws IOException {
    ServerSocket server =null;
    BufferedWriter bw = null;
    Socket socket=null;
    //創建socket連接
    server =new ServerSocket(9001);
    while (true){
        socket = server.accept();
        System.out.println("一個客戶端建立連接!");
        //給客戶端發送消息 getOutputstream方法把數據輸出到管道
        DataOutputStream data = new DataOutputStream(socket.getOutputStream());
        String msg ="歡迎您!";
        data.writeUTF(msg);
        //服務器接收數據 getInputStream接收從客戶端發來的管道中的數據
        DataInputStream dis = new DataInputStream(socket.getInputStream());
        String msg1 = dis.readUTF();
        System.out.println("客戶端說:"+msg1);
        Utils.close(dis,data,socket);
    }
}

這個例子實現的是一個客戶端對應一個服務器,我們採用getOutputstream和getInputStream兩個方法分別輸出和接收數據。該示例服務器端在本機創建了一個Socket連接,監聽9001端口,服務器端首先發送一個“歡迎您”的消息,將這消息寫入輸出流管道中,待客戶端連接之後,用getInputStream接收管道中的數據並打印(注意:此處是打印到控制檯,如果有界面可以輸出至界面)。下一步,客戶端在控制檯獲取用戶輸入,再寫入管道中,最後發送給服務器。服務器接收並打印出來,這就實現了一個簡單的TCP通信。

當然,上述的例子都是很簡單的,看似實現了一個私聊的功能,實則不然。在實際的應用中,我們不可能發送和接收都是同一條管道,服務器只有一個,而客戶端都是存在多個的,因此我們需要有多條管道。在上述例子中,發送和接收消息都是在同一條管道的,這顯然不現實。這意味着客戶端發送接收消息只能按順序進行。因此我們需要把發送接收都獨立出來,這就需要用到多線程了。我們把發送接收獨立成兩個線程,兩者互不干擾。以這種思路,我們可以寫一個簡單的聊天室,實現羣聊的功能。

代碼如下:

1、發送數據線程:

package com.zr.net.chat;

import com.zr.Utils;

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;

/**
 * 發送數據線程
 */
public class Send implements Runnable {
    //控制檯輸入流
    private BufferedReader console;
    //輸出流
    private DataOutputStream dos;
    //控制線程運行與否
    private boolean isRunning = true;
    public Send(){
        console = new BufferedReader(new InputStreamReader(System.in));
    }
    //初始化
    public Send(Socket socket) throws IOException {
        this();
        try {
            dos =new DataOutputStream(socket.getOutputStream());
        } catch (IOException e) {
            isRunning = false;//出異常則關閉
            Utils.close(dos);
//            e.printStackTrace();
        }
    }
    //從控制檯接收讀取數據
    private String recevieMsgFromConsole(){
        try {
            return console.readLine();
        } catch (IOException e) {
            //e.printStackTrace();
            //讀取失敗返回空串
        }
        return "";
    }
    //發送數據
    public void send(){
        String msg = recevieMsgFromConsole();
        if (null!=msg&&!msg.equals("")){
            try {
                dos.writeUTF(msg);
                dos.flush();
            } catch (IOException e) {
                isRunning = false;
//                e.printStackTrace();
            }
        }
    }
    @Override
    public void run() {
        while (isRunning){
            send();
        }
    }
}

接收數據線程:

package com.zr.net.chat;
import com.zr.Utils;
import java.io.DataInputStream;
import java.io.IOException;
import java.net.Socket;

/**
 * 接收數據線程
 */
public class Receive implements  Runnable {
    //接收數據,讀取,輸入流
    private DataInputStream dis ;
    //線程標識符
    private  boolean isRunning =true;
    public Receive(){

    }
    public Receive(Socket client){
        this();
        try {
            dis = new DataInputStream(client.getInputStream());
        } catch (IOException e) {
//            e.printStackTrace();
            isRunning = false;
            try {
                Utils.close(dis);
            } catch (IOException e1) {
                e1.printStackTrace();
            }
        }
    }
//接收數據
    private String getMsg(){
        String msg = null;
        try {
            msg = dis.readUTF();
        } catch (IOException e) {
            isRunning = false;
            try {
                Utils.close(dis);
            } catch (IOException e1) {
                e1.printStackTrace();
            }
//            e.printStackTrace();
        }
        return msg;
    }
    @Override
    public void run() {
        while (isRunning){
            System.out.println(getMsg());
        }
    }
}

服務器多個管道,接收發送數據,轉發數據給其他客戶端:

             /**
     * 一個客戶端一條通道
     * 1、輸入流
     * 2、輸出流
     * 3、發送數據
     * 4、接收數據
     */
    private   class MyChannel implements Runnable{
        DataInputStream input = null;
        DataOutputStream out = null;
        private boolean isRunning = true;

        public MyChannel(Socket client){
            try {
                input = new DataInputStream(client.getInputStream());
                out = new DataOutputStream(client.getOutputStream());

            } catch (IOException e) {
//                e.printStackTrace();
                try {
                    Utils.close(out,input);
                    isRunning = false;
                } catch (IOException e1) {
                    e1.printStackTrace();
                }
            }
        }
        //發送數據
        private void send(String msg){
            try {
                out.writeUTF(msg);
                out.flush();
            } catch (IOException e) {
                e.printStackTrace();
                isRunning = false;
            }

        }

        //接收數據
        private String receive() throws IOException {
            String msg= "";
            try {
                msg=input.readUTF();

                System.out.println(msg);
            } catch (IOException e) {
                Utils.close(input);
                isRunning = false;
                list.remove(this);//移除自身
            }
            return msg;
        }

        //發送給其他客戶端
        private void sendOthers(){
            try {
                String msg =receive();
                for (MyChannel c:list){
                    if (c ==this){
                        continue;
                    }
                    //發送給其他客戶端
                    c.send(msg);
                }
            } catch (IOException e) {
//                e.printStackTrace();

            }

        }
        @Override
        public void run() {
            while (isRunning){
                sendOthers();
            }
        }

    } 

服務器主線程:這裏用list創建多個客戶端與服務器通信的管道

public class MultiServer {
    //多條道路
    private List<MyChannel> list=new ArrayList<MyChannel>();

    public static void main(String[] args) throws IOException {
        new MultiServer().start();
    }

    public void start() throws IOException {
        ServerSocket server= new ServerSocket(9999);

        while (true){
            Socket socket = server.accept();
            MyChannel myChannel = new MyChannel(socket);
            list.add(myChannel);
            new Thread(myChannel).start();

        }
    }

客戶端:發送接收數據線程獨立運行,互不干擾。

public class Client2  {
    public static void main(String[] args) {
        try {
            Socket client = new Socket("localhost",9999);
            System.out.println("請輸入用戶名:");
            new Thread(new Send(client)).start();
            new Thread(new Receive(client)).start();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

當然這個程序還有很多可以擴展 的地方。

總結如下:

網絡編程我們主要要掌握的其實就是Socket套接字編程,在數據傳輸層其實用到的就是前面所學的IO流,唯一的不同就是這裏的數據是通過管道進行傳輸的。簡單的網絡通信很簡單,通過套接字,監聽端口,連接,IO流交換數據,即可實現,實際上我們還需要加入多線程、循環隊列等技術來進行控制,實現多發多收,消息轉發的功能。我們需要在服務器端建立多個channel,多個通道,確保數據正確無誤的轉發。此外還可以根據自己的需要進行封裝。。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章