Java 网络编程---分布式文件协同编辑器设计与实现

 

目录:

第一部分:Java网络编程知识

(一)简单的Http请求

  一般浏览网页时,使用的时Ip地址,而IP(Internet Protocol,互联网协议)目前主要是IPv4和IPv6.

  IP地址是一个32位整数,一般分成4个八位二进制,为了方便记忆一般将八位整数换算为一个0-255的十进制整数。

 InetAddressTest

   利用Http的这些知识就可以实现一个多线程下载器,以及爬虫的基础向web站点发送GET/POST请求:

  (1)一个简单的多线程下载器

  1 import java.net.*;
  2 import java.io.RandomAccessFile;
  3 import java.io.InputStream;
  4 public class DownUtil
  5 {
  6     //下载路径
  7     private String path;
  8     //指定下载文件存储的位置
  9     private String targetFile;
 10     //定义使用多少线程下载
 11     private int threadNum;
 12     //定义下载线程对象
 13     private DownThread[] threads;
 14     //定义下载文件的总大小
 15     private int fileSize;
 16 
 17     public DownUtil(String path,String target,int threadNum)
 18     {
 19         this.path = path;
 20         this.targetFile=target;
 21         this.threadNum=threadNum;
 22         //初始化threads数组
 23         threads=new DownThread[threadNum];
 24     }
 25     public void download() throws Exception
 26     {
 27         URL url = new URL(path);
 28         HttpURLConnection conn = (HttpURLConnection) url.openConnection();
 29         conn.setConnectTimeout(5*1000);
 30         conn.setRequestMethod("GET");
 31         conn.setRequestProperty(
 32             "Accept",
 33             "image/gift,image/jpeg,image/pjpeg,image/pjpeg"
 34             +"application/x-shockwave-flash,application/xaml+xml"
 35             +"application/vnd.ms-xpsdocument,application/x-ms-xbap"
 36             +"application/x-ms-application,application/vnd.ms-excel"
 37             +"application/vnd.ms-powerpoint,application/msword,*/*"
 38         );
 39         conn.setRequestProperty("Accept-Language","zh-CN");
 40         conn.setRequestProperty("Charset","UTF-8");
 41         conn.setRequestProperty("Connection","Keep-Alive");
 42         //获得文件大小
 43         fileSize = conn.getContentLength();
 44         conn.disconnect();
 45         int currentPartSize=fileSize/threadNum+1;
 46         RandomAccessFile file = new RandomAccessFile(targetFile,"rw");
 47         //设置本地文件大小
 48         file.setLength(fileSize);
 49         file.close();
 50         for(int i=0;i<threadNum;i++)
 51         {
 52             //计算每个线程开始的位置
 53             int startPos = i*currentPartSize;
 54             //每个线程使用一个RandomAccessFile下载
 55             RandomAccessFile currentPart=new RandomAccessFile(targetFile,"rw");
 56             //定位线程下载的位置
 57             currentPart.seek(startPos);
 58             //创建下载线程
 59             threads[i]=new DownThread(startPos,currentPartSize,currentPart);
 60             //启动线程
 61             threads[i].start();
 62         }
 63     }
 64     //获取下载的完成比
 65     public double getCompleteRate()
 66     {
 67         //统计多个线程已经下载的总大小
 68         int sumSize=0;
 69         for(int i=0;i<threadNum;i++)
 70         {
 71             sumSize+=threads[i].length;
 72         }
 73         return sumSize*1.0/fileSize;
 74     }
 75     private class DownThread extends Thread
 76     {
 77         //当前的下载位置
 78         private int startPos;
 79         //当前线程负责下载的文件的大小
 80         private int currentPartSize;
 81         //当前线程需要下载文件块
 82         private RandomAccessFile currentPart;
 83         //定义该现场当前已经下载的字节数
 84         public int length;
 85         public DownThread(int startPos,int currentPartSize,RandomAccessFile currentPart)
 86         {
 87             this.startPos=startPos;
 88             this.currentPartSize=currentPartSize;
 89             this.currentPart=currentPart;
 90         }
 91         public void run()
 92         {
 93             try
 94             {
 95                 URL url = new URL(path);
 96                 HttpURLConnection conn = (HttpURLConnection) url.openConnection();
 97                 conn.setConnectTimeout(5*1000);
 98                 conn.setRequestMethod("GET");
 99                 conn.setRequestProperty(
100                     "Accept",
101                     "image/gift,image/jpeg,image/pjpeg,image/pjpeg"
102                     +"application/x-shockwave-flash,application/xaml+xml"
103                     +"application/vnd.ms-xpsdocument,application/x-ms-xbap"
104                     +"application/x-ms-application,application/vnd.ms-excel"
105                     +"application/vnd.ms-powerpoint,application/msword,*/*"
106                 );
107                 conn.setRequestProperty("Accept-Language","zh-CN");
108                 conn.setRequestProperty("Charset","UTF-8");
109                 InputStream inStream=conn.getInputStream();
110                 //跳过startPos个字节,只下载自己负责的那部分文件
111                 inStream.skip(this.startPos);
112                 byte[] buffer=new byte[1024];
113                 int hasRead=0;
114                 //读取网络数据,并写入文件
115                 while(length<currentPartSize && (hasRead=inStream.read(buffer))!=-1)
116                 {
117                     currentPart.write(buffer,0,hasRead);
118                     length+=hasRead;
119                 }
120                 currentPart.close();
121                 inStream.close();
122             }
123             catch (Exception e)
124             {
125                 e.printStackTrace();
126             }
127         }
128     }
129 }
View Code

  (2)发生GET/POST请求

  1 import java.net.URLConnection;
  2 import java.net.URL;
  3 import java.util.Map;
  4 import java.util.List;
  5 import java.io.BufferedReader;
  6 import java.io.InputStreamReader;
  7 import java.io.PrintWriter;
  8 class GetPostTest
  9 {
 10     /**
 11      *想指定URL发送GET请求
 12      *@param url 发送请求的URL
 13      *@param param 请求参数,格式满足key=value&key2=value2的形式
 14      *@return URL 代表远程资源的响应
 15      */
 16      public static String sendGet(String url,String param)
 17     {
 18          String result = "";
 19          String urlName=url+"?"+param;
 20          try
 21          {
 22             URL realUrl=new URL(urlName);
 23             //打开和URL之间的连接
 24             URLConnection conn=realUrl.openConnection();
 25             //设置通用的请求属性
 26             conn.setRequestProperty("accept","*/*");
 27             conn.setRequestProperty("connection","Keep-Alive");
 28             conn.setRequestProperty("user-agent","Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2906.0 Safari/537.36");
 29             //建立实际链接
 30             conn.connect();
 31             Map<String,List<String>> map =conn.getHeaderFields();
 32             //遍历所有相应头字段
 33             for(String key:map.keySet())
 34              {
 35                 System.out.println(key+"---->"+map.get(key));
 36              }
 37              try(
 38                 //定义BufferedReader输入流来读取URL响应
 39                 BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream(),"utf-8")))
 40              {
 41                 String line;
 42                 while((line=in.readLine())!=null)
 43                  {
 44                     result+="
"+line;
 45                  }
 46              }
 47          }
 48          catch (Exception e)
 49          {
 50             System.out.println("发送GET请求出现异常!"+e);
 51              e.printStackTrace();
 52          }
 53          return result;
 54     }
 55     /**
 56      *想指定URL发送POST请求
 57      *@param url 发送请求的URL
 58      *@param param 请求参数,格式满足key=value&key2=value2的形式
 59      *@return URL 代表远程资源的响应
 60      */
 61     public static String sendPost(String url,String param)
 62     {
 63         String result="";
 64         try
 65         {
 66             URL realUrl=new URL(url);
 67             //打开和URL之间的连接
 68             URLConnection conn=realUrl.openConnection();
 69             //设置通用的请求属性
 70             conn.setRequestProperty("accept","*/*");
 71             conn.setRequestProperty("connection","Keep-Alive");
 72             conn.setRequestProperty("user-agent","Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2906.0 Safari/537.36");
 73             conn.setDoOutput(true);
 74             conn.setDoInput(true);
 75             try(
 76                 //获取URLConnection对象对应的输出流
 77             PrintWriter out =new PrintWriter(conn.getOutputStream()) )
 78             {
 79                 //发送请求参数
 80                 out.print(param);
 81                 //flush输出流的缓冲
 82                 out.flush();                
 83             }
 84             try(
 85                 //定义BufferedReader输入流来读取URL响应
 86                 BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream(),"utf-8")))
 87              {
 88                 String line;
 89                 while((line=in.readLine())!=null)
 90                  {
 91                     result+="
"+line;
 92                  }
 93              }
 94         }
 95         catch (Exception e)
 96         {
 97             System.out.println("发送POST请求出现异常!"+e);
 98              e.printStackTrace();
 99         }
100         return result;
101     }
102     public static void main(String[] args)
103     {
104         //String s =GetPostTest.sendGet("https://www.jd.com",null);
105         //System.out.println(s);
106         String s1=GetPostTest.sendPost("http://my.just.edu.cn/","user=1245532105&pwd=095493");
107         String s2 =GetPostTest.sendGet("http://my.just.edu.cn/index.portal",null);
108         System.out.println(s1);        
109     }
110 }
View Code

(二)基于TCP协议的网络编程(使用Socket进行通信)

  TCP协议一般是和IP协议一起使用的,TCP/IP协议属于一种可靠的网络协议,它可以在通信的两端各建立一个Socket,使得两端形成一个虚拟的网络链路。于是两端的程序就可以通过这个虚拟链路进行通信。

  通过安装IP协议,可以保证计算机之间发送和接受数据,但是无法解决数据分组在传输过程中的问题。所以需要TCP协议来提供可靠并且无差错的通信服务。

  通过java的封装好的Socket可以实现一个简单的聊天程序。这个程序由服务端程序和客户端程序组成,为了实现用户聊天需要在服务端除了Server用于接受客户端请求并建立对应的Socket,还得通过一个ServerThread来为每个客户端建立一个线程用于监听客户端的消息,并向客户端发送消息。而在客户端除了一个Client程序用于建立和服务器端的Socket并且将用户输入的数据发送给服务器端,还需要一个ClientThread用来建立一个线程用于监听服务器端发来的数据并显示出来。

 1 import java.io.PrintStream;
 2 import java.io.IOException;
 3 import java.net.ServerSocket;
 4 import java.net.Socket;
 5 import java.util.List;
 6 import java.util.Collections;
 7 import java.util.ArrayList;
 8 import java.lang.Thread;
 9 class MyServer
10 {
11     public static final int SERVER_PORT=30000;
12     //利用MyMap保存每个客户端名字和对应的输出流
13     public static MyMap<String,PrintStream> clients = new MyMap<String,PrintStream>();
14     public void init()
15     {
16         try
17         (
18             //建立监听的ServerSocket
19             ServerSocket ss = new ServerSocket(SERVER_PORT))
20         {
21             //采用死循环一直接受客户端的请求
22             while(true)
23             {
24                 Socket s= ss.accept();
25                 new MyServerThread(s).start();
26             }
27         }
28         catch (IOException ex)
29         {
30             System.out.println("服务器启动失败,请检查端口:"+SERVER_PORT+"是否已经被占用?");
31         }
32     }
33     public static void main(String[] args)
34     {
35         MyServer server=new MyServer();
36         server.init();
37     }
38 }
Server
 1 import java.net.Socket;
 2 import java.io.BufferedReader;
 3 import java.io.IOException;
 4 import java.io.InputStreamReader;
 5 import java.io.PrintStream;
 6 public class MyServerThread extends Thread
 7 {
 8     //定义当前线程所处理的Socket
 9     private Socket s=null;
10     //该Socket对应的输入流
11     BufferedReader br=null;
12     //该Socket对应的输出流
13     PrintStream ps=null;
14     public MyServerThread(Socket s) throws IOException
15     {
16         this.s=s;
17     }
18     public void run()
19     {
20         try
21         {
22             //获取该Socket对应得输入流
23             br=new BufferedReader(new InputStreamReader(s.getInputStream()));
24             //获取该Socket对应的输出流
25             ps=new PrintStream(s.getOutputStream());
26             String line = null;
27             //采用循环不断地从Socket中读取客户端发来的数据
28             while( (line=br.readLine())!=null)
29             {
30                 //如果读取到MyProtrocol.USER_ROUND开始,并以其结束则可以确定读到的是用户的登录名
31                 if(line.startsWith(MyProtocol.USER_ROUND)&&line.endsWith(MyProtocol.USER_ROUND))
32                 {
33                     //得到真实消息
34                     String userName=getRealMsg(line);
35                     //如果用户名重复
36                     if(MyServer.clients.map.containsKey(userName))
37                     {
38                         System.out.println("用户名重复");
39                         ps.println(MyProtocol.NAME_REP);
40                     }
41                     else
42                     {
43                         System.out.println(userName+"登陆成功");
44                         ps.println(MyProtocol.LOGIN_SUCCESS);
45                         MyServer.clients.put(userName,ps);
46                     }
47                 }
48                 //如果读到以MyProtocol.PRIVATE_ROUND开始,并以其结束,则可以确定是私聊信息,私聊信息只向制定输出流发送
49                 else if(line.startsWith(MyProtocol.PRIVATE_ROUND)&&line.endsWith(MyProtocol.PRIVATE_ROUND))
50                 {
51                     //得到真实消息
52                     String userAndMsg = getRealMsg(line);
53                     //以SPLIT_SIGN分割,前半部分是私聊用户名,后一部分是内容
54                     //System.out.println(userAndMsg);
55                     //System.out.println(MyProtocol.SPLIT_SIGN);
56                     String user = userAndMsg.split(MyProtocol.SPLIT_SIGN)[0];
57                     String msg = userAndMsg.split(MyProtocol.SPLIT_SIGN)[1];
58                     //获取私聊用户对应的输出流,并发送私聊信息
59                     MyServer.clients.map.get(user).println(MyServer.clients.getKeyByValue(ps)+"悄悄对你说:"+msg);
60                 }
61                 //公聊,对所有Socket发
62                 else
63                 {
64                     //获取真实消息
65                     String msg=getRealMsg(line);
66                     //遍历clients中的每个输出流
67                     for(PrintStream clientPs:MyServer.clients.valueSet())
68                     {
69                         clientPs.println(MyServer.clients.getKeyByValue(ps)+"说:"+msg);
70                     }
71                 }
72             }
73         }
74         //捕获到异常,表明该Socket有问题,将该程序对应的输出流从Map中删除
75         catch (IOException ex)
76         {
77             MyServer.clients.removeByValue(ps);
78             System.out.println(MyServer.clients.map.size());
79             //关闭网络和IO资源
80             try
81             {
82                 if(br!=null) br.close();
83                 if(ps!=null) ps.close();
84                 if(s!=null) s.close();
85             }
86             catch (IOException e)
87             {
88                 e.printStackTrace();
89             }
90         }
91     }
92     //将读到的内容去掉前后的协议,恢复成真实数据
93     private String getRealMsg(String line)
94     {
95         return line.substring(MyProtocol.PROTOCOL_LEN,line.length()-MyProtocol.PROTOCOL_LEN);
96     }
97 }
ServerThread
  1 import java.io.PrintStream;
  2 import java.io.IOException;
  3 import java.net.UnknownHostException;
  4 import java.io.InputStreamReader;
  5 import java.io.BufferedReader;
  6 import java.net.ServerSocket;
  7 import java.net.Socket;
  8 import java.net.InetAddress;
  9 import javax.swing.JOptionPane;
 10 class MyClient
 11 {
 12     private static final int SERVER_PORT=30000;
 13     private Socket socket;
 14     private PrintStream ps;
 15     private BufferedReader brServer;
 16     private BufferedReader keyIn;
 17     public void init()
 18     {
 19         try
 20         {
 21             //初始化代表键盘的输入流
 22             keyIn = new BufferedReader(new InputStreamReader(System.in));
 23             //链接到服务器
 24             socket = new Socket("127.0.0.1",SERVER_PORT);
 25             //获取该Socket对应的输入流和输出流
 26             ps = new PrintStream(socket.getOutputStream());
 27             brServer=new BufferedReader(new InputStreamReader(socket.getInputStream()));
 28             String tip="";
 29             //采用循环不断弹出对话框要求输入用户名
 30             while(true)
 31             {
 32                 String userName=JOptionPane.showInputDialog(tip+"输入用户名");
 33                 //用户输入用户名后前后加上协议字符串后发送
 34                 ps.println(MyProtocol.USER_ROUND+userName+MyProtocol.USER_ROUND);
 35                 //读取服务器的响应
 36                 String result = brServer.readLine();
 37                 //如果用户名重复则开始下次循环
 38                 if(result.equals(MyProtocol.NAME_REP))
 39                 {
 40                     tip="用户名重复,请重试!";
 41                     continue;
 42                 }
 43                 //如果服务器返回登陆成功,则循环结束
 44                 if(result.equals(MyProtocol.LOGIN_SUCCESS))
 45                 {
 46                     break;
 47                 }
 48             }
 49         }
 50         //补货到异常,关闭网络资源,并推出程序
 51         catch (UnknownHostException ex)
 52         {
 53             System.out.println("找不到远程服务器,请确定服务器已经启动!");
 54             closeRs();
 55             System.exit(1);
 56         }
 57         catch(IOException ex)
 58         {
 59             System.out.println("网络异常!请重新登陆");
 60             closeRs();
 61             System.exit(1);
 62         }
 63         //以该socket对应的输入流启动Client线程
 64         new MyClientThread(brServer).start();
 65     }
 66     //定义一个读取键盘输出,并向网络发送的方法
 67     private void readAndSend()
 68     {
 69         try
 70         {
 71             //不断地读取键盘的输入
 72             String line=null;
 73             while((line=keyIn.readLine())!=null)
 74             {
 75                 //如果发送的信息中有冒号,并且以//开头,则认为发送私聊信息
 76                 if(line.indexOf(":")>0&&line.startsWith("//"))
 77                 {
 78                     line=line.substring(2);
 79                     //System.out.println(MyProtocol.PRIVATE_ROUND+line.split(":")[0]+MyProtocol.SPLIT_SIGN+line.split(":")[1]+MyProtocol.PRIVATE_ROUND);
 80                     ps.println(MyProtocol.PRIVATE_ROUND+line.split(":")[0]+MyProtocol.SPLIT_SIGN+line.split(":")[1]+MyProtocol.PRIVATE_ROUND);
 81                 }
 82                 else 
 83                 {
 84                     ps.println(MyProtocol.MSG_ROUND+line+MyProtocol.MSG_ROUND);
 85                 }
 86             }
 87         }
 88         //捕获到异常,关闭网络资源,并退出程序
 89         catch (IOException ex)
 90         {
 91             System.out.println("网络异常!请重新登陆");
 92             closeRs();
 93             System.exit(1);
 94         }
 95     }
 96     //关闭Socket、输入流、输出流的方法
 97     private void closeRs()
 98     {
 99         try
100         {
101             if(keyIn!=null) ps.close();
102             if(brServer!=null) brServer.close();
103             if(ps!=null) ps.close();
104             if(socket!=null) keyIn.close();
105         }
106         catch (IOException ex)
107         {
108             ex.printStackTrace();
109         }
110     }
111     public static void main(String[] args)
112     {
113         MyClient client=new MyClient();
114         client.init();
115         client.readAndSend();
116     }
117 }
Client
 1 import java.io.PrintStream;
 2 import java.io.IOException;
 3 import java.io.InputStreamReader;
 4 import java.io.BufferedReader;
 5 import java.net.ServerSocket;
 6 import java.net.Socket;
 7 import java.net.InetAddress;
 8 import java.lang.Thread;
 9 class MyClientThread extends Thread
10 {
11     //该客户端线程负责处理输入流
12     BufferedReader br=null;
13     public MyClientThread(BufferedReader br)
14     {
15         this.br=br;
16     }
17     public void run()
18     {
19         try
20         {
21             String line=null;
22             //不断的读取Socket输入流的内容,并将其打印输出
23             while((line=br.readLine())!=null)
24             {
25                 System.out.println(line);
26             }
27         }
28         catch (Exception e)
29         {
30             e.printStackTrace();
31         }
32         finally
33         {
34             try
35             {
36                 if(br!=null) br.close();
37             }
38             catch (IOException ex)
39             {
40                 ex.printStackTrace();
41             }
42         }
43     }
44 }
ClientThread

  JDK1.4开始,Java提供了NIO API用于开发高性能的网络服务器,借助NIO可以不必为每个客户端都建立一个线程。下面我将利用NIO改变这个聊天程序。

第二部分:分布式文件协同编辑器

第一部分 Java网络编程知识

  

 

附件:《实验指导报告书》

分布式文件协同编辑器

实验指导书--1

 

一、实验目的

    加深对分布式系统基本概念的理解,灵活运用多种分布式互斥与同步访问的算法;掌握网络编程的基本方法,熟悉Socket套接字的使用,实现网络间的通信程序;设计并初步实现一个“分布式文件协同编辑器”原型系统。

二、实验要求

1、有N个网络用户编辑同一磁盘上的多个文件,文件的存取服务由文件服务器完成,网络上的用户通过客户端软件完成协同编辑工作。编辑器架构如图1所示:

 
   

2、设计并实现分布式互斥与同步访问,实现多用户协同编辑。

3、设计并初步实现一个“分布式文件协同编辑器”。

4、可以在Window或Linux下完成。

 

三、实验内容

   实验内容由两部分组成:

第一部分:编写文件服务器程序,详细要求有:

  1. 实现对磁盘文件的存取服务。
  2. 实现与客户端软件的文件传输服务(客户端指定文件名称,通过Socket实现)
  3. 不能使用集中控制策略。

第二部分:编写客户端软件,具体要求如下:

  1. 实现对文件简单的编辑/浏览功能(只要能查看/改变文件内容即可);
  2. 实现与文件服务器的文件传输功能(客户端指定文件名称,通过Socket实现);
  3. 实现多个客户端软件之间的通讯功能,能实现协同工作的功能;
  4. 实现分布式的互斥编辑功能。

四、实验方法

1、实验有两个方案(同学们也可自己设计新的方案):

方案一:获取并发用户列表的方法:客户端软件在访问文件时,先在子网内广播信息,访问该文件的其它客户端软件应答。

方案二:分布式互斥访问可以使用令牌环算法。

2、实现分布式互斥编辑功能(同学们也可自己设计新的方案):

1)        多个客户可以同时浏览文件内容(已经提交的版本)。

2)        当文件加互斥锁时,多个客户也可以同时浏览文件内容(旧版本),但是只能由加互斥锁的用户编辑文件(未提交的版本),而且提交之后,必须通知其他浏览该文件的用户,以便其它用户获得最新版本的内容。

3)        进入编辑状态之前,首先要获得互斥锁。而且在任意时刻,只能一个用户对文件进行编辑。

原文地址:https://www.cnblogs.com/darkworker/p/6230177.html