`
吖龙Sam
  • 浏览: 20285 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

Java中的Socket编程(4)-实例

    博客分类:
  • Java
阅读更多

纸上得来终觉浅,绝知此事要躬行。—陆游
意思是说,从书本上得来的知识终归是浅薄的,要透彻的认知事务还必须亲自实践。
这句话告诉我们,除了要掌握好理论知识,还必须得亲自动手做实践,来证明结论。理论引导实践,实践推翻理论。正是这个道理!

前面三章,我们对Socket编程进行了一个比较全面的阐述,相信大家对Socket编程有了一个比较全面的认识,今天我们来实现一个实例,加深对Socket编程的理解。

1、实例:实现一个质数判断程序。即
客户端在控制台输入一个数字,服务端告诉客户端该数字是否是质数。

1)服务端程序流程:
(1)获取客户端输入的数据
(2)做判断处理
(3)返回处理结果

2)客户端程序流程:
(1)用户在控制台输入数字
(2)获取用户输入的数字并发送给服务端
(3)获取服务端响应结果,并输出在控制台中

3)说明:
(1)质数(素数):除了1和本身,没有其他公约数
(2)为节省流量,服务端程序不直接告诉客户端结果,而是通过规定的协议,返回状态码。
协议规定为(这个协议自己任意定):0 是质数, 1 不是质数, 2 输入非法

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;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 服务端:判断客户端输入的数字是否是质数
 * 步骤:
 * 1.获取客户端输入的数据
 * 2.做判断处理
 * 3.返回处理结果
 * 
 * @author Sam
 *
 */
public class PrimeNumServer {
	
	// 服务端监听的端口号
	private static final int PORT = 10000;

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		try {
			ServerSocket ss = new ServerSocket(PORT);
			System.out.println("Server 等待客户端接入...");
			while (true) {
				// 监听客户端请求
				Socket socket = ss.accept();
				// 监听到有客户端接入,则开启一个子线程处理请求
				new Thread(new Task(socket)).start();
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * 处理Socket请求任务类
	 *
	 */
	private static class Task implements Runnable {
		
		private Socket socket;
		private BufferedReader buffRreader;
		private BufferedWriter buffWriter;
		
		public Task(Socket socket) {
			try {
				this.socket = socket;
				// 字符读取流
				this.buffRreader = new BufferedReader(
						new InputStreamReader(socket.getInputStream()));
				// 字符写入流
				this.buffWriter = new BufferedWriter(
						new OutputStreamWriter(socket.getOutputStream()));
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

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

		/**
		 * 处理请求
		 */
		private void handlRequest() {
			try {
				// 接收客户端数据
				String data = receive();
				// 处理逻辑
				String result = checkIsPrimeNum(data);
				// 发送响应结果
				send(result);
			} finally {
				StreamUtils.close(buffRreader, buffWriter);
				close(socket);
			}
		}

		/**
		 * 检查是否是质数,根据协议规定,返回状态码
		 * @param numStr
		 * @return 0 是质数, 1 不是质数, 2 输入非法
		 */
		private String checkIsPrimeNum(String numStr) {
			// 先判断输入的是否是数字
			boolean isNumber = isNumber(numStr);
			// 是数字,进一步判断是否是质数(素数)
			if (isNumber) {
				int number = Integer.parseInt(numStr);
				return isPrimeNumber(number);
				
			} else {// 输入非数字,则认为是非法的
				return "2";
			}
		}

		/**
		 * 判断是否是数字
		 * @param numStr
		 * @return
		 */
		private boolean isNumber(String numStr) {
			Pattern pattern = Pattern.compile("[0-9]*");
			Matcher matcher = pattern.matcher(numStr);
			return matcher.matches();
		}
		
		/**
		 * 判断是否是质数
		 * @param number
		 * @return 0 是质数, 1 不是质数, 2 输入非法
		 */
		private String isPrimeNumber(int number) {
			if (number < 2) {// 如果数字小于2,则认为是非法
				return "2";
			} else {
				// 真正做质数的判断逻辑
				for (int i=2; i<=number-1; i++) {
					/*
					 * 用2至number-1之间的所有数去整除number,
					 * 如果有一个能被整除,说明是number非质数
					 */
					if (number % i == 0) {
						return "1";
					}
				}
				return "0";
			}
		}

		/**
		 * 接收客户端发送过来的数据
		 * @return
		 */
		private String receive() {
			try {
				return buffRreader.readLine();
			} catch (IOException e) {
				e.printStackTrace();
			}
			return null;
		}
		
		/**
		 * 向客户端发送响应结果
		 * @param result
		 */
		private void send(String result) {
			try {
				buffWriter.write(result);
				buffWriter.newLine();// 写一个换行符
				buffWriter.flush();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		
	}
	
	/**
	 * 关闭客户端Socket
	 * @param socket
	 */
	private static void close(Socket socket) {
		if (socket != null) {
			try {
				socket.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
			socket = null;
		}
	}

}


服务端代码分析:
1、在isNumber(String numStr)方法中判断一个字符串是否是数字,用了一个正则匹配,匹配规则为:字符串是由0~9之间的数组成,并且可以出现零次或多次;有关更详细的匹配规则可以参看JDK文档中的:java.util.regex.Pattern类
2、在isPrimeNumber(int number)方法中判断一个数是否是质数:
用2至number-1之间的所有数去整除number,
1)如果有一个能被整除说明是number非质数;
2)如果所有的数都不未来整除,则说明是质数。
3、为了避免代码冗余,在finally子句中使用了一个StreamUtils流工具类来关闭资源,最后会把这个工具类的代码贴上。

2)客户端代码:

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

/**
 * 客户端:输入一个数字
 * 步骤:
 * 1.用户在控制台输入数字
 * 2.获取用户输入的数字并发送给服务端
 * 3.获取服务端响应结果
 * 
 * @author Sam
 *
 */
public class PrimeNumClient {
	
	// 主机
	private static final String HOST = "127.0.0.1";
	// 端口号
	private static final int PORT = 10000;

	private static Socket socket;
	private static BufferedReader buffReader;
	private static BufferedWriter buffWriter;
	
	private static BufferedReader keyboardReader;
	
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		try {
			init();
			System.out.println("请在下面输入质数:");
			// 获取键盘输入流
			keyboardReader = new BufferedReader(new InputStreamReader(System.in));
			// 发送数据
			send(keyboardReader.readLine());
			// 接收响应结果
			String result = receive();
			// 处理结果
			handleResult(result);
			
		} catch (IOException e) {
			e.printStackTrace();
		} finally {// 释放资源
			StreamUtils.close(buffReader, buffWriter);
			StreamUtils.close(keyboardReader);
			close(socket);
		}
	}

	/**
	 * 初始化操作
	 */
	private static void init() {
		try {
			// 连接到指定的主机与端口号的服务器上
			socket = new Socket(HOST, PORT);
			InputStream in = socket.getInputStream();
			OutputStream out = socket.getOutputStream();
			
			buffReader = new BufferedReader(new InputStreamReader(in));
			buffWriter = new BufferedWriter(new OutputStreamWriter(out));
			
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	/**
	 * 向服务端发送数据
	 * @param data
	 */
	private static void send(String data) {
		try {
			buffWriter.write(data);
			buffWriter.newLine();// 写一个换行符
			buffWriter.flush();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * 接收服务端响应结果
	 * @return
	 */
	private static String receive() {
		try {
			return buffReader.readLine();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return null;
	}
	
	/**
	 * 处理服务端返回的结果
	 */
	private static void handleResult(String result) {
		
		if ("0".equals(result)) {// 是质数
			System.out.println("您输入的是质数");
			
		} else if ("1".equals(result)) {// 不是质数
			System.out.println("您输入的不是质数");
			
		} else if ("2".equals(result)) { // 输入非法
			System.out.println("警告!您输入非法!");
			
		}
	}
	
	/**
	 * 关闭客户端Socket
	 * @param socket
	 */
	private static void close(Socket socket) {
		if (socket != null) {
			try {
				socket.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
			socket = null;
		}
	}
	
}


客户端代码分析:
1、获取用户输入的数据,往服务端写数据时,一定要记得用newLine()方法写一个换行符,否则服务端一直处于读取状态,不会停止,因为读不到结束符;同理,服务端在往客户端写入响应结果时,也要用newLine()方法写一个换行符,否则客户端也会一直处于阻塞状态;
2、获取服务端返回的响应结果,根据之前规定的协议,在控制台中输出相应的提示信息。

3)IO流工具类StreamUtils代码:


import java.io.IOException;
import java.io.Reader;
import java.io.Writer;

/**
 * IO流工具
 * @author Sam
 *
 */
public class StreamUtils {

	/**
	 * 同时关闭字符读取流与字符写入流
	 * @param reader
	 * @param writer
	 */
	public static void close(Reader reader, Writer writer) {
		close(reader);
		close(writer);
	}
	
	/**
	 * 关闭字符读取流
	 * @param reader
	 */
	public static void close(Reader reader) {
		if (reader != null) {
			try {
				reader.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
			reader = null;// 置空
		}
	}
	
	/**
	 * 关闭字符写入流
	 * @param writer
	 */
	public static void close(Writer writer) {
		if (writer != null) {
			try {
				writer.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
			writer = null;// 置空
		}
	}
}


IO流工具类代码分析:
一般操作中,比较常用的,而且代码变动不大的,我们都会封装成一个工具类,以后直接使用,而不用重复的写。既避免代码冗余,又提高工作效率。

3、运行结果:
注意:先运行Server程序,再运行Client程序。为了测试结果,多次运行Client程序,并输入不同的数据,得到不同的提示信息。

1)Server程序控制台:


2)Client程序控制台
(1)运行第1次:



(2)运行第2次:



(3)运行第3次:



(4)运行第4次:



4、总结:
程序代码的逻辑并不复杂,但是基本做到了代码结构化。每个功能点都封装成一个方法,不同的业务放在不同的代码块中。这样以后要是某个模块出现了问题,就找相应的模块修改,而不被其他逻辑所干扰;实际开始中,也应该是这种思想。
  • 大小: 5.3 KB
  • 大小: 6.5 KB
  • 大小: 6.9 KB
  • 大小: 6.9 KB
  • 大小: 7.3 KB
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics