Socket 網(wǎng)絡(luò)編程(一)

Socket

Socket,又稱“套接字”募逞,應(yīng)用程序通常通過“套接字”向網(wǎng)絡(luò)發(fā)出請求或應(yīng)答網(wǎng)絡(luò)請求蛋铆。

Socket 和 ServerSocket類位于 java.net 包中。對于一個網(wǎng)絡(luò)連接來說放接,套接字是平等的刺啦,不因?yàn)樵诜?wù)器端或在客戶端而產(chǎn)生不同級別,它們的工作都是通過 SocketImpl 類及其子類完成的纠脾。

套接字之間的連接過程可以分為四個步驟:
1)服務(wù)器監(jiān)聽
2)客戶端請求服務(wù)器
3)服務(wù)器確認(rèn)
4)客戶端確認(rèn)

示例代碼:

Server.java

public class Server {

    final static int PROT = 8765;
    
    public static void main(String[] args) {
        
        ServerSocket server = null;
        try {
            server = new ServerSocket(PROT);
            System.out.println(" server start .. ");
            //進(jìn)行阻塞
            Socket socket = server.accept();
            //新建一個線程執(zhí)行客戶端的任務(wù)
            new Thread(new ServerHandler(socket)).start();
            
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(server != null){
                try {
                    server.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            server = null;
        }
    }

Client.java

public class Client {

    final static String ADDRESS = "127.0.0.1";
    final static int PORT = 8765;
    
    public static void main(String[] args) {
        
        Socket socket = null;
        BufferedReader in = null;
        PrintWriter out = null;
        
        try {
            socket = new Socket(ADDRESS, PORT);
            in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            out = new PrintWriter(socket.getOutputStream(), true);
            
            //向服務(wù)器端發(fā)送數(shù)據(jù)
            out.println("接收到客戶端的請求數(shù)據(jù)...");
            out.println("接收到客戶端的請求數(shù)據(jù)1111...");
            String response = in.readLine();
            System.out.println("Client: " + response);
            
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(in != null){
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(out != null){
                try {
                    out.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            if(socket != null){
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            socket = null;
        }
    }
}

ServerHandler.java

public class ServerHandler implements Runnable{

    private Socket socket ;
    
    public ServerHandler(Socket socket){
        this.socket = socket;
    }
    
    @Override
    public void run() {
        BufferedReader in = null;
        PrintWriter out = null;
        try {
            in = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
            out = new PrintWriter(this.socket.getOutputStream(), true);
            String body = null;
            while(true){
                body = in.readLine();
                if(body == null) break;
                System.out.println("Server :" + body);
                out.println("服務(wù)器端回送響的應(yīng)數(shù)據(jù).");
            }
            
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(in != null){
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(out != null){
                try {
                    out.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            if(socket != null){
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            socket = null;
        }
    }
}

運(yùn)行結(jié)果:

服務(wù)端

Server :接收到客戶端的請求數(shù)據(jù)...
Server :接收到客戶端的請求數(shù)據(jù)1111...

客戶端

Client: 服務(wù)器端回送響的應(yīng)數(shù)據(jù).

采用線程池和任務(wù)隊列實(shí)現(xiàn)偽異步 IO玛瘸,將客戶端的 socket 封裝成 task 任務(wù)(實(shí)現(xiàn) Runnable 接口的類),然后投遞到線程池中去苟蹈。

示例代碼:

Server.java

public class Server {

    final static int PORT = 8765;

    public static void main(String[] args) {
        ServerSocket server = null;
        BufferedReader in = null;
        PrintWriter out = null;
        try {
            server = new ServerSocket(PORT);
            System.out.println("Server start");
            Socket socket = null;
            HandlerExecutorPool executorPool = new HandlerExecutorPool(50, 1000);
            while(true){
                socket = server.accept();
                executorPool.execute(new ServerHandler(socket));
            }
            
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(in != null){
                try {
                    in.close();
                } catch (Exception e1) {
                    e1.printStackTrace();
                }
            }
            if(out != null){
                try {
                    out.close();
                } catch (Exception e2) {
                    e2.printStackTrace();
                }
            }
            if(server != null){
                try {
                    server.close();
                } catch (Exception e3) {
                    e3.printStackTrace();
                }
            }
            server = null;              
        }   
    }   
}

HandlerExecutorPool.java

public class HandlerExecutorPool {

    private ExecutorService executor;
    public HandlerExecutorPool(int maxPoolSize, int queueSize){
        this.executor = new ThreadPoolExecutor(
                Runtime.getRuntime().availableProcessors(),
                maxPoolSize, 
                120L, 
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<Runnable>(queueSize));
    }
    
    public void execute(Runnable task){
        this.executor.execute(task);
    }   
}

ServerHandler.java

public class ServerHandler implements Runnable {

    private Socket socket;
    public ServerHandler (Socket socket){
        this.socket = socket;
    }
    
    @Override
    public void run() {
        BufferedReader in = null;
        PrintWriter out = null;
        try {
            in = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
            out = new PrintWriter(this.socket.getOutputStream(), true);
            String body = null;
            while(true){
                body = in.readLine();
                if(body == null) break;
                System.out.println("Server:" + body);
                out.println("Server response");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(in != null){
                try {
                    in.close();
                } catch (Exception e1) {
                    e1.printStackTrace();
                }
            }
            if(out != null){
                try {
                    out.close();
                } catch (Exception e2) {
                    e2.printStackTrace();
                }
            }
            if(socket != null){
                try {
                    socket.close();
                } catch (Exception e3) {
                    e3.printStackTrace();
                }
            }
            socket = null;          
        }       
    }
}

Client.java

public class Client {
    
    final static String ADDRESS = "127.0.0.1";
    final static int PORT =8765;
    
    public static void main(String[] args) {
        Socket socket = null;
        BufferedReader in = null;
        PrintWriter out = null;
        try {
            socket = new Socket(ADDRESS, PORT);
            in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            out = new PrintWriter(socket.getOutputStream(), true);
            
            out.println("Client request");
            
            String response = in.readLine();
            System.out.println("Client:" + response);
            
            
        }  catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } finally {
            if(in != null){
                try {
                    in.close();
                } catch (Exception e1) {
                    e1.printStackTrace();
                }
            }
            if(out != null){
                try {
                    out.close();
                } catch (Exception e2) {
                    e2.printStackTrace();
                }
            }
            if(socket != null){
                try {
                    socket.close();
                } catch (Exception e3) {
                    e3.printStackTrace();
                }
            }
            socket = null;              
        }       
    }
}

運(yùn)行結(jié)果:

服務(wù)端

Server start
Server:Client request

客戶端

Client:Server response

IO (BIO) 和 NIO 的區(qū)別:其本質(zhì)就是阻塞和非阻塞的區(qū)別糊渊。

阻塞:應(yīng)用程序在獲取網(wǎng)絡(luò)數(shù)據(jù)時,如果網(wǎng)絡(luò)傳輸數(shù)據(jù)很慢慧脱,那么程序就一直等著渺绒,直到傳輸完畢為止。

非阻塞:應(yīng)用程序直接可以獲取已經(jīng)準(zhǔn)備好的數(shù)據(jù)菱鸥,無需等待宗兼。

IO 為同步阻塞,NIO 為同步非阻塞氮采。NIO 并沒有實(shí)現(xiàn)異步殷绍,在 JDK1.7之后,升級了 NIO 庫包鹊漠,支持異步非阻塞通信模型主到,即 NIO2.0(AIO)殖侵。

同步和異步:同步和異步一般是面向操作系統(tǒng)與應(yīng)用程序?qū)?IO 操作的層面上來區(qū)別的。

同步時镰烧,應(yīng)用程序會直接參與 IO 讀寫操作拢军,并且應(yīng)用程序會直接阻塞到某一個方法上,直到數(shù)據(jù)準(zhǔn)備就緒怔鳖,或者采用輪詢的策略實(shí)時檢查數(shù)據(jù)的就緒狀態(tài)茉唉,如果就緒則獲取數(shù)據(jù)。

異步時结执,所有的 IO 讀寫操作交給操作系統(tǒng)處理度陆,與應(yīng)用程序沒有直接關(guān)系,當(dāng)操作系統(tǒng)完成 IO 讀寫操作時献幔,會給應(yīng)用程序發(fā)送通知懂傀,應(yīng)用程序直接拿走數(shù)據(jù)即可。

NIO 編程介紹

NIO 中三個重要概念:Buffer蜡感,Channel蹬蚁,Selector。

Buffer:Buffer 是一個對象郑兴,它包含一些要寫入或者要讀取的數(shù)據(jù)犀斋。在面向流的 IO 中,可以將數(shù)據(jù)直接寫入或讀取到 Stream 對象中情连。在 NIO 庫中叽粹,所有數(shù)據(jù)都是用緩沖區(qū)處理的。緩沖區(qū)實(shí)際上是一個數(shù)組却舀,通常是一個字節(jié)數(shù)組(ByteBuffer)虫几,也可以使用其他類型的數(shù)組。每一種 Java 基本數(shù)據(jù)類型都對應(yīng)了一種緩沖區(qū)(除了 boolean 類型)挽拔。

Channel:通道與流的不同之處在于辆脸,通道是雙向的,而流是單向的(一個流必須是 InputStream 或 OutputStream 的子類)篱昔。通道可以用于讀每强、寫或者二者同時進(jìn)行始腾,最重要的是可以和多路復(fù)用器結(jié)合起來州刽,有多種的狀態(tài)位,方便多路復(fù)用器去識別浪箭。

Selector:Selector 會不斷地輪詢注冊在其上的通道穗椅,如果某個通道發(fā)生了讀寫操作,這個通道就處于就緒狀態(tài)奶栖,會被 Selector 輪詢出來匹表,然后通過 SelectionKey 可以取得就緒的 Channel 集合门坷,從而進(jìn)行后續(xù)的 IO 操作。一個 Selector 可以負(fù)責(zé)成千上萬個 Channel 通道袍镀,沒有上限默蚌,這也就意味著,只要一個線程負(fù)責(zé) Selector 的輪詢苇羡,就可以接入成千上萬個客戶端绸吸。

Selector 線程就類似一個管理者,管理了成千上萬個管道设江,然后輪詢哪個管道的數(shù)據(jù)已經(jīng)準(zhǔn)備好锦茁,通知 CPU 執(zhí)行 IO 的讀取或?qū)懭氩僮鳌?/p>

Selector 模式:當(dāng) IO 事件(管道)注冊到 Selector 以后,Selector 會分配給每個管道一個 key 值叉存。Selector 以輪詢的方式進(jìn)行查找注冊的所有 IO 事件(管道)码俩,當(dāng) IO 事件(管道)準(zhǔn)備就緒后,Selector 就會識別歼捏,通過 key 值找到相應(yīng)的管道稿存,進(jìn)行相關(guān)的數(shù)據(jù)處理操作。

示例代碼:

Server.java

public class Server implements Runnable{
    //1 多路復(fù)用器(管理所有的通道)
    private Selector seletor;
    //2 建立緩沖區(qū)
    private ByteBuffer readBuf = ByteBuffer.allocate(1024);
    //3 
    private ByteBuffer writeBuf = ByteBuffer.allocate(1024);
    public Server(int port){
        try {
            //1 打開路復(fù)用器
            this.seletor = Selector.open();
            //2 打開服務(wù)器通道
            ServerSocketChannel ssc = ServerSocketChannel.open();
            //3 設(shè)置服務(wù)器通道為非阻塞模式
            ssc.configureBlocking(false);
            //4 綁定地址
            ssc.bind(new InetSocketAddress(port));
            //5 把服務(wù)器通道注冊到多路復(fù)用器上瞳秽,并且監(jiān)聽阻塞事件
            ssc.register(this.seletor, SelectionKey.OP_ACCEPT);
            
            System.out.println("Server start, port :" + port);
            
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        while(true){
            try {
                //1 必須要讓多路復(fù)用器開始監(jiān)聽
                this.seletor.select();
                //2 返回多路復(fù)用器已經(jīng)選擇的結(jié)果集
                Iterator<SelectionKey> keys = this.seletor.selectedKeys().iterator();
                //3 進(jìn)行遍歷
                while(keys.hasNext()){
                    //4 獲取一個選擇的元素
                    SelectionKey key = keys.next();
                    //5 直接從容器中移除就可以了
                    keys.remove();
                    //6 如果是有效的
                    if(key.isValid()){
                        //7 如果為阻塞狀態(tài)
                        if(key.isAcceptable()){
                            this.accept(key);
                        }
                        //8 如果為可讀狀態(tài)
                        if(key.isReadable()){
                            this.read(key);
                        }
                        //9 寫數(shù)據(jù)
                        if(key.isWritable()){
                            //this.write(key); //ssc
                        }
                    }
                    
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    
    private void write(SelectionKey key){
        //ServerSocketChannel ssc =  (ServerSocketChannel) key.channel();
        //ssc.register(this.seletor, SelectionKey.OP_WRITE);
    }

    private void read(SelectionKey key) {
        try {
            //1 清空緩沖區(qū)舊的數(shù)據(jù)
            this.readBuf.clear();
            //2 獲取之前注冊的socket通道對象
            SocketChannel sc = (SocketChannel) key.channel();
            //3 讀取數(shù)據(jù)
            int count = sc.read(this.readBuf);
            //4 如果沒有數(shù)據(jù)
            if(count == -1){
                key.channel().close();
                key.cancel();
                return;
            }
            //5 有數(shù)據(jù)則進(jìn)行讀取 讀取之前需要進(jìn)行復(fù)位方法(把position 和limit進(jìn)行復(fù)位)
            this.readBuf.flip();
            //6 根據(jù)緩沖區(qū)的數(shù)據(jù)長度創(chuàng)建相應(yīng)大小的byte數(shù)組挠铲,接收緩沖區(qū)的數(shù)據(jù)
            byte[] bytes = new byte[this.readBuf.remaining()];
            //7 接收緩沖區(qū)數(shù)據(jù)
            this.readBuf.get(bytes);
            //8 打印結(jié)果
            String body = new String(bytes).trim();
            System.out.println("Server : " + body);
            
            // 9..可以寫回給客戶端數(shù)據(jù) 
            
        } catch (IOException e) {
            e.printStackTrace();
        }
        
    }

    private void accept(SelectionKey key) {
        try {
            //1 獲取服務(wù)通道
            ServerSocketChannel ssc =  (ServerSocketChannel) key.channel();
            //2 執(zhí)行阻塞方法
            SocketChannel sc = ssc.accept();
            //3 設(shè)置阻塞模式
            sc.configureBlocking(false);
            //4 注冊到多路復(fù)用器上,并設(shè)置讀取標(biāo)識
            sc.register(this.seletor, SelectionKey.OP_READ);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    public static void main(String[] args) {
        
        new Thread(new Server(8765)).start();;
    }
}

Client.java

public class Client {

    //需要一個Selector 
    public static void main(String[] args) {
        
        //創(chuàng)建連接的地址
        InetSocketAddress address = new InetSocketAddress("127.0.0.1", 8765);
        
        //聲明連接通道
        SocketChannel sc = null;
        
        //建立緩沖區(qū)
        ByteBuffer buf = ByteBuffer.allocate(1024);
        
        try {
            //打開通道
            sc = SocketChannel.open();
            //進(jìn)行連接
            sc.connect(address);
            
            while(true){
                //定義一個字節(jié)數(shù)組寂诱,然后使用系統(tǒng)錄入功能:
                byte[] bytes = new byte[1024];
                System.in.read(bytes);
                
                //把數(shù)據(jù)放到緩沖區(qū)中
                buf.put(bytes);
                //對緩沖區(qū)進(jìn)行復(fù)位
                buf.flip();
                //寫出數(shù)據(jù)
                sc.write(buf);
                //清空緩沖區(qū)數(shù)據(jù)
                buf.clear();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(sc != null){
                try {
                    sc.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        
    }
}

運(yùn)行結(jié)果:

客戶端(控制臺輸入)

hello nio

服務(wù)端

Server start, port :8765
Server : hello nio
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末拂苹,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子痰洒,更是在濱河造成了極大的恐慌瓢棒,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,589評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件丘喻,死亡現(xiàn)場離奇詭異脯宿,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)泉粉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,615評論 3 396
  • 文/潘曉璐 我一進(jìn)店門连霉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人嗡靡,你說我怎么就攤上這事跺撼。” “怎么了讨彼?”我有些...
    開封第一講書人閱讀 165,933評論 0 356
  • 文/不壞的土叔 我叫張陵歉井,是天一觀的道長。 經(jīng)常有香客問我哈误,道長哩至,這世上最難降的妖魔是什么躏嚎? 我笑而不...
    開封第一講書人閱讀 58,976評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮菩貌,結(jié)果婚禮上卢佣,老公的妹妹穿的比我還像新娘。我一直安慰自己箭阶,他們只是感情好珠漂,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,999評論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著尾膊,像睡著了一般媳危。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上冈敛,一...
    開封第一講書人閱讀 51,775評論 1 307
  • 那天待笑,我揣著相機(jī)與錄音,去河邊找鬼抓谴。 笑死暮蹂,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的癌压。 我是一名探鬼主播仰泻,決...
    沈念sama閱讀 40,474評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼滩届!你這毒婦竟也來了集侯?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,359評論 0 276
  • 序言:老撾萬榮一對情侶失蹤帜消,失蹤者是張志新(化名)和其女友劉穎棠枉,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體泡挺,經(jīng)...
    沈念sama閱讀 45,854評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡辈讶,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,007評論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了娄猫。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片贱除。...
    茶點(diǎn)故事閱讀 40,146評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖媳溺,靈堂內(nèi)的尸體忽然破棺而出月幌,到底是詐尸還是另有隱情,我是刑警寧澤褂删,帶...
    沈念sama閱讀 35,826評論 5 346
  • 正文 年R本政府宣布飞醉,位于F島的核電站冲茸,受9級特大地震影響屯阀,放射性物質(zhì)發(fā)生泄漏缅帘。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,484評論 3 331
  • 文/蒙蒙 一难衰、第九天 我趴在偏房一處隱蔽的房頂上張望钦无。 院中可真熱鬧,春花似錦盖袭、人聲如沸失暂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,029評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽弟塞。三九已至,卻和暖如春拙已,著一層夾襖步出監(jiān)牢的瞬間决记,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,153評論 1 272
  • 我被黑心中介騙來泰國打工倍踪, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留系宫,地道東北人。 一個月前我還...
    沈念sama閱讀 48,420評論 3 373
  • 正文 我出身青樓建车,卻偏偏與公主長得像扩借,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子缤至,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,107評論 2 356

推薦閱讀更多精彩內(nèi)容

  • Java NIO(New IO)是從Java 1.4版本開始引入的一個新的IO API潮罪,可以替代標(biāo)準(zhǔn)的Java I...
    JackChen1024閱讀 7,555評論 1 143
  • 原文地址http://www.importnew.com/19816.html 概述 NIO主要有三個核心部分:C...
    期待現(xiàn)在閱讀 865評論 0 4
  • 繼上一篇文章《網(wǎng)絡(luò)編程之IO與NIO阻塞分析》的講解,已經(jīng)知道了網(wǎng)絡(luò)編程的基本方式领斥,今天將繼續(xù)進(jìn)行網(wǎng)絡(luò)編程相關(guān)概念...
    landy8530閱讀 1,118評論 0 3
  • 上一張自己寫的字错洁,不喜勿噴~用濾鏡自己做的~ 昨天晚上昊昊突然對我說感覺和舍友走不到一起,三觀不一樣戒突,好像無論宿...
    一米六姑娘閱讀 177評論 3 2
  • 人生難得好知己屯碴,己人己己不己心〔泊妫苦短情短意更短导而,是是非非何處斷。子欲親為力不足隔崎,上有妻老下有少今艺。苦逼生活何是頭爵卒,為...
    小世界的大熱巴之靜初張閱讀 141評論 0 0