java.io与网络通信

Java基础

浏览数:244

2019-5-20

文件IO

java.io.File是用于操作文件或目录的类:

File file = new File("hello.txt");

实例化File时不关心路径的目标并不会去读取文件或目录. File类提供了一些有用的方法:

  • isFile(): 判断路径指向的是否为文件

  • createNewFile(): 当路径指向的文件不存在时创建一个空文件

  • exists(): 判断路径指向的文件是否存在

  • delete(): 删除路径指向的文件或目录

  • length(): 返回文件的长度

  • isDirectory(): 判断路径指向的是否为目录

  • mkdir(): 根据路径创建空目录, 父目录必须存在

  • mkdirs(): 根据路径创建空目录, 会创建必要的父目录

  • list(): 以String[]类型返回目录中所有文件名

  • listFiles(): 以File[]类型返回目录中所有文件

字符流

Java使用流来读写文件, 字符流用来读写文本文件. 所有的字符流类都在java.io包中.

读取文本文件:

File file = new File("a.txt");
FileReader fin  = new FileReader(file);
BufferedReader reader = new BufferedReader(fin);
try {
    String str = reader.readLine();
    System.out.println(str);
}
catch (IOException e) {
    e.printStackTrace();
}
finally {
    reader.close();
    fin.close();
}

FileReader也可以直接用文件名创建. new FileReader("a.txt").

写入文本文件:

FileWriter fout = new FileWriter("a.txt", true);
BufferedWriter writer = new BufferedWriter(fout);
try {
    String str = "Hello World";
    writer.write(str);
}
catch (IOException e) {
    e.printStackTrace();
}
finally {
    writer.close();
    fout.close();
}

FileWriter的第二个参数为append, true代表在文件尾追加, 默认false代表清空文件重写.

字节流

字节流用于读写二进制文件, 其读写的数据以byte[]类型存储.

所有字节流类都在java.io包中.

读取文件:

File file = new File("a.in");
FileInputStream fin = new FileInputStream(file);
Byte[] buf = new Byte[512];

try {
    fin.read(buf);
}
catch {IOException e} {
    e.printStackTrace();
}
finally {
    fin.close();
}

写文件:

File file = new File("a.in");
FileOutputStream fout = new FileOutputStream(file);
Byte[] buf = new Byte[512];
try {
    // write sth in buf 
    fin.write(buf);
}
catch {IOException e} {
    e.printStackTrace();
}
finally {
    fout.close();
}

标准输入输出

java.lang.System对象中维护了3个标准流, 用于终端输入输出:

  • System.out, 标准输出流, PrintStream对象
  • System.err: 标准错误流, PrintStream对象
  • System.in: 标准输入流, FileInputStream对象

在需要的时候我们可以将它们重定向到文件.

重定向标准输出:

File file = new File("a.out");
FileOutputStream fout  = new FileOutputStream(file);
PrintStream pout = new PrintStream(fout);
PrintStream stdout = System.out;  // save

System.setOut(pout);
System.setOut(stdout); // recover

因为标准输入是字节流, 我们需要把它们转换成需要的类型才能使用:

int n;
byte[] buf = new byte[1024];
n = System.in.read(buf);
String s = new String(buf, 0, n);
System.out.println(s);

java.util.Scanner允许用迭代器的方式读取输入:

Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()) {
    // scanner.next();  // return Object
    scanner.nextInt(); // return int
}

scanner可以直接将输入转换为内置类型使用很方便.

网络IO

TCP客户端

java.net.Socket是一个用作Tcp客户端的Socket. 从Socket中获得InputStreamOutputStream对象就可以与服务器通过Tcp连接通信了.

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;

public class TcpClient {
    public static void main(String[] args) throws IOException {

        Socket client = new Socket("127.0.0.1", 5000);
        client.setSoTimeout(10000);

        PrintStream out = new PrintStream(client.getOutputStream());

        BufferedReader buf =  new BufferedReader(new InputStreamReader(client.getInputStream()));

        String[] msgs = {"你好", "世界"};

        for (String msg : msgs) {
            out.println(msg);

            while (true) {
                String echo = buf.readLine();
                if (echo != null) {
                    System.out.println(echo);
                    break;
                }
            }
        }
    }
}

上述示例中发送完一条消息则进入轮询, 查看是否有服务端消息.

java.net.ServerSocket则是一个用作Tcp服务端的socket.

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;


public class TcpServer implements Runnable {

    private Socket client = null;
    private String address;

    public TcpServer(Socket client) {
        this.client = client;
    }

    @Override
    public void run() {
        try {
            String host = client.getInetAddress().toString();
            String port = Integer.toString(client.getPort());

            PrintStream out = new PrintStream(client.getOutputStream());
            System.out.println("Get Connection");
            BufferedReader buf = new BufferedReader(new InputStreamReader(client.getInputStream()));

            address = host + ":" + port;
            System.out.println("get connection from" + address);

            while (true) {

                String str = buf.readLine();
                if (str != null) {
                    out.println(str);
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        ArrayList<Thread> list = new ArrayList<Thread>();

        try {
            ServerSocket socket = new ServerSocket(5000);

            while (true) {
                // accept a new connection
                Socket client = socket.accept();
                Thread thread = new Thread(new TcpServer(client));
                list.add(thread);
                thread.start();
            }
        }
        catch (IOException ioe) {
            ioe.printStackTrace();
        }
    }

}

上述示例是一个多线程服务器, ServerSocket监听5000端口. 当有客户端连接该端口时, socket.accept()将返回一个java.net.Socket对象.

创建一个线程持有Socket对象, 并与客户端进行通信.

UDP客户端

java.net.Datagram可以用作UDP客户端, 其接收到的数据报被封装为java.net.DatagramPacket.

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;


public class UdpClient {
    public static void main(String[] args) throws IOException {
        DatagramSocket socket = new DatagramSocket();
        String[] msgs = {"1", "2.3", "520"};
        for (String msg : msgs) {
            byte[] buf = msg.getBytes();
            InetAddress addr = InetAddress.getByName("127.0.0.1");
            DatagramPacket packet = new DatagramPacket(buf, buf.length, addr, 5000);
            socket.send(packet);

            while (true) {
                byte[] recvBuf = new byte[256];
                DatagramPacket recvPacket = new DatagramPacket(recvBuf, recvBuf.length);
                socket.receive(recvPacket);
                String echo = new String(recvPacket.getData(), 0, recvPacket.getLength());
                if (echo != null) {
                    System.out.println(echo);
                    break;
                }
            }
        }
    }

}

DatagramSocket的send方法用于发送数据报, receive用于接收数据报.

UDP服务器

import java.io.IOException;
import java.net.DatagramSocket;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.Socket;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicInteger;

public class UdpServer implements Runnable {

    private DatagramSocket socket = null;
    private DatagramPacket packet = null;
    private String address;

    public UdpServer(DatagramSocket socket, DatagramPacket packet) {
        this.socket = socket;
        this.packet = packet;
    }

    @Override
    public void run() {
        try {
            String msg = new String(packet.getData());
            UdpServer.sum += Double.parseDouble(msg);
            UdpServer.count += 1;

            int port = packet.getPort();
            InetAddress address = packet.getAddress();

            System.out.println("get msg from" + address);

            String response = new String(recvPacket.getData(), 0, recvPacket.getLength());
            DatagramPacket sendPacket = new DatagramPacket(response.getBytes(), response.getBytes().length, address, port);
            socket.send(sendPacket);
        }
        catch (IOException ioe) {
            ioe.printStackTrace();
        }
    }

    public static void main(String[] args) {
        AtomicInteger numThreads = new AtomicInteger(0);
        ArrayList<Thread> list = new ArrayList<Thread>();

        try {
            DatagramSocket socket = new DatagramSocket(5000);
            while (true) {
                byte[] buf = new byte[100];
                DatagramPacket packet = new DatagramPacket(buf, buf.length);
                socket.receive(packet);

                Thread thread = new Thread(new UdpServer(socket, packet));
                list.add(thread);
                thread.start();
                numThreads.incrementAndGet();
            }
        }
        catch (IOException ioe) {
            ioe.printStackTrace();
        }
    }

}

因为UDP不需要维护连接, 服务端和客户端的socket是同样的.

上文是一个多线程UDP服务器, 不过所有线程持有同一个socket对象和要处理的数据报. 为了保证线程安全最好给socket加锁.

作者:-Finley-