实现功能
- 验证用户登录
- 两个在线用户间的文字聊天
- 多用户群聊
- 两个在线用户之间传输二进制文件
- 发送离线信息
- 传输离线文件
- 文件断点续传
框架结构
-
服务器框架结构
-
客户端框架结构
主要功能设计及实现
为方便表述,约定usr表示用户名,password为密码。srcusr为发送信息或文件的用户;dstusr为接收信息或接收文件的用户。message为文字信息;onlinefile为在线文件信息;offlinefile为离线文件信息。
- 服务器端主要数据结构
变量名 | 类型 | 解释 |
---|---|---|
usr | Hashtable<String,String> | 存储用户名和密码 |
logined | Hashtable<String,Socket> | 存储已登录用户名和对应Socket,方便转发信息和文件 |
work | Hashtable<String,Boolean> | 存储在线转发文件任务。 key为文件信息;value=false表示该文件尚未上传至服务器;value=true表示该文件已上传至服务器 ,用户可以进行下载 |
logoutinfo | Hashtable<String,Queue<String>> | 存储转发离线信息任务。 key为dstusr;value为一个队列,存储srcusr和具体信息内容 |
logoutwork | Hashtable<String,Boolean> | 存储转发离线文件任务。 key为文件信息;value=false表示该文件尚未上传至服务器;value=true表示该文件已上传至服务器 ,用户可以进行下载 |
- 客户端主要数据结构
变量名 | 类型 | 解释 |
---|---|---|
loginFlag | boolean | 登录标志。loginFlag=true表示客户端已登录;loginFlag=false表示客户端尚未登录,不可发送信息或文件 |
friends | TreeSet<String> | 在线好友列表(已登录) |
users | TreeSet<String> | 存储好友列表(已登录+未登录) |
receivefile | String | 保存接收文件信息 |
- 验证用户登录
客户端向服务器发送usr和password,服务器端验证usr和password是否合法。若成功登录,服务器向usr发送已登录用户和未登录用户名,通知已登录用户user已经成功登录。 - 两个在线用户间的文字聊天
客户端将信息内容(srcusr+dstusr+message)发送给服务器,服务器转发信息给dstusr。 - 多用户群聊
若dstusr为*,服务器将信息转发给所有的在线用户。
若dstusr为**,服务器将信息转发给所有的用户(在线+离线)。 - 两在线用户间传输文件
客户端向服务器发送消息(onlinefile);服务器端work增加在线传输任务,并初始化work[onlinefile]=false;客户端向服务器传送文件,文件传送完毕时,服务器端设置work[onlinefile]=true,并通知destusr下载文件。 - 文件断点续传
客户端上服务器上传文件前,首先获取已传输字节数量,使用RandomAccessFile.seek()方法在断点出续传。 - 离线文件传输
与在线文件传输的区别是,当dstusr登录时,再通知dstusr下载文件。 - 共享数据加锁
需要对服务器端logined、work、logoutinfo、logoutwork加锁。
在程序中,使用ReentrantLock()对上述4个变量加锁。
客户端和服务器间通信协议设计
因NAT穿透难以实现,本聊天程序采用CS模式,无配合P2P模式。传输协议为TCP。
所传送信息的第一个字符用于区分不同类型的信息,具体CS之间传输信息的协议如下:
- 服务器接收信息(客户端发送信息)协议
$<usr> <password> --用户登录
#<dstusr> <message> --向dest用户转发信息
%<dstusr> <filelength> <filename> --向dest用户转发文件 - 客户端接收(服务器发送信息)协议
*<users> --用户列表
@<srcusr> <message> --来自source用户的信息
$<usr> --用户user已登录
!<usr> --用户user已登出
#<src> <filename> <filelength> --来自用户source的文件,是否接收
%y/n/e --服务器回复登录情况。%y成功登录;%n账号或密码错误;%e重复登录
客户端指令设计
- help --show how to use commands
- login <usr> <password> --log in
- onlinefriendlist --show online friend list
- friendlist --show friend list
- info <friendname> <message> --send message to friend. When friendname is *, send message to friends who are online; when friendname is **, send message to all friends.
- file <friendname> <filename> --send a file in current folder to friend
- exit --log out
使用示例
首先,服务器端设置登录账号和密码,程序中默认设置如下:
username | password |
---|---|
Jack | 123456 |
Mary | 123456 |
Wangming | 123456 |
MRY | 123456 |
110 | 123456 |
启动服务器后启动客户端,尝试输入以下指令:
help //get help
login Jack 123456 //登录
friendlist //显示所有的用户列表
onlinefriendlist //显示上线的用户列表
info Mary Hello have a good day! //向Mary发送信息"Hello have a good day!"
info * Hello, my online friends //向所有的在线用户发送信息"Hello, my online friends"
info ** Hello, all my friends //向所有用户发送信息"Hello, all my friends"
file Mary demofile.exe //向Mary发送文件demofile.exe
exit //退出程序
问题记录
- 传输文件名和文件长度等信息时,起初使用对象流,程序出现java.io.EOFException报错。经查看此博客,发现以下两点:
a. 对象流不同于普通的字节流,当对象流中没有数据时,程序却尝试读取数据,会报EOFException;而字节流就不会出现这种情况,字节流会返回-1
b. ObjectInputStream写入的数据,在ObjectOutputStream上读取时,应该按照相同的数据类型依次读取,否则数据类型不等会抛出EOFException
当传送文件信息时使用对象流,因为传输时延,服务器端会检测到无对象流数据,抛出EOFException。
最终,自己使用PrintWriter传递文件信息。将文件名、长度等信息合成一个字符串,使用PrintWriter传输给服务器。 - 当上传和下载文件使用同一个端口时,出现了死锁
当文件上传和下载使用同一个端口时,服务器不可以同时进行上传、下载文件作业,否则会出现死锁。
最终,自己将服务器文件上传和下载功能分离,两者各使用一个端口。
code
服务器:
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.*;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Server{
private ServerSocket messageServer;//收发信息服务器socket
private ServerSocket receivefileServer;//接收文件服务器socket
private ServerSocket sendfileServer;//发送文件服务器socket
private Socket messageSocket;
private Socket receivefileSocket;
private Socket sendfileSocket;
private Hashtable<String,Socket> logined=new Hashtable<String,Socket>();//存储已登录用户socket
private Lock locklogined = new ReentrantLock();
private Hashtable<String,String> user=new Hashtable<String,String>();//存储用户名及其密码,不需要加锁
private Hashtable<String,Boolean> work=new Hashtable<String,Boolean>();//转发在线文件任务
private Lock lockwork= new ReentrantLock();
private Hashtable<String,Queue<String>> logoutinfo =new Hashtable<String,Queue<String>>();//转发离线信息
private Lock locklogoutinfo= new ReentrantLock();
private Hashtable<String,Boolean> logoutwork=new Hashtable<String,Boolean>();//转发离线文件任务
private Lock locklogoutwork= new ReentrantLock();
Server(){//初始化用户和密码
user.put("Jack", "123456");
user.put("Mary","123456");
user.put("Wangming","123456");
user.put("MRY","123456");
user.put("110","123456");
createSocket();
}
public void createSocket() {
try {
messageServer = new ServerSocket(1978);
receivefileServer=new ServerSocket(1979);
sendfileServer=new ServerSocket(1980);
new Thread(new Runnable(){//收发信息服务器线程,使用线程池
@Override
public void run() {
BlockingQueue<Runnable> bq = new ArrayBlockingQueue<Runnable>(10);
ThreadPoolExecutor tpe1 = new ThreadPoolExecutor(10, 20, 50, TimeUnit.MILLISECONDS, bq);
while (true) {
try {
messageSocket = messageServer.accept();
System.out.println("waiting for client.....");
System.out.println("connected....." + messageSocket);
tpe1.execute(new messageServerThread(messageSocket));
} catch (IOException e) {
e.printStackTrace();
}// 创建套接字对象
}
}
}).start();
new Thread(new Runnable(){//接收文件服务器线程,使用线程池
@Override
public void run() {
BlockingQueue<Runnable> bq = new ArrayBlockingQueue<Runnable>(10);
ThreadPoolExecutor tpe2 = new ThreadPoolExecutor(5, 10, 50, TimeUnit.MILLISECONDS, bq);
while (true) {
try {
receivefileSocket=receivefileServer.accept();
tpe2.execute(new receiverServerThread(receivefileSocket));
} catch (IOException e) {
e.printStackTrace();
}
}
}
}).start();
new Thread(new Runnable(){//转发文件服务器线程,使用线程池
@Override
public void run() {
BlockingQueue<Runnable> bq = new ArrayBlockingQueue<Runnable>(10);
ThreadPoolExecutor tpe3 = new ThreadPoolExecutor(5, 10, 50, TimeUnit.MILLISECONDS, bq);
while (true) {
try {
sendfileSocket=sendfileServer.accept();
tpe3.execute(new senderServerThread(sendfileSocket));
} catch (IOException e) {
e.printStackTrace();
}
}
}
}).start();
while (true) {
System.out.println("waiting for client.....");
messageSocket = messageServer.accept();// 创建套接字对象
System.out.println("connected....." + messageSocket);
new messageServerThread(messageSocket).start();// 创建并启动线程对象
}
} catch (IOException e) {
e.printStackTrace();
}
}
class messageServerThread extends Thread{
Socket socket;
String usernameforThread;
public messageServerThread(Socket socket){
this.socket=socket;
}
public void run() {
try {
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));// 创建输入流对象
while (true) {
String info = in.readLine().trim();// 读取信息
System.out.println(info);
if(info.startsWith("$")){//登录
String username = "";
String password="";
String message[]=info.substring(1).split(" ");
username=message[0];
password=message[1];
if(user.containsKey(username)){
if(!logined.containsKey(username)){
System.out.println("username exits");
if(user.get(username).equals(password)){
System.out.println("password correct");
locklogined.lock();
logined.put(username,socket);
locklogined.unlock();
usernameforThread=username;
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);// 创建输出流对象
out.println("%y");
out.flush();
//传送user
Set<String> set1 = user.keySet();
out = new PrintWriter(socket.getOutputStream(), true);
out.println("*"+set1.toString());
//传送在线user
Set<String> set = logined.keySet();// 获得集合中所有键的Set视图
Iterator<String> keyIt = set.iterator();// 获得所有键的迭代器
while(keyIt.hasNext()){
String receiveKey = keyIt.next();// 获得表示接收信息的键
Socket s = logined.get(receiveKey);// 获得与该键对应的套接字对象
if(!receiveKey.equals(username)){
out = new PrintWriter(socket.getOutputStream(), true);
out.println("$"+receiveKey);
out.flush();
out = new PrintWriter(s.getOutputStream(), true);// 创建输出流对象
out.println("$"+username);
out.flush();
}
}
//传送离线信息
if(logoutinfo.containsKey(usernameforThread)){
Queue<String> q=logoutinfo.get(usernameforThread);
out = new PrintWriter(socket.getOutputStream(), true);
while(!q.isEmpty()){
String m=q.poll();
out.println(m);
out.flush();
}
}
//传送离线文件
Set<String> set3 = logoutwork.keySet();
keyIt = set3.iterator();
while(keyIt.hasNext()){
String f=keyIt.next();
String para[]=f.split(" ");
if(usernameforThread.equals(para[1])&&logoutwork.get(f)){
out = new PrintWriter(socket.getOutputStream(), true);
out.println("#"+f);
out.flush();
locklogoutinfo.lock();
logoutinfo.remove(f);
locklogoutinfo.unlock();
}
}
}else{//密码错误
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);// 创建输出流对象
out.println("%n");
out.flush();
}
}else{//用户已登录
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);// 创建输出流对象
out.println("%e");
out.flush();
}
}else{//无此用户
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);// 创建输出流对象
out.println("%n");
out.flush();
}
}else if(info.startsWith("#")){//转发信息
String dest="";
String message="";
int index;
for(index=0;index<info.length();index++){
if(info.charAt(index)==' '){
break;
}
}
dest=info.substring(1, index);
message=info.substring(index+1);
if(dest.equals("*")){//在线用户
Set<String> set = logined.keySet();// 获得集合中所有键的Set视图
Iterator<String> keyIt = set.iterator();// 获得所有键的迭代器
while(keyIt.hasNext()){
String receiveKey = keyIt.next();// 获得表示接收信息的键
Socket s = logined.get(receiveKey);// 获得与该键对应的套接字对象
if(!receiveKey.equals(usernameforThread)){
PrintWriter out = new PrintWriter(s.getOutputStream(), true);// 创建输出流对象
out.println("@"+usernameforThread+" "+message);
out.flush();
}
}
}else if(dest.equals("**")){//所有用户
Set<String> set = user.keySet();// 获得集合中所有键的Set视图
Iterator<String> keyIt = set.iterator();// 获得所有键的迭代器
while(keyIt.hasNext()){
String receiveKey = keyIt.next();// 获得表示接收信息的键
if(logined.containsKey(receiveKey)){
Socket s = logined.get(receiveKey);// 获得与该键对应的套接字对象
if(!receiveKey.equals(usernameforThread)){
PrintWriter out = new PrintWriter(s.getOutputStream(), true);// 创建输出流对象
out.println("@"+usernameforThread+" "+message);
out.flush();
}
}else{
locklogoutinfo.lock();
if(!logoutinfo.containsKey(receiveKey)){
logoutinfo.put(receiveKey,new LinkedList<String>());
}
logoutinfo.get(receiveKey).add("@"+usernameforThread+" "+message);
locklogoutinfo.unlock();
}
}
}else if(logined.containsKey(dest)){
Socket s = logined.get(dest);// 获得与该键对应的套接字对象
PrintWriter out = new PrintWriter(s.getOutputStream(), true);// 创建输出流对象
out.println("@"+usernameforThread+" "+message);
out.flush();
}else{
if(!logoutinfo.containsKey(dest)){
logoutinfo.put(dest,new LinkedList<String>());
}
logoutinfo.get(dest).add("@"+usernameforThread+" "+message);
}
}else if(info.startsWith("%")){//转发文件
String key=info.substring(1);
String para[]=key.split(" ");
if(logined.containsKey(para[1])){//在线传文件
lockwork.lock();
work.put(key, false);
lockwork.unlock();
System.out.println("tranfort"+" "+key);
new Thread(new Runnable(){
@Override
public void run() {
while(!work.get(key)){
;
}
try {
PrintWriter out = new PrintWriter(logined.get(para[1]).getOutputStream(), true);
out.println("#"+key);
out.flush();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}// 创建输出流对象
}
}).start();
}else{//离线传文件
locklogoutwork.lock();
logoutwork.put(key,false);
locklogoutwork.unlock();
System.out.println("tranfort"+" "+key);
new Thread(new Runnable(){
@Override
public void run() {
while(!logoutwork.get(key)){
;
}
try {
if(logined.containsKey(para[1])){
PrintWriter out = new PrintWriter(logined.get(para[1]).getOutputStream(), true);
out.println("#"+key);
out.flush();
locklogoutwork.lock();
logoutwork.remove(key);
locklogoutwork.unlock();
}
} catch (IOException e) {
e.printStackTrace();
}// 创建输出流对象
}
}).start();
}
}else{
System.out.println("unknown information!");
}
}
} catch (IOException e) {
System.out.println(socket + "has exited");
Set<String> set = logined.keySet();// 获得集合中所有键的Set视图
Iterator<String> keyIt = set.iterator();// 获得所有键的迭代器
while(keyIt.hasNext()){
String receiveKey = keyIt.next();// 获得表示接收信息的键
Socket s = logined.get(receiveKey);// 获得与该键对应的套接字对象
if(!receiveKey.equals(usernameforThread)){
PrintWriter out;
try {
out = new PrintWriter(s.getOutputStream(), true);
out.println("!"+usernameforThread);
out.flush();
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
}
locklogined.lock();
logined.remove(usernameforThread);
locklogined.unlock();
}
}
}
class receiverServerThread extends Thread{
Socket socket;
String path;
String savapath;
String source;
String dest;
String filename;
long filelength;
OutputStream outputStream;
InputStream inputStream;
FileInputStream fileInput;
FileOutputStream fileoutput;
int buffersize=8*1024;
String key;
public receiverServerThread(Socket socket){
this.socket=socket;
System.out.println(this.socket);
}
public void run(){
receiveFile();
}
public void receiveFile() {
try {
inputStream=socket.getInputStream();
outputStream=socket.getOutputStream();
BufferedReader in = new BufferedReader(new InputStreamReader(inputStream));
String key = in.readLine().trim();
String para[]=key.split(" ");
source=para[0];
dest=para[1];
filename=para[2];
filelength=Integer.valueOf(para[3]).longValue();
System.out.println("*"+source+" "+dest+" "+filename+" "+filelength);
File dir=new File("database/"+source);
if (!dir.exists()) {
dir.mkdirs();
}
String savePath = "database/"+source+"/"+filename; // 定义完整的存储路径
File parameter = new File(savePath); // 创建文件的存储路径
if (!parameter.exists()) {
try {
parameter.createNewFile();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
RandomAccessFile access = new RandomAccessFile(parameter,"rw");
access.seek(parameter.length());
PrintWriter out = new PrintWriter(outputStream, true);// 创建输出流对象
out.println(parameter.length());
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
byte[] buffer = new byte[buffersize];//创建接收缓冲区
int len=-1;
while((len = inputStream.read(buffer))!=-1){
access.write(buffer,0,len);
}
if(parameter.length()>=filelength){
if(work.containsKey(key)){
lockwork.lock();
work.replace(key, true);
lockwork.unlock();
}
if(logoutwork.containsKey(key)){
locklogoutwork.lock();
logoutwork.replace(key, true);
locklogoutwork.unlock();
}
System.out.println("update file finished!");
}
access.close();
out.close();
in.close();
} catch (IOException e2) {
// TODO Auto-generated catch block
e2.printStackTrace();
}finally{
try {
inputStream.close();
outputStream.close();
socket.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
class senderServerThread extends Thread{
Socket socket;
String path;
OutputStream outputStream;
InputStream inputStream;
FileInputStream fileInput;
FileOutputStream fileoutput;
int buffersize=8*1024;
public senderServerThread(Socket socket){
this.socket=socket;
}
public void run(){
sendfile();
}
void sendfile(){
try {
inputStream=socket.getInputStream();
outputStream=socket.getOutputStream();
BufferedReader in = new BufferedReader(new InputStreamReader(inputStream));
String key = in.readLine().trim();
System.out.println(key);
System.out.println(work.get(key));
System.out.println(logoutwork.get(key));
if((work.containsKey(key)&&work.get(key))||(logoutwork.containsKey(key)&&logoutwork.get(key))){
String para[]=key.split(" ");
String source=para[0];
String dest=para[1];
String filename=para[2];
long filelength=Integer.valueOf(para[3]).longValue();
path="database/"+source+"/"+filename;
FileInputStream fileInput = new FileInputStream(path);
int size = -1;
byte[] buffer = new byte[buffersize];
int sum=0;
while ((size = fileInput.read(buffer, 0, buffersize)) !=-1) {
sum+=size;
System.out.println(sum/1024);
outputStream.write(buffer, 0, size);
outputStream.flush();
}
System.out.println("download finish");
lockwork.lock();
work.remove(key);
lockwork.unlock();
locklogoutwork.lock();
logoutwork.remove(key);
locklogoutwork.unlock();
fileInput.close();
}
outputStream.flush();
in.close();
} catch (Exception e) {
e.printStackTrace();
}finally{
try {
inputStream.close();
outputStream.close();
socket.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
public static void main(String args[]) {
new Server();
}
}
客户端:
import java.io.*;
import java.net.*;
import java.util.*;
import java.text.DecimalFormat;
public class Client {
PrintWriter out;// 声明输出流对象
private boolean loginFlag = false;// 为true时表示已经登录,为false时表示未登录
private Set<String> friends=new TreeSet<String>();//在线好友列表
private Set<String> users=new TreeSet<String>();//好友列表
private String myself;//本机用户名
private String receivefile;//当服务器询问是否接收文件时,保存文件的信息。若同意接收,则使用此信息向服务器请求发送文件
private String ipv4="127.0.0.1";//服务器ip地址,我的服务器地址为47.98.153.188,需要修改成相应的服务器地址
/**
* author:XJTU mry
* time:2021.04.20
*/
public static void main(String args[]) {
new Client();
}
Client(){
createClientSocket();
}
public void createClientSocket() {
try {
Socket socket = new Socket(ipv4, 1978);// 创建套接字对象,连接服务器
//1978为服务器信息转发端口,1979为服务器接收文件端口,1980为服务器发送文件端口
out = new PrintWriter(socket.getOutputStream(), true);// 创建输出流对象
new ClientThreadreceiveMessage(socket).start();//客户端接收信息线程
new ClientThreadsendMessage(socket).start();//客户端发送信息线程
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
class ClientThreadreceiveMessage extends Thread {//客户端接收信息类。为内部类,可以访问Client类中定义的变量
Socket socket;
public ClientThreadreceiveMessage (Socket socket) {//构造函数,传入已经连接服务器后的socket
this.socket = socket;
}
public void run() {
try {
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));// 创建输入流对象
while (true) {
String info = in.readLine().trim();// 读取信息
//System.out.println(info);
if(info.startsWith("@")){
String source="";
String message="";
int index;
for(index=0;index<info.length();index++){
if(info.charAt(index)==' '){
break;
}
}
source=info.substring(1, index);
message=info.substring(index+1);
System.out.println("$"+source+":"+message);
}else if(info.startsWith("$")){
friends.add(info.substring(1));
}else if(info.startsWith("!")){
if(friends.contains(info.substring(1))){
friends.remove(info.substring(1));
}
}else if(info.startsWith("#")){
receivefile=info.substring(1);
String para[]=receivefile.split(" ");
System.out.println("#A file from"+" "+para[0]+" "+"name:"+para[2]+" "+"size:"+para[3]+"B");
System.out.println("please type: y/n");
}else if(info.startsWith("%")){//回复登录情况
if(info.charAt(1)=='y'){
loginFlag=true;
System.out.println("log in successed!");
}if(info.charAt(1)=='e'){
System.out.println("log in failed! The user has logged in!");
System.out.println("please try again");
}else if(info.charAt(1)=='n'){
System.out.println("log in failed! password is wrong!");
System.out.println("please try again");
}
}else if(info.startsWith("*")){
String usrs[]=info.substring(2,info.length()-1).split(",");
for(int i=0;i<usrs.length;i++){
users.add(usrs[i].trim());
}
}
else{
System.out.println("unknown information!");
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
class ClientThreadsendMessage extends Thread{//客户端发送信息类。为内部类,可以访问Client类中定义的变量
Socket socket;
Scanner scanner = new Scanner(System.in);
String command="";
Socket receiveFileSocket;
Socket sendFileSocket;
ClientThreadsendMessage(Socket socket){
this.socket=socket;
}
void help(){
System.out.println("login <username> <password> --log in");
System.out.println("onlinefriendlist --show online friend list");
System.out.println("friendlist --show friend list");
System.out.println("info <friendname> <message> --send message to friend");
System.out.println("file <friendname> <filename> --send file to friend");
System.out.println("exit --log out");
}
public void run(){
while(!loginFlag){
System.out.println("please log in first");
command=scanner.nextLine().trim();
if(loginFlag)break;
if(command.equals("help")){
help();
}
if(command.equals("exit")){
System.exit(0);
}
if(command.startsWith("login")){
String cmd="$";
StringTokenizer oo = new StringTokenizer(command.substring(5));
List<String> tmp=new ArrayList<String>();
while (oo.hasMoreTokens()){
tmp.add(oo.nextToken());
}
if(tmp.size()==2){
cmd=cmd+tmp.get(0)+" "+tmp.get(1);
myself=tmp.get(0);
out.println(cmd.trim());
out.flush();
}else{
System.out.println("unknown command");
}
}
}
while(true){
if(command.equals("friendlist")){
System.out.println(users.toString());
}else if(command.equals("onlinefriendlist")){
System.out.println(friends.toString());
}else if(command.equals("help")){
help();
}else if(command.startsWith("info")){
String cmd="#";
StringTokenizer oo = new StringTokenizer(command.substring(4));
while (oo.hasMoreTokens()){
cmd+=oo.nextToken();
cmd+=" ";
}
int index=-1;
for(int i=0;i<cmd.length();i++){
if(cmd.charAt(i)==' '){
index=i;
break;
}
}
if(index>0){
if(users.contains(cmd.substring(1, index))||cmd.substring(1,index).equals("*")||cmd.substring(1,index).equals("**")){
out.println(cmd.trim());
out.flush();
}else{
System.out.println("dest user doesn't exist!");
}
}else{
System.out.println("unknown command!");
}
}else if(command.startsWith("file")){
String cmd="%";
List<String> tmp=new ArrayList<String>();
StringTokenizer oo = new StringTokenizer(command.substring(4));
while (oo.hasMoreTokens()){
tmp.add(oo.nextToken());
}
if(tmp.size()==2){
if(users.contains(tmp.get(0))){
File src = new File(tmp.get(1));
if(src.exists()){
cmd=cmd+myself+" "+tmp.get(0)+" "+src.getName()+" "+src.length();
//System.out.println(cmd);
out.println(cmd.trim());
out.flush();
try {
sleep(500);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
Socket sendFilSocket;
try {
sendFilSocket = new Socket(ipv4, 1979);
//System.out.println(myself+" "+tmp.get(0)+" "+src.getName()+" "+src.length());
new senderServerThread(sendFilSocket,myself,tmp.get(0),src.getName(),src.length()).start();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}else{
System.out.println("file doesn't exit!");
}
}else{
System.out.println("dest user doesn't exist!");
}
}else{
System.out.println("unkown command!");
}
}else if(command.equals("y")){
try {
Socket socket = new Socket(ipv4, 1980);
new receiverServerThread(socket,receivefile).start();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}// 创建套接字对象
}else if(command.equals("n")){
;
}else if(command.equals("exit")){
System.exit(0);
}else{
System.out.println("unkown command!");
}
command=scanner.nextLine().trim();
}
}
}
class senderServerThread extends Thread{//客户端向服务器上传文件类,先传递文件名字、长度等基本信息,再传送文件内容
Socket socket;
String path;
String source;
String dest;
String filename;
long filelength;
OutputStream outputStream;
InputStream inputStream;
FileInputStream fileInput;
FileOutputStream fileoutput;
int buffersize=8*1024;
public senderServerThread(Socket socket,String source,String dest,String filename,long filelength){
this.socket=socket;
this.source=source;
this.dest=dest;
this.filename=filename;
this.filelength=filelength;
this.path=filename;
}
public void run(){
sendfile();
}
void sendfile(){
try {
inputStream=socket.getInputStream();
outputStream=socket.getOutputStream();
PrintWriter out = new PrintWriter(outputStream, true);// 创建输出流对象
out.println(source+" "+dest+" "+filename+ " "+filelength);
BufferedReader in = new BufferedReader(new InputStreamReader(inputStream));
String Stringlen = in.readLine().trim();
long len=Integer.valueOf(Stringlen).longValue();
//System.out.println("断点续传:"+len);
File file = new File(path);
RandomAccessFile access = new RandomAccessFile(file,"r");
access.seek(len);
int size = -1;
byte[] buffer = new byte[buffersize];
long sum=0;
sum+=len;
System.out.println("file upload start");
ConsoleProgressBarDemo cpb = new ConsoleProgressBarDemo(50, '#');
while ((size = access.read(buffer, 0, buffersize)) !=-1) {
sum+=size;
cpb.show((int)(sum*100/filelength));
outputStream.write(buffer, 0, size);
outputStream.flush();
}
System.out.println("upload ended");
outputStream.flush();
access.close();
in.close();
out.close();
} catch (Exception e) {
e.printStackTrace();
}finally{
try {
inputStream.close();
outputStream.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
class receiverServerThread extends Thread{
//客户端下载文件类,先向服务器传递文件名字、长度等基本信息(Client类变量receivefile),然后接收文件具体内容
Socket socket;
String path;
String savapath;
String source;
String dest;
String filename;
long filelength;
OutputStream outputStream;
InputStream inputStream;
FileInputStream fileInput;
FileOutputStream fileoutput;
int buffersize=8*1024;
String receivefile;
public receiverServerThread(Socket socket,String receivefile){
this.socket=socket;
String para[]=receivefile.split(" ");
source=para[0];
dest=para[1];
filename=para[2];
filelength=Integer.valueOf(para[3]).longValue();
this.receivefile=receivefile.trim();
}
public void run(){
receiveFile();
}
public void receiveFile() {
try {
inputStream=socket.getInputStream();
outputStream=socket.getOutputStream();
PrintWriter out = new PrintWriter(outputStream, true);// 创建输出流对象
out.println(receivefile);
File dir = new File("database/"+source); // 创建文件的存储路径
if (!dir.exists()) {
dir.mkdirs();
}
String savePath = "database/"+source+"/"+filename; // 定义完整的存储路径
File parameter = new File(savePath); // 创建文件的存储路径
if (!parameter.exists()) {
try {
parameter.createNewFile();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
FileWriter fileWriter;
fileWriter = new FileWriter(parameter);
fileWriter.write("");
fileWriter.flush();
fileWriter.close();
FileOutputStream file = new FileOutputStream(parameter, false);
byte[] buffer = new byte[buffersize];//创建接收缓冲区
int len=-1;
long sum=0;
ConsoleProgressBarDemo cpb = new ConsoleProgressBarDemo(50, '#');
while((len = inputStream.read(buffer))!=-1){
file.write(buffer, 0, len);
sum+=len;
cpb.show((int)(sum*100/filelength));
if(sum>=filelength){
break;
}
}
System.out.println("download ended");
file.close();
out.close();
} catch (IOException e2) {
e2.printStackTrace();
}finally{
try {
inputStream.close();
outputStream.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public class ConsoleProgressBarDemo {//进度条类,显示进度条
private int barLen;
private char showChar;
private DecimalFormat formater = new DecimalFormat("#.##%");
public ConsoleProgressBarDemo(int barLen, char showChar) {
this.barLen = barLen;
this.showChar = showChar;
}
public void show(int value) {
if (value < 0 || value > 100) {
return;
}
reset();
// 比例
float rate = (float) (value*1.0 / 100);
// 比例*进度条总长度=当前长度
draw(barLen, rate);
if (value == 100L) {
afterComplete();
}
}
private void draw(int barLen, float rate) {
int len = (int) (rate * barLen);
System.out.print("Progress: ");
for (int i = 0; i < len; i++) {
System.out.print(showChar);
}
for (int i = 0; i < barLen-len; i++) {
System.out.print(" ");
}
System.out.print(" |" + format(rate));
}
private void reset() {
System.out.print('\r');
}
private void afterComplete() {
System.out.print('\n');
}
private String format(float num) {
return formater.format(num);
}
}
}