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

时间:2023-03-09 09:05:46
Java 网络编程---分布式文件协同编辑器设计与实现

目录:

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

(一)简单的Http请求

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

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

 InetAddressTest

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

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

 import java.net.*;
import java.io.RandomAccessFile;
import java.io.InputStream;
public class DownUtil
{
//下载路径
private String path;
//指定下载文件存储的位置
private String targetFile;
//定义使用多少线程下载
private int threadNum;
//定义下载线程对象
private DownThread[] threads;
//定义下载文件的总大小
private int fileSize; public DownUtil(String path,String target,int threadNum)
{
this.path = path;
this.targetFile=target;
this.threadNum=threadNum;
//初始化threads数组
threads=new DownThread[threadNum];
}
public void download() throws Exception
{
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(5*1000);
conn.setRequestMethod("GET");
conn.setRequestProperty(
"Accept",
"image/gift,image/jpeg,image/pjpeg,image/pjpeg"
+"application/x-shockwave-flash,application/xaml+xml"
+"application/vnd.ms-xpsdocument,application/x-ms-xbap"
+"application/x-ms-application,application/vnd.ms-excel"
+"application/vnd.ms-powerpoint,application/msword,*/*"
);
conn.setRequestProperty("Accept-Language","zh-CN");
conn.setRequestProperty("Charset","UTF-8");
conn.setRequestProperty("Connection","Keep-Alive");
//获得文件大小
fileSize = conn.getContentLength();
conn.disconnect();
int currentPartSize=fileSize/threadNum+1;
RandomAccessFile file = new RandomAccessFile(targetFile,"rw");
//设置本地文件大小
file.setLength(fileSize);
file.close();
for(int i=0;i<threadNum;i++)
{
//计算每个线程开始的位置
int startPos = i*currentPartSize;
//每个线程使用一个RandomAccessFile下载
RandomAccessFile currentPart=new RandomAccessFile(targetFile,"rw");
//定位线程下载的位置
currentPart.seek(startPos);
//创建下载线程
threads[i]=new DownThread(startPos,currentPartSize,currentPart);
//启动线程
threads[i].start();
}
}
//获取下载的完成比
public double getCompleteRate()
{
//统计多个线程已经下载的总大小
int sumSize=0;
for(int i=0;i<threadNum;i++)
{
sumSize+=threads[i].length;
}
return sumSize*1.0/fileSize;
}
private class DownThread extends Thread
{
//当前的下载位置
private int startPos;
//当前线程负责下载的文件的大小
private int currentPartSize;
//当前线程需要下载文件块
private RandomAccessFile currentPart;
//定义该现场当前已经下载的字节数
public int length;
public DownThread(int startPos,int currentPartSize,RandomAccessFile currentPart)
{
this.startPos=startPos;
this.currentPartSize=currentPartSize;
this.currentPart=currentPart;
}
public void run()
{
try
{
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(5*1000);
conn.setRequestMethod("GET");
conn.setRequestProperty(
"Accept",
"image/gift,image/jpeg,image/pjpeg,image/pjpeg"
+"application/x-shockwave-flash,application/xaml+xml"
+"application/vnd.ms-xpsdocument,application/x-ms-xbap"
+"application/x-ms-application,application/vnd.ms-excel"
+"application/vnd.ms-powerpoint,application/msword,*/*"
);
conn.setRequestProperty("Accept-Language","zh-CN");
conn.setRequestProperty("Charset","UTF-8");
InputStream inStream=conn.getInputStream();
//跳过startPos个字节,只下载自己负责的那部分文件
inStream.skip(this.startPos);
byte[] buffer=new byte[1024];
int hasRead=0;
//读取网络数据,并写入文件
while(length<currentPartSize && (hasRead=inStream.read(buffer))!=-1)
{
currentPart.write(buffer,0,hasRead);
length+=hasRead;
}
currentPart.close();
inStream.close();
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
}

  (2)发生GET/POST请求

 import java.net.URLConnection;
import java.net.URL;
import java.util.Map;
import java.util.List;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
class GetPostTest
{
/**
*想指定URL发送GET请求
*@param url 发送请求的URL
*@param param 请求参数,格式满足key=value&key2=value2的形式
*@return URL 代表远程资源的响应
*/
public static String sendGet(String url,String param)
{
String result = "";
String urlName=url+"?"+param;
try
{
URL realUrl=new URL(urlName);
//打开和URL之间的连接
URLConnection conn=realUrl.openConnection();
//设置通用的请求属性
conn.setRequestProperty("accept","*/*");
conn.setRequestProperty("connection","Keep-Alive");
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");
//建立实际链接
conn.connect();
Map<String,List<String>> map =conn.getHeaderFields();
//遍历所有相应头字段
for(String key:map.keySet())
{
System.out.println(key+"---->"+map.get(key));
}
try(
//定义BufferedReader输入流来读取URL响应
BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream(),"utf-8")))
{
String line;
while((line=in.readLine())!=null)
{
result+="\n"+line;
}
}
}
catch (Exception e)
{
System.out.println("发送GET请求出现异常!"+e);
e.printStackTrace();
}
return result;
}
/**
*想指定URL发送POST请求
*@param url 发送请求的URL
*@param param 请求参数,格式满足key=value&key2=value2的形式
*@return URL 代表远程资源的响应
*/
public static String sendPost(String url,String param)
{
String result="";
try
{
URL realUrl=new URL(url);
//打开和URL之间的连接
URLConnection conn=realUrl.openConnection();
//设置通用的请求属性
conn.setRequestProperty("accept","*/*");
conn.setRequestProperty("connection","Keep-Alive");
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");
conn.setDoOutput(true);
conn.setDoInput(true);
try(
//获取URLConnection对象对应的输出流
PrintWriter out =new PrintWriter(conn.getOutputStream()) )
{
//发送请求参数
out.print(param);
//flush输出流的缓冲
out.flush();
}
try(
//定义BufferedReader输入流来读取URL响应
BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream(),"utf-8")))
{
String line;
while((line=in.readLine())!=null)
{
result+="\n"+line;
}
}
}
catch (Exception e)
{
System.out.println("发送POST请求出现异常!"+e);
e.printStackTrace();
}
return result;
}
public static void main(String[] args)
{
//String s =GetPostTest.sendGet("https://www.jd.com",null);
//System.out.println(s);
String s1=GetPostTest.sendPost("http://my.just.edu.cn/","user=1245532105&pwd=095493");
String s2 =GetPostTest.sendGet("http://my.just.edu.cn/index.portal",null);
System.out.println(s1);
}
}

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

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

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

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

 import java.io.PrintStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.List;
import java.util.Collections;
import java.util.ArrayList;
import java.lang.Thread;
class MyServer
{
public static final int SERVER_PORT=30000;
//利用MyMap保存每个客户端名字和对应的输出流
public static MyMap<String,PrintStream> clients = new MyMap<String,PrintStream>();
public void init()
{
try
(
//建立监听的ServerSocket
ServerSocket ss = new ServerSocket(SERVER_PORT))
{
//采用死循环一直接受客户端的请求
while(true)
{
Socket s= ss.accept();
new MyServerThread(s).start();
}
}
catch (IOException ex)
{
System.out.println("服务器启动失败,请检查端口:"+SERVER_PORT+"是否已经被占用?");
}
}
public static void main(String[] args)
{
MyServer server=new MyServer();
server.init();
}
}

Server

 import java.net.Socket;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
public class MyServerThread extends Thread
{
//定义当前线程所处理的Socket
private Socket s=null;
//该Socket对应的输入流
BufferedReader br=null;
//该Socket对应的输出流
PrintStream ps=null;
public MyServerThread(Socket s) throws IOException
{
this.s=s;
}
public void run()
{
try
{
//获取该Socket对应得输入流
br=new BufferedReader(new InputStreamReader(s.getInputStream()));
//获取该Socket对应的输出流
ps=new PrintStream(s.getOutputStream());
String line = null;
//采用循环不断地从Socket中读取客户端发来的数据
while( (line=br.readLine())!=null)
{
//如果读取到MyProtrocol.USER_ROUND开始,并以其结束则可以确定读到的是用户的登录名
if(line.startsWith(MyProtocol.USER_ROUND)&&line.endsWith(MyProtocol.USER_ROUND))
{
//得到真实消息
String userName=getRealMsg(line);
//如果用户名重复
if(MyServer.clients.map.containsKey(userName))
{
System.out.println("用户名重复");
ps.println(MyProtocol.NAME_REP);
}
else
{
System.out.println(userName+"登陆成功");
ps.println(MyProtocol.LOGIN_SUCCESS);
MyServer.clients.put(userName,ps);
}
}
//如果读到以MyProtocol.PRIVATE_ROUND开始,并以其结束,则可以确定是私聊信息,私聊信息只向制定输出流发送
else if(line.startsWith(MyProtocol.PRIVATE_ROUND)&&line.endsWith(MyProtocol.PRIVATE_ROUND))
{
//得到真实消息
String userAndMsg = getRealMsg(line);
//以SPLIT_SIGN分割,前半部分是私聊用户名,后一部分是内容
//System.out.println(userAndMsg);
//System.out.println(MyProtocol.SPLIT_SIGN);
String user = userAndMsg.split(MyProtocol.SPLIT_SIGN)[0];
String msg = userAndMsg.split(MyProtocol.SPLIT_SIGN)[1];
//获取私聊用户对应的输出流,并发送私聊信息
MyServer.clients.map.get(user).println(MyServer.clients.getKeyByValue(ps)+"悄悄对你说:"+msg);
}
//公聊,对所有Socket发
else
{
//获取真实消息
String msg=getRealMsg(line);
//遍历clients中的每个输出流
for(PrintStream clientPs:MyServer.clients.valueSet())
{
clientPs.println(MyServer.clients.getKeyByValue(ps)+"说:"+msg);
}
}
}
}
//捕获到异常,表明该Socket有问题,将该程序对应的输出流从Map中删除
catch (IOException ex)
{
MyServer.clients.removeByValue(ps);
System.out.println(MyServer.clients.map.size());
//关闭网络和IO资源
try
{
if(br!=null) br.close();
if(ps!=null) ps.close();
if(s!=null) s.close();
}
catch (IOException e)
{
e.printStackTrace();
}
}
}
//将读到的内容去掉前后的协议,恢复成真实数据
private String getRealMsg(String line)
{
return line.substring(MyProtocol.PROTOCOL_LEN,line.length()-MyProtocol.PROTOCOL_LEN);
}
}

ServerThread

 import java.io.PrintStream;
import java.io.IOException;
import java.net.UnknownHostException;
import java.io.InputStreamReader;
import java.io.BufferedReader;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.InetAddress;
import javax.swing.JOptionPane;
class MyClient
{
private static final int SERVER_PORT=30000;
private Socket socket;
private PrintStream ps;
private BufferedReader brServer;
private BufferedReader keyIn;
public void init()
{
try
{
//初始化代表键盘的输入流
keyIn = new BufferedReader(new InputStreamReader(System.in));
//链接到服务器
socket = new Socket("127.0.0.1",SERVER_PORT);
//获取该Socket对应的输入流和输出流
ps = new PrintStream(socket.getOutputStream());
brServer=new BufferedReader(new InputStreamReader(socket.getInputStream()));
String tip="";
//采用循环不断弹出对话框要求输入用户名
while(true)
{
String userName=JOptionPane.showInputDialog(tip+"输入用户名");
//用户输入用户名后前后加上协议字符串后发送
ps.println(MyProtocol.USER_ROUND+userName+MyProtocol.USER_ROUND);
//读取服务器的响应
String result = brServer.readLine();
//如果用户名重复则开始下次循环
if(result.equals(MyProtocol.NAME_REP))
{
tip="用户名重复,请重试!";
continue;
}
//如果服务器返回登陆成功,则循环结束
if(result.equals(MyProtocol.LOGIN_SUCCESS))
{
break;
}
}
}
//补货到异常,关闭网络资源,并推出程序
catch (UnknownHostException ex)
{
System.out.println("找不到远程服务器,请确定服务器已经启动!");
closeRs();
System.exit(1);
}
catch(IOException ex)
{
System.out.println("网络异常!请重新登陆");
closeRs();
System.exit(1);
}
//以该socket对应的输入流启动Client线程
new MyClientThread(brServer).start();
}
//定义一个读取键盘输出,并向网络发送的方法
private void readAndSend()
{
try
{
//不断地读取键盘的输入
String line=null;
while((line=keyIn.readLine())!=null)
{
//如果发送的信息中有冒号,并且以//开头,则认为发送私聊信息
if(line.indexOf(":")>0&&line.startsWith("//"))
{
line=line.substring(2);
//System.out.println(MyProtocol.PRIVATE_ROUND+line.split(":")[0]+MyProtocol.SPLIT_SIGN+line.split(":")[1]+MyProtocol.PRIVATE_ROUND);
ps.println(MyProtocol.PRIVATE_ROUND+line.split(":")[0]+MyProtocol.SPLIT_SIGN+line.split(":")[1]+MyProtocol.PRIVATE_ROUND);
}
else
{
ps.println(MyProtocol.MSG_ROUND+line+MyProtocol.MSG_ROUND);
}
}
}
//捕获到异常,关闭网络资源,并退出程序
catch (IOException ex)
{
System.out.println("网络异常!请重新登陆");
closeRs();
System.exit(1);
}
}
//关闭Socket、输入流、输出流的方法
private void closeRs()
{
try
{
if(keyIn!=null) ps.close();
if(brServer!=null) brServer.close();
if(ps!=null) ps.close();
if(socket!=null) keyIn.close();
}
catch (IOException ex)
{
ex.printStackTrace();
}
}
public static void main(String[] args)
{
MyClient client=new MyClient();
client.init();
client.readAndSend();
}
}

Client

 import java.io.PrintStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.BufferedReader;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.InetAddress;
import java.lang.Thread;
class MyClientThread extends Thread
{
//该客户端线程负责处理输入流
BufferedReader br=null;
public MyClientThread(BufferedReader br)
{
this.br=br;
}
public void run()
{
try
{
String line=null;
//不断的读取Socket输入流的内容,并将其打印输出
while((line=br.readLine())!=null)
{
System.out.println(line);
}
}
catch (Exception e)
{
e.printStackTrace();
}
finally
{
try
{
if(br!=null) br.close();
}
catch (IOException ex)
{
ex.printStackTrace();
}
}
}
}

ClientThread

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

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

第一部分 Java网络编程知识

  

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

分布式文件协同编辑器

实验指导书--1

 

一、实验目的

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

二、实验要求

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

 
   

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

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

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

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

三、实验内容

实验内容由两部分组成:

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

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

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

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

四、实验方法

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

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

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

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

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

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

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