建立Socket连接

基本知识

TCP编程:
 1. 客户端/服务端(Client/Server)
 2. C/S架构

我们开发一种产品时,需要编写客户端程序和服务端程序,服务端程序放在服务器上,客户端程序给用户使用。

对于网络编程:一台电脑,代码也认为是多台电脑

Socket(网络套接字):可以认为是一条网络连接

ServerSocket:在指定端口建立监听,可以接收socket

Socket对象中可以得到输入流和输出流,直接使用即可,
 1. 输入流可以从网络对端接收数据,
 2. 输出流可以把数据发送到网络对端,

在使用过滤流的时候需要注意,C/S双方创建过滤流的顺序应该不同,否则会使程序发生阻塞。

DEMO
 1. DEMO1:服务器和客户端互相发送字符串
 2. DEMO2:服务器给客户端发送一个文件
 3. DEMO3:发送文件时支持断点续传

flush()方法
 1. 刷新缓冲区,强制缓冲区的数据流出

Flushable接口中定义了flush,OutputStream继承了这个接口
FileOutputStream究其根源,flush什么也没写

  BufferedOutputStream是一个过滤流,之所以可以缓冲,是因为内部定义了一个byte[],当byte[]达到一定大小的时候,统一写出去。
这个流中的flush有作用,强行把byte[]中的数据写出去。

ObjectOutputStream中也有缓冲区

close()会执行flush()

代码示例

客户端

  1. package demo;
  2. import java.io.IOException;
  3. import java.net.Socket;
  4. import java.net.UnknownHostException;
  5. public class MyClient {
  6. public static void main(String[] args) throws UnknownHostException, IOException {
  7. /**
  8. * 现在编写客户端,我们不需要关系自己客户端的地址
  9. * 但是一定要知道服务器的地址,这样才能连接服务器
  10. * 服务器开放的端口也是必须要知道的
  11. */
  12. Socket ss = new Socket("192.168.1.100", 6767);
  13. System.out.println("服务器连接成功");
  14. }
  15. }

服务端

  1. import java.io.IOException;
  2. import java.net.ServerSocket;
  3. import java.net.Socket;
  4. public class MyServer {
  5. public static void main(String[] arg) throws IOException {
  6. //建立网络服务端对象,监听6767端口
  7. ServerSocket serv = new ServerSocket(6767);
  8. System.out.println("服务器启动成功,等待用户的接入");
  9. //等待用户接入,直到用户接入为止
  10. Socket sc = serv.accept();
  11. System.out.println("有客户端接入,客户IP:"+sc.getInetAddress());
  12. }
  13. }

运行

然后先运行服务端(服务器端)的程序,再运行客户端程序,效果如下:
 1. 服务端运行效果

1610803477099.png

 2. 客户端运行效果

1610803477133.png

这也验证了一句话: 对于网络编程:一台电脑,代码也认为是多台电脑。

Socket基本流数据传输

示意图

1610803477172.png

基本流程序示例

服务端工程程序

  1. public class MyServer {
  2. public static void main(String[] arg) throws IOException {
  3. //建立网络服务端对象,监听6767端口
  4. ServerSocket serv = new ServerSocket(6767);
  5. System.out.println("服务器启动成功,等待用户的接入");
  6. //等待用户接入,直到用户接入为止
  7. Socket sc = serv.accept();
  8. System.out.println("有客户端接入,客户IP:"+sc.getInetAddress());
  9. //从socket中得到网络输入流,接收来自网络的数据
  10. InputStream in = sc.getInputStream();
  11. //从socket中得到网络输出流,把数据发送到网络上
  12. OutputStream out = sc.getOutputStream();
  13. //接收缓冲区
  14. byte[] b = new byte[1024];
  15. //接收数据
  16. int len = in.read(b);
  17. String str = new String(b, 0, len);
  18. System.out.println("来自客户端的消息是: "+ str);
  19. //往客户端发送消息
  20. out.write("你好,我是服务端,欢迎光临".getBytes());
  21. sc.close();
  22. }
  23. }

客户端工程程序

public class MyClient {
    public static void main(String[] args) throws UnknownHostException, IOException {
        /**
         * 现在编写客户端,我们不需要关系自己客户端的地址
         * 但是一定要知道服务器的地址,这样才能连接服务器
         * 服务器开放的端口也是必须要知道的
         */
        Socket ss = new Socket("192.168.1.100", 6767);
        System.out.println("服务器连接成功");

        //从socket中得到网络输入流,接收来自网络的数据
        InputStream in = ss.getInputStream();
        //从socket中得到网络输出流,把数据发送到网络上
        OutputStream out = ss.getOutputStream();

        out.write("你好,我是客户端".getBytes());

        //接收来自服务端的信息
        //接收缓冲区
        byte[] b = new byte[1024];
        //接收数据
        int len = in.read(b);
        String str = new String(b, 0, len);
        System.out.println("来自服务端的消息是: "+ str);

        ss.close();
    }
}

程序运行结果
 1. 服务端程序的运行结果:

1610803477217.png

客户端程序的运行结果:

1610803477256.png

Socket过滤流数据传输

过滤流属于高级流,过滤流可以写对象,万物皆对象

高级流程序示例

服务端程序:

public class MyServer {
    public  static void main(String[] arg) throws IOException, ClassNotFoundException {
        //建立网络服务端对象,监听6767端口
        ServerSocket serv = new ServerSocket(6767);
        System.out.println("服务器启动成功,等待用户的接入");
        //等待用户接入,直到用户接入为止
        Socket sc = serv.accept();
        System.out.println("有客户端接入,客户IP:"+sc.getInetAddress());

        //从socket中得到网络输入流,接收来自网络的数据
        InputStream in = sc.getInputStream();
        //从socket中得到网络输出流,把数据发送到网络上
        OutputStream out = sc.getOutputStream();

        //创建过滤流,注意服务端和客户端的顺序不同
        ObjectInputStream ois = new ObjectInputStream(in);
        ObjectOutputStream oos = new ObjectOutputStream(out);

        //接收来自客户端的消息
        String s1 = (String)ois.readObject();
        System.out.println(s1);

        //往客户端发送消息
        oos.writeObject("你好,我是服务端,很霸气的!");

        sc.close();
    }
}

客户端程序

public class MyClient {
    public static void main(String[] args) throws UnknownHostException, IOException, ClassNotFoundException {
    /**
     * 现在编写客户端,我们不需要关系自己客户端的地址
     * 但是一定要知道服务器的地址,这样才能连接服务器
     * 服务器开放的端口也是必须要知道的
     */
    Socket ss = new Socket("192.168.1.100", 6767);
    System.out.println("服务器连接成功");

    //从socket中得到网络输入流,接收来自网络的数据
    InputStream in = ss.getInputStream();
    //从socket中得到网络输出流,把数据发送到网络上
    OutputStream out = ss.getOutputStream();

    //创建过滤流,注意服务端和客户端的顺序不同
    ObjectOutputStream oos = new ObjectOutputStream(out);
    ObjectInputStream ois = new ObjectInputStream(in);

    //往服务端发送消息
    oos.writeObject("你好,我是客户端,很帅很帅");

    //接收来自服务端的消息
    String s1 = (String)ois.readObject();
    System.out.println(s1);
    ss.close();
    }
}

程序运行结果:
 1. 服务端程序运行结果

1610803477304.png

 2. 客户端程序运行结果

1610803477346.png

高级流注意事项

创建过滤流(高级流)时,服务端和发送端的顺序应该不同,如果相同,数据将不能互相发送。

1610803477399.png

Socket传文件

文件传输流程

示意图

1610803477435.png

  本示意图只是演示了文件从服务端发送到客户端过程,从客户端到服务端的原理一样,电脑A本地文件<—>JVM<—>网络通道<—>电脑B本地文件<—>JVM<—>网络通道。

发送端

服务器给客户端发送一个文件。

网络服务器工程程序示例:

public class FileServer {
    public static void main(String[] args) throws IOException {
        //监听12345端口
        ServerSocket ser = new ServerSocket(12345);
        System.out.println("等待用户接入服务器!!!");

        //等待用户接入,知道用户接入为止
        Socket sc = ser.accept();
        //获取sc的IO流
        InputStream in = sc.getInputStream();
        OutputStream out = sc.getOutputStream();
        //为了简化操作,创建过滤流
        ObjectInputStream ois = new ObjectInputStream(in);
        ObjectOutputStream oos = new ObjectOutputStream(out);

        //获取服务器端的本地文件信息
        File f1 = new File("F:\\abc\\Xshell_Plus_v6.0.0095.7z");
        //获取文件的IO流
        //创建输入流,把文件读取到java的jvm中
        InputStream FileIn = new FileInputStream(f1);
        //发送文件名
        oos.writeObject(f1.getName());

        /*发送文件*/
        //创建128k缓冲区,用于缓冲本地文件
        byte[] b = new byte[1024*128];

        for(int len = 0;(len = FileIn.read(b)) != -1;) {
            //将数据写入到网络中
            out.write(b, 0, len); 
        }

        System.out.println("文件发送完成!!!");
        //关闭网络流
        sc.close();
        //关闭文件流
        FileIn.close();
    }
}

接收端

客户端接收服务端发送的一个文件。

网络客户端工程程序示例:

public class FileClient {
    public static void main(String[] args) throws UnknownHostException, IOException, ClassNotFoundException {

        //通过socket(网络通道),连接服务器
        Socket sc = new Socket("192.168.1.100", 12345);
        System.out.println("成功连接服务器.......");

        //从网络套字节socket获取,网络IO流
        OutputStream out = sc.getOutputStream();
        InputStream in = sc.getInputStream();

        //创建过滤流
        //注意,服务端与客户端的顺序相反
        ObjectOutputStream oos = new ObjectOutputStream(out);
        ObjectInputStream ois = new ObjectInputStream(in);

        //接收文件名
        String FileName = (String)ois.readObject();
        //获取本地存放的目录信息
        File f1 = new File("F:\\download\\"+FileName);
        //创建文件输出流
        OutputStream FileOut = new FileOutputStream(f1);

        System.out.println("开始接收文件"+ FileName);
        //从网络接收文件的缓冲区
        byte[] b= new byte[1024*128];
        //将文件写入到本地中
        for(int len = 0; (len = in.read(b)) != -1 ;) {
            //从JVM中读取数据
            FileOut.write(b, 0, len);
        }
        //关闭网络流
        sc.close();
        //关闭文件流
        FileOut.close();
        System.out.println("接收文件完成 ");
    }
}

断点续传

  需要服务端和客户端进行协商,首先客户端要判断文件的大小,将文件的大小传给服务器,然后服务器进行响应,xc代表续传,xin重头开始传,bu不传。

其实重头开始传和续传对于客户端是一样的,只不过重传的跳过文件比特大小为0。

  本程序如果想要演示断点续传,可以利用360安全软件(功能大全->网络优化->360流量防火墙)限制java的虚拟机速度(前提是要运行java程序,才会启用虚拟机javaw),当传一部分数据时,关闭java程序的运行,再次运行程序,查看是否可以断点续传。

1610803477502.png

客户端程序

public class FileClient {
    public static void main(String[] args) throws UnknownHostException, IOException, ClassNotFoundException {
        //通过socket(网络通道),连接服务器
        Socket sc = new Socket("192.168.1.100", 12345);
        System.out.println("成功连接服务器.......");

        //从网络套字节socket获取,网络IO流
        OutputStream out = sc.getOutputStream();
        InputStream in = sc.getInputStream();

        //创建过滤流
        //注意,服务端与客户端的顺序相反
        ObjectOutputStream oos = new ObjectOutputStream(out);
        ObjectInputStream ois = new ObjectInputStream(in);

        //接收文件名
        String FileName = (String)ois.readObject();
        //获取本地存放的目录信息
        File f1 = new File("F:\\download\\"+FileName);

        //将客户端本地文件的大小,传送给服务端
        //服务端进行响应,"xc"续传和重头开始传,"bc"不传
        oos.writeLong(f1.length());
        oos.flush();//刷新缓冲区,强制缓冲区数据流出

        System.out.println("本地文件大小:"+ f1.length());
        //获取服务器的响应
        String str = (String)ois.readObject();
        //根据服务器的响应,开始进行接收数据
        if("xc".equals(str)) {//断点续传和重传
            //创建文件输出流,从jvm输出到本地
            //true表示输出的数据追加到所接收的文件
            OutputStream FileOut = new FileOutputStream(f1, true);

            System.out.println("开始接收文件"+ FileName);
            //从网络接收文件的缓冲区
            byte[] b= new byte[1024*128];
            //将文件写入到本地中
            for(int len = 0; (len = in.read(b)) != -1 ;) {
                //从JVM中读取数据
                FileOut.write(b, 0, len);
            }
            //关闭网络流
            sc.close();
            //关闭文件流
            FileOut.close();
            System.out.println("接收文件完成 ");
        }else if("bu".equals(str)) {
            System.out.println("本地存在将下载文件");
        }

    }
}

服务端程序

public class FileServer {
    public static void main(String[] args) throws IOException {
        //监听12345端口
        ServerSocket ser = new ServerSocket(12345);
        System.out.println("等待用户接入服务器!!!");

        //等待用户接入,知道用户接入为止
        Socket sc = ser.accept();
        //获取sc的IO流
        InputStream in = sc.getInputStream();
        OutputStream out = sc.getOutputStream();
        //为了简化操作,创建过滤流
        ObjectInputStream ois = new ObjectInputStream(in);
        ObjectOutputStream oos = new ObjectOutputStream(out);

        //获取服务器端的本地文件信息
        File f1 = new File("F:\\abc\\Xshell_Plus_v6.0.0095.7z");
        //获取文件的IO流
        //创建输入流,把文件读取到java的jvm中
        InputStream FileIn = new FileInputStream(f1);
        //发送文件名
        oos.writeObject(f1.getName());

        //接收客户端的文件大小
        long overSize = ois.readLong();
        System.out.println("客户端的文件大小: " + overSize);

        if( f1.length() == overSize) {
            oos.writeObject("bu");
            System.out.println("客户端文件存在无需再传");

        }else if( overSize >= 0) { //断点续传和重传
            oos.writeObject("xc");
            System.out.println("服务端开始发送文件给客户端......");
            //跳过文件的大小为overSize 比特开始读取文件
            FileIn.skip(overSize);
            System.out.println("文件的起始位置: " + overSize);

            /*发送文件*/
            //创建128k缓冲区,用于缓冲本地文件
            byte[] b = new byte[1024*128];

            for(int len = 0;(len = FileIn.read(b)) != -1;) {
                //将数据写入到网络中
                out.write(b, 0, len); 
            }
            System.out.println("文件发送完成!!!");

            //关闭网络流
            sc.close();
            //关闭文件流
            FileIn.close();
        }

    }
}

输出流中的flush方法

//通过socket(网络通道),连接服务器
Socket sc = new Socket("192.168.1.100", 12345);
//从网络套字节socket获取,网络IO流
OutputStream out = sc.getOutputStream();
//创建过滤流
//注意,服务端与客户端的顺序相反
ObjectOutputStream oos = new ObjectOutputStream(out);
oos.writeLong(f1.length());
oos.flush();

flush():刷新缓冲区,强制缓冲区数据流出

  当我们把数据写入到jvm的缓冲区中后,如果没有达到缓冲区大小数据不会从缓冲区中流出,只有达到缓冲区大小数据才会从缓冲区中流出,而flush()方法就是不管你的缓冲区是否满了,我都将强制缓冲区的数据流出。

Flushable接口中定义了flush(),OutputStream继承了这个接口。
 1. FileOutputStream中的flush什么也没写。

  BufferedOutputStream是一个过滤流,之所以缓冲,是因为内部定义了一个byte数组,当byte[]达到一定大小的时候,统一写出去。
 1. ObjectOutputStream中也有一个缓冲区。

执行close()方法也会执行flush()方法。

基本流没有缓冲区。
 1. 这个流中的flush有作用,强行把byte[]中的数据写出去。