Socket编程——功能类似QQ的Java聊天程序(文字聊天、传输文件)

实现功能

  1. 验证用户登录
  2. 两个在线用户间的文字聊天
  3. 多用户群聊
  4. 两个在线用户之间传输二进制文件
  5. 发送离线信息
  6. 传输离线文件
  7. 文件断点续传

框架结构

  1. 服务器框架结构
    在这里插入图片描述

  2. 客户端框架结构
    在这里插入图片描述

主要功能设计及实现

为方便表述,约定usr表示用户名,password为密码。srcusr为发送信息或文件的用户;dstusr为接收信息或接收文件的用户。message为文字信息;onlinefile为在线文件信息;offlinefile为离线文件信息。

  1. 服务器端主要数据结构
变量名类型解释
usrHashtable<String,String>存储用户名和密码
loginedHashtable<String,Socket>存储已登录用户名和对应Socket,方便转发信息和文件
workHashtable<String,Boolean>存储在线转发文件任务。
key为文件信息;value=false表示该文件尚未上传至服务器;value=true表示该文件已上传至服务器 ,用户可以进行下载
logoutinfoHashtable<String,Queue<String>>存储转发离线信息任务。
key为dstusr;value为一个队列,存储srcusr和具体信息内容
logoutworkHashtable<String,Boolean>存储转发离线文件任务。
key为文件信息;value=false表示该文件尚未上传至服务器;value=true表示该文件已上传至服务器 ,用户可以进行下载
  1. 客户端主要数据结构
变量名类型解释
loginFlagboolean登录标志。loginFlag=true表示客户端已登录;loginFlag=false表示客户端尚未登录,不可发送信息或文件
friendsTreeSet<String>在线好友列表(已登录)
usersTreeSet<String>存储好友列表(已登录+未登录)
receivefileString保存接收文件信息
  1. 验证用户登录
    客户端向服务器发送usr和password,服务器端验证usr和password是否合法。若成功登录,服务器向usr发送已登录用户和未登录用户名,通知已登录用户user已经成功登录。
  2. 两个在线用户间的文字聊天
    客户端将信息内容(srcusr+dstusr+message)发送给服务器,服务器转发信息给dstusr。
  3. 多用户群聊
    若dstusr为*,服务器将信息转发给所有的在线用户。
    若dstusr为**,服务器将信息转发给所有的用户(在线+离线)。
  4. 两在线用户间传输文件
    客户端向服务器发送消息(onlinefile);服务器端work增加在线传输任务,并初始化work[onlinefile]=false;客户端向服务器传送文件,文件传送完毕时,服务器端设置work[onlinefile]=true,并通知destusr下载文件。
  5. 文件断点续传
    客户端上服务器上传文件前,首先获取已传输字节数量,使用RandomAccessFile.seek()方法在断点出续传。
  6. 离线文件传输
    与在线文件传输的区别是,当dstusr登录时,再通知dstusr下载文件。
  7. 共享数据加锁
    需要对服务器端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

使用示例

首先,服务器端设置登录账号和密码,程序中默认设置如下:

usernamepassword
Jack123456
Mary123456
Wangming123456
MRY123456
110123456

启动服务器后启动客户端,尝试输入以下指令:

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                             //退出程序

问题记录

  1. 传输文件名和文件长度等信息时,起初使用对象流,程序出现java.io.EOFException报错。经查看此博客,发现以下两点:
    a. 对象流不同于普通的字节流,当对象流中没有数据时,程序却尝试读取数据,会报EOFException;而字节流就不会出现这种情况,字节流会返回-1
    b. ObjectInputStream写入的数据,在ObjectOutputStream上读取时,应该按照相同的数据类型依次读取,否则数据类型不等会抛出EOFException
    当传送文件信息时使用对象流,因为传输时延,服务器端会检测到无对象流数据,抛出EOFException。
    最终,自己使用PrintWriter传递文件信息。将文件名、长度等信息合成一个字符串,使用PrintWriter传输给服务器。
  2. 当上传和下载文件使用同一个端口时,出现了死锁
    当文件上传和下载使用同一个端口时,服务器不可以同时进行上传、下载文件作业,否则会出现死锁。
    最终,自己将服务器文件上传和下载功能分离,两者各使用一个端口。

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);
        }
    }
}

  • 5
    点赞
  • 46
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值