Socket跨平台通信——服务端Android、客户端iOS

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/zhang5690800/article/details/53377961

本文讲述了:
1、如何在Android上搭建Mina服务端(使用Mina 2.0.15、编程环境Eclipse);
2、如何在iOS上建立Socket客户端(使用CocoaAsyncSocket第三方通讯框架、Swift3.0);
3、Android iOS间的TCPSocket通讯测试。


一、在Android上搭建Mina服务端

1、下载Mina最新版
直接进官网:http://mina.apache.org/ 进入后左边栏上方有一个 Latest DownloadsMina x.x.xx,点击进入后如下图。
这里写图片描述

点击标注的这项,进入下一个界面。
这里写图片描述

用它建议的这个链接下载Mina最新版,下载后是一个zip包。解压它,如下图。(我用的是Mina 2.0.15,都一样)
这里写图片描述

好,到这里Mina的准备工作算是完成了。

2、下面开始搭建Mina服务端,打开Eclipse,建立Android工程(这里将工程命名为MinaServer)。然后就是使用Mina了,就是在自己的工程中导入Mina提供的第三方jar包。
在刚才解压得到的Mina文件夹中找到 dist - mina-core-x.x.xxlib - slf4j-api-x.x.xx这两个Jar包,然后将他们拖到我们工程的libs文件夹中,选中它俩 右击 - Build Path - Add to Build Path,下面图片是导入后的图片,导入后会多出一个Referenced Libraries 里面有我们刚刚添加的那两个第三方包。到这里我们就将Mina导入进了我们的项目。
(其实导入包的方法不只这一种,还有别的方法,我也尝试了,因为别的方法看上去比这一种显得正规,但是在程序运行中会报错:Could not find class ‘org.apache.mina.transport.socket.nio.NioSocketAcceptor’, referenced from method alex.example.minaserver.MainActivity.onCreate,这个问题的解决方案就是用这种看着不专业的jar包导入方法。网上有不少人遇到此问题,可见此坑之深…)
这里写图片描述

3、写Mina服务端代码(这里不做解释,因为我也没彻底玩透Mina框架,只是会简单使用,也和大家一样是小白)
打开 MinaActivity.java

package alex.example.minaserver;

import java.net.InetSocketAddress;
import java.nio.charset.Charset;

import org.apache.mina.core.service.IoAcceptor;
import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IdleStatus;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.filter.codec.textline.LineDelimiter;
import org.apache.mina.filter.codec.textline.TextLineCodecFactory;
import org.apache.mina.transport.socket.nio.NioSocketAcceptor;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;

public class MainActivity extends Activity {

    // Mina相关声明-----------------------
    private IoAcceptor acceptor;
    private static int PORT = 4000; // 端口号,要求客户端与服务器端一致

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        try {
            Log.d("Mina", "开始启动 MinaServer...");
            // 创建一个非阻塞的server端的Socket
            acceptor = new NioSocketAcceptor();
            // 设置过滤器(使用mina提供的文本换行符编解码器)
            acceptor.getFilterChain().addLast("codec",
                    new ProtocolCodecFilter(new TextLineCodecFactory(Charset.forName("UTF-8"), LineDelimiter.WINDOWS.getValue(), LineDelimiter.WINDOWS.getValue())));
            // 设置读取数据的换从区大小
            // acceptor.getSessionConfig().setReadBufferSize(2048);
            // 读写通道10秒内无操作进入空闲状态
            acceptor.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE, 10);
            // 为接收器设置管理服务
            acceptor.setHandler(new DemoServerHandler());
            ((NioSocketAcceptor) acceptor).setReuseAddress(true); // 端口重用,如果有客户端在wait状态,服务器重启但是端口释放不掉
            // 绑定端口
            acceptor.bind(new InetSocketAddress(PORT));
            Log.d("Mina", "服务器启动成功,端口号为:" + PORT);
            Toast.makeText(MainActivity.this, "服务器启动成功", Toast.LENGTH_LONG).show();
        } catch (Exception e) {
            Log.d("Mina", "服务器启动异常..." + e);
            Toast.makeText(MainActivity.this, "服务器启动失败", Toast.LENGTH_LONG).show();
            e.printStackTrace();
        }

    }

    // Mina --- IoHandlerAdapter处理Mina通信中的各种事件 --------------------
    public class DemoServerHandler extends IoHandlerAdapter {

        // 从端口接受消息,会响应此方法来对消息进行处理
        public void messageReceived(IoSession session, Object message) throws Exception {
            super.messageReceived(session, message);
            String recvMsg = message.toString();
            Log.d("Mina", "*** Start *** 服务器收到消息:" + recvMsg);
            session.write("Received");
        }

        // 向客服端发送消息后会调用此方法
        public void messageSent(IoSession session, Object message) throws Exception {
            super.messageSent(session, message);
            // session.close(true); // 加上这句话实现短连接的效果,向客户端成功发送数据后断开连接
            Log.d("Mina", "*** End *** 向客户端发送消息:" + message.toString());
        }

        // 关闭与客户端的连接时会调用此方法
        public void sessionClosed(IoSession session) throws Exception {
            super.sessionClosed(session);
            Log.d("Mina", "服务器与客户端断开连接...");
        }

        // 服务器与客户端创建连接
        public void sessionCreated(IoSession session) throws Exception {
            super.sessionCreated(session);
            Log.d("Mina", "服务器与客户端创建连接...");
        }

        // 服务器与客户端连接打开
        public void sessionOpened(IoSession session) throws Exception {
            Log.d("Mina", "服务器与客户端连接打开...");
            super.sessionOpened(session);
        }

        // 服务器进入空闲状态IDLE会调用此方法
        public void sessionIdle(IoSession session, IdleStatus status) throws Exception {
            super.sessionIdle(session, status);
            Log.d("Mina", "服务器进入空闲状态...");
        }

        // 服务器捕捉到异常会调用此方法
        public void exceptionCaught(IoSession session, Throwable cause) throws Exception {
            super.exceptionCaught(session, cause);
            Log.d("Mina", "服务器出现异常:" + cause.toString());
        }

    }

}

这还不算完,因为这个App需要联网,所以别忘了添加权限。
打开 AndroidManifest.xml(权限有的用不到,带着没妨碍)

<!-- **************** 网 络 ***************** -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

4、Mina服务端到此就完成了,下面运行下试试,我是用的真机调试的,所以不方便图片展示,下图只展示Log中的内容。
这里写图片描述


二、在iOS上建立Socket客户端(Swift3.0)

1、下载和使用CocoaAsyncSocket。
https://github.com/robbiehanson/CocoaAsyncSocket
下载完成解压得到如下图文件夹。
这里写图片描述

下面开始将它导入到我们的App项目中,将上图圈中的两个文件直接拖进xCode左边栏的工程中,如下图。
这里写图片描述

将这两个文件拖进xCode后它会提示你是否建立一个Bridge-Header,就点击Create。
然后点击这个Bridge-Header文件并输入。

#import "GCDAsyncSocket.h"

这时候按下Command+B重新编译一下。这样CocoaAsyncSocket就导入完毕了。

2、客户端程序

界面展示
这里写图片描述

编写代码,进入ViewController.Swift

//
//  ViewController.swift
//  AsyncSocket_Exp
//
//  Created by 大老虎 on 2016/11/29.
//  Copyright © 2016年 Tiger. All rights reserved.
//

import UIKit

class ViewController: UIViewController, GCDAsyncSocketDelegate {

    @IBOutlet weak var serveripInput: UITextField!
    @IBOutlet weak var msgInput: UITextField!
    @IBOutlet weak var conBtn: UIButton!
    @IBOutlet weak var sendBtn: UIButton!
    @IBOutlet weak var msgView: UITextView!

    let serverPort: UInt16 = 4000 // 和上面的服务端一致

    var clientSocket:GCDAsyncSocket!

    override func viewDidLoad() {
        super.viewDidLoad()
        sendBtn.isEnabled = false // Socket未连接成功时发送按钮不能用
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    // 连接服务器按钮事件
    @IBAction func conBtnClick(_ sender: AnyObject) {
        if serveripInput.text?.isEmpty == false { // 如果IP地址不为空则开始连接Socket
            clientSocket = GCDAsyncSocket()
            clientSocket.delegate = self
            clientSocket.delegateQueue = DispatchQueue.global()
            do {
                try clientSocket.connect(toHost: serveripInput.text!, onPort: serverPort)
            } catch {
                print("error")
                conBtn.backgroundColor = UIColor.red
            }
        } else {
            msgView.insertText("IP地址不能为空!\n")
        }
    }

    func socket(_ sock: GCDAsyncSocket, didConnectToHost host: String, port: UInt16) -> Void {
        print("连接成功")
        sendBtn.isEnabled = true // 连接成功后发送按钮设为可用
        clientSocket.readData(withTimeout: -1, tag: 0)
    }

    func socketDidDisconnect(_ sock: GCDAsyncSocket, withError err: Error?) -> Void {
        print("与服务器断开连接")
    }

    func socket(_ sock: GCDAsyncSocket, didRead data: Data, withTag tag: Int) -> Void {
        // 1、获取客户端发来的数据,把 NSData 转 NSString
        let readClientDataString: NSString? = NSString(data: data as Data, encoding: String.Encoding.utf8.rawValue)
        print("---Data Recv---")
        print(readClientDataString)

        // 2、主界面UI显示数据
        DispatchQueue.main.async {
            let showStr: NSMutableString = NSMutableString()
            showStr.append(self.msgView.text)
            showStr.append(readClientDataString! as String)
            showStr.append("\n")
            self.msgView.text = showStr as String
        }

        // 3、处理请求,返回数据给客户端OK
        // let serviceStr: NSMutableString = NSMutableString()
        // serviceStr.append("OK\r\n")
        // clientSocket.write(serviceStr.data(using: String.Encoding.utf8.rawValue)!, withTimeout: -1, tag: 0)

        // 4、每次读完数据后,都要调用一次监听数据的方法
        clientSocket.readData(withTimeout: -1, tag: 0)
    }

    // 发送消息按钮事件
    @IBAction func sendBtnClick(_ sender: AnyObject) {
        if msgInput.text?.isEmpty == false { // 如果消息不为空则发送
            let serviceStr: NSMutableString = NSMutableString()
            serviceStr.append(self.msgInput.text!)
            serviceStr.append("\r\n")
            clientSocket.write(serviceStr.data(using: String.Encoding.utf8.rawValue)!, withTimeout: -1, tag: 0)
        }
    }

}

这段代码和上一篇博文99%是一样的,只是在发送的地方不同,每次发送的结尾都要加上\r\n,只有这样才能保证Android Mina服务端能收到我们发送的信息,否则,客户端只能与服务端建立连接,而客户端发送过去的信息服务端无法正常处理。


三、跨平台通信测试

1、打开服务端和客户端程序;
2、看服务端的Log中是否建立服务端成功;
3、在客户端输入服务端的ip地址,这个ip地址指的是局域网中的地址,外网ip地址我没测试过,点击连接。双方Log是否连接成功。
4、客户端发送信息给服务端,服务端收到信息反馈给客户端,客户端接受到反馈信息,完毕。
5、关闭客户端,服务端Log显示断开连接。
6、测试完毕。

下面是我测试的图片,我的服务端使用的三星平板电脑,客户端使用的模拟器iphone6s 10.0,客户端也在真机上跑过,没问题,真机使用的iphoneSE 10.1.1。
这里写图片描述

这里写图片描述

这里写图片描述

大功告成啦!大家可以自己试一试,这里不提供源码下载了,所有核心代码都在文中了。大家加油!

展开阅读全文

没有更多推荐了,返回首页