java.io与网络通信

文件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加锁.

posted @ 2017-05-28 21:42  -Finley-  阅读(2412)  评论(0编辑  收藏  举报