JavaSE第一百零九讲:TCP连接建立的一般过程与代码详解

来源:互联网 发布:mysql负载均衡方案 编辑:程序博客网 时间:2024/04/30 05:42

  上一讲内容中我们讲了使用socket建立连接的一些分析以及流程。这一讲我们继续剖析上一讲以下的内容,同时用代码实现这种连接。

1. 使用ServerSocket和Socket实现服务器端和客户端的 Socket通信,流程

1) 建立Socket连接

2) 获得输入/输出流

3)读/写数据

4) 关闭输入/输出流

5) 关闭Socket

程序Demo如下,在网络编程的过程中,有一点比较注意的是在编写服务端的同时还要想怎么与客户端进行交互。

package com.ahuier.network;import java.net.ServerSocket;import java.net.Socket;/* * 服务端 */public class TcpServer {    public static void main(String[] args) throws Exception {                //获得ServerSocket对象,并且利用其构造方法指定端口号        ServerSocket ss = new ServerSocket(5000);                /*         * 调用accet()方法,与客户端建立连接,它返回一个Socket对象         * 代码执行到以下这行的时候,它就不往下执行了,除非它与某一个客户端建立好连接         */        Socket socket = ss.accept();         System.out.println("Hello World!");                 }}
package com.ahuier.network;import java.io.IOException;import java.net.Socket;import java.net.UnknownHostException;/* * 客户端 */public class TcpClient {    public static void main(String[] args) throws Exception, IOException {                //通过Socket对象,指定好机器和端口号之后与服务端进行连接        //这边构造方法两个参数,第一参数host可以 是"localhost"、本机IP地址、127.0.0.1 这三种形式都可以        //port参数指定与服务端的端口号        Socket socket = new Socket("127.0.0.1", 5000);                    }}
先编译执行服务端,再编译执行客户端,则服务端输出 Hello World。说明服务端已经与客户端建立连接了,倘若没有建立好连接,它就会停留在accet()语句上,而不会执行下面的输出代码了。

2. 基于上一个程序,我们来实现一下在这个连接上进行双向的数据传送,客户端向服务端发送一个单词"Hello World",服务端接受到之后回送一个"welcome"。实现这种需求

package com.ahuier.network;import java.io.InputStream;import java.io.OutputStream;import java.net.ServerSocket;import java.net.Socket;/* * 服务端 */public class TcpServer {    public static void main(String[] args) throws Exception {                //获得ServerSocket对象,并且利用其构造方法指定端口号        ServerSocket ss = new ServerSocket(5000);                /*         * 调用accet()方法,与客户端建立连接,它返回一个Socket对象         * 代码执行到以下这行的时候,它就不往下执行了,除非它与某一个客户端建立好连接         */        Socket socket = ss.accept();                 /*         * 服务端获取到输入流,然后从这个流里面读到客户端发送过来的数据         */                InputStream is = socket.getInputStream();                //获取到流之后,就可以读数据了        byte[] buffer = new byte[200];                int length = is.read(buffer);        System.out.println(new String(buffer, 0, length));      /*      用这种方式来读取流,出现错误了?这个问题暂时搞不明白,回去好好研究        int length = 0;                while(-1 != (length = is.read(buffer, 0, buffer.length))){            String str = new String(buffer, 0, length);            System.out.println(str);        }*/                //获得到客户端发过来的数据后,再向客户端发送数据:"welcome";        OutputStream os = socket.getOutputStream();        os.write("welcome".getBytes()); //写到输出流中                is.close();        os.close();        socket.close();    }}
package com.ahuier.network;import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;import java.net.Socket;import java.net.UnknownHostException;/* * 客户端 */public class TcpClient {    public static void main(String[] args) throws Exception, IOException {                //通过Socket对象,指定好机器和端口号之后与服务端进行连接        //这边构造方法两个参数,第一参数host可以 是"localhost"、本机IP地址、127.0.0.1 这三种形式都可以        //port参数指定与服务端的端口号        Socket socket = new Socket("127.0.0.1", 5000);                /*         * 客户端要向服务端发送"Hello World",则客户端将它发到输出流中         * 所以它可以获取到这个输出流,然后写到流里面         * 服务端获取输入流,再从这个流里面读到数据         */                //在客户端中获取输出流,它对应于服务端的输入流        OutputStream os = socket.getOutputStream();        os.write("hello world".getBytes());                //获取输入流,接受服务端发过来的"welcome"数据        InputStream is = socket.getInputStream();        byte[] buffer = new byte[200];                        int length = is.read(buffer);        System.out.println(new String(buffer, 0, length));                /*      用这种方式来读取流,出现错误了?这个问题暂时搞不明白,回去好好研究        int length = 0;                while(-1 != (length = is.read(buffer, 0, buffer.length))){            String str = new String(buffer, 0, length);            System.out.println(str);        }*/                        is.close();        os.close();        socket.close();    }}

编译执行服务端,再编译执行客户端,查看他们的输出,服务端输出hello world,客户端输出 welcome。

【说明】:刚才我们用另外一种方式去读流里面的数据,显然这种方式是不好的,因为这样我们最多只能读取20个,而while循环的不起作用,while循环的那种我们注释掉了,我不知道这种情况是为什么?期待张龙老师能够很好解答。我们暂时用第二种方式去读取流里面的内容。


3. 显然上述这种双向通信不是非常好,因为服务端与客户端建立连接之后,服务端一定要等待客户端的输入然后自己再返回一个输出,这在实际的开发过程显然是没有任何意义的,因为客户端要进行与服务端的双向通信,那么服务器端与客户端谁先发送消息,谁后发送消息,这写都是无法预料的,而且服务也不一定是先发送消息之后还要等待客户端回消息,有可能服务端发送消息之后还要继续发送消息,这些都是现实应用场景中很常见的一种情况。这种情况下必须而且只能通过线程来进行解决。

   

详细讲解过程,请关注下回分解。


[2013.11.13更新]---------------------------------------------------------------

【注意】:在上面程序中读取流的过程出现错误的原因无意中明白了所以特此更新次博文

问题在于重新构建字符串的过程中有正在读取流的内容所以会导致程序失败,类似的读取流或者写入流的写法在平时的程序应用中很常见,这里我们参考一下Android源代码的这一个模块的写法,读者可以仿照这种写法

ByteArrayOutputStream 字节数组流,仅仅表示的是在内存中是可以显示出来的,所以这个流可以不需要关闭

    源码路径:workspace\packages\apps\Camera\tests\src\com\android\camera\activity\CameraTestCase    private void readBlankJpeg() {        InputStream ins = getActivity().getResources().openRawResource(R.raw.blank);        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();        int size = 0;        // Read the entire resource into a local byte buffer.        byte[] buffer = new byte[1024];        try {            while((size = ins.read(buffer, 0, 1024)) >= 0){                outputStream.write(buffer, 0, size);            }        } catch (IOException e) {            // ignore        } finally {            Util.closeSilently(ins);        }        mBlankJpeg = outputStream.toByteArray();
从缓存区中读取(read)数据,读去多少返回的时候size就是多少,一旦读取完成之后,size为-1,跳出while,表示读取完毕。

 


原创粉丝点击