Android中的Socket

1. UDP

(1)访问网络必须添加权限,访问网络必须添加权限,访问网络必须添加权限,重要的事情说三遍。

(2)简述

UDP协议是面向报文的,简单地说,利用UDP访问网络的步骤就是“寄快递”:通过DatagramPacket(快件)把数据和地址打包,然后用DatagramSocket(你)进行数据报的收发,至于中途是怎么传送,那就是快递员的事了,不归我们管(也因此,UCP的传输是不可靠的,可能会出现丢包的情况,跟某些快递简直一毛一样)。

InetAddress:记录访问的host等信息。

DatagramPacket:包装数据和访问地址,相当于一个快件。

DatagramSocket:用于发送和接收数据报,相当于快件的寄件人和收件人。

(3)简单示例

String serverIp = "111.111.111.11"; // 访问主机ip
InetAddress address = InetAddress.getByName(serverIp);
DatagramSocket socket = new DatagramSocket(8888); // 根据需要可在实例化时指定端口号

// 网络操作不能在UI线程进行
new Thread() {
    @Override
    public void run() {
        try {
            // 发送数据
            String msg = "hello";
            byte[] msgBytes = msg.getBytes();
            DatagramPacket packet = new DatagramPacket(msgBytes, msgBytes.length, mAddress, mPort);
            mSocket.send(packet);
            
            // 接收数据
            byte[] returnMsgBytes = new byte[1024];
            DatagramPacket returnPacket = new DatagramPacket(returnMsgBytes, returnMsgBytes.length, mAddress, mPort);
            // receive()方法是阻塞的,会一直等待接收到包
            mSocket.receive(returnPacket);
            
            String serverMsg = new String(returnPacket.getData(), 0, returnMsgBytes.length);
            Log.d("test", serverMsg);
            
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}.start();

从例子里我们可以看到,使用UDP就是打包数据、收发数据包这两步。

2. TCP

(1)还是权限,别忘了

(2)简述

与UDP不同,TCP是面向连接的,通过Socket对象创建连接,拿到一个输入流和一个输出流,然后再关闭连接前,可以一直发送与接收数据。

过程类似打电话,首先你得输入对方的电话号码(访问地址),然后拨通电话(创建连接通道),然后你说话(发送数据),或者听对方说话(接收数据),最后挂断电话(关闭连接)。

TCP创建连接时会经过三次握手,而断开连接时经过四次挥手。

(3)简单示例

try {
    // 创建连接
    Socket socket = new Socket("111.111.111.11", 8888); // 访问地址111.111.111.11:8888
    // 拿到输入流(电话听筒)、输出流(电话话筒)
    InputStream inputStream = socket.getInputStream();
    OutputStream outputStream = socket.getOutputStream();

    final BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
    BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(outputStream));

    // 接收数据
    new Thread() {
        @Override
        public void run() {
            try {
                String line;
                while ((line = br.readLine()) != null) {
                    System.out.println(line);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }.start();

    // 发送数据
    String line = "test";
    bw.write(line);
    bw.newLine();
    bw.flush();

} catch (IOException e) {
    e.printStackTrace();
}

可以看到,与UDP不同,TCP是创建连接后,在断开连接前都可以直接通过输入输出流传输数据,不需要另外将数据打包。

(4)在安卓中应用

Activity:

public class TCPActivity extends AppCompatActivity {

    // 发送消息的按钮
    private Button mSendBtn;
    // 输入框
    private EditText mMsgEt;
    // 显示消息内容的文本框
    private TextView mContentTv;

    private TCPClientBiz mBiz = new TCPClientBiz();
    private boolean isConnected = false;

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

        initView();
        initEvent();

        mBiz.connect(new TCPClientBiz.OnConnectedListener() {
            @Override
            public void onSucceed() {
                // 连接成功
                isConnected = true;
            }
        });
    }

    private void initView() {
        mSendBtn = findViewById(R.id.send_btn);
        mMsgEt = findViewById(R.id.msg_et);
        mContentTv = findViewById(R.id.content_tv);
    }

    private void initEvent() {
        mSendBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                final String msg = mMsgEt.getText().toString().trim();
                if (!msg.isEmpty()) {
                    if (isConnected) {
                        mMsgEt.setText("");
                        // 发送消息
                        mBiz.send(msg);
                    }
                }
            }
        });

        // 接收服务器的消息
        mBiz.setOnReceivedListener(new TCPClientBiz.OnReceivedListener() {
            @Override
            public void onReceived(String serverMsg) {
                mContentTv.append(formatMsg(serverMsg));
            }

            @Override
            public void onError(Exception e) {
                Toast.makeText(TCPActivity.this, e.getMessage(), Toast.LENGTH_SHORT).show();
            }
        });
    }

    private String formatMsg(String msg) {
        return msg + "\n";
    }
}

业务类:

public class TCPClientBiz {

    private InputStream inputStream;
    private OutputStream outputStream;

    private Handler handler = new Handler(Looper.getMainLooper());

    // 异步连接,所以需要一个回调,告知已经连接成功
    public void connect(final OnConnectedListener onConnectedListener) {
        new Thread() {
            @Override
            public void run() {
                try {
                    Socket socket = new Socket("169.254.165.37", 9999);
                    inputStream = socket.getInputStream();
                    outputStream = socket.getOutputStream();
                    onConnectedListener.onSucceed();

                    while (true) {
                        // 不断接收服务器消息
                        receive();
                    }

                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }.start();
    }

    private void receive() {
        try {
            BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
            final String line;
            if ((line = br.readLine()) != null) {
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        if (onReceivedListener != null) {
                            onReceivedListener.onReceived(line);
                        }
                    }
                });
            }
        } catch (final IOException e) {
            handler.post(new Runnable() {
                @Override
                public void run() {
                    if (onReceivedListener != null) {
                        onReceivedListener.onError(e);
                    }
                }
            });
        }
    }

    public void send(final String msg) {
        new Thread() {
            @Override
            public void run() {
                try {
                    BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(outputStream));
                    bw.write(msg);
                    bw.newLine();
                    bw.flush();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }.start();
    }

    private OnReceivedListener onReceivedListener;

    public void setOnReceivedListener(OnReceivedListener onReceivedListener) {
        this.onReceivedListener = onReceivedListener;
    }

    // 接收消息接口
    public interface OnReceivedListener {

        void onReceived(String serverMsg);

        void onError(Exception e);
    }

    // 连接成功接口
    public interface OnConnectedListener {
        void onSucceed();
    }
}

在安卓中运用需要注意一些点:第一,连接是异步,要添加回调,否则可能导致空指针异常;第二,网络操作中老生常谈的问题,UI操作注意不要在子线程中。

posted @ 2018-03-11 22:30  jyau  阅读(858)  评论(0编辑  收藏  举报