Java Socket通信问题小结

关于Java Socket通信中的文件传输的一个问题。
这就要从老师布置的java聊天器实验说起了,当时我听到这个作业的时候,自然是不屑一顾满脑子都想着要大干一场整出一个大学生激情网聊的玩意儿啦~~~但是现实给了我重重的一击。我选择实现的版本是纯字符界面的版本,因为我觉得搞那么多界面都是花里胡哨的,毫无luan用,然而…在我看完隔壁同学连登陆界面都做出来了,已经开发到手动拖文件发送,一点一个聊天窗口,各种酷炫各种帅的时候。再回头看看自己做得这都是啥玩意啊!!!全是辣鸡啊。。。于是现在已经决定完全放弃这版,转而学习spring,企图将web作业一起给干了。

当然,上述的与这篇博客的主要内容完全无关,是的,为什么要说这么多废话呢,一个是因为好久没写博客了,想记录一下自己这些天主要干的一些事情,还有就是因为看了一个沙雕日剧<我是大哥大>,整个人仿佛都沙雕化了…

对了,最近在图书馆看到了一本书,叫Java Puzzle挺有趣的,写的是一些java编程出现的刁钻无比的怪问题,作者写的另一本书Efficient Java十分经典。因此此博客也会长期记录自己的一些沙雕问题,如果有解决的就写,没解决的就一直留着吧。随缘,就这么佛系。

-------------------------------------------不对称的分割线^^-----------------------------------------------------------------

问题一:

在字符界面Socket通信时,当你正在与别人通信时,你正在输入消息,但还没输入完,这时候别人发给你的消息到了,就会将你的消息给打断,你之前输入的消息将全部消失,以下是例子以及演示效果

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.concurrent.TimeUnit;

public class Test {
    public static void main(String[] args) throws Exception {
        BufferedReader in = new BufferedReader( new InputStreamReader(System.in));
        new Thread(() -> {
            //模拟在两秒后收到消息
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (Exception e) {

            }
            System.out.println("\n有消息过来了");
        }).start();
        String messageToSend = in.readLine();
        System.out.println(messageToSend);
    }
}

运行结果(绿色的为我输入的值,黑色的为System.out.print()输出的值)
在这里插入图片描述
在上面这个程序中我模拟了自己作为输入方正在打字,然后在2s(或者随机取一个值)后收到了对方发送来的消息,于是我之前正在输入的东西就给清除了,我实在是不知道是因为什么原因导致的。猜测是因为print()在打印到控制台的时候会覆写缓冲区,除了使用gui使输入信息和接受进行使用不同的区域外,暂时没有想到更好的方法。

问题二

Java Socket传送文件时出现read()阻塞,这个问题感觉还是十分经典的,我在神奇的互联网上找到了各种解决方案:

  1. 在传完文件后,使用shutdownoutput()单方面关闭socket的输出流,但能保留socket的连接,只不过这么做会使你无法继续发送文件第二次,算是一个弊端吧。
  2. 设置socket的最大阻塞时长,使用socket.setSoTimeout()方法,弊端与第一个一样,都是在关闭连接后使接收方收到结束传输的信号。
  3. 约定结束标志,比如读到某个特定的字符就结束传输。与之原理类似的是先发送文件长度过去,当读到文件长度这么长的字节就停止读取,我所采用的方法就是这一种。

可是,在我采用了上述的第三种方法后,我还是遇到了类似的问题,最终我定位到的是程序阻塞在接收方的read(buf, 0, 1)处,这我就懵逼了。通过瞎鸡儿试我发现了一个解决方案:在发送端输入文件名后让线程睡眠100ms(当然时间可以自己调试一下看多少)。这里的原理我依旧不明白,大家可以参考这位老哥的博客,大意是因为网络传输速度与读写速度不匹配?我也不懂,可能我看了那本书后才会懂吧。。
以下是一个例子,让大家感受一下,服务器端只负责接受一个客户端的连接,并不断接收客户端传送过来的文件,最大收20次就结束,客户端连上服务端后不断发送同一个文件直到服务端不再接受。我在客户端设置了上面说的时间间隔,当把时间间隔调的很小或者不设置时,服务端收了一次之后就会报错。运行需要的条件是在程序所在目录下有一个receive文件夹接受文件,要发送的文件可以自己在客户端那里改:

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
//服务器端,文件接收端
public class Server {
    ServerSocket ss;
    BufferedReader in;
    Server() throws Exception {
        ss = new ServerSocket(9099);
        System.out.println("服务器启动");
    }

    public  void start() throws Exception {
        Socket client = ss.accept();
        int len = 0;
        in = new BufferedReader(new InputStreamReader(client.getInputStream()));
        while (len < 20) {
            int count = 0;
            byte[] data = new byte[1024];
            long fileLength = Long.parseLong(in.readLine());
            String fileName = in.readLine();
            BufferedOutputStream writeFile = new BufferedOutputStream(
                    new FileOutputStream("receive/" + fileName));
            BufferedInputStream readFile = new BufferedInputStream(client.getInputStream());
            while (count++ < fileLength) {
                readFile.read(data, 0 ,1);
                writeFile.write(data,0,1);
                writeFile.flush();
            }
            writeFile.close();
            System.out.println(fileName + " 接受成功" + ++len + "次");
        }
    }

    public static void main(String[] args) throws Exception{
        new Server().start();
    }
}
import java.io.*;
import java.net.Socket;
import java.util.concurrent.TimeUnit;
//客户端,文件发送端
public class Client {
    Socket so;

    Client() throws Exception {
        so = new Socket("127.0.0.1", 9099);
    }

    void start() throws Exception {
        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
        PrintStream out = new PrintStream(so.getOutputStream(),true);
        while (true) {
            System.out.println("文件名:");
            String filename = "a.txt";
            //WARNING!!!这里请修改参数自己试试
            TimeUnit.MILLISECONDS.sleep(100);
            File file = new File(filename);
            int len = 0;
            BufferedInputStream readFile = new BufferedInputStream(new FileInputStream(file));
            out.println(file.length());
            out.println(filename);
            byte[] buf = new byte[1024];
            while ((len = readFile.read(buf)) != -1) {
                out.write(buf, 0, len);
            }
            readFile.close();
            System.out.println("文件传输完成");
        }
    }

    public static void main(String[] args) throws Exception {
        new Client().start();
    }
}

运行效果:
有时间间隔
Java Socket通信问题小结_第1张图片
没有时间间隔或很小:
Java Socket通信问题小结_第2张图片
在洗澡的过程中,沐浴着热水感到十分舒爽的我突然醍醐灌顶!!!洗干净了脑子的我突然想明白了一些事情,这一刻仿佛一道闪电击中了我。我之所所以出现问题是因为自己的代码逻辑有问题,我的代码结构是客户端发送文件到服务端,然后服务端转发文件至目的客户端,我的逻辑是发送文件端一个字节一个字节地读文件,然后一个字节一个字节地发送到服务端,服务端同时一个字节一个字节地发往目的客户端。接着我在发送文件的客户端发送完所有字节后就提示完成文件上传,接着这个客户端就可以进行下一次文件传输了。too navie! ! !正确的做法应该等接收文件的客户端完成文件存储后发送一个文件已收到的信号,同时发送端发送完就应该等接收端回传收到信号再开始下一次传输。是的,就是这样,我真的爱死洗澡了!!
让我们继续上面的例子:

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
//与上面例子的服务端的唯一差别在收到文件后发送success信号给发送端
public class Server {
    ServerSocket ss;
    BufferedReader in;
    Server() throws Exception {
        ss = new ServerSocket(9099);
        System.out.println("服务器启动");
    }

    public  void start() throws Exception {
        Socket client = ss.accept();
        int len = 0;
        in = new BufferedReader(new InputStreamReader(client.getInputStream()));
        PrintWriter out = new PrintWriter(client.getOutputStream(), true);
        while (len < 20) {
            int count = 0;
            byte[] data = new byte[1024];
            long fileLength = Long.parseLong(in.readLine());
            String fileName = in.readLine();
            BufferedOutputStream writeFile = new BufferedOutputStream(
                    new FileOutputStream("receive/" + fileName));
            BufferedInputStream readFile = new BufferedInputStream(client.getInputStream());
            while (count++ < fileLength) {
                readFile.read(data, 0 ,1);
                writeFile.write(data,0,1);
                writeFile.flush();
            }
            writeFile.close();
            System.out.println(fileName + " 接受成功" + ++len + "次");
            out.println("success");
        }
    }

    public static void main(String[] args) throws Exception{
        new Server().start();
    }
}
import java.io.*;
import java.net.Socket;
import java.util.concurrent.TimeUnit;
//与上一个例子的客户端唯一差别在于发送完文件后等待接收端回传success信号再进行下一个文件传输,
//同时没有了TimeUnit.SECONDS.sleep()
public class Client {
    Socket so;

    Client() throws Exception {
        so = new Socket("127.0.0.1", 9099);
    }

    void start() throws Exception {
        BufferedReader in = new BufferedReader(new InputStreamReader(so.getInputStream()));
        PrintStream out = new PrintStream(so.getOutputStream(),true);
        while (true) {
            System.out.println("文件名:");
            String filename = "a.txt";
            //TimeUnit.MILLISECONDS.sleep(100);
            File file = new File(filename);
            int len = 0;
            BufferedInputStream readFile = new BufferedInputStream(new FileInputStream(file));
            out.println(file.length());
            out.println(filename);
            byte[] buf = new byte[1024];
            while ((len = readFile.read(buf)) != -1) {
                out.write(buf, 0, len);
            }
            readFile.close();
            if (in.readLine().equalsIgnoreCase("success")) {
                System.out.println("文件传输完成");
            }
        }
    }

    public static void main(String[] args) throws Exception {
        new Client().start();
    }
}

运行结果
Java Socket通信问题小结_第3张图片

你可能感兴趣的:(Java)