Java中的Socket编程(3)-深入

阅读更多

读不在三更五鼓,功只怕一曝十寒。—郭沫若
意思是说,学习不在某一时的努力,而要锲而不舍的坚持才能成功!
这句话告诉我们,学习是一个循序渐进的积累过程,急于求成是不可取的,而想一劳永逸,想到的时候就用功一时,想不到的时候就疏于学业,这样也只能算是学无所成的。

上一次我们实现了一个服务端和客户端同时读写的程序,但是服务端在处理完一个客户端的请求之后就结束了,而不能继续接收其他客户端的请求,这种方式还不能满足我们实际开发中的需要。今天我们来实现一个更贴近实际情况的程序:

1、需求:一个服务端异步处理多个客户端的请求
核心点:服务端通过accept方法监听客户端请求,当接收到某个客户端请求时,开启一个新的线程来处理请求,然后继续回到监听状态。这样就实现了异步处理客户端请求的程序。

2、代码实现:
1)服务端代码:

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * 服务端
 * 需求:多个客户端连接同一个服务端
 * @author Sam
 *
 */
public class MultipleServer {
	
	/** 编码 */
	public static final String ENCODING = "UTF-8";

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		
		try {
			// 创建一个服务端ServerSocket,监听指定端口的请求
			ServerSocket ss = new ServerSocket(10000);
			System.out.println("Server 等待客户端接入...");
			while (true) {
				// 一直循环监听客户端请求
				Socket socket = ss.accept();
				// 开启一个新线程处理Socket请求
				new Thread(new AsynSocketTask(socket)).start();
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * 用于处理Socket请求的任务类
	 *
	 */
	static class AsynSocketTask implements Runnable {
		
		private Socket socket;
		private BufferedReader buffReader;
		private BufferedWriter buffWriter;
		
		public AsynSocketTask(Socket socket) {
			try {
				this.socket = socket;
				// 获取Socket中的输入输出流,并使用带缓冲区的字符流
				buffReader = new BufferedReader(
						new InputStreamReader(socket.getInputStream(), ENCODING));
				buffWriter = new BufferedWriter(
						new OutputStreamWriter(socket.getOutputStream(), ENCODING));
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

		@Override
		public void run() {
			handleSocketRequest();
		}

		/**
		 * 处理客户端请求
		 */
		private void handleSocketRequest() {
			try {
				////////////////  读操作    //////////////////
				// 获取客户端数据
				String line = null;
				StringBuilder sBuilder = new StringBuilder();
				// 一行一行的读
				while ( (line=buffReader.readLine()) != null ) {
					if (line.indexOf("eof") != -1) {// 读到结束标记,则跳出循环
						break;
					}
					sBuilder.append(line);
				}
				System.out.println("Server 来自客户端的数据:"+ sBuilder.toString());
				
				////////////////  写操作    //////////////////
				// 读完之后,往客户端写一句
				buffWriter.write("Hello Client!");
				buffWriter.newLine();// 写一个换行符
				buffWriter.write("eof");// 写一个结束标记符
				buffWriter.newLine();
				
				buffWriter.close();// 关闭该流的同时,也会释放与之关联的所有资源
				buffReader.close();
				socket.close();
				
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

}


服务端代码分析:
1、在主程序中,我们使用了一个while(true)死循环,监听客户端请求,当监听到有请求过来,就开启一个新线程来处理,然后主程序回到原来的监听状态。实际开发中也是这种情况的,服务端应该一直处于开启状态,可以随时响应客户端请求。
2、为了避免出现乱码,服务端和客户端读写数据统一使用UTF-8编码格式。
3、为了提高读写效率,程序中我们使用了带缓存区的字符输入输出流,一行一行的读写数据;其中readLine方法是阻塞式操作的,只有读到一个换行、回车或换行回车符时才会执行往后的操作,否则一直处于阻塞状态。而newLine方法是往流中写一个换行符。

2)客户端代码:

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.Socket;

/**
 * 客户端
 * @author Sam
 *
 */
public class MultipleClient {
	
	/** 编码 */
	public static final String ENCODING = "UTF-8";

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		try {
			// 与服务端建立连接(服务端主机号,服务端监听的端口号)
			Socket socket = new Socket("127.0.0.1" , 10000);
			// 获取Socket中的输入输出流,并使用带缓冲区的字符流
			BufferedWriter buffWriter = new BufferedWriter(
					new OutputStreamWriter(socket.getOutputStream(), ENCODING));
			BufferedReader buffReader = new BufferedReader(
					new InputStreamReader(socket.getInputStream(), ENCODING));
			
			///////////////  写操作    ///////////////
			// 往服务端写数据
			buffWriter.write("Hello Server!");
			buffWriter.newLine();// 写一个换行符
			buffWriter.write("eof");// 写一个结束标记符
			buffWriter.newLine();
			buffWriter.flush();
			
			///////////////  读操作    ///////////////
			// 写完之后,读取服务端返回的数据
			socket.setSoTimeout(10*1000);// 设置超时时长10秒
			String line = null;
			StringBuilder sBuilder = new StringBuilder();
			// 一行一行的读
			while ( (line=buffReader.readLine()) != null ) {
				if (line.indexOf("eof") != -1) {// 读到结束标记,则跳出循环
					break;
				}
				sBuilder.append(line);
			}
			
			buffWriter.close();
			buffReader.close();
			socket.close();
			
			System.out.println("Client 来自服务端的数据:" + sBuilder.toString());
			
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

}


客户端代码分析:
1、这里的代码逻辑,跟前两篇文章中的代码是一样的,只是有个需要注意的方法:setSoTimeout(int timeout)
设置超时时长,单位是毫秒。当设置了超时时长大于0时,表示Socket在这一时间内,如果没读取到数据,则不会一直阻塞在那里,而是抛出一个SocketTimeoutException异常。

3、运行结果:
注意:先运行Server程序,再运行Client程序。为体现服务端可以处理多个客户端请求,把Client程序拷贝到硬盘的某个路径,编辑文件,修改往服务端写数据的内容,然后在dos控制台再运行一次。即
Java中的Socket编程(3)-深入_第1张图片

结果如下:
1)Server程序控制台:


2)Client程序控制台:


3)dos命令控制台:


4、总结:
经过前面两章和本章的学习,我相信大家对Socket会有了一个更深入的了解,前面两章的内容均是为了本章内容而做铺垫的,实际开发中,就是使用本章这种方式,一个服务端异步响应多个客户端请求。以后更复杂的程序也是在这基础上拓展的,要学会融会贯通,以不变应万变。
  • Java中的Socket编程(3)-深入_第2张图片
  • 大小: 22.5 KB
  • Java中的Socket编程(3)-深入_第3张图片
  • 大小: 9 KB
  • Java中的Socket编程(3)-深入_第4张图片
  • 大小: 26.8 KB
  • Java中的Socket编程(3)-深入_第5张图片
  • 大小: 68.8 KB
  • 查看图片附件

你可能感兴趣的:(socket,java,java网络编程)