Socket的分析与使用

本文深入解析Android中的Socket通信原理及应用,包括ServerSocket与Socket的工作机制、SocketOptions配置选项及其实现类SocketImpl的详细说明。并通过一个客户端和服务端的例子展示了如何在Android应用中实现Socket通信。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Socket可以说是android中跨进程通信的一种方式,有时候也可以用于简单的网络传输,对于套接字连接,需要了解ServerSocket和Socket这两个流式套接字,这两个类的具体实现是有PlainSocketImpl实现的,它是SocketImpl的子类,同时SocketImpl也是一个是实现了SocketOption的抽象类。所以要了解Socket怎么用,就需要对这些都了解。下面介绍。



一、ScoketOptions



SocketOptions是为Scoket定义的一个用于设置或者获取Socket各种属性(比如 连接超时,缓冲区大小......)的接口。这里面包含了很多重要的Socket属性信息以及两个get,set方法,了解它对于以后使用Socket进行连接通讯,可以更加得心应手。现在按照笔者认为的便于大家理解的顺序来讲解。


①、设置SocketOptions里的各数据项的值:


public void setOption(int optID, Object val) throws SocketException;


用于将val值设置给指定的optID对象。


②、获取SocketOptions的各项数据值:


 public Object getOption(int optID) throws SocketException;


获取SocketOptions中指定optID的值。


接下来你会看到一堆静态常量,你可能会奇怪的是,final定义的常量为什么可以重新设置值。这里先打个预防针,因为SocketOptions定义的常量都是默认的常量值,也就是说你完全可以不理这些,你自己直接使用系统的值,但如果你要设置某个具体数据的值,可以通过setOptions来设置,这个方法的具体实现是在AcstractPlainSocketImpl里面,是根据optID来设置optID对应的局部变量的值。因此,我们调用此方法并不是直接修改optID的静态常量值,这点从ScoketOptions是一个接口就可以看出。下面介绍各个optID代表的意思。



③、public static final int SO_LINGER = 128


表示如果在关闭Socket的时候仍然有缓冲数据未发送,那么Socket在关闭之前应该等待的时间,128表示128秒。此值可以设为0,表示无论缓冲区是否还有数据未发送,当前的Socke都会马上关闭,并且close方法会立即执行。此值应该介于0-65535之间,大于65535将当作65535来对待。如果在指定的等待时间内,数据发送完毕,那么socket会正常关闭,否则将会强制关闭。



④、public static final int SO_TIMEOUT = 4102


表示连接超时时间(即在读取操作的时间不可以超过此时间值),如果是0表示没有连接超时时间,此值不可以是负值,单位是毫秒。



⑤、public static final int TCP_NODELAY = 1


指示数据是马上发送还是先缓存之后在发送,如果是马上发送会导致效率底下,缓存发送会有很高的效率。



⑥、public static final int SO_KEEPALIVE = 8


表示Socket是否发送检测连接是否存活的消息。如果在链接过程中,一直未得到对方的应答,socket会一直尝试连接,直到连接成功。


SocketOptions定义了连接的基本属性,接下来解析SocketImpl这个抽象类,此类定义了socket基本的方法,了解了这些方法的含义,有助于更好的了解Socket,因为Socket内部的操作是通过SocketImpl的子类来实现的。



二、SocketImpl



SocketImpl是一个抽象类,是所有流式套接字的基础,流式套接字主要有两个ServerSocket和Socket,前者一般用于服务端后者用于客户端,作为服务端通常会嵌入两种流式套接字,分别为ServerSocket表示用于监听客户端的对象,一旦连接成功会返回一个Socket,代表客户端的连接对象。而SocketImpl封装了一些上述流式套接字会用到的基本方法,下面来了解:



①、端口,ip

 protected InetAddress address
 protected int port;
protected int localport


其中address,port表示远程连接对象的主机嗲之和端口,localport表示绑定本地设备的端口。


②、监听:


protected abstract void accept(SocketImpl newSocket) throws IOException;


在有新的连接请求进来之前,会一直处于阻塞等待的状态,此方法用于监听客户端连接。


③、绑定:


protected abstract void bind(InetAddress address, int port) throws IOException;


将socket对象绑定到指定的本地主机地址和端口号。


④、关闭连接:


protected abstract void close() throws IOException;


关闭socket连接对象,此方法会导致导致socket对象不可再连接访问。


⑤、建立连接:


  protected abstract void connect(String host, int port) throws IOException;


此方法用于将socket对象连接到远程主机地址和端口号。


⑥、获取远程地址:


 protected InetAddress getInetAddress()


获取当前socket对象的远程连接对象的地址。


⑦、获取输入流:


 protected abstract InputStream getInputStream()


获取当前Socket对象的输入流,用来读取数据。


⑧、获取本地绑定端口:


 protected int getLocalPort()


获取socket绑定到本地地址的端口号。


⑨、获取输出流:


protected abstract OutputStream getOutputStream()

获取socket对象的输出流,用于发送数据。


⑩、获取远程连接对象的端口号


 protected int getPort()


此方法如果是被ServerScoket调用的话,是没有意义的。


more:关闭输入流输出流:


protected void shutdownInput() throws IOException
protected void shutdownOutput() throws IOException 

关闭输入流输出流。


SocketImpl是一个抽象类,它只是定义了用于scoket通信机制的基本方法,在Android中,默认使用的是PlainSocketImpl来实现基本的通讯细节。如果有需要可以自己实现SocketImpl的通信细节,可以参考PlainSocketImpl。下面讲解Socket。



三、Socket



主要用于提供基于Tcp传输协议的客户端套接字对象。要点如下:


①、构造函数:

 public Socket()
 
 
 public Socket(String dstName, int dstPort) 

两个构造函数都是使用了默认的SocketImpl的子类PlainSocketImpl的基本实现机制。后者还指定了远程连接对象的地址和端口。

②、关闭:

public synchronized void close()

调用了这个方法之后,就再也不可以连接到这个Socket对象了,除非重新创建一个实例。

③、获取远程连接对象的地址:

  public InetAddress getInetAddress()

此对象会返回远程连接对象的地址,如果返回null说明当前socket还未连接到远程对象。


④、输入流输出流:

public InputStream getInputStream()

public OutputStream getOutputStream()

获取输入流和输出流,输入流用来读取数据,输出流用来发送数据。


⑤、关闭输入流输出流:

public void shutdownInput() 

public void shutdownOutput()

前者用于关闭输入流,一旦关闭了,将不再接收任何远程对象发送的信息。如果在关闭了输入流之后又进行读取数据的操作会导致异常。
后者用于关闭输出流,一旦关闭,将不再发送任何数据。


⑥、绑定本地地址


 public void bind(SocketAddress localAddr)

用于将socket绑定到本地地址和本地端口,如果localAddr为null的话,将会从本地地址寻找任一可使用端口作为绑定端口。


⑦、远程连接


 public void connect(SocketAddress remoteAddr, int timeout) 

连接到指定地址的远程对象,timeout表示连接超时时间,可以传值0,0表示无穷大的连接超时时间。


综述,对于ScoketOptions里面提到的很多属性,可以同个scoket.setXXX的方法来设置。可以看到scoket封装了很多基本的方法,使用socket通信的时候,我们只需要关注scoket(如果需要自定义SokcetImpl,请参考plainSocketImpl)这一块就行了。下面再来了解ServerSocket.



四、ServerSocket



ServerSocket表示一个用于等待客户端连接的服务端对象,一旦有客户端连接成功,就可以进行一些适当的回应。同时,ServerSocket的socket通信机制也是又ScoketImpl来实现的,默认的是PlainSockImpl。ServerSocker用法上和Socket有很大相似之处,但也有不用的地方,比如ServerSocket拥有自己的阻塞队列用于接收客户端请求。下面讲解:


①构造函数:


public ServerSocket(int port) throws IOException

 public ServerSocket(int port, int backlog) throws IOException 

前者用于创建一个用指定端口指定的ServerSocket对象,后者顺带制订了阻塞队列的长度,这个值默认是50.一旦接受的客户端连接对象数量超过了这个值,那么所有超过这个值得连接对象都会被拒绝。


②、监听客户端连接:


 public Socket accept() throws IOException


用于监听客户端连接,此方法会导致线程阻塞,直到有新的连接进来,并且会返回一个socket对象,用于和客户端通信。


③、关闭监听:


public void close() throws IOException


调用此方法后,任何尝试建立连接都会失败。


ServerSocket很大程度上和Socket相似,这里就介绍这么多,接下来用一个例子讲解。


ServiceClient:


package com.example.hy.second.Socket;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.PersistableBundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

import com.example.hy.second.R;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StringReader;
import java.net.Socket;

/**
 * Created by X1Carbon on 2016/6/16.
 */
public class ServiceClient extends Activity {

    private EditText etContent;
    private TextView tvMsg;
    private Button btSend;
    private final String path = "127.0.0.1";
    private Socket socket = null;
    private BufferedReader reader = null;
    private PrintWriter writer = null;
    private final int SOCKET_CONNECTED = 1, RECEIVED_MSG = 2;

    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case SOCKET_CONNECTED:
                    btSend.setEnabled(true);
                    break;
                case RECEIVED_MSG:
                    tvMsg.setText(tvMsg.getText().toString() + "来自服务端的消息:" + msg.obj +"\n");
                    break;
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.socket_layout);
        etContent = (EditText) findViewById(R.id.etContent);
        tvMsg = (TextView) findViewById(R.id.tvMsg);
        btSend = (Button) findViewById(R.id.btSend);
        Intent intent = new Intent(ServiceClient.this, ServiceSocket.class);
        startService(intent);
        clientThread.start();
    }

    private Thread clientThread = new Thread(new Runnable() {
        @Override
        public void run() {
            while (socket == null) {
                try {
                    Message message=handler.obtainMessage();

                    socket = new Socket(path, 8887);
                    reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                    writer = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()),true);
                    handler.sendEmptyMessage(SOCKET_CONNECTED);
                    while (!ServiceClient.this.isFinishing()) {
                        readReply();
                    }
                } catch (Exception e) {
                    e.printStackTrace();

                } finally {
                    CloseUtils.close(reader);
                    CloseUtils.close(writer);

                }
            }
        }
    });

    private void readReply() {
        if (reader != null) {
            try {
                String msg = reader.readLine();
                if(msg!=null)
                     Message.obtain(handler, RECEIVED_MSG, msg).sendToTarget();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public void send(View view) {
        if (writer != null) {
            String msg = etContent.getText().toString();
            writer.println(msg);
            tvMsg.setText(tvMsg.getText().toString() + "我发送了:" + msg + "\n");
        } else {
            Toast.makeText(ServiceClient.this, "尚未链接", Toast.LENGTH_LONG).show();
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        try {
            socket.shutdownOutput();
            socket.shutdownInput();
            socket.close();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}


serviceSocket:


package com.example.hy.second.Socket;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.support.annotation.Nullable;
import android.util.Log;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Random;

/**
 * Created by X1Carbon on 2016/6/16.
 */
public class ServiceSocket extends Service {

    private boolean isServiceClose = false;
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    private Thread serviceThread = new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                ServerSocket server = new ServerSocket(8887);
                while (!isServiceClose) {
                    final Socket socket = server.accept();
                    new Thread(new Runnable() {
                        @Override
                        public void run() {
                            processSocket(socket);
                        }
                    }).start();

                }
            } catch (Exception err) {

            }

        }
    });

    private synchronized void processSocket(Socket socket) {
        BufferedReader reader = null;
        PrintWriter writer = null;
        Random random = new Random();
        try {
            reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            writer = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()),true);
            while (!isServiceClose) {
                String clientMsg = reader.readLine();//当收到的信息为null说明客户端关闭连接了
                Log.i("service", "收到来自客户端的消息:" + clientMsg);
                if (clientMsg == null)
                    break;
                else {
                    writer.println(clientMsg.length());
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            CloseUtils.close(reader);
            CloseUtils.close(writer);

            try {
                socket.shutdownInput();
                socket.shutdownOutput();
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }

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

    @Override
    public void onCreate() {
        super.onCreate();
        serviceThread.start();
    }
}


closeUtils:


package com.example.hy.second.Socket;

import java.io.Closeable;
import java.io.IOException;

/**
 * Created by X1Carbon on 2016/6/16.
 */
public class CloseUtils {

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


布局:


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <EditText
        android:id="@+id/etContent"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="请输入内容" />

    <Button
        android:id="@+id/btSend"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="send"
        android:enabled="false"
        android:text="发送" />

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <TextView
            android:id="@+id/tvMsg"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    </ScrollView>
</LinearLayout>


运行效果:





接下来读者好好体会一下(写的有点烦,所以没怎么解释,不过应该看得懂)。




---------文章写自:HyHarden---------

--------博客地址:http://blog.youkuaiyun.com/qq_25722767-----------













---------文章写自:HyHarden---------

--------博客地址:http://blog.youkuaiyun.com/qq_25722767-----------

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值