• 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)        进入编辑状态之前,首先要获得互斥锁。而且在任意时刻,只能一个用户对文件进行编辑。

  • 相关阅读:
    AM3715/DM3730 更改内存大小后kernel boot不起来的解决办法
    xslt转换xml文档&&xslt call java方法
    VSCode 运行go test显示打印日志
    tomcat作为windows服务的参数配置,特别是PermSize的设置
    高亮显示web页表格行
    深入分析 Java 中的中文编码问题
    webwork和spring多配置文件的方法
    Bug笔记:诡异的$.ajax
    C#多态
    委托的本质
  • 原文地址:https://www.cnblogs.com/darkworker/p/6230177.html
Copyright © 2020-2023  润新知