Java中的Socket的用法

一叭喜、Java Socket的分類

Java中的Socket分為普通的Socket和NioSocket。

二、普通Socket

Java中的網(wǎng)絡(luò)通信時通過Socket實現(xiàn)的,Socket分為ServerSocket和Socket兩大類磅崭,ServerSocket用于服務(wù)器端,可以通過accept方法監(jiān)聽請求瓦哎,監(jiān)聽請求后返回Socket砸喻,Socket用于完成具體數(shù)據(jù)傳輸,客戶端也可以使用Socket發(fā)起請求并傳輸數(shù)據(jù)蒋譬。ServerSocket的使用可以分為三步:

創(chuàng)建ServerSocket割岛。ServerSocket的構(gòu)造方法有5個,其中最方便的是ServerSocket(int port)羡铲,只需要一個port就可以了蜂桶。
調(diào)用創(chuàng)建出來的ServerSocket的accept方法進(jìn)行監(jiān)聽儡毕。accept方法是阻塞方法也切,也就是說調(diào)用accept方法后程序會停下來等待連接請求,在接受請求之前程序?qū)⒉粫^續(xù)執(zhí)行腰湾,當(dāng)接收到請求后accept方法返回一個Socket雷恃。
使用accept方法返回的Socket與客戶端進(jìn)行通信
  如下代碼,我們在服務(wù)器端創(chuàng)建ServerSocket费坊,并調(diào)用accept方法監(jiān)聽Client的請求倒槐,收到請求后返回一個Socket。為了方便用瀏覽器測試附井,響應(yīng)是http協(xié)議格式讨越。

public class Server {
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        try {
            ServerSocket server = new ServerSocket(8080);
            while (true){
                Socket socket = server.accept();

                InputStreamReader in = new InputStreamReader(socket.getInputStream());
                BufferedReader br =  new BufferedReader(in);
                String content;
                System.out.println(1);

                //while ((content = br.readLine()) != null && content != ""){ //用content != ""或者不判斷content是否為空两残,會導(dǎo)致阻塞
                while ((content = br.readLine()) != null && !"".equals(content)){
                   System.out.println(content);
                }
                System.out.println(2);
                PrintWriter printWriter = new PrintWriter(socket.getOutputStream(), true);
                printWriter.println("HTTP/1.1 200 OK");
                printWriter.println("Content-Type:text/html;charset=utf-8");
                String body = "hello,nio1";
                printWriter.println("Content-Length:" + body.getBytes().length);
                printWriter.println();
                printWriter.println(body);
                printWriter.close();
                socket.close();
            }        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

然后我們再看看客戶端的Socket代碼,Socket的使用也是一樣把跨,首先創(chuàng)建一個Socket人弓,Socket的構(gòu)造方法非常多,這里用的是Socket(String host, int port)着逐,把目標(biāo)主機的地址和端口號傳入即可ServerSocket和Client在同一主機下崔赌,那么Client中的IP地址需要更改為:127.0.0.1,Socket創(chuàng)建的過程就會跟服務(wù)器端建立連接耸别,創(chuàng)建完Socket后健芭,再創(chuàng)建Writer和Reader來傳輸數(shù)據(jù),數(shù)據(jù)傳輸完成后釋放資源關(guān)閉連接秀姐。

public class Client {
    public static void main(String[] args) {
        //創(chuàng)建客戶端socket建立連接慈迈,指定服務(wù)器地址和端口
        try {
            //Socket socket = new Socket("www.reibang.com",80);
            Socket socket = new Socket("127.0.0.1",8080);
            //獲取輸出流,向服務(wù)器端發(fā)送信息
            OutputStream outputStream = socket.getOutputStream();//字節(jié)輸出流
            PrintWriter pw = new PrintWriter(outputStream); //將輸出流包裝為打印流
            pw.write("用戶名:admin;密碼:123");
            pw.flush();
            socket.shutdownOutput();

            //獲取輸入流囊扳,讀取服務(wù)器端的響應(yīng)
            InputStream inputStream = socket.getInputStream();
            BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
            String info = null;
            while((info = br.readLine())!=null){
                //System.out.println("我是客戶端吩翻,服務(wù)器說:"+info);
                System.out.println(info);
            }
            socket.shutdownInput();

            //關(guān)閉資源
            br.close();
            inputStream.close();
            pw.close();
            outputStream.close();
            socket.close();

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

二、NioSocket的用法

從JDK1.4開始锥咸,Java增加了新的IO模式-nio(new IO)狭瞎,nio在底層采用了新的處理方式,極大提高了IO的效率搏予。我們使用的Socket也是IO的一種熊锭,nio提供了相應(yīng)的工具:ServerSocketChannel和SocketChannel,他們分別對應(yīng)原來的ServerSocket和Socket雪侥。

在了解NioSocket之前我們先了解Buffer碗殷、Channel、Selector速缨。為了方便理解锌妻,我們來看個例子,要過圣誕節(jié)了旬牲,需要給同學(xué)們發(fā)賀卡和蘋果仿粹,班長這時候又是最辛苦的,每次拿一個蘋果和一張賀卡發(fā)給一個同學(xué)原茅,發(fā)送完成后回來再取一張賀卡和一個蘋果發(fā)給另一個同學(xué)吭历,直到全班同學(xué)都拿到賀卡和蘋果為止,這就是普通Socket處理方式擂橘,來一個請求晌区,ServerSocket就進(jìn)行處理,處理完成后繼續(xù)接受請求,這種方式效率很低袄嗜簟恼五!還是圣誕節(jié)的例子,班長發(fā)現(xiàn)班委不止他一個哭懈,就通知了生活委員(女)和組織委員(男)來幫助他發(fā)賀卡和蘋果唤冈,女生的賀卡是粉色的,男生的賀卡是藍(lán)色的银伟,生活委員負(fù)責(zé)從全班的賀卡中挑選女生的賀卡你虹,而組織委員則負(fù)責(zé)男生的賀卡,然后生活委員和組織委員分別以宿舍為單位通知宿舍長來領(lǐng)取宿舍同學(xué)的賀卡和蘋果彤避,班長將圣誕節(jié)發(fā)蘋果和賀卡的工作布置給兩個班委后傅物,就可以繼續(xù)干其他工作了。這就是NioSocket琉预,Buffer就是所有傳遞的貨物董饰,也就是例子中的蘋果和賀卡,而Channel就是傳遞貨物的通道圆米,也就是例子中的宿舍長卒暂,負(fù)責(zé)將禮物搬回自己宿舍,而生活委員和組織委員充當(dāng)了Selector的職責(zé)娄帖,負(fù)責(zé)禮物的分揀也祠。

ServerSocketChannel可以使用自己的靜態(tài)工廠方法open創(chuàng)建,每個ServerSocketChannel對應(yīng)一個ServerSocket(通過調(diào)用其socket()獲冉佟)诈嘿,如果直接使用獲取的ServerSocket來監(jiān)聽請求,那么還是普通ServerSocket削葱,而通過將獲取的ServerSocket綁定端口號來實現(xiàn)NioSocket奖亚。ServerSocketChannel可以通過configureBlocking方法來設(shè)置是否采用阻塞模式,如果設(shè)置為非阻塞模式析砸,就可以調(diào)用register方法注冊Selector來使用了昔字。

Selector可以通過其靜態(tài)工廠方法open創(chuàng)建,創(chuàng)建后通過Channel的register方法注冊到ServerSocketChannel或者SocketChannel上首繁,注冊完成后Selector就可以通過select方法來等待請求作郭,select方法有一個long類型參數(shù),代表最長等待時間蛮瞄,如果在這段時間內(nèi)收到相應(yīng)操作的請求則返回可以處理的請求的數(shù)量所坯,否則在超時后返回0谆扎,如果傳入的參數(shù)為0或者無參數(shù)的重載方法挂捅,select方法會采用阻塞模式知道有相應(yīng)操作請求的出現(xiàn)。當(dāng)接收到請求后Selector調(diào)用selectdKeys方法返回SelectionKey集合。

SelectionKey保存了處理當(dāng)前請求的Channel和Selector闲先,并且提供了不同的操作類型状土。Channel在注冊Selector時可以通過register的第二個參數(shù)選擇特定的操作(請求操作、連接操作伺糠、讀操作蒙谓、寫操作),只有在register中注冊了相應(yīng)的操作Selector才會關(guān)心相應(yīng)類型操作的請求训桶。

介紹了這么多估計大家也煩了累驮,我們就來看看服務(wù)器端NioSocket的處理過程吧:

1、創(chuàng)建ServerSocketChannel并設(shè)置相應(yīng)的端口號舵揭、是否為阻塞模式
2谤专、創(chuàng)建Selector并注冊到ServerSocketChannel上
3、調(diào)用Selector的selector方法等待請求
4午绳、Selector接收到請求后使用selectdKeys返回SelectionKey集合
5置侍、使用SelectionKey獲取到channel、selector和操作類型并進(jìn)行具體操作拦焚。

public class NettyHttpServer {
    public static void main(String[] args) throws InterruptedException {
        int port = 8080;
        EventLoopGroup bossGroup = new NioEventLoopGroup(2);
        EventLoopGroup workerGroup = new NioEventLoopGroup(16);
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.option(ChannelOption.SO_BACKLOG, 128)
                    .childOption(ChannelOption.TCP_NODELAY, true)
                    .childOption(ChannelOption.SO_KEEPALIVE, true)
                    .childOption(ChannelOption.SO_REUSEADDR, true)
                    .childOption(ChannelOption.SO_RCVBUF, 32 * 1024)
                    .childOption(ChannelOption.SO_SNDBUF, 32 * 1024)
                    .childOption(EpollChannelOption.SO_REUSEPORT, true)
                    .childOption(ChannelOption.SO_KEEPALIVE, true)
                    .childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);

            b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
                    .handler(new LoggingHandler(LogLevel.INFO))
                    .childHandler(new HttpInitializer());

            Channel ch = b.bind(port).sync().channel();
            System.out.println("開啟netty http服務(wù)器蜡坊,監(jiān)聽地址和端口為 http://127.0.0.1:" + port + '/');
            ch.closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

public class HttpInitializer extends ChannelInitializer<SocketChannel> {
    
    @Override
    public void initChannel(SocketChannel ch) {
        ChannelPipeline p = ch.pipeline();
        p.addLast(new HttpServerCodec());
        //p.addLast(new HttpServerExpectContinueHandler());
        p.addLast(new HttpObjectAggregator(1024 * 1024));
        p.addLast(new HttpHandler());
    }
}
public class HttpHandler extends ChannelInboundHandlerAdapter {
    
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) {
        ctx.flush();
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        try {
            //logger.info("channelRead流量接口請求開始,時間為{}", startTime);
            FullHttpRequest fullRequest = (FullHttpRequest) msg;
            String uri = fullRequest.uri();
            //logger.info("接收到的請求url為{}", uri);
            if (uri.contains("/test")) {
                handlerTest(fullRequest, ctx, "hello,kimmking");
            } else {
                handlerTest(fullRequest, ctx, "hello,others");
            }
    
        } catch(Exception e) {
            e.printStackTrace();
        } finally {
            ReferenceCountUtil.release(msg);
        }
    }

    private void handlerTest(FullHttpRequest fullRequest, ChannelHandlerContext ctx, String body) {
        FullHttpResponse response = null;
        try {
            String value = body; 

//            httpGet ...  http://localhost:8801
//            返回的響應(yīng)赎败,"hello,nio";
//            value = reponse....

            response = new DefaultFullHttpResponse(HTTP_1_1, OK, Unpooled.wrappedBuffer(value.getBytes("UTF-8")));
            response.headers().set("Content-Type", "application/json");
            response.headers().setInt("Content-Length", response.content().readableBytes());

        } catch (Exception e) {
            System.out.println("處理出錯:"+e.getMessage());
            response = new DefaultFullHttpResponse(HTTP_1_1, NO_CONTENT);
        } finally {
            if (fullRequest != null) {
                if (!HttpUtil.isKeepAlive(fullRequest)) {
                    ctx.write(response).addListener(ChannelFutureListener.CLOSE);
                } else {
                    response.headers().set(CONNECTION, KEEP_ALIVE);
                    ctx.write(response);
                }
                ctx.flush();
            }
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        ctx.close();
    }

客戶端代碼通普通Socket一樣秕衙,Socket socket = new Socket("127.0.0.1",8080);表示與服務(wù)器端建立連接,從而執(zhí)行服務(wù)器端的handleAccept()方法僵刮,給ServerSocketChannel注冊selector以及添加SelectionKey.OP_READ參數(shù)灾梦,表示selector關(guān)心讀方法。然后通過PrintWrite在客戶端將內(nèi)容發(fā)送給服務(wù)器端妓笙,服務(wù)器端執(zhí)行handleRead方法對接收到的內(nèi)容進(jìn)行處理若河,并將結(jié)果返回給客戶端,客戶端通過BufferedReader接受數(shù)據(jù)寞宫,最后關(guān)閉連接萧福。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市辈赋,隨后出現(xiàn)的幾起案子鲫忍,更是在濱河造成了極大的恐慌,老刑警劉巖钥屈,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件悟民,死亡現(xiàn)場離奇詭異,居然都是意外死亡篷就,警方通過查閱死者的電腦和手機射亏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人智润,你說我怎么就攤上這事及舍。” “怎么了窟绷?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵锯玛,是天一觀的道長。 經(jīng)常有香客問我兼蜈,道長攘残,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任为狸,我火速辦了婚禮蜂绎,結(jié)果婚禮上疾呻,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好畴博,可當(dāng)我...
    茶點故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布兄一。 她就那樣靜靜地躺著要出,像睡著了一般巢价。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上立叛,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天负敏,我揣著相機與錄音,去河邊找鬼秘蛇。 笑死其做,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的赁还。 我是一名探鬼主播妖泄,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼艘策!你這毒婦竟也來了蹈胡?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤朋蔫,失蹤者是張志新(化名)和其女友劉穎罚渐,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體驯妄,經(jīng)...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡荷并,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了青扔。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片源织。...
    茶點故事閱讀 39,834評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡翩伪,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出雀鹃,到底是詐尸還是另有隱情,我是刑警寧澤励两,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布黎茎,位于F島的核電站,受9級特大地震影響当悔,放射性物質(zhì)發(fā)生泄漏傅瞻。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一盲憎、第九天 我趴在偏房一處隱蔽的房頂上張望嗅骄。 院中可真熱鬧,春花似錦饼疙、人聲如沸溺森。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽屏积。三九已至,卻和暖如春磅甩,著一層夾襖步出監(jiān)牢的瞬間炊林,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工卷要, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留渣聚,地道東北人。 一個月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓僧叉,卻偏偏與公主長得像奕枝,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子瓶堕,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,779評論 2 354

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

  • IO一直是軟件開發(fā)中的核心部分之一默辨,而隨著互聯(lián)網(wǎng)技術(shù)的提高,IO的重要性也越來越重苍息∷跣遥縱觀開發(fā)界壹置,能夠巧妙運用IO,...
    squirrels閱讀 480評論 0 3
  • 最近在整理socket知識表谊,特意做了一個筆記钞护,以供以后查閱。 1.簡介 IP協(xié)議對應(yīng)于網(wǎng)絡(luò)層爆办,TCP協(xié)議對應(yīng)于傳輸...
    九樂檸檬閱讀 1,210評論 0 3
  • 一难咕、Socket通道 新的socket通道類可以運行非阻塞模式并且是可選擇的。這兩個性能可以激活大程序(如網(wǎng)絡(luò)服務(wù)...
    Java架構(gòu)師筆記閱讀 2,438評論 0 3
  • 前一段時間距辆,博主利用忙里偷閑的時間余佃,對Java Socket通信進(jìn)行了一個簡單的描述,由淺入深跨算,循序漸進(jìn)的將Jav...
    長道閱讀 9,363評論 11 26
  • # Java NIO # Java NIO屬于非阻塞IO爆土,這是與傳統(tǒng)IO最本質(zhì)的區(qū)別。傳統(tǒng)IO包括socket和文...
    Teddy_b閱讀 595評論 0 0