20175211 "Experiment 5 network programming and security" 6- (OPTIONAL) test report

First, the results show

Cloud link code

  • Achieve four operations in parentheses

  • May be a decimal ( "."), Positive and negative numbers (by "±" key), the square root of the result ( "√"), the input result ( "of Mr") calculated last time is cleared and the result of the formula ( " CE "), backspace (" ← ")

  • Exception handling, comprising about malformed formula, deletions parentheses, division by zero errors

  • Fractional rational mode switching mode and

  • Login, registration (not yet completed the back-end database connection)

Second, the experimental content and process

1, needs analysis & Page Layout

Page Layout Reference iPhone comes with a calculator, but to achieve brackets button, find the row is not good-looking rectangle. . So pay more MR and the square root function.
Taking into account to meet rational calculation and score calculation, so the design of a menu to switch modes. While scores were calculated can not handle floating-point numbers, just move the decimal point key to change /.
Way to be a sign-on capabilities, plans to use fractional mode only after the user successfully logs in, has not yet been completed.

In summary, we need three Activity, MainActivity achieve calculator, LoginActivity achieve login, RegisterActivity achieve registration. Focus is MainActivity

The following list of documents

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="cn.edu.besti.is.onlinecalculator">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".LoginActivity"
            android:label="@string/login_screen_title"
            android:parentActivityName=".MainActivity">//ActionBar出现返回键,设置上一级界面
        </activity>
        <activity android:name=".RegisterActivity"
            android:label="注册"
            android:parentActivityName=".LoginActivity">
        </activity>
    </application>
    <uses-permission android:name="android.permission.INTERNET" />//允许该应用程序链接网络
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

</manifest>

Layout file activity_main.xmlas follows

<?xml version="1.0" encoding="utf-8"?>
<GridLayout
    xmlns:android="http://schemas.android.com/apk/res/android"

    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:useDefaultMargins="false"
    android:alignmentMode="alignBounds"
    android:columnOrderPreserved="false"
    android:layout_gravity="center_horizontal"
    android:background="#111"
    android:columnCount="4"
    android:rowCount="7"
    >

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="200sp"
        android:layout_row="0"
        android:layout_column="3">

        <TextView
            android:id="@+id/textView1"
            android:layout_width="match_parent"
            android:ellipsize="start"
            android:singleLine="true"
            android:gravity="center|start"
            android:layout_height="90sp"
            android:layout_gravity="center_horizontal"
            android:background="#111"
            android:text=""
            android:textAppearance="?android:attr/textAppearanceLarge"
            android:textColor="#fff"
            android:textSize="45sp" />

        <TextView
            android:id="@+id/textView2"
            android:layout_width="match_parent"
            android:layout_height="110sp"
            android:layout_gravity="bottom"
            android:gravity="end|center"
            android:background="#000"
            android:text=""
            android:singleLine="true"
            android:textColor="#fff"
            android:textSize="60sp" />
    </FrameLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_row="1"
        android:layout_column="3"
        android:layout_gravity="top"
        android:orientation="horizontal"
        >
        
        <Button
            android:id="@+id/button1_1"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:layout_margin="3sp"
            
            android:textSize="45sp"
            android:text="CE"
            android:background="@drawable/button_style1"
            />


        <Button
            android:id="@+id/button1_2"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:layout_margin="3sp"

            android:textSize="45sp"
            android:background="@drawable/button_style1"
            android:text="±" />

        <Button
            android:id="@+id/button1_3"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:layout_margin="3sp"

            android:textSize="45sp"
            android:background="@drawable/button_style1"
            android:text="←" />

        <Button
            android:id="@+id/button1_4"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:layout_margin="3sp"

            android:textSize="45sp"
            android:textColor="#fff"
            android:background="@drawable/button_style2"
            android:text="√" />


    </LinearLayout>

    ···

</GridLayout>

Use GridLayout with LinearLayout and FrameLayout, FrameLayout comprising two TextView, respectively, and the calculated result of the expression entered by the user.
Each LinearLayout on behalf of a row of buttons, different buttons to set a different style to button_style1.xmlan example

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" >
    <item android:state_pressed="true">//按下时的样式
        <shape android:shape="rectangle">//圆角按钮
            <solid android:color="#eee"/>//颜色
            <corners android:radius="8dip"/>//圆角程度
        </shape>
    </item>

    <item android:state_pressed="false">//松开时的样式
        <shape android:shape="rectangle">
            <solid android:color="#bbb"/>
            <corners android:radius="8dip"/>
        </shape>
    </item>

</selector>

The main interface effect is as follows

The layout of other pages see cloud link code

2, MainActivity

package cn.edu.besti.is.onlinecalculator;

import android.content.Intent;
import android.os.StrictMode;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

import java.util.LinkedList;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private Button[] buttons = new Button[23];
    private int[] ids = new int[]{
            R.id.button1_1, R.id.button1_2, R.id.button1_3, R.id.button1_4,
            R.id.button2_1, R.id.button2_2, R.id.button2_3, R.id.button2_4,
            R.id.button3_1, R.id.button3_2, R.id.button3_3, R.id.button3_4,
            R.id.button4_1, R.id.button4_2, R.id.button4_3, R.id.button4_4,
            R.id.button5_1, R.id.button5_2, R.id.button5_3, R.id.button5_4,
            R.id.button6_1, R.id.button6_2, R.id.button6_3
    };
    private TextView textView1, textView2;
    private String result = "0";
    private LinkedList<String> expr = new LinkedList<>();
    private String Mod = "Rational";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().detectDiskReads().detectDiskWrites().detectNetwork().penaltyLog().build());
        StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectLeakedSqlLiteObjects().detectLeakedClosableObjects().penaltyLog().penaltyDeath().build());
        for (int i = 0; i < ids.length; i++) {
            buttons[i] = findViewById(ids[i]);
            buttons[i].setOnClickListener(this);
        }
        this.textView1 = findViewById(R.id.textView1);
        this.textView2 = findViewById(R.id.textView2);
    }

    //onClick方法处理各种点击事件
    @Override
    public void onClick(View view) {
        int id = view.getId();
        Button button = view.findViewById(id);
        String current = button.getText().toString();
        String token;
        StringBuilder expression = new StringBuilder();
        if (current.equals("CE")) {
            expr.clear();
            result = "0";
        } else if (current.equals("±")) {
            if (!expr.isEmpty()) {
                token = expr.pollLast();
                if (!calcArithmatic.isOperator(token)) {
                    if (token.contains("-")) {
                        token = token.replaceAll("-", "");
                    } else {
                        token = "-" + token;
                    }
                }
                expr.offerLast(token);
            }
        } else if (current.equals("←")) {
            expr.pollLast();
        } else if (current.equals(".") || current.equals("/")) {
            if (!expr.isEmpty()) {
                token = expr.pollLast();
                if (!calcArithmatic.isOperator(token)) {
                    if (!token.contains(current)) {
                        token += current;
                    }
                }
                expr.offerLast(token);
            }
        } else if (current.equals("=")) {//按下等号时,在本地将中缀表达式转为后缀表达式,传输给服务端,接收服务器的计算结果
            if (!expr.isEmpty()) {
                for (String s : expr) {
                    expression.append(" ").append(s);
                }
                try {
                    MyBC myBC = new MyBC();
                    final String formula = myBC.getEquation(expression.toString().trim());
                    try {
                        result = Client.Connect(formula, Mod);
                    } catch (Exception e) {
                        Toast.makeText(this, "请检查网络连接", Toast.LENGTH_SHORT).show();
                    }
                } catch (ExprFormatException e) {
                    result = e.getMessage();
                } catch (ArithmeticException e0) {
                    result = "Divide Zero Error";
                } finally {
                    expr.clear();
                }
            }
        } else if (current.equals("√")) {
            if (Mod.equals("Rational")) {
                result = String.valueOf(Math.sqrt(Double.parseDouble(result)));
            }
        } else if (current.equals("Mr")) {
            if (result.matches("[0-9.\\-/]+")) {
                current = result;
                expr.offerLast(current);
            }
        } else if (calcArithmatic.isOperator(current)) {
            expr.offerLast(current);
        } else {
            if (!expr.isEmpty()) {
                token = expr.pollLast();
                if (calcArithmatic.isOperator(token)) {
                    expr.offerLast(token);
                    expr.offer(current);
                } else {
                    token += current;
                    expr.offerLast(token);
                }
            } else {
                expr.offerLast(current);
            }
        }
        for (String s : expr) {
            expression.append(" ").append(s);
        }
        textView1.setText(expression.toString().trim());
        textView2.setText(result);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }


    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.option1:
                Intent intent = new Intent(this, LoginActivity.class);
                startActivity(intent);
                return true;
            case R.id.option2:
                expr.clear();
                result = "";
                if (item.getTitle().equals("分数模式")) {
                    buttons[21].setText("/");
                    item.setTitle("有理数模式");
                    Mod = "Fraction";
                } else {
                    buttons[21].setText(".");
                    item.setTitle("分数模式");
                    Mod = "Rational";
                }
                return true;
            default:
                return super.onOptionsItemSelected(item);
        }
    }
}
  • 使用private LinkedList<String> expr = new LinkedList<>();来处理用户的每次点击造成的输入,方便将数字和操作符分开,如果队尾元素是数字或小数点,而当前值是数字或小数点,则对队尾元素进行字符串拼接;如果队尾元素是操作符,就直接入队。最后计算结果的时候,依次拼接队中元素,形成中缀表达式。

  • MyBC实现中缀转后缀,大致流程如下,异常处理未体现

具体代码如下

package cn.edu.besti.is.onlinecalculator;

import java.util.EmptyStackException;
import java.util.Stack;
import java.util.StringTokenizer;

class MyBC extends calcArithmatic{
    private Stack<String> OpStack;
    private String output="";

    MyBC(){
        OpStack = new Stack<>();
    }
    private void Shunt(String expr)throws ExprFormatException{
        String token;
        StringTokenizer tokenizer = new StringTokenizer(expr);
        while (tokenizer.hasMoreTokens()){
            token=tokenizer.nextToken();
            if (isOperator(token)){
                if (token.equals(")")){
                    try{
                        while (!OpStack.peek().equals("(")) {
                            output = output.concat(OpStack.pop() + " ");
                        }
                        OpStack.pop();
                    }catch (EmptyStackException e){
                        throw new ExprFormatException("Missing '('");
                    }
                }
                else if (!OpStack.empty()){
                    if(judgeValue(token)>judgeValue(OpStack.peek()) || token.equals("(")) {
                        OpStack.push(token);
                    }
                    else {
                        while (!OpStack.empty() && judgeValue(token)<=judgeValue(OpStack.peek())){
                            output=output.concat(OpStack.pop()+" ");
                        }
                        OpStack.push(token);
                    }
                } else {
                    OpStack.push(token);
                }
            } else {
                output=output.concat(token+" ");
            }
        }
        while (!OpStack.empty()){
            if (OpStack.peek().equals("(")){
                throw new ExprFormatException("Missing ')'");
            }
            output=output.concat(OpStack.pop()+" ");
        }
    }
    private int judgeValue(String str){
        int value;
        switch(str){
            case "(":
                value=1;
                break;
            case "+":
            case "-":
                value=2;
                break;
            case "×":
            case "÷":
                value=3;
                break;
            case ")":
                value=4;
                break;
            default:
                value=0;
        }
        return value;
    }
    String getEquation(String str) throws ExprFormatException{
        Shunt(str);
        return output;
    }
}
  • 输入等号后如下连接到客户端,捕获不同异常来输出不同结果,如果网络连接超时,弹出Toast提示
try {
    MyBC myBC = new MyBC();
    final String formula = myBC.getEquation(expression.toString().trim());
    try {
            result = Client.Connect(formula, Mod);
        } catch (Exception e) {
            Toast.makeText(this, "请检查网络连接",Toast.LENGTH_SHORT).show();
        }
    } catch (ExprFormatException e) {
        result = e.getMessage();
    } catch (ArithmeticException e0) {
        result = "Divide Zero Error";
    } finally {
        expr.clear();
    }
}

正常来说进行网络请求必须在线程中进行,但是因为我们只是一个小程序,阻塞一下没有什么问题,所以我就直接在主进程里面发送请求,需在MainActivity里面加上如下代码

StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().detectDiskReads().detectDiskWrites().detectNetwork().penaltyLog().build());
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectLeakedSqlLiteObjects().detectLeakedClosableObjects().penaltyLog().penaltyDeath().build());

同时Android应用默认不开启网络连接,要在清单文件里声明
<uses-permission android:name="android.permission.INTERNET" />

  • 以下代码实现选择菜单功能
@Override
public boolean onCreateOptionsMenu(Menu menu) {
    getMenuInflater().inflate(R.menu.main, menu);
    return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
        case R.id.option1:
            Intent intent = new Intent(this, LoginActivity.class);
            startActivity(intent);
            return true;
        case R.id.option2:
            expr.clear();
            result = "";
            if (item.getTitle().equals("分数模式")) {
                buttons[21].setText("/");
                item.setTitle("有理数模式");
                Mod = "Fraction";
            } else {
                buttons[21].setText(".");
                item.setTitle("分数模式");
                Mod = "Rational";
            }
            return true;
        default:
            return super.onOptionsItemSelected(item);
    }
}

点击切换模式以后,仅仅是将"."按钮的值改成"/",因为在处理点击事件的时候也是根据被点击按钮的值来决定行为的。
同时切换模式后相应的改变菜单里模式按钮的文字。
menu.xml如下,app:showAsAction="never"决定该菜单按钮的位置,never代表永远折叠在菜单中

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <item android:id="@+id/option1"
        android:title="登录"
        app:showAsAction="never" />

    <item android:id="@+id/option2"
        android:title="分数模式"
        app:showAsAction="never"/>
</menu>

3、Client类

我定义了Client类来完成发送请求和收发数据,在这个过程中进行加密传输,代码如下

package cn.edu.besti.is.onlinecalculator;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

import java.io.*;
import java.security.*;
import java.security.spec.X509EncodedKeySpec;
import java.net.*;

public class Client {
    public static String Connect(String formula, String mod) throws Exception {
        String mode = "AES";
        Socket mysocket;
        DataInputStream in;
        DataOutputStream out;
        mysocket = new Socket();
        mysocket.connect(new InetSocketAddress("172.30.7.19", 2010),5000);
        mysocket.setSoTimeout(5000);
        in = new DataInputStream(mysocket.getInputStream());
        out = new DataOutputStream(mysocket.getOutputStream());
        //使用AES进行后缀表达式的加密
        KeyGenerator kg = KeyGenerator.getInstance(mode);
        kg.init(128);
        SecretKey k = kg.generateKey();//生成密钥
        byte[] mkey = k.getEncoded();
        Cipher cp = Cipher.getInstance(mode);
        cp.init(Cipher.ENCRYPT_MODE, k);
        byte[] ptext = formula.getBytes("UTF8");
        byte[] ctext = cp.doFinal(ptext);

        //将加密后的后缀表达式传送给服务器
        String out1 = B_H.parseByte2HexStr(ctext);
        out.writeUTF(out1);

        //创建客户端DH算法公、私钥
        KeyPair keyPair = Key_DH5_6.createPubAndPriKey();
        PublicKey pbk = keyPair.getPublic();//Client公钥
        PrivateKey prk = keyPair.getPrivate();//Client私钥

        //将公钥传给服务器
        byte[] cpbk = pbk.getEncoded();
        String CpubKey = B_H.parseByte2HexStr(cpbk);
        out.writeUTF(CpubKey);
        Thread.sleep(1000);

        //接收服务器公钥
        String SpubKey = in.readUTF();
        byte[] spbk = H_B.parseHexStr2Byte(SpubKey);
        KeyFactory kf = KeyFactory.getInstance("DH");
        PublicKey serverPub = kf.generatePublic(new X509EncodedKeySpec(spbk));

        //生成共享信息,并生成AES密钥
        SecretKeySpec key = KeyAgree5_6.createKey(serverPub, prk);

        //对加密后缀表达式的密钥进行加密,并传给服务器
        cp.init(Cipher.ENCRYPT_MODE, key);
        byte[] ckey = cp.doFinal(mkey);
        String Key = B_H.parseByte2HexStr(ckey);
        out.writeUTF(Key);

        out.writeUTF(mod);
        //接收服务器回答
        return in.readUTF();
    }
}

如下设置连接请求超时时间为5秒
mysocket.connect(new InetSocketAddress("172.30.7.19", 2010),5000);
如下设置收发数据超时时间为5秒
mysocket.setSoTimeout(5000);
密码学部分参考我搭档的博客

4、服务器端

服务器端简单的用Java实现,代码如下

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.security.*;
import java.security.spec.X509EncodedKeySpec;

public class Server extends Thread {
    Socket socketOnServer;

    public Server(Socket socketOnServer) {
        super();
        this.socketOnServer = socketOnServer;
    }

    public static void main(String[] args) {
        ServerSocket serverForClient;
        try {
            serverForClient = new ServerSocket(2010);
            while (true) {
                System.out.println(currentThread()+"等待客户呼叫:");
                Socket socketOnServer = serverForClient.accept();
                new Server(socketOnServer).start();
            }
        } catch (IOException e1) {
            System.out.println(e1.getMessage());
        }

    }

    @Override
    public void run() {
        String mode = "AES";

        DataOutputStream out = null;
        DataInputStream in = null;

        String result;
        try {
            out = new DataOutputStream(socketOnServer.getOutputStream());
            in = new DataInputStream(socketOnServer.getInputStream());

            //接收加密后的后缀表达式
            String cformula = in.readUTF();
            byte cipher[] = H_B.parseHexStr2Byte(cformula);

            //接收Client端公钥
            String push = in.readUTF();
            byte np[] = H_B.parseHexStr2Byte(push);
            KeyFactory kf = KeyFactory.getInstance("DH");
            PublicKey ClientPub = kf.generatePublic(new X509EncodedKeySpec(np));

            //创建服务器DH算法公、私钥
            KeyPair keyPair = Key_DH5_6.createPubAndPriKey();
            PublicKey pbk = keyPair.getPublic();//Server公钥
            PrivateKey prk = keyPair.getPrivate();//Server私钥

            //将服务器公钥传给Client端
            byte cpbk[] = pbk.getEncoded();
            String CpubKey = B_H.parseByte2HexStr(cpbk);
            out.writeUTF(CpubKey);
            Thread.sleep(1000);

            //生成共享信息,并生成AES密钥
            SecretKeySpec key = KeyAgree5_6.createKey(ClientPub, prk);

            String k = in.readUTF();//读取加密后密钥
            byte[] encryptKey = H_B.parseHexStr2Byte(k);
            String mod = in.readUTF();
            //对加密后密钥进行解密
            Cipher cp = Cipher.getInstance(mode);
            cp.init(Cipher.DECRYPT_MODE, key);
            byte decryptKey[] = cp.doFinal(encryptKey);

            //对密文进行解密
            SecretKeySpec plainkey = new SecretKeySpec(decryptKey, mode);
            cp.init(Cipher.DECRYPT_MODE, plainkey);
            byte[] plain = cp.doFinal(cipher);

            //计算后缀表达式结果
            String formula = new String(plain);
            MyDC myDC = new MyDC(mod);
            try {
                result = myDC.calculate(formula);
                //后缀表达式formula调用MyDC进行求值
            } catch (ExprFormatException e) {
                result = e.getMessage();
            } catch (ArithmeticException e0) {
                result = "Divide Zero Error";
            }
            //将计算结果传给Client端
            out.writeUTF(result);
        } catch (Exception e) {
            System.out.println("客户已断开" + e);
        }
    }
}
  • 服务端必须使用多线程来防止阻塞
  • MyDC流程大致如下,真正在计算时会根据是有理数模式还是分数模式使用不同的计算规则

  • 密码学部分同样参考我搭档的博客

5、登录、注册部分

要实现ActionBar出现返回键,在清单文件中相应的Activity下设置parentActivityName

<activity android:name=".LoginActivity"
    android:label="@string/login_screen_title"
    android:parentActivityName=".MainActivity">//ActionBar出现返回键,设置上一级界面
</activity>

未完待续,随缘更新(这已经超出实验的范围了,我只是随便玩玩)

三、遇到问题及解决

  • 问题1:测试时client尝试连接127.0.0.1一直连接不上,服务端一直在等待客户呼叫,没有任何反应。
  • 问题1解决:说明根本就没有向我主机发送请求,原来Android程序中尝试连接localhost,程序会将Android手机作为主机,当然连不到我服务端所在的电脑。应该将地址改为内网地址

  • 问题2:使用DH算法协商密钥时需要写入文件,但是Android虚拟手机没有写权限。。
  • 问题2解决(并没有):没有找到改权限的方法,所以只能直接传输密钥,不经过文件。

四、心得体会

虽然时间不是很充裕,但还是想熬夜敲代码,因为我不确定我到底有没有这个能力完成它,自然要挑战一下。因为没有系统地学过Android,很多地方都是现查现学,参考别人的代码改,总的来说我觉得最后做出来的东西还算比较满意。在这个过程中我更加深入的了解了Android的开发机制,学会了一些小技巧,一些组件的用法等等,同时对Java web编程也有了一定了解,我觉得Android其实和Web编程还是有相似之处的,希望之后能将数据库部分完成,“活学活用”一下。

五、码云链接

Guess you like

Origin www.cnblogs.com/20175211lyz/p/10937683.html