20145212 实验五《Java网络编程》
一、实验内容
- 1.运行下载的TCP代码,结对进行,一人服务器,一人客户端;
- 2.利用加解密代码包,编译运行代码,一人加密,一人解密;
- 3.集成代码,一人加密后通过TCP发送;
- 4.结对伙伴:20145223杨梦云,我负责服务端,她负责客户端。
注:加密使用AES或者DES/AES或者DES加密密钥key并发送,使用服务器的公钥加密/公钥算法使用RSA或DH/检验发送信息的完整性使用MD5或者SHA3;
- 5.完成Blog。
二、实验步骤
1.学习TCP
根据如下程序,体会TCP含义。
- 服务器端:
package chapter9;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class ServerTest
{
public static final int PORT = 8081;
public static void main(String[] args) throws IOException
{
ServerSocket s = new ServerSocket(PORT);
System.out.println("Started:"+s);
try
{
Socket socket = s.accept();
try
{
System.out.println("Connection accepted:"+socket);
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())),true);
while(true)
{
String str=in.readLine();
if(str.equals("END"))
break;
System.out.print("Echoing:"+str);
out.println(str);
}
}
finally
{
System.out.println("closing...");
socket.close();
}
}
finally
{
s.close();
}
}
}
- 客户端:
package chapter9;
import java.io.*;
import java.net.InetAddress;
import java.net.Socket;;
public class ClientTest
{
public static void main(String[] args) throws IOException
{
InetAddress addr = InetAddress.getByName("192.168.1.35");
System.out.println("addr ="+addr);
Socket socket = new Socket(addr,ServerTest.PORT);
try
{
System.out.println("socket="+socket);
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())),true);
for(int i =0;i<0;i++)
{
out.println("howdy"+i);
String str = in.readLine();
System.out.println(str);
}
out.println("END");
}
finally
{
System.out.println("closing...");
socket.close();
}
}
}
2.信息安全传送:
发送方A——————>接收方B
A加密时,用B的公钥
B解密时,用B的私钥
·发送方A对信息(明文)采用DES密钥加密,使用RSA加密前面的DES密钥信息,最终将混合信息进行传递。同时用hash函数将明文进行用作验证。
·接收方B接收到信息后,用RSA解密DES密钥信息,再用RSA解密获取到的密钥信息解密密文信息,最终就可以得到我们要的信息(明文)。用hash函数对解出的明文进行验证。
3.运行加解密包中的程序
·运行DES加密代码
import java.io.*;
import javax.crypto.*;
public class Skey_DES{
public static void main(String args[]) throws Exception{
KeyGenerator kg=KeyGenerator.getInstance("DESede");
kg.init(168);
SecretKey k=kg.generateKey( );
FileOutputStream f=new FileOutputStream("key1.dat");
ObjectOutputStream b=new ObjectOutputStream(f);
b.writeObject(k);
}
}
import java.io.*;
import java.security.*;
public class Skey_kb{
public static void main(String args[]) throws Exception{
FileInputStream f=new FileInputStream("key1.dat");
ObjectInputStream b=new ObjectInputStream(f);
Key k=(Key)b.readObject( );
byte[ ] kb=k.getEncoded( );
FileOutputStream f2=new FileOutputStream("keykb1.dat");
f2.write(kb);
// 打印密钥编码中的内容
for(int i=0;i<kb.length;i++){
System.out.print(kb[i]+",");
}
}
}
String keyString=”abcd”;
byte[]keyData=keyString.getBytes();
secretKey myDeskey=new SecretKeySpec(keyData,”DES”);
·获取密钥生成器
KeyGenerator kg=KeyGenerator.getInstance("DESede");
·Java中KeyGenerator类中提供了创建对称密钥的方法。Java中的类一般使用new操作符通过构造器创建对象,但KeyGenerator类不是这样,它预定义了一个静态方法getInstance()
,通过它获得KeyGenerator类型的对象。这种类成为工厂类或工厂。
·方法getInstance( )的参数为字符串类型,指定加密算法的名称。可以是 “Blowfish”、“DES”、“DESede”、“HmacMD5”或“HmacSHA1”等。这些算法都可以实现加密,这里我们不关心这些算法的细节,只要知道其使用上的特点即可。
·初始化密钥生成器
该步骤一般指定密钥的长度。如果该步骤省略的话,会根据算法自动使用默认的密钥长度。指定长度时,若第一步密钥生成器使用的是“DES”算法,则密钥长度必须是56位;若是“DESede”,则可以是112或168位,其中112位有效;若是“AES”,可以是128, 192或256位。
·生成密钥SecretKey k=kg.generateKey( );
使用第一步获得的KeyGenerator类型的对象中generateKey( )方法可以获得密钥。其类型为SecretKey类型,可用于以后的加密和解密。
·通过对象序列化方式将密钥保存在文件中
FileOutputStream f=new FileOutputStream("key1.dat");
ObjectOutputStream b=new ObjectOutputStream(f); b.writeObject(k);
·ObjectOutputStream类中提供的writeObject方法可以将对象序列化,以流的方式进行处理。这里将文件输出流作为参数传递给ObjectOutputStream类的构造器,这样创建好的密钥将保存在文件key1.data中。
4.最终,服务器端代码如下:
package javaapplication40;
import java.net.*;
import java.io.*;
import java.security.*;
import javax.crypto.*;
import javax.crypto.spec.*;
public class MyServer {
/**
* @param args the command line arguments
*/
public static void main(String args[]) {
ServerSocket sc = null;
ServerSocket sc1 = null;
Socket socket=null;
Socket socket1=null;
try {
sc= new ServerSocket(4431);//创建服务器套接字
sc1= new ServerSocket(4430);//创建服务器套接字
System.out.println("端口号:" + sc.getLocalPort());
System.out.println("服务器1已经启动...");
System.out.println("端口号:" + sc1.getLocalPort());
System.out.println("服务器2已经启动...");
socket = sc.accept(); //等待客户端连接
System.out.println("已经建立连接");
socket1 = sc1.accept(); //等待客户端连接
System.out.println("已经建立1连接");
//获得网络输入流对象的引用
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
BufferedReader in1 = new BufferedReader(new InputStreamReader(socket1.getInputStream()));
////获得网络输出流对象的引用
System.out.print("已接收\n");
PrintWriter out=new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())),true);
String aline=in.readLine();//读取客户端传送来的数据
String aline1=in1.readLine();
System.out.print("写入文件中...\n");
byte [] bytes = aline.getBytes("GB2312");
byte [] bytes1 = aline1.getBytes("GB2312");
aline = new String(bytes, "GB2312");
aline1 = new String(bytes1, "GB2312");
FileOutputStream s=new FileOutputStream("key1.dat");
s.write(bytes);
FileOutputStream s1=new FileOutputStream("SEnc.dat");
s1.write(bytes1);
System.out.print("已写入文件\n");
//生成解密密钥
FileInputStream f=new FileInputStream("key1.dat");
ObjectInputStream b=new ObjectInputStream(f);
Key k=(Key)b.readObject( );
byte[ ] kb=k.getEncoded( );
FileOutputStream f2=new FileOutputStream("keykb1.dat");
f2.write(kb);
System.out.print("生成解密密钥\n");
// 打印密钥编码中的内容
for(int i=0;i<kb.length;i++){
// System.out.print(kb[i]+",");
}
//解密
FileInputStream a=new FileInputStream("SEnc.dat");
int num=a.available();
byte[ ] ctext=new byte[num];
a.read(ctext);
FileInputStream f1=new FileInputStream("keykb1.dat");
int num2=f1.available();
byte[ ] keykb=new byte[num2];
f1.read(keykb);
SecretKeySpec e=new SecretKeySpec(keykb,"DESede");
Cipher cp=Cipher.getInstance("DESede");
cp.init(Cipher.DECRYPT_MODE, k);
byte []ptext=cp.doFinal(ctext);
String p=new String(ptext,"GB2312");
System.out.print("解密中...\n");
System.out.println(p);
//返回
System.out.println("从客户端接收到信息为:"+p); //通过网络输出流返回结果给客户端
out.println(p);
out.close();
in.close();
sc.close();
} catch (Exception e) {
System.out.println(e);
}
}
}
三、实验中遇到的问题及解决方案
- 出现java.net.BindException:Address already in use: JVM_Bind
通过百度原因,我们知道这是因为端口已经被占用,只需要找一个没有被占用的端口就能解决这个问题。
- 两台电脑无法联通
1.在实验室时我们起初是从网络上搜ip出来的地址,然后显示连接超时。之后用在命令行中用ipconfig查看地址,找到自己的IPv4地址,但是换上之后还是失败了——连接超时。
2.我们认为可能是两台电脑没有在同一个网络里而不发链接成功,尝试着用一台电脑连接网络然后wifi给另一台电脑使用,然后再次连接两台电脑,终于连通成功了。
- 连接成功后出现java.net.SocketException: Connection reset
该异常在客户端和服务器端均有可能发生,引起该异常的原因有两个,第一个就是如果一端的Socket被关闭(或主动关闭或者因为异常退出而引起的关闭),另一端仍发送数据,发送的第一个数据包引发该异常(Connect reset by peer)。另一个是一端退出,但退出时并未关闭该连接,另一端如果在从连接中读数据则抛出该异常(Connection reset)。简单的说就是在连接断开后的读和写操作引起的。
实验中出现了问题是因为代码在运行到读写数据的时候链接已经失效,通过修改,最终成功。
服务端:
客户端:
## PSP(Personal Software Process)时间
步骤 | 耗时 | 百分比 |
---|---|---|
需求分析 | 20min | 8.3% |
设计 | 60min | 25.0% |
代码实现 | 80min | 33.3% |
测试 | 40min | 16.7% |
分析总结 | 40min | 16.7% |