朝花夕拾之socket的基本使用以及mina框架簡單介紹

工欲善其事,必先利其器,從互聯網誕生到如今,基本上全部的程序都是網絡程序,不多有單機版的程序了。 而網絡編程的本質是兩個設備之間的數據交換,固然,在計算機網絡中,設備主要指計算機。咱們如今進行網絡編程,基本上都是使用已經封裝好的框架,畢竟本身實現維護一套網絡編程框架,費時費力不說,作出來以後還不必定好用,有那時間多保養保養頭髮。 html

Socket簡介

記得大學學習java的時候,並無接觸網絡編程相關的基礎知識,一直都是單機版程序,沒有見識到網絡編程的美妙,因此對socket這個東西既熟悉又陌生。熟悉的緣由是在MFC實驗課中接觸到socket知識的,沒錯,就是那門古老的開發語言,如今已經銷聲匿跡了,陌生的緣由大概也就不言而喻了。好了,說了那麼多,那socket究竟是什麼呢?java

Socket是應用層與TCP/IP協議族通訊的中間軟件抽象層,它是一組接口 。不少同窗會把tcp、udp協議和socket搞混,其實Socket只是一種鏈接模式,不是協議。tcp、udp是兩個最基本的協議,不少其它協議都是基於這兩個協議。android

用socket能夠建立tcp鏈接,也能夠建立udp鏈接,這意味着,用socket能夠建立任何協議的鏈接。簡單的來講,socket至關於一艘船,你把目的地(ip+端口)告訴它,把要運輸的貨物搬上去,而後它將貨物送往目的地。這個貨物具體須要怎麼運輸,運輸完成以後是否還要通知你已經到達目的地,這就是實現協議的區別了。git

Socket使用

首先來回顧一下socket的基本使用,建立一個服務端所需步驟github

  1. 建立ServerSocket對象綁定監聽端口。
  2. 經過accept()方法監聽客戶端的請求。
  3. 創建鏈接後,經過輸入輸出流讀取客戶端發送的請求信息。
  4. 經過輸出流向客戶端發送請求信息。
  5. 關閉相關資源。

嗯。。。Talk is cheap,Show me the code:apache

public class SocketServer {
    public static void main(String[] args) {
        //第一步
        SocketServer socketServer = new SocketServer();
        socketServer.startServer(2333);
    }
    private void startServer(int port) {
        try {
            ServerSocket serverSocket = new ServerSocket(port);
            System.out.println("服務器已啓動,等待客戶鏈接...");
            //第二步 調用accept()方法開始監聽,等待客戶端的鏈接 這個方法會阻塞當前線程
            Socket socket = serverSocket.accept();
            System.out.println("客戶端鏈接成功");
            //第三步 創建輸入輸出流讀數據
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
            String receivedMsg;
            while ((receivedMsg = bufferedReader.readLine()) != null && !("end").equals(receivedMsg)) {
                System.out.println("客戶端:" + receivedMsg);
                //第四步 給客戶端發送請求
                String response = "hello client";
                System.out.println("我(服務端):" + response);
                bufferedWriter.write(response+ "\n");
                bufferedWriter.flush();
            }
            //關閉相關資源
            socket.close();
            serverSocket.close();
            bufferedWriter.close();
            bufferedReader.close();

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
複製代碼

建立一個客戶端所需步驟,其實和服務端代碼差很少:編程

  1. 建立Socket對象,指明須要鏈接的服務器的地址和端口。
  2. 創建鏈接後,經過輸出流向服務器發送請求信息。
  3. 經過輸入流獲取服務器的響應信息。
  4. 關閉相關資源
public class SocketClient {
    public static void main(String[] args){
        SocketClient socketClient = new SocketClient();
        socketClient.startClient(2333);
    }
    void startClient(int port){
        try {
            Socket clientSocket = new Socket("localhost",port);
            System.out.println("客戶端已啓動");
            //給服務器發消息
            BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(clientSocket.getOutputStream()));
            //接收服務器傳過來的消息
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
            //鍵盤輸入消息 發送給服務端
            BufferedReader inputReader = new BufferedReader(new InputStreamReader(System.in));
            String readLine = null;
            while (!(readLine = inputReader.readLine()).equals("bye")){
                System.out.println("我(客戶端):" + readLine);
                //將鍵盤輸入的消息發送給服務器
                bufferedWriter.write(readLine+"\n");
                bufferedWriter.flush();
                String response = bufferedReader.readLine();
                System.out.println("服務端: " + response);
            }
            bufferedWriter.close();
            inputReader.close();
            clientSocket.close();
        }catch (IOException e){
            e.printStackTrace();
        }
        
    }
}
複製代碼

以上對於socket的示例比較簡單,只實現了客戶端給服務器發送消息,服務器收到消息並回復客戶端。現實的狀況其實很複雜,如服務器高併發的處理,客戶端怎麼監聽服務器隨時發來的消息,客戶端斷線重連機制,心跳包的處理,還有在對消息的拆包、粘包的處理等等。而引入mina框架,咱們沒必要關注複雜的網絡通訊的實現,只需專一於具體的業務邏輯。服務器

Mina框架簡介

MINA框架是對java的NIO包的一個封裝,簡化了NIO程序開發的難度,封裝了不少底層的細節,讓開發者把精力集中到業務邏輯上來。可能有一些同窗不知道NIO是什麼,這裏簡單介紹一下,NIO就是new IO,是jdk1.4引入的,它是同步非阻塞的,好比一個服務器多個客戶端,客戶端發送的請求都會註冊到服務器的多路複用器上,多路複用器輪詢到鏈接有I/O請求時才啓動一個線程進行處理。它適用於鏈接數目多且鏈接比較短的架構,好比聊天服務器。網絡

mina簡單使用

Mina 的API 將真正的網絡通訊與咱們的應用程序隔離開來,你只須要關心你要發送、接收的數據以及你的業務邏輯便可。若是想要使用mina,須要先去apache下載mina的jar包:mina下載地址,我使用的是2.0.16版本,下載解壓以後,只須要使用mina-core-2.0.16.jar和slf4j-android.jar這兩個包便可.session

先看看咱們實現的效果圖(因爲是視頻轉gif,可能不太好看出來,就是一個簡單的文本通信):

server
client

建立服務端

前面說mina將網絡通訊與咱們的應用程序隔離開來,那咱們看看怎麼樣實現一個TCP的服務端,最近在學習kotlin,之後的代碼應該都用kotlin展現了:

//建立一個非阻塞的service端的socket
val acceptor = NioSocketAcceptor()
//設置編解碼器 ProtocolCodecFilter攔截器 網絡傳輸須要將對象轉換爲字節流
acceptor.filterChain.addLast("codec",ProtocolCodecFilter(TextLineCodecFactory()))
//設置讀取數據的緩衝區大小
acceptor.sessionConfig.readBufferSize = 2048
//讀寫通道10秒內無操做進入空閒狀態 
acceptor.sessionConfig.setIdleTime(IdleStatus.BOTH_IDLE, 10)
//綁定端口
acceptor.bind(InetSocketAddress(8080))
複製代碼

這段代碼咱們就初始化了一個TCP服務端,其中,編解碼器使用的是mina自帶的換行符編解碼器工廠,設置編解碼器是由於在網絡上傳輸數據時,發送端發送數據須要將對象轉換爲字節流進行傳輸,接收端收到數據後再將字節流轉換回來。至關於雙方約定一套規則,具體規則能夠本身定,也能夠用現成的。我這裏只須要發送文本,就用內置的啦。

網絡通訊已經實現,那發送、接收數據呢?咱們具體的業務邏輯都在IoHandler這個類中進行處理,編寫一個類繼承IoHandlerAdapter ,並重寫它的幾個方法,記得在bind端口以前調用acceptor.setHandler(MyIoHandlerAdapter()),否則沒法監聽到具體事件 :

/** * 向客戶端發送消息後會調用此方法 * @param session * @param message * @throws Exception */
    @Throws(Exception::class)
    override fun messageSent(session: IoSession?, message: Any?) {
        super.messageSent(session, message)
        LogUtils.i("服務器發送消息成功")
    }

    /** * 從端口接受消息,會響應此方法來對消息進行處理 * @param session * @param message * @throws Exception */
    @Throws(Exception::class)
    override fun messageReceived(session: IoSession?, message: Any?) {
        super.messageReceived(session, message)
        val msg = message!!.toString()
        LogUtils.i("服務器接收消息成功:$msg")
    }

    /** * 服務器與客戶端建立鏈接 * @param session * @throws Exception */
    @Throws(Exception::class)
    override fun sessionCreated(session: IoSession?) {
        super.sessionCreated(session)
        LogUtils.i("服務器與客戶端建立鏈接")
    }

    /** * 服務器與客戶端鏈接打開 * @param session * @throws Exception */
    @Throws(Exception::class)
    override fun sessionOpened(session: IoSession?) {
        super.sessionOpened(session)
        LogUtils.i("服務器與客戶端鏈接打開")
    }

    /** * 關閉與客戶端的鏈接時會調用此方法 * @param session * @throws Exception */
    @Throws(Exception::class)
    override fun sessionClosed(session: IoSession?) {
        super.sessionClosed(session)
        LogUtils.i("關閉與客戶端的鏈接時會調用此方法")
    }

    /** * 服務器進入空閒狀態 * @param session * @param status * @throws Exception */
    @Throws(Exception::class)
    override fun sessionIdle(session: IoSession?, status: IdleStatus?) {
        super.sessionIdle(session, status)
        LogUtils.i("服務器進入空閒狀態")
    }

    /** * 異常 * @param session * @param cause * @throws Exception */
    @Throws(Exception::class)
    override fun exceptionCaught(session: IoSession?, cause: Throwable) {
        super.exceptionCaught(session, cause)
        LogUtils.i("服務器異常$cause")
    }
複製代碼

你們應該注意到IoSession這個東西了,每個方法參數裏都有它,那它具體是幹什麼的呢?

IoSession是一個接口,這個接口用於表示Server 端與Client 端的鏈接,IoAcceptor.accept()的時候返回實例。這個接口有以下經常使用的方法:

  1. WriteFuture write(Object message):這個方法用於寫數據,該操做是異步的。
  2. CloseFuture close(boolean immediately):這個方法用於關閉IoSession,該操做也是異步的,參數指定true 表示當即關閉,不然就在全部的寫操做都flush 以後再關閉。
  3. Object setAttribute(Object key,Object value):這個方法用於給咱們向會話中添加一些屬性,這樣能夠在會話過程當中均可以使用,相似於HttpSession 的setAttrbute()方法。IoSession 內部使用同步的HashMap 存儲你添加的自定義屬性。
  4. SocketAddress getRemoteAddress():這個方法獲取遠端鏈接的套接字地址。
  5. void suspendWrite():這個方法用於掛起寫操做,那麼有void resumeWrite()方法與之配對。對於read()方法一樣適用。
  6. ReadFuture read():這個方法用於讀取數據, 但默認是不能使用的, 你須要調用IoSessionConfig 的setUseReadOperation(true)纔可使用這個異步讀取的方法。通常咱們不會用到這個方法,由於這個方法的內部實現是將數據保存到一個BlockingQueue,假如是Server 端,由於大量的Client 端發送的數據在Server 端都這麼讀取,那麼可能會致使內存泄漏,但對於Client,可能有的時候會比較便利。
  7. IoService getService():這個方法返回與當前會話對象關聯的IoService 實例。

換言之,拿到IoSession就可以進行兩端打call了,什麼?你問我怎麼打call?

fun sendText(message: String,client IoSession){
    var ioBuffer = IoBuffer.allocate(message.toByteArray().size)
    ioBuffer.put(message.toByteArray())
    ioBuffer.flip()
    client.write(ioBuffer)
}
複製代碼
建立客戶端

不管是Server 端仍是Client 端,在Mina中的執行流程都是同樣的。惟一不一樣的就是IoService 的Client 端實現是IoConnector。

val connector = NioSocketConnector()
// 設置連接超時時間
connector.connectTimeoutMillis = 15000
// 添加過濾器
connector.filterChain.addLast("codec",ProtocolCodecFilter(TextLineCodecFactory()))
connector.setDefaultRemoteAddress(InetSocketAddress(ip, port))
val future = connector.connect()
future.awaitUninterruptibly()// 等待鏈接建立完成
var session = future.session// 得到IoSession
複製代碼

IoHandlerAdapter 和服務端同樣,這裏不作過多介紹。

最後貼上服務端代碼:

class MinaServer : IoHandlerAdapter(){
    private val acceptor: NioSocketAcceptor
    private var isConnected = false
    private var handler: Handler by Delegates.notNull()
    init {
        //建立一個非阻塞的service端的socket
        acceptor = NioSocketAcceptor()
        //設置編解碼器 ProtocolCodecFilter攔截器 網絡傳輸須要將對象轉換爲字節流
        acceptor.filterChain.addLast("codec",
                ProtocolCodecFilter(TextLineCodecFactory()))
        //設置讀取數據的緩衝區大小
        acceptor.sessionConfig.readBufferSize = 2048
        //讀寫通道10秒內無操做進入空閒狀態
        acceptor.sessionConfig.setIdleTime(IdleStatus.BOTH_IDLE, 10)
        handler = Handler()

    }

    fun connect(port: Int): MinaServer {
        if (isConnected)
            return this
        thread {
            try {
                //註冊回調 監聽和客戶端之間的消息
                acceptor.handler = this
                acceptor.isReuseAddress = true
                //綁定端口
                acceptor.bind(InetSocketAddress(port))
                isConnected = true
                handler.post {
                    connectCallback?.onOpened()
                }
            } catch (e: Exception) {
                e.printStackTrace()
                handler.post {
                    connectCallback?.onError(e)
                }
                LogUtils.i("服務器鏈接異常")
                isConnected = false
            }
        }
        return this
    }

    fun sendText(message: String){
        for (client in acceptor.managedSessions.values){
            var ioBuffer = IoBuffer.allocate(message.toByteArray().size)
            ioBuffer.put(message.toByteArray())
            ioBuffer.flip()
            client.write(ioBuffer)
        }
    }

    /** * 向客戶端發送消息後會調用此方法 * @param session * @param message * @throws Exception */
    @Throws(Exception::class)
    override fun messageSent(session: IoSession?, message: Any?) {
        super.messageSent(session, message)
        LogUtils.i("服務器發送消息成功")
        connectCallback?.onSendSuccess()
    }

    /** * 從端口接受消息,會響應此方法來對消息進行處理 * @param session * @param message * @throws Exception */
    @Throws(Exception::class)
    override fun messageReceived(session: IoSession?, message: Any?) {
        super.messageReceived(session, message)
        handler.post {
            connectCallback?.onGetMessage(message)
        }
        val msg = message!!.toString()
        LogUtils.i("服務器接收消息成功:$msg")
    }

    /** * 服務器與客戶端建立鏈接 * @param session * @throws Exception */
    @Throws(Exception::class)
    override fun sessionCreated(session: IoSession?) {
        super.sessionCreated(session)
        handler.post {
            connectCallback?.onConnected()
        }
        LogUtils.i("服務器與客戶端建立鏈接")
    }

    /** * 服務器與客戶端鏈接打開 * @param session * @throws Exception */
    @Throws(Exception::class)
    override fun sessionOpened(session: IoSession?) {
        super.sessionOpened(session)
        LogUtils.i("服務器與客戶端鏈接打開")
    }

    /** * 關閉與客戶端的鏈接時會調用此方法 * @param session * @throws Exception */
    @Throws(Exception::class)
    override fun sessionClosed(session: IoSession?) {
        super.sessionClosed(session)
        handler.post {
            connectCallback?.onDisConnected()
        }
        LogUtils.i("關閉與客戶端的鏈接時會調用此方法")
    }

    /** * 服務器進入空閒狀態 * @param session * @param status * @throws Exception */
    @Throws(Exception::class)
    override fun sessionIdle(session: IoSession?, status: IdleStatus?) {
        super.sessionIdle(session, status)
        LogUtils.i("服務器進入空閒狀態")
    }

    /** * 異常 * @param session * @param cause * @throws Exception */
    @Throws(Exception::class)
    override fun exceptionCaught(session: IoSession?, cause: Throwable) {
        super.exceptionCaught(session, cause)
        handler.post {
            connectCallback?.onError(cause)
        }
        LogUtils.i("服務器異常$cause")
    }

    private var connectCallback:ConnectCallback? = null
    fun setConnectCallback(callback:ConnectCallback){
        this.connectCallback = callback
    }
    interface ConnectCallback{
        fun onSendSuccess()
        fun onGetMessage(message: Any?)
        fun onOpened()
        fun onConnected()
        fun onDisConnected()
        fun onError(cause: Throwable)
    }

}
複製代碼

在服務端中的activity中使用:

var mServer = MinaServer()
            mServer
                    .connect(2333)
                    .setConnectCallback(object : MinaServer.ConnectCallback {
                        override fun onSendSuccess() {
                            //發送消息成功
                        }

                        override fun onGetMessage(message: Any?) {
                            //接收消息成功
                            val msg = message.toString()
                        }

                        override fun onOpened() {
                            
                        }
                        override fun onConnected() {

                        }

                        override fun onDisConnected() {

                        }

                        override fun onError(cause: Throwable) {
                            Toast.makeText(applicationContext, "服務器異常" + cause.toString(), Toast.LENGTH_SHORT).show()
                        }

                    })
複製代碼

再看客戶端代碼:

lass MinaClient : IoHandlerAdapter(){
    private val connector: NioSocketConnector
    private var session: IoSession? = null

    var isConnected = false
    private var handler:Handler by Delegates.notNull()
    init {
        connector = NioSocketConnector()
        // 設置連接超時時間
        connector.connectTimeoutMillis = 15000
        // 添加過濾器
        connector.filterChain.addLast("codec",
                ProtocolCodecFilter(TextLineCodecFactory()))
        handler = Handler()
    }

    fun connect(ip: String, port: Int): MinaClient {
        if (isConnected)
            return this
        thread {
            connector.handler = this
            connector.setDefaultRemoteAddress(InetSocketAddress(ip, port))
            //開始鏈接
            try {
                val future = connector.connect()
                future.awaitUninterruptibly()// 等待鏈接建立完成
                session = future.session// 得到session
                isConnected = session != null && session!!.isConnected
            } catch (e: Exception) {
                e.printStackTrace()
                handler.post {
                    connectCallback?.onError(e)
                }
                println("客戶端連接異常...")
            }
        }
        return this
    }
    fun disConnect(){
        if (isConnected){
            session?.closeOnFlush()
            connector.dispose()
        }else{
            connectCallback?.onDisConnected()
        }
    }

    fun sendText(message: String){
        var ioBuffer = IoBuffer.allocate(message.toByteArray().size)
        ioBuffer.put(message.toByteArray())
        ioBuffer.flip()
        session?.write(ioBuffer)
    }
    /** * 向服務端端發送消息後會調用此方法 * @param session * @param message * @throws Exception */
    @Throws(Exception::class)
    override fun messageSent(session: IoSession?, message: Any?) {
        super.messageSent(session, message)
        LogUtils.i("客戶端發送消息成功")
        handler.post {
            connectCallback?.onSendSuccess()
        }
    }

    /** * 從端口接受消息,會響應此方法來對消息進行處理 * @param session * @param message * @throws Exception */
    @Throws(Exception::class)
    override fun messageReceived(session: IoSession?, message: Any?) {
        super.messageReceived(session, message)
        LogUtils.i("客戶端接收消息成功:")
        handler.post {
            connectCallback?.onGetMessage(message)
        }
    }

    /** * 服務器與客戶端建立鏈接 * @param session * @throws Exception */
    @Throws(Exception::class)
    override fun sessionCreated(session: IoSession?) {
        super.sessionCreated(session)
        LogUtils.i("服務器與客戶端建立鏈接")
        handler.post {
            connectCallback?.onConnected()
        }
    }

    /** * 服務器與客戶端鏈接打開 * @param session * @throws Exception */
    @Throws(Exception::class)
    override fun sessionOpened(session: IoSession?) {
        super.sessionOpened(session)
        LogUtils.i("服務器與客戶端鏈接打開")
    }

    /** * 關閉與客戶端的鏈接時會調用此方法 * @param session * @throws Exception */
    @Throws(Exception::class)
    override fun sessionClosed(session: IoSession?) {
        super.sessionClosed(session)
        LogUtils.i("關閉與客戶端的鏈接時會調用此方法")
        isConnected = false
        handler.post {
            connectCallback?.onDisConnected()
        }
    }

    /** * 客戶端進入空閒狀態 * @param session * @param status * @throws Exception */
    @Throws(Exception::class)
    override fun sessionIdle(session: IoSession?, status: IdleStatus?) {
        super.sessionIdle(session, status)
        LogUtils.i("客戶端進入空閒狀態")
    }

    /** * 異常 * @param session * @param cause * @throws Exception */
    @Throws(Exception::class)
    override fun exceptionCaught(session: IoSession?, cause: Throwable) {
        super.exceptionCaught(session, cause)
        LogUtils.i("客戶端異常$cause")
        handler.post {
            connectCallback?.onError(cause)
        }
    }
    private var connectCallback:ConnectCallback? = null
    fun setConnectCallback(callback:ConnectCallback){
        this.connectCallback = callback
    }
    interface ConnectCallback{
        fun onSendSuccess()
        fun onGetMessage(message: Any?)
        fun onConnected()
        fun onDisConnected()
        fun onError(cause: Throwable)
    }
}
複製代碼

客戶端的activity中使用:

var mClient = MinaClient()
        mClient
                .connect("192.168.0.108", 2333)
                .setConnectCallback(object : MinaClient.ConnectCallback {
                    override fun onGetMessage(message: Any?) {
                        val msg = message.toString()
                    }
                    override fun onConnected() {
                        
                    }

                    override fun onDisConnected() {
                        Toast.makeText(applicationContext, "斷開鏈接成功", Toast.LENGTH_SHORT).show()
                    }
                    override fun onError(cause: Throwable) {
                        Toast.makeText(applicationContext, "服務器異常" + cause.toString(), Toast.LENGTH_SHORT).show()
                    }

                    override fun onSendSuccess() {

                    }

                })
複製代碼

界面佈局比較簡單,就是一個recyclerview+幾個button,若是以爲我講得不夠清楚T^T,能夠到github上查看源碼:minaSimple

相關文章
相關標籤/搜索