概述
工欲善其事,必先利其器,从互联网诞生到现在,基本上所有的程序都是网络程序,很少有单机版的程序了。 而网络编程的本质是两个设备之间的数据交换,当然,在计算机网络中,设备主要指计算机。我们现在进行网络编程,基本上都是使用已经封装好的框架,毕竟自己实现维护一套网络编程框架,费时费力不说,做出来之后还不一定好用,有那时间多保养保养头发。
Socket简介
记得大学学习java的时候,并没有接触网络编程相关的基础知识,一直都是单机版程序,没有见识到网络编程的美妙,所以对socket这个东西既熟悉又陌生。熟悉的原因是在MFC实验课中接触到socket知识的,没错,就是那门古老的开发语言,现在已经销声匿迹了,陌生的原因大概也就不言而喻了。好了,说了那么多,那socket到底是什么呢?
Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口 。很多同学会把tcp、udp协议和socket搞混,其实Socket只是一种连接模式,不是协议。tcp、udp是两个最基本的协议,很多其它协议都是基于这两个协议。
用socket可以创建tcp连接,也可以创建udp连接,这意味着,用socket可以创建任何协议的连接。简单的来说,socket相当于一艘船,你把目的地(ip+端口)告诉它,把要运输的货物搬上去,然后它将货物送往目的地。这个货物具体需要怎么运输,运输完成之后是否还要通知你已经到达目的地,这就是实现协议的区别了。
Socket使用
首先来回顾一下socket的基本使用,创建一个服务端所需步骤
- 创建ServerSocket对象绑定监听端口。
- 通过accept()方法监听客户端的请求。
- 建立连接后,通过输入输出流读取客户端发送的请求信息。
- 通过输出流向客户端发送请求信息。
- 关闭相关资源。
嗯。。。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();
}
}
}
复制代码
创建一个客户端所需步骤,其实和服务端代码差不多:
- 创建Socket对象,指明需要连接的服务器的地址和端口。
- 建立连接后,通过输出流向服务器发送请求信息。
- 通过输入流获取服务器的响应信息。
- 关闭相关资源
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()
的时候返回实例。这个接口有如下常用的方法:
WriteFuture write(Object message):
这个方法用于写数据,该操作是异步的。CloseFuture close(boolean immediately):
这个方法用于关闭IoSession,该操作也是异步的,参数指定true 表示立即关闭,否则就在所有的写操作都flush 之后再关闭。Object setAttribute(Object key,Object value):
这个方法用于给我们向会话中添加一些属性,这样可以在会话过程中都可以使用,类似于HttpSession 的setAttrbute()方法。IoSession 内部使用同步的HashMap 存储你添加的自定义属性。SocketAddress getRemoteAddress():
这个方法获取远端连接的套接字地址。void suspendWrite():
这个方法用于挂起写操作,那么有void resumeWrite()方法与之配对。对于read()方法同样适用。ReadFuture read():
这个方法用于读取数据, 但默认是不能使用的, 你需要调用IoSessionConfig 的setUseReadOperation(true)才可以使用这个异步读取的方法。一般我们不会用到这个方法,因为这个方法的内部实现是将数据保存到一个BlockingQueue,假如是Server 端,因为大量的Client 端发送的数据在Server 端都这么读取,那么可能会导致内存泄漏,但对于Client,可能有的时候会比较便利。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的基本使用以及mina框架简单介绍的全部内容,希望文章能够帮你解决朝花夕拾之socket的基本使用以及mina框架简单介绍所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复