20175203 2018-2019 实验五《网络编程与安全》
知识重点(摘自实验资料)
- 栈 :(Stack)是一种只允许在表尾插入和删除的线性表,有先进后出(FILO),后进先出(LIFO)的特点。允许插入和删除的一端称为栈顶(top),另一端称为栈底(bottom)
栈的一个应用是用来对四则运算表达式进行求值:
eg:表达式Exp = S1 + OP + S2
(S1 ,S2是两个操作数,OP为运算符)有三种标识方法: OP + S1 + S2 为前缀表示法;S1 + OP + S2 为中缀表示法;S1 + S2 + OP 为后缀表示法 例如:Exp = a * b + (c - d / e) * f 前缀式: + * a b * - c / d e f 中缀式: a * b + c - d / e * f 后缀式: a b * c d e / - f * + 我们可以看出: 操作数之间的相对次序不变; 运算符的相对次序不同; 中缀式丢失了括弧信息,致使运算次序不确定; 前缀式的运算规则为:连续出现的两个操作数和在它们之前且紧靠它们的运算符构成一个最小表达式; 后缀式的运算规则为:运算符在式中出现的顺序恰为表达式的运算顺序;每个运算符和在它之前出现且紧靠它的两个操作数构成一个最小表达式。后缀表示法是波兰逻辑学家J.Lukasiewicz于1929年提出的,又叫做逆波兰表达式。
Linux命令
dc
可以用来对逆波兰式表达式进行求值,dc
的打印类命令:p:打印栈顶元素并换行
n: 打印栈顶元素并将其弹出栈,完毕后不换行 P: putchar ( int(栈顶元素) % 256) 并弹栈顶,不换行 f: 从栈顶至栈底打印栈中所有值,每个一行dc
的运算符:+: 依次弹出w1与w2,将w2+w1压栈。精度为结果值精度
-: 依次弹出w1与w2,将w2-w1压栈: 依次弹出w1与w2,将w2w1压栈。精度为结果值精度与precision中较大值 / : 依次弹出w1与w2,将w2/w1压栈。精度为precision % : 依次弹出w1与w2,将w2-w2/w1*w1压栈 ~ : 依次弹出w1与w2,依次将w2/w1与w2%w1压栈 ^ : 依次弹出w1与w2,将w2^((int)w1)压栈。精度为w2精度与precision中较大值 | : 依次弹出w1 w2与w3,将 w3 ^ ((int)w2) (mod w1) 压栈。w1 w3 需为整数 v : 弹出w1,将sqrt(v)压栈。精度为precisiondc
支持栈操作: c : 清空栈 d : 将栈顶元素复制并压栈 r : 交换栈顶两元素 XXX对逆波兰式求值时,不需要再考虑运算符的优先级,只需从左到右扫描一遍后缀表达式即可。求值伪代码如下:
设置一个操作数栈,开始栈为空;
从左到右扫描后缀表达式,遇操作数,进栈; 若遇运算符,则从栈中退出两个元素,先退出的放到运算符的右边,后退出的放到运算符左边,运算后的结果再进栈,直到后缀表达式扫描完毕。 此时,栈中仅有一个元素,即为运算的结果。任务一
要求
- 参考http://www.cnblogs.com/rocedu/p/6766748.html#SECDSA
- 结对实现中缀表达式转后缀表达式的功能 MyBC.java
- 结对实现从上面功能中获取的表达式中实现后缀表达式求值的功能,调用MyDC.java
上传测试代码运行结果截图和码云链接
实验代码
- 中缀转后缀
import java.util.*;public class MyBC { private Stackstack; private List list; private String message, Message = ""; public MyBC() { stack = new Stack ();//用来暂时存放运算符的栈 list = new ArrayList ();//用来暂时存放操作数及运算符的列表 } public void conversion(String expr) { //中缀转后缀 String token; StringTokenizer tokenizer = new StringTokenizer(expr); while (tokenizer.hasMoreTokens()) { //当tokenizer有下一个值时,进行循环,并把值赋给token token = tokenizer.nextToken(); if (token.equals("(")) { //如果是左括号,入栈 stack.push(token); } else if (token.equals("+") || token.equals("-")) { //如果是“+”或“-”,继续判断栈是否为空 if (!stack.empty()) { //如果栈非空,判断栈顶元素是什么 if (stack.peek().equals("(")) { //如果栈顶为“(”,运算符入栈 stack.push(token); } else { //否则先把栈顶元素移除,加到列表中,再将运算符入栈 list.add(stack.pop()); stack.push(token); } } else { //若栈为空,运算符入栈 stack.push(token); } } else if (token.equals("*") || token.equals("÷")) { //如果是“*”或“÷”,继续判断栈是否为空 if (!stack.empty()) { //如果栈非空,判断栈顶元素是什么 if (stack.peek().equals("*") || stack.peek().equals("÷")) { //如果栈顶为“*”或“÷”,先把栈顶元素移除,加到列表中,再将运算符入栈 list.add(stack.pop()); stack.push(token); } else { //如果栈顶为其他,运算符直接入栈 stack.push(token); } } else { //如果栈为空,运算符直接入栈 stack.push(token); } } else if (token.equals(")")) { //如果遇到“)”,开始循环 while (true) { //先把栈顶元素移除并赋给A String A = stack.pop(); if (!A.equals("(")) { //如果A不为“(”,则加到列表 list.add(A); } else { //如果A为“(”,退出循环 break; } } } else { //如果为操作数,进入列表 list.add(token); } } while (!stack.empty()) { //将栈中元素取出,加到列表中,直到栈为空 list.add(stack.pop()); } ListIterator li = list.listIterator();//返回此列表元素的列表迭代器(按适当顺序)。 while (li.hasNext()) { //将迭代器中的元素依次取出,并加上空格作为分隔符 Message += li.next() + " "; li.remove(); } message = Message; } public String getMessage() { return message; }}
- 后缀表达式计算
import java.util.*;public class MyDC { private final char ADD = '+'; private final char SUBTRACT = '-'; private final char MUTIPLY = '*'; private final char DIVIDE = '/'; private Stackstack; public MyDC(){ stack = new Stack (); } public int evaluate(String expr){ int op1,op2,result = 0; String token; StringTokenizer tokenizer = new StringTokenizer(expr); while(tokenizer.hasMoreTokens()){ token = tokenizer.nextToken(); if(isOperator(token)){ op2 = (stack.pop().intValue()); op1 = (stack.pop().intValue()); result = evalSingleOp(token.charAt(0),op1,op2); stack.push(new Integer(result)); } else { stack.push(new Integer((Integer.parseInt(token)))); } } return result; } private boolean isOperator(String token){ return (token.equals("+")||token.equals("-")||token.equals("*")||token.equals("/")); } private int evalSingleOp(char operation,int op1,int op2) { int result = 0; switch (operation){ case ADD: result = op1+op2; break; case SUBTRACT: result = op1-op2; break; case MUTIPLY: result = op1*op2; break; case DIVIDE: result = op1/op2; } return result; }}
*测试
import java.util.*;public class Calculator { public static void main(String[] args) { String expression; int result; MyBC nts = new MyBC(); MyDC value = new MyDC(); System.out.println("Please input a nifix expression"); Scanner in = new Scanner(System.in); expression = in.nextLine(); nts.conversion(expression); System.out.println("The postfix expression is :"+nts.getMessage()); result = value.evaluate(nts.getMessage()); System.out.println("The result is :"+result); }}
实验截图
任务二
要求
- 注意责任归宿,要会通过测试证明自己没有问题
- 基于Java Socket实现客户端/服务器功能,传输方式用TCP
- 客户端让用户输入中缀表达式,然后把中缀表达式调用MyBC.java的功能转化为后缀表达式,把后缀表达式通过网络发送给服务器
- 服务器接收到后缀表达式,调用MyDC.java的功能计算后缀表达式的值,把结果发送给客户端
- 客户端显示服务器发送过来的结果
上传测试结果截图和码云链接
实验代码
- 查看本机IP地址
import java.net.*;public class Address { public static void main(String[] args) throws UnknownHostException { InetAddress net = InetAddress.getLocalHost(); System.out.println(net.toString()); }}
注:这个其实可以在网上找公用IP但是有可能会和其他冲突造成无服务器连接的情况,运行后得到自己电脑的IP地址后就能进行后续步骤啦。
*服务器端
import java.io.*;import java.net.*;public class Server2 { public static void main(String[] args) throws IOException { int answer; //while (true) { ServerSocket serverForClient = null; Socket socketOnServer = null; DataOutputStream out = null; DataInputStream in = null; try { serverForClient = new ServerSocket(2010); } catch (IOException e1) { System.out.println(e1); } try { System.out.println("等待客户呼叫"); socketOnServer = serverForClient.accept(); //堵塞状态,除非有客户呼叫 out = new DataOutputStream(socketOnServer.getOutputStream()); in = new DataInputStream(socketOnServer.getInputStream()); String s = in.readUTF(); // in读取信息,堵塞状态 System.out.println("服务器收到客户的提问:" + s); MyDC myDC = new MyDC(); answer = myDC.evaluate(s); out.writeUTF(answer + ""); Thread.sleep(500); } catch (Exception e) { System.out.println("客户已断开" + e); } //} }}
*客户端
import java.io.*;import java.net.*;public class Client2 { public static void main(String args[]) { System.out.println("客户端启动..."); //while (true) { Socket mysocket; DataInputStream in = null; DataOutputStream out = null; try { mysocket = new Socket("192.168.56.1", 2010); in = new DataInputStream(mysocket.getInputStream()); out = new DataOutputStream(mysocket.getOutputStream()); System.out.println("请输入:"); String str = new BufferedReader(new InputStreamReader(System.in)).readLine(); MyBC turner = new MyBC(); turner.conversion(str); String str1 = turner.getMessage(); out.writeUTF(str1); String s = in.readUTF(); //in读取信息,堵塞状态 System.out.println("客户收到服务器的回答:" + s); Thread.sleep(500); } catch (Exception e) { System.out.println("服务器已断开" + e); } //} }}
实验截图
任务三
要求
- 基于Java Socket实现客户端/服务器功能,传输方式用TCP
- 客户端让用户输入中缀表达式,然后把中缀表达式调用MyBC.java的功能转化为后缀表达式,把后缀表达式用3DES或AES算法加密后通过网络把密文发送给服务器
- 服务器接收到后缀表达式表达式后,进行解密(和客户端协商密钥,可以用数组保存),然后调用MyDC.java的功能计算后缀表达式的值,把结果发送给客户端
- 客户端显示服务器发送过来的结果
上传测试结果截图和码云链接
实验代码
- 服务器端
import javax.crypto.Cipher;import javax.crypto.spec.SecretKeySpec;import java.io.*;import java.net.ServerSocket;import java.net.Socket;public class Server3 { public static void main(String args[]) { MyDC mydc = new MyDC(); ServerSocket serverForClient = null; Socket socketOnServer = null; DataOutputStream out = null; DataInputStream in = null; try { serverForClient = new ServerSocket(5203); } catch (IOException e1) { System.out.println(e1); } try { System.out.println("等待客户呼叫"); socketOnServer = serverForClient.accept(); //堵塞状态,除非有客户呼叫 System.out.println("客户已连接"); out = new DataOutputStream(socketOnServer.getOutputStream()); in = new DataInputStream(socketOnServer.getInputStream()); String leng = in.readUTF(); // in读取信息,堵塞状态 byte ctext[] = new byte[Integer.parseInt(leng)]; for (int i = 0;i
- 客户端
import javax.crypto.Cipher;import java.io.*;import java.net.Socket;import java.security.Key;import java.util.Scanner;public class Client3 { public static void main(String args[]) { Socket mysocket; MyBC mybc = new MyBC(); DataInputStream in = null; DataOutputStream out = null; Scanner scanner = new Scanner(System.in); String str; try { mysocket = new Socket("192.168.56.1", 5203); in = new DataInputStream(mysocket.getInputStream()); out = new DataOutputStream(mysocket.getOutputStream()); System.out.println("客户端启动..."); FileInputStream f = new FileInputStream("key1.dat"); ObjectInputStream b = new ObjectInputStream(f); Key key = (Key) b.readObject(); Cipher cp = Cipher.getInstance("DESede"); cp.init(Cipher.ENCRYPT_MODE, key); System.out.println("请输入中缀表达式:"); str = scanner.nextLine(); mybc.conversion(str); String str1 = mybc.getMessage(); byte ptext[] = str1.getBytes("UTF-8"); byte ctext[] = cp.doFinal(ptext); System.out.println("被加密的后缀表达式:"); for (int i = 0; i < ctext.length; i++) { System.out.print(ctext[i] + ","); } System.out.println(""); out.writeUTF(ctext.length + ""); for (int i = 0; i < ctext.length; i++) { out.writeUTF(ctext[i] + ""); //System.out.print(); } String s = in.readUTF(); //in读取信息,堵塞状态 System.out.println("客户收到服务器的回应:" + s); } catch (Exception e) { System.out.println("服务器已断开" + e); } }}
*注:其中包含DES生成密钥及DH公钥和私钥使用的是娄老师博客中的代码https://www.cnblogs.com/rocedu/p/6683948.html
就不在此一一列出了
实验截图
任务四
实验要求
- 基于Java Socket实现客户端/服务器功能,传输方式用TCP
- 客户端让用户输入中缀表达式,然后把中缀表达式调用MyBC.java的功能转化为后缀表达式,把后缀表达式用3DES或AES算法加密通过网络把密文发送给服务器
- 客户端和服务器用DH算法进行3DES或AES算法的密钥交换
- 服务器接收到后缀表达式表达式后,进行解密,然后调用MyDC.java的功能计算后缀表达式的值,把结果发送给客户端
- 客户端显示服务器发送过来的结果
上传测试结果截图和码云链接
代码
- 服务器端
import javax.crypto.Cipher;import javax.crypto.SecretKey;import javax.crypto.spec.SecretKeySpec;import java.io.*;import java.net.ServerSocket;import java.net.Socket;import java.security.Key;public class Server4 { public static void main(String args[]) { MyDC mydc = new MyDC(); ServerSocket serverForClient = null; Socket socketOnServer = null; DataOutputStream out = null; DataInputStream in = null; try { serverForClient = new ServerSocket(5203); } catch (IOException e1) { System.out.println(e1); } try { System.out.println("等待客户呼叫"); socketOnServer = serverForClient.accept(); //堵塞状态,除非有客户呼叫 System.out.println("客户已连接"); out = new DataOutputStream(socketOnServer.getOutputStream()); in = new DataInputStream(socketOnServer.getInputStream()); Key_DH.DH("Spub.dat","Spri.dat"); int len = Integer.parseInt(in.readUTF()); byte np[] = new byte[len]; for (int i = 0;i
- 客户端
import javax.crypto.Cipher;import javax.crypto.SecretKey;import javax.crypto.spec.SecretKeySpec;import java.io.*;import java.net.Socket;import java.security.Key;import java.util.Optional;import java.util.Scanner;public class Client4 { public static void main(String args[]) { MyBC mybc = new MyBC(); Socket mysocket; DataInputStream in = null; DataOutputStream out = null; Scanner scanner = new Scanner(System.in); String str; try { mysocket = new Socket("192.168.56.1", 5203); System.out.println("客户端启动..."); in = new DataInputStream(mysocket.getInputStream()); out = new DataOutputStream(mysocket.getOutputStream()); System.out.println("请输入中缀表达式:"); str = scanner.nextLine(); Key_DH.DH("Cpub.dat","Cpri.dat"); FileInputStream fp = new FileInputStream("Cpub.dat"); ObjectInputStream bp = new ObjectInputStream(fp); Key kp = (Key) bp.readObject(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(kp); byte[] kb = baos.toByteArray(); out.writeUTF(kb.length + ""); for (int i = 0; i < kb.length; i++) { out.writeUTF(kb[i] + ""); } Thread.sleep(1000); int len = Integer.parseInt(in.readUTF()); byte np[] = new byte[len]; for (int i = 0;i
实验截图
任务五
实验要求
- 基于Java Socket实现客户端/服务器功能,传输方式用TCP
- 客户端让用户输入中缀表达式,然后把中缀表达式调用MyBC.java的功能转化为后缀表达式,把后缀表达式用3DES或AES算法加密通过网络把密文和明文的MD5値发送给服务器
- 客户端和服务器用DH算法进行3DES或AES算法的密钥交换
- 服务器接收到后缀表达式表达式后,进行解密,解密后计算明文的MD5值,和客户端传来的MD5进行比较,一致则调用MyDC.java的功能计算后缀表达式的值,把结果发送给客户端
客户端显示服务器发送过来的结果
实验代码
*服务器端
import javax.crypto.Cipher;import javax.crypto.spec.SecretKeySpec;import java.io.*;import java.net.ServerSocket;import java.net.Socket;import java.security.Key;public class Server5 { public static void main(String args[]) { MyDC mydc = new MyDC(); ServerSocket serverForClient = null; Socket socketOnServer = null; DataOutputStream out = null; DataInputStream in = null; try { serverForClient = new ServerSocket(5203); } catch (IOException e1) { System.out.println(e1); } try { System.out.println("等待客户呼叫"); socketOnServer = serverForClient.accept(); //堵塞状态,除非有客户呼叫 System.out.println("客户已连接"); out = new DataOutputStream(socketOnServer.getOutputStream()); in = new DataInputStream(socketOnServer.getInputStream()); Key_DH.DH("Spub.dat","Spri.dat"); int len = Integer.parseInt(in.readUTF()); byte np[] = new byte[len]; for (int i = 0;i
- 客户端
import javax.crypto.Cipher;import javax.crypto.spec.SecretKeySpec;import java.io.*;import java.net.Socket;import java.security.Key;import java.util.Scanner;public class Client5 { public static void main(String args[]) { MyBC mybc = new MyBC(); Socket mysocket; DataInputStream in = null; DataOutputStream out = null; Scanner scanner = new Scanner(System.in); String str; try { mysocket = new Socket("192.168.56.1", 5203); System.out.println("客户端启动..."); in = new DataInputStream(mysocket.getInputStream()); out = new DataOutputStream(mysocket.getOutputStream()); System.out.println("请输入中缀表达式:"); str = scanner.nextLine(); Key_DH.DH("Cpub.dat","Cpri.dat"); FileInputStream fp = new FileInputStream("Cpub.dat"); ObjectInputStream bp = new ObjectInputStream(fp); Key kp = (Key) bp.readObject(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(kp); byte[] kb = baos.toByteArray(); out.writeUTF(kb.length + ""); for (int i = 0; i < kb.length; i++) { out.writeUTF(kb[i] + ""); } Thread.sleep(500); int len = Integer.parseInt(in.readUTF()); byte np[] = new byte[len]; for (int i = 0;i
实验截图
码云链接
https://gitee.com/mayuda20175203/20175203/tree/master/src/test05
实验感想
本次实验感受最深的就是做的比较晚,到最后之前和我结对的小伙伴已经和别人跑了,,,这个问题其实很早以前就有,总是在赶DDL,手忙脚乱的,以后一定要注意提前,即使有问题也可以从容些。
然后本次实验相当于是将密码学和java相结合,其中的中缀后缀式则是在大一下的离散数学中接触的,实验中比较受启迪的就是编程不应局限于题目要求中所给出的两个模块(即客户端和服务器端),也可以有自己的添加,然后在两个大模块中调用就完事儿了,比如其中的DES,这个我觉得就是“高内聚,低耦合”的一种体现。