基于Socket的网络通信(Java实现)

记录一下计算机网络的作业,顺便就当作Socket编程的一点点入门教程了


首先,你需要了解Socket的本质。
Socket,即套接字,是一种API,底层可以是TCP,UDP等协议,你可以认为Socket是通信的媒介,载体。。。你可以通过Socket获取输入流/输出流(这里的输入和输出都是像Client向Sever输入这种,有着通信含义的输入输出)。

对Socket的介绍就到这了,我感觉介绍到这已经足够了(

接下来介绍一下Socket的核心(我认为),即输入输出(流),毕竟通信离不开输入和输出,即信息的交换。
下面以输入为例,输出和输入没啥区别
输入流分为以下几种(Socket里的)

  • InputStream
  • InputStreamReader
  • BufferedReader
  • DataInputStream

总结对比

底层流数据类型缓冲主要用途
InputStream字节流byte读取二进制数据
InputStreamReader字节→字符char字节流→字符流(即读取文本)
BufferedReader字符流String高效读取文本(支持readLine()
DataInputStream字节流结构化数据高效读取二进制数据(intlong等)

因此,显然可以发现在读取文本的时候可以使用BufferedReader,在读取文件的时候更适合使用DataInputStream(当然你可以都用InputStream,不过会比较慢)

下面就给出一些Socket编程的例子,首先是Client与Server之间的文字通信,在这里以“系统时间查询”作为例子。
这里简单说一下需要实现的功能

  1. 客户端向服务器端发送字符串”Time”。
  2. 服务器端收到该字符串后,返回当前系统时间。
  3. 客户端向服务器端发送字符串”Exit”。
  4. 服务器端返回”Bye”,然后结束TCP连接。

很显然,这里用BufferedReader就很不错,至于输出,值得一提的是这里我并没有用“BufferedWriter”,而是用了“PrintWriter”,PrintWriter相对于BufferedWriter多了一个格式化输出(自动换行之类的,比较方便,虽然会慢一点)

下面直接上源码


GetTimeServer.java

public class GetTimeServer {
    private final ServerSocket serverSocket;

    public GetTimeServer(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }

    private BufferedReader getReader(Socket socket) throws IOException {
        return new BufferedReader(new InputStreamReader(socket.getInputStream()));//获取这个socket的输入流
    }

    private PrintWriter getWriter(Socket socket) throws IOException {
        return new PrintWriter(socket.getOutputStream(), true);//获取这个socket的输出流
    }

    public void service() throws IOException {
        Socket socket;
        socket = serverSocket.accept();
        BufferedReader reader = getReader(socket);
        PrintWriter writer = getWriter(socket);
        writer.println(socket.getInetAddress() + ":" + socket.getPort() + "已连接");
        System.out.println(socket.getInetAddress() + ":" + socket.getPort() + "已连接");
        while (true) {
            String msg = null;
            while ((msg = reader.readLine()) != null) {
                System.out.println("Client "+socket.getInetAddress()+"说: " + msg);
                if (msg.equals("Exit")) {
                    writer.println("OK,Bye~");
                    socket.close();
                    return;
                }
                if (msg.equals("Time")) {
                    writer.println("现在是: " + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
                }
            }
        }
    }

    public static void main(String[] args) throws IOException {
        new GetTimeServer(6666).service();
    }
}


GetTimeClient.java

public class GetTimeClient {
    private final Socket socket;
    public GetTimeClient(String host,int port) throws IOException {
        this.socket = new Socket(host,port);
    }
    private PrintWriter getWriter() throws IOException {
        return new PrintWriter(socket.getOutputStream(),true);
    }
    private BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(socket.getInputStream()));
    }
    public void connect() throws IOException {
        BufferedReader reader = getReader();
        PrintWriter writer = getWriter();
        System.out.println("Sever: "+reader.readLine());
        Scanner sc = new Scanner(System.in);
        String msg;
        while((msg = sc.next())!=null){
            writer.println(msg);
            System.out.println("Sever: "+reader.readLine());
            if (msg.equals("Exit")){
                socket.close();
                break;
            }
        }
    }
    public static void main(String[] args) throws IOException {

        new GetTimeClient("47.107.253.223",6666).connect();
    }
}

附上一张运行效果图~

Server_Client_Time.png


再举一个文件上传的例子

  1. 客户端从用户输入获得待请求的文件名。
  2. 客户端向服务器端发送文件名。
  3. 服务器端收到文件名后,传输文件。
  4. 客户端接收文件,重命名并存储在硬盘。

看到这里,你可能会想,用BufferedReader读取文件名,再用DataInputStream写入文件?
很遗憾,这并不合适,不同的流串用会导致缓冲区混乱,从而导致写入的文件有缺失。
因此统一使用DataInputStream是更合适的选择
下面直接给源码


FileServer.java

public class FileServer {
    private final ServerSocket serverSocket;
    public FileServer(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }

    void service() throws IOException {
        Socket socket  = serverSocket.accept();
        System.out.println("收到"+socket.getInetAddress()+"的请求,已建立连接...");
        DataInputStream in = new DataInputStream(socket.getInputStream());
        String fileName = in.readUTF();
        long fileSize = in.readLong();

        System.out.println("接收到客户端上传文件的参数,上传文件名为: "+fileName+",预计上传文件大小为: "+fileSize+"字节");
        String filename = "/study/"+socket.getInetAddress()+"_"+fileName;

        FileOutputStream out = new FileOutputStream(filename);//写入到本地的流
        byte[] data = new byte[64];//缓冲区为64字节
        int read,cnt=0;//read记录每次写入一共写入几个字节,cnt记录总共写入了几个字节
        while ((read = in.read(data)) != -1) {
            out.write(data, 0, read);
            cnt+=read;
            System.out.println("写入了"+read+"字节");
        }
        System.out.println("实际共写入了"+cnt+"字节,程序结束,Bye~");
        out.close();
        in.close();
        socket.close();
        serverSocket.close();
    }
    public static void main(String[] args) throws IOException {
        new FileServer(6666).service();
    }
}


FileClient.java

public class FileClient {
    private final Socket socket;
    public FileClient(String host, int port) throws IOException {
        this.socket = new Socket(host,port);
    }

    void upload(File file) throws IOException {
        DataOutputStream out = new DataOutputStream(socket.getOutputStream());
        FileInputStream in = new FileInputStream(file);
        out.writeUTF(file.getName());//写入文件名字到socket中
        out.writeLong(file.length());//写入文件大小到socket中
        out.flush();

        byte[] data = new byte[64];
        int read,cnt=0;
        while ((read = in.read(data)) != -1) {
            out.write(data, 0, read);
            cnt+=read;
            System.out.println("上传了"+read+"字节");
        }
        System.out.println("上传完毕,共上传了"+cnt+"字节");
        in.close();
        out.close();
        socket.close();
    }

    public static void main(String[] args) throws IOException {
        System.out.println("请输入要上传的文件路径: ");
        String filePath = new Scanner(System.in).nextLine();
        File file = new File(filePath);
        new FileClient("47.107.253.223",6666).upload(file);
    }
}

附上一张运行效果图~

File.png

我要成为大剑天尊!