题目:
实验一:Socket程序设计试验
【实验目的及要求】在Uinx/Linux/Windows 环境下通过socket方式实现一个基于Client/Server 或是P2P模式的文件传输程序。要求:要求独立完成。【实验原理和步骤】1. 确定传输模式:通过socket方式实现一个基于Client/Server或P2P模式的文件传输程序。2. 如果选择的是Client/Server模式的文件传输程序,则需要分别实现客户端和服务器端程序。客户端:用面向连接的方式实现通信。采用Socket 类对象,接收服务器发送的文件并保存在特定的位置。服务器端:监听客户请求,读取磁盘文件并向客户端发送文件。注意:需要实现文件的读写操作。3. 如果选择的是P2P模式的文件传输程序,则需要实现一个Peer程序,它即是客户端,也是服务器端。Peer程序需要实现文件上传、下载及文件读写等操作。
我选的是Client/Server传输模式,因为简单嘛。以后有空要把P2P也弄一下,稍微改写代码就行了应该。(虽然应该这种东西几乎都是不可靠加坑爹的= =)
拿到题目的时候,我第一个疑问就是:文件传输,是仅限于txt格式的文本呢?还是指的任意文件?
为什么会这样想呢,因为书上给出的流式Socket的例子是发送一个String类型的消息。我就想,如果是txt文本的话,直接用同样的方法发过去,再把缓冲区里的文本写到文件里就行了。事实证明我果然是Too simple, too naive啊。新手嘛,体谅体谅= =所以果断题意是传输任意文件。我当时没考虑直接用书本的BufferedReader和PrintWriter行不行(比如传一个文件流参数进去,以后有空回来要试试,后天还要考人工智能,所以苦逼的我就先把这问题放一放了,直觉上是可以的)。当时觉得一定有个类是可以很好解决这个问题的,于是就去百度了,发现了一篇不错的东西: 博主这里是一个传输大文件的demo。我在其中发现了我想要的东西:DataInputStream和DataOutputStream,这两个东东可以实现文件流传输。
因为这两个类差不多,我就在帮助文档查了下前者。解释如下:
数据输入流允许应用程序以与机器无关方式从底层输入流中读取基本 Java 数据类型。应用程序可以使用数据输出流写入稍后由数据输入流读取的数据。DataInputStream 对于多线程访问不一定是安全的。 线程安全是可选的,它由此类方法的使用者负责。
继承关系:java.lang.Object->java.io.InputStream->java.io.FilterInputStream(没打错)->java.io.DataInputStream
看来这个具体类(抽象类InputStream的子类)有个牛逼的地方就是允许应用程序以与机器无关方式从底层输入流中读取基本 Java 数据类型。(貌似感觉BufferedReader和PrintWriter可以搞起文件了)
不管了,先自己用着。参考样例里用了BufferedInputStream类,我没用,因为感觉不需要。而且帮助文档解释的不是特别清楚(吐槽:还是MSDN给力啊= =)。事实证明我的想法是对的。
为什么要用流式Socket呢?数据报的不行?
因为流式Socket中有一个ServerSocket类,专门用来接收请求并建立连接。很方便。
照着印象打完了,跑起,成功传输,txt,pdf和jpg都无压力。好,至少框架是OK的。剩下来就是把指定原文件改成由用户在控制台输入文件名了。
思路:用String存起来,在通过数据Socket发到服务器,服务器接收,更新路径。
其实很简单,但是也出错了。
一开始用DataInputStream和DataOutputStream传byte数组,再在服务器用toString转换,发现会乱码,导致无法读取。
于是想对策,能不能用被冷落很久了的BufferedReader和PrintWriter?想了一想,好像真行,又百度了一下,网友推荐String类型的网络传输还是用BufferedReader和PrintWriter比较省心。果断敲进去了,再跑,OK。任务完成。
还留了个疑问,getInputStream()的返回值能不能赋给多个对象?close掉其中一个对其他的有没有影响?(以后写demo检查下,今天时间赶呀)
另外今天对用InputStream和OutputStream的了解又深了些。以前老记不住JAVA的输入。不像C有scanf那么方便。现在熟悉了InputStream和BufferedReader之后,发现其实很简单:
BufferedReader consoleReader = new BufferedReader(new InputStreamReader(System.in));
把BufferedReader想象成一个缓冲区,然后传InputStreamReader进来是告诉BufferedReader,你是一个输入的缓冲区,最后再用System.in定向从哪输入。很好理解。
实验代码:
client:
1 import java.io.BufferedReader; 2 import java.io.DataInputStream; 3 import java.io.DataOutputStream; 4 import java.io.FileOutputStream; 5 import java.io.IOException; 6 import java.io.InputStreamReader; 7 import java.io.OutputStream; 8 import java.io.OutputStreamWriter; 9 import java.io.PrintWriter; 10 11 import java.net.InetAddress; 12 import java.net.Socket; 13 import java.net.UnknownHostException; 14 15 import java.util.Scanner; 16 17 18 //Client类 19 public class client { 20 21 public static void main(String[] args){ 22 try { 23 24 //定义服务器地址 25 InetAddress acceptorHost = InetAddress.getByName("localhost"); 26 27 //定义服务器端口 28 int acceptorPort = 10000; 29 30 Socket clientSocket = null; 31 32 //定义存放所接收文件的路径 33 String fileName = "E:\\dstest\\moveto\\"; 34 35 while(true) 36 { 37 38 Scanner cin = new Scanner(System.in); 39 40 //输入-1,则关闭客户;输入其余字符继续传送下一个文件 41 System.out.println("enter -1 if you want to quit or any other to stay"); 42 int flag = cin.nextInt(); 43 if(flag == -1) 44 break; 45 46 //初始化Socket,构造时就会链接,同时具备连接和传输数据的功能 47 clientSocket = new Socket(acceptorHost,acceptorPort); 48 49 System.out.println("enter the file name you want to download\n"); 50 51 52 BufferedReader consoleReader = new BufferedReader(new InputStreamReader(System.in)); 53 String temp = consoleReader.readLine(); 54 55 //新建一个保存当前所接收文件的文件路径的String对象,防止固定路径fileName被修改 56 String fullfileName = fileName + temp; 57 58 //发送所需的文件的文件名至服务器 59 OutputStream sendFileName = clientSocket.getOutputStream(); 60 PrintWriter sendFile = new PrintWriter(new OutputStreamWriter(sendFileName)); 61 sendFile.println(temp); 62 63 //冲刷缓冲区,使数据全部写入缓冲区,以防Socket的意外关闭 64 sendFile.flush(); 65 66 67 DataInputStream clientInput = new DataInputStream(clientSocket.getInputStream()); 68 DataOutputStream clentOutput = new DataOutputStream(new FileOutputStream(fullfileName)); 69 70 //定义缓冲区 71 int bufferSize = 8192; 72 byte[] buff = new byte[bufferSize]; 73 74 //从文件流读入byte数组,再输出到Data Socket的缓冲流里 75 while(true) 76 { 77 int read = 0; 78 if(clientInput != null) 79 read = clientInput.read(buff); 80 81 if(read == -1) 82 break; 83 84 clentOutput.write(buff,0,read); 85 } 86 sendFile.close(); 87 88 //冲刷缓冲区,使数据全部写入缓冲区,以防Socket的意外关闭 89 clentOutput.flush(); 90 91 clientInput.close(); 92 clentOutput.close(); 93 94 95 System.out.println("file is received"); 96 97 } 98 clientSocket.close(); 99 System.out.println("Client closed");100 101 102 } catch (UnknownHostException e) {103 // TODO Auto-generated catch block104 e.printStackTrace();105 } catch (IOException e) {106 // TODO Auto-generated catch block107 e.printStackTrace();108 }109 }110 }
server:
1 import java.io.*; 2 import java.net.ServerSocket; 3 import java.net.Socket; 4 import java.util.Scanner; 5 6 7 8 //Server类 9 public class test1 { 10 11 public static void main(String arg[]) { 12 13 //定义接收连接Socket端口号 14 final int port = 10000; 15 16 ServerSocket myServerSocket; 17 18 try { 19 20 //初始化接收连接Socket 21 myServerSocket = new ServerSocket(port); 22 23 Socket dataSocket = null; 24 25 //定义存放待传送文件的服务器路径 26 String fileName = "E:\\dstest\\distributedSystem\\"; 27 28 29 while(true) 30 { 31 32 Scanner cin = new Scanner(System.in); 33 34 //输入-1,则关闭接收连接Socket与服务器;输入其余字符继续传送下一个文件 35 System.out.println("-1 to shut the Server or any other to stay\n"); 36 int flag = cin.nextInt(); 37 if(flag == -1) 38 break; 39 40 //等待客户端发起连接请求 41 System.out.println("Receiving and Waiting..."); 42 43 //接收连接 44 dataSocket = myServerSocket.accept(); 45 System.out.println("Connected!"); 46 47 //获取输入流,通过输入流获取文件名 48 BufferedReader readName = new BufferedReader(new InputStreamReader(dataSocket.getInputStream())); 49 String temp = readName.readLine(); 50 System.out.println("file name is: " + temp); 51 52 //新建一个保存当前所需文件的文件路径的String对象,防止固定路径fileName被修改 53 String fullfileName = fileName + temp; 54 55 //创建File变量,获取文件长度 56 File fi = new File(fullfileName); 57 System.out.println("文件长度: " + (int)fi.length()); 58 59 //DataInputStream和DataOutputStream都可以选java.io或是org.omg.CORBA。 本次实验基于本机,所以不存在不同环境下的通讯,所以用java.io里的即可。 60 //InputStream 是抽象的超类,不能new,所以用具体的DataInputStream类。书上的OutputStream全部是直接被赋值的,不是new. 61 62 DataInputStream fileIn = new DataInputStream(new FileInputStream(fullfileName)); 63 DataOutputStream fileOut = new DataOutputStream(dataSocket.getOutputStream()); 64 65 //定义缓冲区 66 int bufferSize = 8192; 67 byte[] buff = new byte[bufferSize]; 68 69 //从文件流读入byte数组,再输出到Data Socket的缓冲流里 70 while(true) 71 { 72 int read = 0; 73 if(fileIn != null) 74 read = fileIn.read(buff); 75 76 if(read == -1) 77 break; 78 79 fileOut.write(buff,0,read); 80 } 81 82 //冲刷缓冲区,使数据全部写入缓冲区,以防Socket的意外关闭 83 fileOut.flush(); 84 85 fileIn.close(); 86 fileOut.close(); 87 88 System.out.println("file has been sent"); 89 90 //关闭数据Socket 91 dataSocket.close(); 92 System.out.println("data socket is closed"); 93 } 94 95 //关闭接收连接Socket与服务器 96 myServerSocket.close(); 97 System.out.println("Sever is closed"); 98 99 } catch (IOException e) {100 // TODO Auto-generated catch block101 e.printStackTrace();102 }103 104 105 106 }107 }