朝花夕拾之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

相关文章
相关标签/搜索