Android Socket通信详解及聊天程序示例

网络编程的一些介绍

目前较为流行的网络编程模型是客户机/服务器(C/S)结构。即通信双方一方作为服务器等待客户提出请求并予以响应。客户则在需要服务时向服务器提 出申请。服务器一般作为守护进程始终运行,监听网络端口,一旦有客户请求,就会启动一个服务进程来响应该客户,同时自己继续监听服务端口,使后来的客户也 能及时得到服务。

 

TCPTranfer Control Protocol的 简称,是一种面向连接的保证可靠传输的协议。通过TCP协议传输,得到的是一个顺序的无差错的数据流。发送方和接收方的成对的两个socket之间必须建立连接,以便在TCP协议的基础上进行通信,当一个socket(通常都是server socket)等待建立连接时,另一个socket可以要求进行连接,一旦这两个socket连接起来,它们就可以进行双向数据传输,双方都可以进行发送 或接收操作。

 

UDPUser Datagram Protocol的简称,是一种无连接的协议,每个数据报都是一个独立的信息,包括完整的源地址或目的地址,它在网络上以任何可能的路径传往目的地,因此能否到达目的地,到达目的地的时间以及内容的正确性都是不能被保证的。

 

UDP1,每个数据报中都给出了完整的地址信息,因此无需要建立发送方和接收方的连接。

      2UDP传输数据时是有大小限制的,每个被传输的数据报必须限定在64KB之内。

      3UDP是一个不可靠的协议,发送方所发送的数据报并不一定以相同的次序到达接收方

 

TCP1,面向连接的协议,在socket之间进行数据传输之前必然要建立连接,所以在TCP中需要连接时间。

      2TCP传输数据大小限制,一旦连接建立起来,双方的socket就可以按统一的格式传输大的数据。

      3TCP是一个可靠的协议,它确保接收方完全正确地获取发送方所发送的全部数据。

 

 

 

Socket

通常我们翻译为套接字,是应用层和传输层之间的一个抽象层,网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket,一个Socket由一个IP地址和一个端口号唯一确定。


ServerSocket

ServerSocket用于监听来自客户端的Socket连接,如果没有连接将会一直等待下去,主要用于构建服务器端。下面是一些ServerSocket的一些常用方法

ServerSocket(int port):用指定的一个端口来创建一个ServerSocket,port的值在0-65535

ServerSocket(int port, int backlog):增加一个改变连接队列长度的参数backlog

ServerSocket(int port, int backlog, InetAddress localAddress):使用localAddress参数来将ServerSocket绑定到指定的IP

 accept():如果接收到连接请求,这个方法返回一个与连接的客户端Socket对应的Socket;否则该方法处于一直等待,阻塞线程。

close():在ServerSocket使用完毕后调用该方法来关闭


Socket

客户端通常可以使用Socket的构造器来连接到指定的服务器

Socket(InetAddress/String remoteAddress, int port):创建连接到指定远程主机、远程端口的Socket,并没有指定本地地址、本地端口,默认使用本地主机的默认IP地址,默认使用系统动态分配的端口。

Socket(InetAddress/String remoteAddress,int port,InetAddress localAddr,int localPort):创建指定远程主机、远程端口的Socket,并指定本地IP地址和端口,适用于本地主机有多个IP的情况


getInputStream():返回该Socket对象对应的输入流,让程序通过该输入流从Socket里面获取数据

getOutputStream():返回该Socket对象对应的输出流,让程序通过该输出流向Socket写入数据


下面看一个简单的实例

服务端

public static void main(String[] args) throws IOException {
        //创建一个ServerSocket用于监听客户端的请求
        ServerSocket serverSocket = new ServerSocket(3000);
        while (true) {
            //当接收到客户端的请求时,产生一个对应的Socket
            Socket socket = serverSocket.accept();
            OutputStream os = socket.getOutputStream();
            os.write("jkghjgjhg \n".getBytes("utf-8"));
            os.close();
            socket.close();
        }
    }
服务端的程序运行在PC上面,定义一个Java类直接运行就可以了。上面代码是创建了一个ServerSocket在3000的端口监听,接着是一个while的无限循环,因为一个服务端不只对应一个客户端,需要不断的接收来自所以客户端的请求,收到一个请求就产生一个Socket。ServerSocket调用accept方法来接收客户端的请求,得到一个Socket对象,向这个Socket里面写入数据,然后分别关闭流,关闭Socket。


客户端

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "lzy";
    private Context context;
    private TextView mTextView;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        context = this;
        mTextView = (TextView) findViewById(R.id.tv);

    }

    public void bt(View view) {
        new Thread() {
            @Override
            public void run() {
                try {
                    Socket socket = new Socket("172.16.7.234", 3000);
                    BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream(),"utf-8"));
                    String line = br.readLine();
                    Log.i(TAG, "读取数据:" + line);
                    br.close();
                    socket.close();

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

    }
}
首先是建立远程连接的Socket,然后通过这个Socket获取到数据。这里的端口号和上面的要对应,最好是使用1000以上的,因为1000以下大多是系统使用的,为了避免冲突。


设置Socket的超时时间

setSoTimeout(int timeout) :设置Socket的超时时间,不能小于0,如果使用Socket进行读写操作完成之前已经超出了这个时间,那么就会抛出SocketException。


设置Socket连接服务器超时时间

        //创建一个无连接的Socket
        Socket socket = new Socket();
        //让Socket连接到远程服务器,如果经过1秒没有连接到则认为超时
        socket.connect(new InetSocketAddress(host,port),1000);
在Socket的构造参数里面并没有设置超时时长这个参数,所以需要先创建一个无连接的Socket,调用它的connect方法连接远程服务器,而这里就可以设置一个超时时长的参数。


下面再写一个聊天室的小应用来看看Socket的使用

Android Socket通信详解及聊天程序示例_第1张图片

界面很简单,在下面发送消息,发送到服务端,然后服务端再把这条消息遍历发送给所有连接了的客户端,显示在上面。这里只是简单的实现,很多不好打地方就不要太纠结了。


服务端

在Android Studio创建一个服务端,点击File->New->New Module,创建一个Java Library,看代码

public class MyServer {
    public static List listSocket = new ArrayList<>();

    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(3000);
        while (true) {
            //等待连接
            Socket socket = serverSocket.accept();
            System.out.println("a target online");
            listSocket.add(socket);
            //每当客户端连接成功 后启动一条线程为该客户端服务
            new Thread(new ServerThread(socket)).start();
        }
    }
}


listSocket是用于保存客户端连接后产生的Socket对象。

这段代码首先是在3000的端口号创建一个ServerSocket,然后是一个while的死循环调用ServerSocket的accept方法不停的接收来自客户端的连接请求,然后为这个客户端开启一个线程为其服务


/**
 * Created by lzy on 2017/1/23.
 */
public class ServerThread implements Runnable {

    private Socket socket;
    private BufferedReader br;

    public ServerThread(Socket socket) throws IOException {
        this.socket = socket;
        br = new BufferedReader(new InputStreamReader(socket.getInputStream(), "utf-8"));
    }

    @Override
    public void run() {
        String content;
        //不断把客服端的数据读取出来
        while ((content = readFromClient()) != null) {

            //把收到的消息遍历发给每一个 连接了的客户端
            for (Iterator iterator = MyServer.listSocket.iterator(); iterator.hasNext(); ) {
                Socket socket = iterator.next();
                //打印出客服端的Ip和端口号
                System.out.println("IP:" + socket.getInetAddress() + "    port:" + socket.getPort());
                try {
                    OutputStream os = socket.getOutputStream();
                    os.write((content + "\n").getBytes("utf-8"));
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private String readFromClient() {
        try {
            return br.readLine();
        } catch (IOException e) {
            e.printStackTrace();
            MyServer.listSocket.remove(socket);
        }
        return null;
    }
}

在这个线程中把Socket的数据读到BufferedReader里面,然后通过一个while循环把消息读取来遍历发送给每一个连接了的客户端。

readFromClient用来读取接收到的客户端数据,如果发生了异常就把这个Socket移除。


客户端

MainActivity

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "lzy";
    private List list = new ArrayList<>();
    private RecyclerView mRecyclerView;
    private Context context;
    private ChatAdapter adapter;
    private EditText mEditText;
    private ClientThread clientThread;

    //用于发送接收到的服务端的消息,显示在界面上
    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            list.add(msg.obj.toString());
            adapter.notifyDataSetChanged();
        }
    };


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        context = this;
        mRecyclerView = (RecyclerView) findViewById(R.id.rv);
        mEditText = (EditText) findViewById(R.id.et);

        mRecyclerView.setLayoutManager(new LinearLayoutManager(context));
        adapter = new ChatAdapter(context, list);
        mRecyclerView.setAdapter(adapter);

        clientThread = new ClientThread(handler);
        new Thread(clientThread).start();

        //点击发送提交数据到服务器
        mEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
            @Override
            public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
                if (actionId == EditorInfo.IME_ACTION_SEND) {
                    Toast.makeText(context, "提交", Toast.LENGTH_SHORT).show();
                    Message msg = new Message();
                    msg.what = 0;
                    msg.obj = mEditText.getText().toString().trim();
                    clientThread.revHandler.sendMessage(msg);
                    mEditText.setText("");
                }
                return false;
            }
        });
    }
}


这里ClientThread是用来处理一些与网络相关的一些操作,主线程当然不能执行耗时的任务。

这里定义的Handler是用来接收显示更新界面的,因为当收到服务器的数据是在子线程中,更新界面操作需要在主线程中,所以需要这个Handler来处理。

当点击发送时,就把消息发送给服务器


ClientThread

/**
 * Created by lzy on 2017/1/23.
 */
public class ClientThread implements Runnable {
    private static final String TAG = "lzy";
    private OutputStream os;
    private BufferedReader br;
    private Socket socket;


    //用于向UI发送消息
    private Handler handler;
    //接收UI线程的消息(当用户点击发送)
    public Handler revHandler;

    public ClientThread(Handler handler) {
        this.handler = handler;
    }

    @Override
    public void run() {
        //创建一个无连接的Socket
        socket = new Socket();
        try {
            //连接到指定的IP和端口号,并指定10s的超时时间
            socket.connect(new InetSocketAddress("172.16.7.234", 3000), 10000);
            //接收服务端的数据
            br = new BufferedReader(new InputStreamReader(socket.getInputStream(), "utf-8"));
            //向服务端发送数据
            os = socket.getOutputStream();

            //读取数据会阻塞,所以创建一个线程来读取
            new Thread(new Runnable() {
                @Override
                public void run() {
                    //接收服务器的消息,发送显示在主界面
                    String content;
                    try {
                        while ((content = br.readLine()) != null) {
                            Message msg = new Message();
                            msg.what = 1;
                            msg.obj = content;
                            handler.sendMessage(msg);
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }).start();


            //非UI线程,自己创建
            Looper.prepare();
            revHandler = new Handler() {
                @Override
                public void handleMessage(Message msg) {
                    //将用户输入的内容写入到服务器,并在前面加上手机型号
                    try {
                        os.write((Build.MODEL + ":" + (msg.obj) + "\n").getBytes("utf-8"));
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            };
            Looper.loop();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

ClientThread子线程用于和服务器建立连接交互数据,当读取到服务器的数据发送给主界面显示,并且把用户发送的数据提交到服务器。都有注释就不多说了

你可能感兴趣的:(Android,基础)