Java網(wǎng)絡(luò)Day05 2020-05-02

內(nèi)容

1.客戶端和服務(wù)器端連接介紹
2.實(shí)現(xiàn)點(diǎn)對(duì)點(diǎn)對(duì)聊
3.實(shí)現(xiàn)群聊

一.客戶端和服務(wù)器端連接介紹

1.注意點(diǎn)

①一般來講,有時(shí)候沒有嚴(yán)格意義的服務(wù)器端和客戶端
比如A端去連接遠(yuǎn)程網(wǎng)絡(luò)中的B端,那么此時(shí)B端就扮演服務(wù)器的角色,A端扮演客戶端的角色
②緩存:當(dāng)B不在線時(shí),A先發(fā)給騰訊的服務(wù)器(這里假如是QQ),這里面的內(nèi)容包括B的IP地址,還有端口號(hào),還有具體的內(nèi)容,當(dāng)B上線時(shí),就發(fā)給B。這里就相當(dāng)于做了一個(gè)緩存。

2.作為一個(gè)客戶端必須提供的兩個(gè)內(nèi)容

①IP地址(通過這個(gè)找到某個(gè)設(shè)備)
②端口號(hào)(找到提供服務(wù)的程序,比如qq)

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

(1)使用Socket實(shí)現(xiàn)連接

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

(2)使用socket實(shí)現(xiàn)網(wǎng)絡(luò)編程的步驟

1.分別在服務(wù)器端和客戶端完成ServerSocket和Socket的創(chuàng)建,這也是我們實(shí)現(xiàn)網(wǎng)絡(luò)通信的基礎(chǔ)
2.打開連接到Socket的相關(guān)的輸入輸出流,進(jìn)行數(shù)據(jù)的通信
3.按照協(xié)議對(duì)Socket進(jìn)行讀寫操作
4.在通信完成以后關(guān)閉輸入輸出流,關(guān)閉socket

如果分兩步的話,就是

第一步是監(jiān)聽(等待數(shù)據(jù)發(fā)送過來),用來接收數(shù)據(jù),需要指定監(jiān)聽的端口號(hào)
第二步是發(fā)送,需要指定發(fā)送到哪個(gè)計(jì)算機(jī)(IP地址),需要指定發(fā)送到這個(gè)計(jì)算機(jī)的哪個(gè)端口。

(3)具體來說

對(duì)于服務(wù)器端可以
①創(chuàng)建ServerSocket
②使用accept等待客戶端連接
③使用OutputStream發(fā)送數(shù)據(jù)
④使用InputStream接收數(shù)據(jù)
⑤在通信完成以后關(guān)閉輸入輸出流,關(guān)閉socket
對(duì)于客戶端可以
①創(chuàng)建Socket對(duì)象,連接服務(wù)器端
②使用InputStream接收數(shù)據(jù)
③使用OutputStream發(fā)送數(shù)據(jù)
④在通信完成以后關(guān)閉輸入輸出流,關(guān)閉socket

二.實(shí)現(xiàn)點(diǎn)對(duì)點(diǎn)對(duì)聊

1.簡(jiǎn)單的接收數(shù)據(jù)

在進(jìn)行較為復(fù)雜的操作之前,我們不妨先進(jìn)行一些簡(jiǎn)單的接收數(shù)據(jù)的操作。比如下面

服務(wù)器端

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

//創(chuàng)建一個(gè)類Server管理服務(wù)器端的內(nèi)容
//1.服務(wù)器端的程序運(yùn)行在服務(wù)器上,所以IP地址默認(rèn)是當(dāng)前電腦的IP地址
class Server{
    private int port;//端口號(hào)
    private ServerSocket serverSocket;
    
    public Server(int port) {
        this.port = port;
    }
    
    public void start() throws IOException {
        //創(chuàng)建提供服務(wù)的socket
        serverSocket = new ServerSocket(port);
        
        //等待用戶連接accept
        //如果有客戶端來連接,就得到這個(gè)客戶端的socket對(duì)象
        //如果沒有客戶端來連接,就阻塞(一直等待)
        Socket socket = serverSocket.accept();
        
        //有了連接,就可以向客戶端發(fā)送響應(yīng)信息
        //輸出流如果輸出完畢,必須關(guān)閉,表示輸出結(jié)束
        OutputStream os = socket.getOutputStream();
        
        PrintStream ps = new PrintStream(os);
        ps.println("連接成功,可以通信了!");
        
        socket.shutdownOutput();//關(guān)閉輸出流
        
        //接收客戶端的響應(yīng)信息
        InputStream is = socket.getInputStream();
        InputStreamReader isr = new InputStreamReader(is);
        BufferedReader br = new BufferedReader(isr);    
        //讀取數(shù)據(jù)
        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.*;

//創(chuàng)建一個(gè)類Client管理客戶端的內(nèi)容
class Client{
    private int port;//客戶端的端口號(hào)
    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 {
        //向服務(wù)器端發(fā)起連接
        socket = new Socket(ipAddr,port);
        
        //接收服務(wù)器端的響應(yīng)信息
        InputStream is = socket.getInputStream();
        InputStreamReader isr = new InputStreamReader(is);
        BufferedReader br = new BufferedReader(isr);    
        //讀取數(shù)據(jù)
        System.out.println(br.readLine());
        
        //向服務(wù)器端發(fā)送數(shù)據(jù)
        OutputStream os = socket.getOutputStream();
        OutputStreamWriter osw = new OutputStreamWriter(os);
        BufferedWriter bw = new BufferedWriter(osw);
        bw.write("你好!");
        bw.flush();//注意,要flush
        //關(guān)閉輸出流
        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();
     }
}

以上程序?qū)崿F(xiàn)了服務(wù)器端的簡(jiǎn)單連接和數(shù)據(jù)的簡(jiǎn)單發(fā)送。下面,讓程序稍微復(fù)雜一點(diǎn)
服務(wù)器端

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

客戶端

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

這樣,在服務(wù)器端發(fā)送數(shù)據(jù),客戶端就可以接收到并且打印出來,這樣就實(shí)現(xiàn)了點(diǎn)對(duì)點(diǎn)的對(duì)聊
注意readLine()這個(gè)方法會(huì)阻塞,也就是說它會(huì)一直等待內(nèi)容,沒有內(nèi)容就一直等著。包括scanner的nextLine()也是,一直等著你輸入,也會(huì)阻塞。

三.實(shí)現(xiàn)群聊

1.引

上面實(shí)現(xiàn)的點(diǎn)對(duì)點(diǎn)對(duì)聊只能是單方向的,不能既發(fā)送數(shù)據(jù)又接收數(shù)據(jù),因?yàn)?strong>只有一個(gè)主線程,程序執(zhí)行是從上到下的。所以若想真正實(shí)現(xiàn)兩者之間的”對(duì)話“,就必須使用多線程。一個(gè)線程是接收數(shù)據(jù)的,一個(gè)線程是發(fā)送數(shù)據(jù)的。不論是客戶端還是服務(wù)器端,都是如此。這里就不再寫,之間實(shí)現(xiàn)群聊功能。

2.介紹

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

3.注意點(diǎn)

一定要在receive接收之后再分發(fā)。
要把每個(gè)客戶端socket保存在數(shù)組

4.具體代碼

服務(wù)器端

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

//創(chuàng)建一個(gè)類Server管理服務(wù)器端的內(nèi)容
//1.服務(wù)器端的程序運(yùn)行在服務(wù)器上,所以IP地址默認(rèn)是當(dāng)前電腦的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() {
        //創(chuàng)建服務(wù)的socket對(duì)象
        try {
            serverSocket = new ServerSocket(port);
        } catch (IOException e) {
            // TODO 自動(dòng)生成的 catch 塊
            e.printStackTrace();
        }
        
        //等待連接
        while(true) {
            try {
                //接收客戶端的連接
                Socket socket = serverSocket.accept();
                
                //保存這個(gè)客戶端對(duì)應(yīng)的socket對(duì)象
                sockets.add(socket);
                
                //創(chuàng)建一個(gè)線程用于發(fā)送數(shù)據(jù)
                new Send(socket).start();
                
                //創(chuàng)建一個(gè)線程用于接收數(shù)據(jù)
                new Receive(socket,sockets).start();
            } catch (Exception e) {
                // TODO 自動(dòng)生成的 catch 塊
                e.printStackTrace();
            }
            
            
        }
    }
}

//發(fā)送線程
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 自動(dòng)生成的 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) {
                //分發(fā)內(nèi)容
                for(Socket s:lists) {
                    //判斷是不是當(dāng)前這個(gè)客戶端
                    if(s != socket) {
                    PrintStream ps = new PrintStream(s.getOutputStream());
                    
                    ps.println(line);
                    }
                }
            }
        } catch (IOException e) {
            // TODO 自動(dòng)生成的 catch 塊
            e.printStackTrace();
        }finally {
            try {
                br.close();
            } catch (IOException e) {
                // TODO 自動(dòng)生成的 catch 塊
                e.printStackTrace();
            }
        }
    }
}
public class Myclass {
   public static void main(String[] args) throws IOException {
         Server server = new Server(8888);
         server.start();
   }
}

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

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

//創(chuàng)建一個(gè)類Client管理客戶端的內(nèi)容
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() {
       //連接服務(wù)器端
       try {
        socket = new Socket(ip,port);
       }catch (IOException e) {
        // TODO 自動(dòng)生成的 catch 塊
        e.printStackTrace();
       } 
       
       //開啟線程發(fā)送數(shù)據(jù)
       new Send(socket).start();
       //開啟線程接收數(shù)據(jù)
       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 自動(dòng)生成的 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 自動(dòng)生成的 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();
     }
}

總結(jié)

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容