Java Socket编程
对于Java Socket编程而言,有两个概念,一个是ServerSocket,一个是Socket。服务端和客户端之间通过Socket建立连接,之后它们就可以进行通信了。首先ServerSocket将在服务端监听某个端口,当发现客户端有Socket来试图连接它时,它会accept该Socket的连接请求,同时在服务端建立一个对应的Socket与之进行通信。这样就有两个Socket了,客户端和服务端各一个。
对于Socket之间的通信其实很简单,服务端往Socket的输出流里面写东西,客户端就可以通过Socket的输入流读取对应的内容。Socket与Socket之间是双向连通的,所以客户端也可以往对应的Socket输出流里面写东西,然后服务端对应的Socket的输入流就可以读出对应的内容。下面来看一些服务端与客户端通信的例子:
1、客户端写服务端读
服务端代码
- public class Server {
- public static void main(String args[]) throws IOException {
- //为了简单起见,所有的异常信息都往外抛
- int port = 8899;
- //定义一个ServerSocket监听在端口8899上
- ServerSocket server = new ServerSocket(port);
- //server尝试接收其他Socket的连接请求,server的accept方法是阻塞式的
- Socket socket = server.accept();
- //跟客户端建立好连接之后,我们就可以获取socket的InputStream,并从中读取客户端发过来的信息了。
- Reader reader = new InputStreamReader(socket.getInputStream());
- char chars[] = new char[64];
- int len;
- StringBuilder sb = new StringBuilder();
- while ((len=reader.read(chars)) != -1) {
- sb.append(new String(chars, 0, len));
- }
- System.out.println("from client: " + sb);
- reader.close();
- socket.close();
- server.close();
- }
- }
服务端从Socket的InputStream中读取数据的操作也是阻塞式的,如果从输入流中没有读取到数据程序会一直在那里不动,直到客户端往Socket的输出流中写入了数据,或关闭了Socket的输出流。当然,对于客户端的Socket也是同样如此。在操作完以后,整个程序结束前记得关闭对应的资源,即关闭对应的IO流和Socket。
客户端代码
- public class Client {
- public static void main(String args[]) throws Exception {
- //为了简单起见,所有的异常都直接往外抛
- String host = "127.0.0.1"; //要连接的服务端IP地址
- int port = 8899; //要连接的服务端对应的监听端口
- //与服务端建立连接
- Socket client = new Socket(host, port);
- //建立连接后就可以往服务端写数据了
- Writer writer = new OutputStreamWriter(client.getOutputStream());
- writer.write("Hello Server.");
- writer.flush();//写完后要记得flush
- writer.close();
- client.close();
- }
- }
对于客户端往Socket的输出流里面写数据传递给服务端要注意一点,如果写操作之后程序不是对应着输出流的关闭,而是进行其他阻塞式的操作(比如从输入流里面读数据),记住要flush一下,只有这样服务端才能收到客户端发送的数据,否则可能会引起两边无限的互相等待。在稍后讲到客户端和服务端同时读和写的时候会说到这个问题。
2、客户端和服务端同时读和写
前面已经说了Socket之间是双向通信的,它既可以接收数据,同时也可以发送数据。
服务端代码
- public class Server {
- public static void main(String args[]) throws IOException {
- //为了简单起见,所有的异常信息都往外抛
- int port = 8899;
- //定义一个ServerSocket监听在端口8899上
- ServerSocket server = new ServerSocket(port);
- //server尝试接收其他Socket的连接请求,server的accept方法是阻塞式的
- Socket socket = server.accept();
- //跟客户端建立好连接之后,我们就可以获取socket的InputStream,并从中读取客户端发过来的信息了。
- Reader reader = new InputStreamReader(socket.getInputStream());
- char chars[] = new char[64];
- int len;
- StringBuilder sb = new StringBuilder();
- while ((len=reader.read(chars)) != -1) {
- sb.append(new String(chars, 0, len));
- }
- System.out.println("from client: " + sb);
- //读完后写一句
- Writer writer = new OutputStreamWriter(socket.getOutputStream());
- writer.write("Hello Client.");
- writer.flush();
- writer.close();
- reader.close();
- socket.close();
- server.close();
- }
- }
在上述代码中首先我们从输入流中读取客户端发送过来的数据,接下来我们再往输出流里面写入数据给客户端,接下来关闭对应的资源文件。而实际上上述代码可能并不会按照我们预先设想的方式运行,因为从输入流中读取数据是一个阻塞式操作,在上述的while循环中当读到数据的时候就会执行循环体,否则就会阻塞,这样后面的写操作就永远都执行不了了。除非客户端对应的Socket关闭了阻塞才会停止,while循环也会跳出。针对这种可能永远无法执行下去的情况的解决方法是while循环需要在里面有条件的跳出来,纵观上述代码,在不断变化的也只有取到的长度len和读到的数据了,len已经是不能用的了,唯一能用的就是读到的数据了。针对这种情况,通常我们都会约定一个结束标记,当客户端发送过来的数据包含某个结束标记时就说明当前的数据已经发送完毕了,这个时候我们就可以进行循环的跳出了。那么改进后的代码会是这个样子:
- public class Server {
- public static void main(String args[]) throws IOException {
- //为了简单起见,所有的异常信息都往外抛
- int port = 8899;
- //定义一个ServerSocket监听在端口8899上
- ServerSocket server = new ServerSocket(port);
- //server尝试接收其他Socket的连接请求,server的accept方法是阻塞式的
- Socket socket = server.accept();
- //跟客户端建立好连接之后,我们就可以获取socket的InputStream,并从中读取客户端发过来的信息了。
- Reader reader = new InputStreamReader(socket.getInputStream());
- char chars[] = new char[64];
- int len;
- StringBuilder sb = new