Android 中的 IPC 方式(五) Socket

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/sinat_29874521/article/details/82735835

Socket 也称为“套接字”,是网络通信中的概念,它分为流式套接字和用户数据报套接字两种,分别对应网络的传输控制层中的 TCP 和 UDP 协议,TCP 协议是面向连接的协议,提供稳定的双向功能,TCP 链接的建立经过“三次握手”才能完成,为了提供稳定的数据传输功能,其本身提供了超时重传机制,因此具有很高的稳定性;而 UDP 是无连接的,提供不稳定的单向连接功能,当然 UDP 也可以实现双向通信功能。在性能上,UDP 具有更好的效率,其缺点是不能保证数据一定能正确传输。尤其是在网络拥堵的情况下。接下来我们演示一个跨进程聊天程序,两个进程可以通过 Socket 来实现信息的传输,Socket 本身可以支持传输任意字节流的。

使用 Socket 来进行通信,有两点需要注意,首先需要声明权限:

    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

其次要注意不鞥在主线程中访问网络。这样会导致无法再 Android 4.0 及其以上的设备中运行,会抛出如下异常:android.os.NetwworkOnMainThreadException。下面开始我们的聊天程序,比较简单,首先在远程 Socket 建立一个 TCP 服务,然后在主界面中链接 TCP 服务,连接上了以后,就可以给服务端发消息。对于我们发送的每一条文本信息,服务端都会随机的回应我们一句话。为了更好的展示 Socket 的工作机制,在服务端我们需要处理下,使其能够和多个客户端同时建立链接并相应。

先看一下服务端设计,当 Service 启动时,会在线程中建立 TCP 服务,这里监听的是 8688 端口,然后就可以等待客户端的连接请求。当客户端连接时,就会生成一个新的 Socket,通过每次新创建的 Socket 就可以分别和不同的客户端通信了。服务端没收到一次客户端的信息就会随机回复一句话给客户端。当客户端连接断开时,服务端也会相应的关闭对应的 Socket 并结束通话线程,这点如何做到呢?方法有很多,这里是通过判断服务端输入的流的返回值来确定的,当客户端断开连接后,服务端这边的输入流会返回 null,这个时候就知道客户端退处理,服务端代码如下:

public class TCPServerService extends Service {

    public static final String TAG = "TCPServerService";
    private boolean mIsServiceDestoryed = true;
    private String [] mDefiendMessages = new String[]{"你好啊,哈哈", "请问你叫什么名字啊?", "今天北京天气不错啊,shy", "你知道吗,我可是可以和多个人同事聊天的哦", "给你讲个笑话吧"};

    @Override
    public void onCreate() {
        super.onCreate();
        new Thread(new TCPServer()).start();
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        mIsServiceDestoryed = false;
    }

    private class TCPServer implements Runnable {

        @Override
        public void run() {
            ServerSocket serverSocket = null;
            try {
                //监听 8688 接口
                serverSocket = new ServerSocket(8688);
            } catch (IOException e) {
                System.err.println("establish tcp server filled, port: 8688");
                e.printStackTrace();
                return;
            }

            while (mIsServiceDestoryed) {
                try {
                    //接受客户端请求
                    final Socket client = serverSocket.accept();
                    new Thread() {
                        @Override
                        public void run() {
                            try {
                                responseClient(client);
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        }
                    }.start();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        private void responseClient(Socket client) throws IOException {
            //用于接收客户端消息
            BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream()));
            //用于向客户端发送消息
            PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(client.getOutputStream())), true);
            out.println("欢迎来到聊天室!");
            while (mIsServiceDestoryed){
                String str = in.readLine();
                Log.i(TAG, "msg from client:" + str);
                if(str == null) {
                    //客户端断开连接
                    break;
                }
                int i = new Random().nextInt(mDefiendMessages.length);
                String msg = mDefiendMessages[i];
                out.println(msg);
                Log.i(TAG, "send msg:" + msg);
            }
            Log.i(TAG, "client quit.");
            close(out);
            close(in);
            client.close();
        }
    }

    public void close(Closeable closeable) {
        try {
            if (closeable != null) {
                closeable.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

接着看一下客户端,客户端 Activity 启动时,会在 onCreate 中开启一个线程去链接服务端的 Socket,至于为什么用线程在前边已经做了介绍。为了确定能够链接成功,这里采用了超时重连的策略,每次连接失败后都会重新尝试建立连接,当然为了降低重试机制的开销,我们加入了休眠机制,即每次重试的时间间隔是 1000 毫秒。

        Socket socket = null;
        while (socket == null) {
            try {
                socket = new Socket("localhost", 8688);
                mClientSocket = socket;
                mPrintWriter = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())), true);
                mHandler.sendEmptyMessage(MESSAGE_SOCKET_CONNECTED);
                Log.i(TAG, "connect server success.");
            } catch (IOException e) {
                e.printStackTrace();
                SystemClock.sleep(1000);
                Log.i(TAG, "connect tcp server failed, retry...");
            }
        }

服务端连接成功后,就可以和服务端进行通信了。下面的代码在线程中通过 while 循环不断地从服务端获取数据,同时在 Activity 退出时,就退出循环并终止线程。

  //接收服务端消息
        try {
            BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            while (!SocketActivity.this.isFinishing()){
                String msg = br.readLine();
                Log.e(TAG, "receive msg:" + msg);
                if(null != msg) {
                    String time = new SimpleDateFormat("HH:mm:ss").format(new Date(System.currentTimeMillis()));
                    final String showMsg = "server" + time + ":" + msg + "\n";
                    mHandler.obtainMessage(MESSAGE_RECEIVE_NEW_MSG, showMsg).sendToTarget();
                }
            }
            close(mPrintWriter);
            close(br);
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

同时,在 Activity 退出时,还要关闭当前的 Socket。如下所示:

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if(mClientSocket != null) {
            try {
                mClientSocket.shutdownInput();
                mClientSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

接着是发送消息的过程,这个就简单了,这里不再详细说明,客户端完整的代码如下:

public class SocketActivity extends Activity implements View.OnClickListener {

    private static final String TAG = "SocketActivity";

    private Socket mClientSocket;
    private PrintWriter mPrintWriter;
    private static final int MESSAGE_RECEIVE_NEW_MSG = 1;
    private static final int MESSAGE_SOCKET_CONNECTED = 2;

    private Button mSendButton;
    private TextView mMessageTextView;
    private EditText mMessageEditText;

    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MESSAGE_RECEIVE_NEW_MSG:
                    mMessageTextView.setText(mMessageTextView.getText().toString() + msg.obj);
                    break;
                case MESSAGE_SOCKET_CONNECTED:
                    mSendButton.setEnabled(true);
                    break;
                default:
                    break;
            }
        }
    };

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_socket);
        mMessageTextView = (TextView) findViewById(R.id.msg_container);
        mSendButton = (Button) findViewById(R.id.send);
        mSendButton.setOnClickListener(this);
        mMessageEditText = (EditText) findViewById(R.id.msg);

        startService(new Intent(this, TCPServerService.class));

        new Thread(new Runnable() {
            @Override
            public void run() {
                connectTCPServer();
            }
        }).start();

    }

    @Override
    public void onClick(View v) {
        final String msg = mMessageEditText.getText().toString();
        if(!TextUtils.isEmpty(msg) && mPrintWriter != null) {
            mPrintWriter.println(msg);
            mMessageEditText.setText("");
            String time = new SimpleDateFormat("HH:mm:ss").format(new Date(System.currentTimeMillis()));
            final String showMsg = "self" + time + ":" + msg + "\n";
            mMessageTextView.setText(mMessageTextView.getText().toString() + showMsg);
        }
    }

    private void connectTCPServer() {
        Socket socket = null;
        while (socket == null) {
            try {
                socket = new Socket("localhost", 8688);
                mClientSocket = socket;
                mPrintWriter = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())), true);
                mHandler.sendEmptyMessage(MESSAGE_SOCKET_CONNECTED);
                Log.i(TAG, "connect server success.");
            } catch (IOException e) {
                e.printStackTrace();
                SystemClock.sleep(1000);
                Log.i(TAG, "connect tcp server failed, retry...");
            }
        }

        //接收服务端消息
        try {
            BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            while (!SocketActivity.this.isFinishing()){
                String msg = br.readLine();
                Log.e(TAG, "receive msg:" + msg);
                if(null != msg) {
                    String time = new SimpleDateFormat("HH:mm:ss").format(new Date(System.currentTimeMillis()));
                    final String showMsg = "server" + time + ":" + msg + "\n";
                    mHandler.obtainMessage(MESSAGE_RECEIVE_NEW_MSG, showMsg).sendToTarget();
                }
            }
            close(mPrintWriter);
            close(br);
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void close(Closeable closeable) {
        try {
            if (closeable != null) {
                closeable.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if(mClientSocket != null) {
            try {
                mClientSocket.shutdownInput();
                mClientSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

上述就是通过 Socket 来进行进程间通信的实例,除了采用 TCP 套接字,还可以采用 UDP 套接字,另外,上面的例子仅仅是一个示例,实际上通过 Socket 不仅能实现进程间他通信,还可以实现设备间的通信,当然前提是这些设备之间的 IP 地址互相可见,这里就不一一说明了。下面我们看下运行效果:

 

猜你喜欢

转载自blog.csdn.net/sinat_29874521/article/details/82735835