朝花夕拾之socket的基本使用以及mina框架简单介绍

工欲善其事,必先利其器,从互联网诞生到现在,基本上所有的程序都是网络程序,很少有单机版的程序了。 而网络编程的本质是两个设备之间的数据交换,当然,在计算机网络中,设备主要指计算机。我们现在进行网络编程,基本上都是使用已经封装好的框架,毕竟自己实现维护一套网络编程框架,费时费力不说,做出来之后还不一定好用,有那时间多保养保养头发。

Socket简介

记得大学学习java的时候,并没有接触网络编程相关的基础知识,一直都是单机版程序,没有见识到网络编程的美妙,所以对socket这个东西既熟悉又陌生。熟悉的原因是在MFC实验课中接触到socket知识的,没错,就是那门古老的开发语言,现在已经销声匿迹了,陌生的原因大概也就不言而喻了。好了,说了那么多,那socket到底是什么呢?

Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口 。很多同学会把tcp、udp协议和socket搞混,其实Socket只是一种连接模式,不是协议。tcp、udp是两个最基本的协议,很多其它协议都是基于这两个协议。

用socket可以创建tcp连接,也可以创建udp连接,这意味着,用socket可以创建任何协议的连接。简单的来说,socket相当于一艘船,你把目的地(ip+端口)告诉它,把要运输的货物搬上去,然后它将货物送往目的地。这个货物具体需要怎么运输,运输完成之后是否还要通知你已经到达目的地,这就是实现协议的区别了。

Socket使用

首先来回顾一下socket的基本使用,创建一个服务端所需步骤

  1. 创建ServerSocket对象绑定监听端口。
  2. 通过accept()方法监听客户端的请求。
  3. 建立连接后,通过输入输出流读取客户端发送的请求信息。
  4. 通过输出流向客户端发送请求信息。
  5. 关闭相关资源。

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

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这两个包即可.

先看看我们实现的效果图(由于是视频转gif,可能不太好看出来,就是一个简单的文本通讯):

创建服务端

前面说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

感谢大家对北风之神SOCKET框架的支持。鼓励。下面是北风之神 3.1的更新内容: 修正BUG: 1.ZYSocketSuper 读取 配置文件的最大连接数 读错问题。 2.ZYSocketSuper 无法断开客户端的问题。 3.BuffList 数据包解析丢失问题。 4.例1,例2.客户端断开忘记释放调用SOCKET.CLOSE()的问题 新增功能 1.添加了一个ReadBytes 构造函数,此函数实现了在数据包在读取前需要回调的方法传入。(可以用来解密,解压缩 等功能) 2.添加了一个BufferFormat 类的构造,此函数实现了在数据包在生成前需要回调的方法传入。(可以用来加密,压缩 等功能) 3.添加了BufferFormat.FormatFCA(object o,FDataExtraHandle dataExtra)静态方法。可以用来在类格式化成数据包的时候进行加密压缩等功能 4.添加了ZYSocket.Security 命名空间,里面有传统的AES,DES算法的加解密类 5.添加了ZYSocket.Compression命名空间,里面有通过Deflate算法压缩类 6.开发了ReadBytes.Data属性,为ReadBytes里面的BYTE[]对象。值得注意的是 ReadBytes.Length为数据包原始长度,如果要得到解压缩后的数据包长度,请访问ReadByte.Data.length 新增代码 加解密实例测试 项目:演示了 AES DES 以及Deflate 的使用方法。 例3 - 例2的加密版 项目:就是讲例2通过DES 加密进行通讯的例子 连接测试工具 项目:很多朋友问我要连接数量测试工具。我一起的真的丢了。找不到了。所以重新写了一个 例4 项目:好多人让我写一个发送文件的例子,现在能如愿以偿了 by luyikk@126.com BLOG:http://blog.csdn.net/luyikk QQ:547386448
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值