Java網絡Day05 2020-05-02

內容

1.客戶端和服務器端連接介紹
2.實現點對點對聊
3.實現群聊

一.客戶端和服務器端連接介紹

1.注意點

①一般來講,有時候沒有嚴格意義的服務器端和客戶端
比如A端去連接遠程網絡中的B端,那么此時B端就扮演服務器的角色,A端扮演客戶端的角色
②緩存:當B不在線時,A先發給騰訊的服務器(這里假如是QQ),這里面的內容包括B的IP地址,還有端口號,還有具體的內容,當B上線時,就發給B。這里就相當于做了一個緩存。

2.作為一個客戶端必須提供的兩個內容

①IP地址(通過這個找到某個設備)
②端口號(找到提供服務的程序,比如qq)

3.客戶端和服務器端如何連接(socket)

(1)使用Socket實現連接

Socket的英文原義是“孔”或“插座”。在網絡編程中,網絡上的兩個程序通過一個雙向的通信連接實現數據的交換,這個連接的一端稱為一個socket。Socket本質是編程接口(API),對TCP/IP的封裝,TCP/IP也要提供可供程序員做網絡開發所用的接口,這就是Socket編程接口;HTTP是轎車,提供了封裝或者顯示數據的具體形式;Socket是發動機,提供了網絡通信的能力。 Socket實質上提供了進程通信的端點。進程通信之前,雙方首先必須各自創建一個端點,否則是沒有辦法建立聯系并相互通信的。正如打電話之前,雙方必須各自擁有一臺電話機一樣。
而serversocket 建立的是socket的服務端,socket建立的是客戶端。所以服務器端使用ServerSocket
客戶端使用Socket

(2)使用socket實現網絡編程的步驟

1.分別在服務器端和客戶端完成ServerSocket和Socket的創建,這也是我們實現網絡通信的基礎
2.打開連接到Socket的相關的輸入輸出流,進行數據的通信
3.按照協議對Socket進行讀寫操作
4.在通信完成以后關閉輸入輸出流,關閉socket

如果分兩步的話,就是

第一步是監聽(等待數據發送過來),用來接收數據,需要指定監聽的端口號
第二步是發送,需要指定發送到哪個計算機(IP地址),需要指定發送到這個計算機的哪個端口

(3)具體來說

對于服務器端可以
①創建ServerSocket
②使用accept等待客戶端連接
③使用OutputStream發送數據
④使用InputStream接收數據
⑤在通信完成以后關閉輸入輸出流,關閉socket
對于客戶端可以
①創建Socket對象,連接服務器端
②使用InputStream接收數據
③使用OutputStream發送數據
④在通信完成以后關閉輸入輸出流,關閉socket

二.實現點對點對聊

1.簡單的接收數據

在進行較為復雜的操作之前,我們不妨先進行一些簡單的接收數據的操作。比如下面

服務器端

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

//創建一個類Server管理服務器端的內容
//1.服務器端的程序運行在服務器上,所以IP地址默認是當前電腦的IP地址
class Server{
    private int port;//端口號
    private ServerSocket serverSocket;
    
    public Server(int port) {
        this.port = port;
    }
    
    public void start() throws IOException {
        //創建提供服務的socket
        serverSocket = new ServerSocket(port);
        
        //等待用戶連接accept
        //如果有客戶端來連接,就得到這個客戶端的socket對象
        //如果沒有客戶端來連接,就阻塞(一直等待)
        Socket socket = serverSocket.accept();
        
        //有了連接,就可以向客戶端發送響應信息
        //輸出流如果輸出完畢,必須關閉,表示輸出結束
        OutputStream os = socket.getOutputStream();
        
        PrintStream ps = new PrintStream(os);
        ps.println("連接成功,可以通信了!");
        
        socket.shutdownOutput();//關閉輸出流
        
        //接收客戶端的響應信息
        InputStream is = socket.getInputStream();
        InputStreamReader isr = new InputStreamReader(is);
        BufferedReader br = new BufferedReader(isr);    
        //讀取數據
        System.out.println(br.readLine());
    }
}
public class Myclass {
   public static void main(String[] args) throws IOException {
       Server server = new Server(8888);
       server.start();
   }
}

客戶端

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

//創建一個類Client管理客戶端的內容
class Client{
    private int port;//客戶端的端口號
    private String ipAddr;//客戶端的IP地址
    private Socket socket;
    
    public Client(int port,String ipAddr) {
         this.port = port;
         this.ipAddr = ipAddr;
    }
    
    public void start() throws UnknownHostException, IOException {
        //向服務器端發起連接
        socket = new Socket(ipAddr,port);
        
        //接收服務器端的響應信息
        InputStream is = socket.getInputStream();
        InputStreamReader isr = new InputStreamReader(is);
        BufferedReader br = new BufferedReader(isr);    
        //讀取數據
        System.out.println(br.readLine());
        
        //向服務器端發送數據
        OutputStream os = socket.getOutputStream();
        OutputStreamWriter osw = new OutputStreamWriter(os);
        BufferedWriter bw = new BufferedWriter(osw);
        bw.write("你好!");
        bw.flush();//注意,要flush
        //關閉輸出流
        socket.shutdownOutput();
    }
}
public class Myclass {
     public static void main(String[] args) throws UnknownHostException, IOException {
         Client client = new Client(8888,"127.0.0.1");
         client.start();
     }
}

以上程序實現了服務器端的簡單連接和數據的簡單發送。下面,讓程序稍微復雜一點
服務器端

        //服務器端不斷輸入
        Scanner scanner = new Scanner(System.in);
        OutputStream os = socket.getOutputStream();
        
        PrintStream ps = new PrintStream(os);
        while(scanner.hasNext()) {
            ps.println(scanner.nextLine());
        }

客戶端

        //接收服務器端的響應信息
        InputStream is = socket.getInputStream();
        InputStreamReader isr = new InputStreamReader(is);
        BufferedReader br = new BufferedReader(isr);    
        //讀取數據(不斷接收)
        String line = null;
        while((line = br.readLine()) != null) {
            System.out.println(line);
        }

這樣,在服務器端發送數據,客戶端就可以接收到并且打印出來,這樣就實現了點對點的對聊
注意readLine()這個方法會阻塞,也就是說它會一直等待內容,沒有內容就一直等著。包括scanner的nextLine()也是,一直等著你輸入,也會阻塞

三.實現群聊

1.引

上面實現的點對點對聊只能是單方向的,不能既發送數據又接收數據,因為只有一個主線程,程序執行是從上到下的。所以若想真正實現兩者之間的”對話“,就必須使用多線程一個線程是接收數據的,一個線程是發送數據的。不論是客戶端還是服務器端,都是如此。這里就不再寫,之間實現群聊功能。

2.介紹

做群聊的時候,服務器起的作用就是 數據的緩存和分發。當一個客戶端發送數據給服務器端之后,服務器端要把這個數據向所有的(除了發送此數據的客戶端)客戶端發送這個數據,從而造成的結果是一個人在群里面發送消息,群里面所有人都能接收到。

3.注意點

一定要在receive接收之后再分發。
要把每個客戶端socket保存在數組

4.具體代碼

服務器端

import java.io.*;
import java.net.*;
import java.util.ArrayList;
import java.util.Scanner;

//創建一個類Server管理服務器端的內容
//1.服務器端的程序運行在服務器上,所以IP地址默認是當前電腦的IP地址
class Server{
    private int port;
    private ServerSocket serverSocket;
    //private Socket socket;
    private ArrayList<Socket> sockets;
    
    public Server(int port) {
        this.port = port;
        sockets = new ArrayList<>();
    }
    
    public void start() {
        //創建服務的socket對象
        try {
            serverSocket = new ServerSocket(port);
        } catch (IOException e) {
            // TODO 自動生成的 catch 塊
            e.printStackTrace();
        }
        
        //等待連接
        while(true) {
            try {
                //接收客戶端的連接
                Socket socket = serverSocket.accept();
                
                //保存這個客戶端對應的socket對象
                sockets.add(socket);
                
                //創建一個線程用于發送數據
                new Send(socket).start();
                
                //創建一個線程用于接收數據
                new Receive(socket,sockets).start();
            } catch (Exception e) {
                // TODO 自動生成的 catch 塊
                e.printStackTrace();
            }
            
            
        }
    }
}

//發送線程
class Send extends Thread{
    private Socket socket;

    
    public Send(Socket socket) {
        this.socket = socket;
 
    }
    
    public void run() {
        Scanner scanner = null;
        PrintStream ps = null;
        
        try {
             scanner = new Scanner(System.in);           
             ps = new PrintStream(socket.getOutputStream());
             while(scanner.hasNext()) {
                 ps.println(scanner.nextLine());
             }
        } catch (IOException e) {
            // TODO 自動生成的 catch 塊
            e.printStackTrace();
        }finally{
            scanner.close();
            ps.close();
        }
    }
}
//接收線程
class Receive extends Thread{
    private Socket socket;
    private ArrayList<Socket> lists;
    
    public Receive(Socket socket,ArrayList<Socket> lists) {
        this.socket = socket;
        this.lists = lists;
    }
    
    public void run() {
        
        BufferedReader br = null;
        try {
            br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            
            String line = null;
            while((line = br.readLine()) != null) {
                //分發內容
                for(Socket s:lists) {
                    //判斷是不是當前這個客戶端
                    if(s != socket) {
                    PrintStream ps = new PrintStream(s.getOutputStream());
                    
                    ps.println(line);
                    }
                }
            }
        } catch (IOException e) {
            // TODO 自動生成的 catch 塊
            e.printStackTrace();
        }finally {
            try {
                br.close();
            } catch (IOException e) {
                // TODO 自動生成的 catch 塊
                e.printStackTrace();
            }
        }
    }
}
public class Myclass {
   public static void main(String[] args) throws IOException {
         Server server = new Server(8888);
         server.start();
   }
}

客戶端(客戶端可以有多個,但是代碼都可以是一樣的,所以我在這里就寫出一個)

import java.io.*;
import java.net.*;
import java.util.Scanner;

//創建一個類Client管理客戶端的內容
class Client{
   private int port;
   private String ip;
   private Socket socket;
   
   public Client(int port,String ip) {
       this.port = port;
       this.ip = ip;
   }
   
   public void start() {
       //連接服務器端
       try {
        socket = new Socket(ip,port);
       }catch (IOException e) {
        // TODO 自動生成的 catch 塊
        e.printStackTrace();
       } 
       
       //開啟線程發送數據
       new Send(socket).start();
       //開啟線程接收數據
       new Receive(socket).start();
   }
    
}

class Send extends Thread{
    private Socket socket;
    
    public Send(Socket socket) {
        this.socket = socket;
    }
    
    public void run() {
        Scanner scanner = null;
        PrintStream ps = null;
        
        try {
            scanner = new Scanner(System.in);
            ps = new PrintStream(socket.getOutputStream());
            
            while(scanner.hasNext()) {
                ps.println(scanner.nextLine());
            }
        } catch (IOException e) {
            // TODO 自動生成的 catch 塊
            e.printStackTrace();
        }finally {
            scanner.close();
            ps.close();
        }
    }
}
class Receive extends Thread{
    private Socket socket;
    
    public Receive(Socket socket) {
        this.socket = socket;
    }
    
    public void run() {
        BufferedReader br = null;
        try {
            br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            String line = null;
            while((line = br.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                br.close();
            } catch (IOException e) {
                // TODO 自動生成的 catch 塊
                e.printStackTrace();
            }
        }
    }
}
public class Myclass {
     public static void main(String[] args) throws UnknownHostException, IOException {
        Client client = new Client(8888,"127.0.0.1");
        client.start();
     }
}

總結

一開始socket沒有搞懂,后來通過查閱很多資料就大概了解是什么意思了。在網絡編程的這些代碼中,很多代碼思路都是一樣的,比如客戶端和服務器端的代碼就有很多相同點和類似的地方。比如都要有端口號呀,然后都有接收和發送數據等等。到今天為止網絡編程學習結束,但是我自知自己還未完全掌握,甚至連百分之五十都可能沒掌握到,沒真正學會,所以還是得抽時間復習網絡編程的這些內容,包括之前Java的部分內容。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。