IPC 之 Socket 的使用

一、概述

  我们知道在开发中,即时通讯、设备间的通信都是使用 Socket 实现,那当然用它来实现进程间通信更是不成问题。Socket 即套接字,是一个对 TCP / IP协议进行封装 的编程调用接口(API) 。通过Socket,我们才能在 Andorid 平台上通过 TCP/IP 协议进行开发。Socket 不是一种协议,而是一个编程调用接口(API),属于传输层(主要解决数据如何在网络中传输)。

  Socket的使用类型主要有两种:

  • 流套接字(streamsocket) :基于 TCP 协议,采用 流的方式 提供可靠的字节流服务
  • 数据报套接字(datagramsocket):基于 UDP 协议,采用 数据报文 提供数据打包发送的服务

  对于 TCP 和 UDP 的讲述,这里就不做说明。

二、Socket 实现 IPC

  IPC 中两个重要角色: 客户端和服务端,示例工程仍旧是两个工程。我们先看服务端如何使用 Socket :

  1. 服务端

  服务端 Service 完整代码:

public class SocketService extends Service {

    private String[] mDefinedMsg = new String[]{"你好啊,哈哈", "请问你叫什么名字啊?", "今天北京天气不错啊", "你知道吗,我可以和多个人同时聊天", "给你讲个笑话吧,爱笑的人运气不会太差。"};

    int NUMBER_OF_CORES = Runtime.getRuntime().availableProcessors();
    int KEEP_ALIVE_TIME = 1;
    TimeUnit KEEP_ALIVE_TIME_UNIT = TimeUnit.SECONDS;
    BlockingQueue<Runnable> taskQueue = new LinkedBlockingQueue<Runnable>();
    ExecutorService executorService = new ThreadPoolExecutor(NUMBER_OF_CORES, NUMBER_OF_CORES * 2, KEEP_ALIVE_TIME, KEEP_ALIVE_TIME_UNIT, taskQueue);

    private boolean isServiceDestroy = false;

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

    @Override
    public void onCreate() {
        super.onCreate();
        executorService.execute(new TcpServer());
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        isServiceDestroy = true;
    }

    private class TcpServer implements Runnable {

        @Override
        public void run() {
            ServerSocket serverSocket = null;
            try {
                //接听本地8688接口
                serverSocket = new ServerSocket(8688);
            } catch (IOException e) {
                e.printStackTrace();
            }
            while (!isServiceDestroy && serverSocket != null) {
                //接受客户端请求
                try {
                    Socket client = serverSocket.accept();
                    executorService.execute(new TalkWithClient(client));
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private class TalkWithClient implements Runnable {

        private Socket socket;

        TalkWithClient(Socket socket) {
            this.socket = socket;
        }

        @Override
        public void run() {
            try {
                //用于接收客户端信息
                BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                //用于向客户端发送信息
                PrintWriter out = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()), true);
                out.println("欢迎来到聊天室");
                while (!isServiceDestroy) {
                    String info = in.readLine();
                    if (info == null) { //客户端断开连接
                        break;
                    }
                    Log.i("CLIENT_INFO", info);
                    int i = new Random().nextInt(mDefinedMsg.length);
                    String msg = mDefinedMsg[i];
                    out.println(msg);
                    Log.i("Server_INFO", msg);

                }
                out.close();
                in.close();
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

  在服务端通过循环的方式轮询是否有客户端接入,并判断服务一旦停止,停止循环。 accpet() 方法在没有客户端接入时,会阻塞当前线程,等到有客户端接入时会继续执行。

 while (!isServiceDestroy && serverSocket != null) {
          //接受客户端请求
          try {
             Socket client = serverSocket.accept();
             executorService.execute(new TalkWithClient(client));
          } catch (IOException e) {
          e.printStackTrace();
      }
 }

  TalkWithClient 是一个 Runnable 实现类,用来与客户端进行交互。通过循环的方式读取客户端传递过来的信息,代码中 readLine() 方法也会阻塞,直到客户端有消息过来继续执行。

while (!isServiceDestroy) {
                    String info = in.readLine();
                    if (info == null) { //客户端断开连接
                        break;
                    }
                    Log.i("CLIENT_INFO", info);
                    int i = new Random().nextInt(mDefinedMsg.length);
                    String msg = mDefinedMsg[i];
                    out.println(msg);
                    Log.i("Server_INFO", msg);

                }

  2. 客户端

  客户端完整代码:

  

public class MainActivity extends AppCompatActivity {

    private static final int MESSAGE_SOCKET_CONNECTED = 1;
    private static final int MESSAGE_RECEIVE_NEW_MSG = 2;

    int NUMBER_OF_CORES = Runtime.getRuntime().availableProcessors();
    int KEEP_ALIVE_TIME = 1;
    TimeUnit KEEP_ALIVE_TIME_UNIT = TimeUnit.SECONDS;
    BlockingQueue<Runnable> taskQueue = new LinkedBlockingQueue<Runnable>();
    ExecutorService executorService = new ThreadPoolExecutor(NUMBER_OF_CORES, NUMBER_OF_CORES * 2, KEEP_ALIVE_TIME, KEEP_ALIVE_TIME_UNIT, taskQueue);

    private Socket mClientSocket;
    private PrintWriter mPrintWriter;
    @SuppressLint("HandlerLeak")
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MESSAGE_SOCKET_CONNECTED:
                    btnSend.setEnabled(true);
                    break;
                case MESSAGE_RECEIVE_NEW_MSG:
                    msgContainer.setText(String.format("%s%s", msgContainer.getText(), msg.obj));
                    break;
            }
        }
    };
    private TextView msgContainer;
    private EditText etMsg;
    private TextView btnSend;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        msgContainer = findViewById(R.id.msg_container);
        etMsg = findViewById(R.id.msg);
        btnSend = findViewById(R.id.btn_send);
        btnSend.setEnabled(false);

        executorService.execute(new Runnable() {
            @Override
            public void run() {
                connectTCPServer();
            }
        });
    }

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

    public void sendMsg(View view) {
        final String msg = etMsg.getText().toString();
        if (!TextUtils.isEmpty(msg) && mPrintWriter != null) {
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    mPrintWriter.println(msg);
                }
            });
            etMsg.setText("");
            String time = formatTimes(System.currentTimeMillis());
            String showMsg = "client" + time + ":" + msg + "\n";
            msgContainer.setText(String.format("%s%s", msgContainer.getText(), 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(mClientSocket.getOutputStream())), true);
                mHandler.sendEmptyMessage(MESSAGE_SOCKET_CONNECTED);
            } catch (IOException e) {
                SystemClock.sleep(1000);
                //retry
            }
        }
        //接收服务器消息
        try {
            BufferedReader br = new BufferedReader(new InputStreamReader(mClientSocket.getInputStream()));
            while (!MainActivity.this.isFinishing()) {
                String msg = br.readLine();
                Log.i("CLIENT_RECEIVE", msg);
                if (msg != null) {
                    String time = formatTimes(System.currentTimeMillis());
                    String showMsg = "server" + time + ":" + msg + "\n";
                    mHandler.obtainMessage(MESSAGE_RECEIVE_NEW_MSG, showMsg).sendToTarget();
                }
            }
            mPrintWriter.close();
            br.close();
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @SuppressLint("SimpleDateFormat")
    private String formatTimes(long millis) {
        return "(" + new SimpleDateFormat("HH:mm:ss").format(new Date(millis)) + ")";
    }
}

  连接服务端:

  connectTCPServer() 方法是用来连接服务端以及接收服务器消息,接收服务器消息仍然通过循环方式轮询。

  注意:

    Socket 实现 IPC 是网络操作,所以一定记得声明权限:

 

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

 

  最终能够进行传输数据,这里数据可以是任意数据,示例中只是传递了字符串。效果:

      

 

  示例工程代码:

   链接: https://pan.baidu.com/s/1Dq710Z4-vVsrNvgGUky09w 密码: 52fv

 

 

 

posted @ 2018-05-04 17:06  Spiderman.L  阅读(1898)  评论(0编辑  收藏  举报