【计算机网络程序设计】多线程文件传输

这篇博客详细介绍了基于Java实现的文件传输协议,包括客户端和服务器端的子线程设计。客户端通过发送文件名、文件大小和文件内容数据包与服务器进行交互,而服务器端负责接收并验证这些信息,确保文件完整传输。整个过程涉及到了数据包的格式定义、缓冲区的使用以及文件指针的管理。

数据包格式

数据包分为标志位、数据类型、数据长度和数据。
1.标志位固定位4B:0x00 00 00 1F;
2.数据类型2B:包括文件名1、文件大小2、文件内容0三种;
3.数据长度2B:特别的,文件大小的数据长度为0x00 04;
4.数据:特别的,文件内容的数据前4Byte为偏置offset,偏置用来指定读写起始位置。
5.传输内容的数据包非数据部分有12B,其余类型均为8B
在这里插入图片描述

协议设计

客户端

1.用户输入文件名和线程数
2.建立连接后,客户端向服务器端发送文件名filename数据包;
3.收到回馈的“OK”后,再发送文件总长度size;
4.再次收到回馈的“OK”后,状态转为“ready”,再发送文件内容数据;
5.每个数据包最多含2048Byte的数据部分,文件名包总长不超2056B,文件内容包总长不超2060B。

服务器

1.建立连接后,服务端正确接收到文件名、文件长度、文件数据,都要回复客户端“OK”;接收错误则断开连接。
2.数据包读完后判断是否是最后一个线程,如果是最后一个线程则关闭文件;
3.最后一线程还需要则判断文件大小是否正确,如果正确无误,返回“传输结束”;有误,则回复“文件大小错误”。

  • 客户端子线程(发送线程)
package com.cn.edu.cuc.计网.Filefinish;

import java.io.*;
import java.net.InetSocketAddress;
import java.net.Socket;

public class FileClientThread extends Thread{

    public FileClient client;

    public FileClientThread(FileClient client) {
        this.client = client;
    }
    // 发送函数
    public boolean sendFileInfo(BufferedReader reader, OutputStream output) throws IOException{
        String response ="";
        byte buf[]=new byte[2056];// 2048bit最大文件长度+8bit
        //封装数据包
        // 1flag
        buf[0]= 0x00;
        buf[1]= 0x00;
        buf[2]= 0x00;
        buf[3]= 0x1F;
        // 2-1filename
        buf[4]= 0x00;//filename_type=1 2B
        buf[5]= 0x01;
        buf[6]=(byte) ((client.filename.length()>>8)& 0x0FF);//filename_length 2B
        buf[7]=(byte) (client.filename.length()& 0x0FF);

        for(int i=0; i < client.filename.length(); i++) {//filename_data
            buf[8+i]=client.filename.getBytes()[i];
        }
        // 写入缓冲区(发送)
        output.write(buf,0,8 + client.filename.length());
        // 收到服务器回复
        if(!reader.readLine().equalsIgnoreCase("OK")){
            System.out.println("upload error");
            return false;
        }
        // 2-2 size
        buf[4] = 0x00;
        buf[5] = 0x02;//filesize_type=2 2B
        buf[6] = 0x00;
        buf[7] = 0x04;//filesize_length=4位16进制数 2B

        buf[8] = (byte)((client.file.length()>>24) & 0x0FF);
        buf[9] = (byte)((client.file.length()>>16) & 0x0FF);
        buf[10] = (byte)((client.file.length()>>8) & 0x0FF);
        buf[11] = (byte)(client.file.length() & 0x0FF);// 四位十六进制数
        // 写入缓冲区(发送)
        output.write(buf,0,8+4);
        // 收到服务器回复
        if(!reader.readLine().equalsIgnoreCase("OK")){
            System.out.println("upload error");
            return false;
        }
        return true;
    }

    public void run() {
        try{
            // 定义并赋值port=13
            InetSocketAddress socketAddress = new InetSocketAddress("127.0.0.1",13);
            Socket clientSocket = new Socket();
            clientSocket.connect(socketAddress);
            System.out.println(getName()+" Server "+socketAddress.getHostString()+" connected");
            //获取输入输出流
            OutputStream output = clientSocket.getOutputStream();
            Reader reader = new InputStreamReader(clientSocket.getInputStream());
            BufferedReader bufferReader = new BufferedReader(reader);
            synchronized (client){
                if(!client.ready){
                    client.ready = sendFileInfo(bufferReader,output);
                }
            }
            String response = "";
            byte buf[] = new byte[2060];//2048文件长度+8头信息+4偏置信息
            //flag 4B
            buf[0]= 0x00;
            buf[1]= 0x00;
            buf[2]= 0x00;
            buf[3]= 0x1F;
            //type0——>filename 2B
            buf[4]= 0x00;
            buf[5]= 0x00;
            long offset=0;
            int n=0;
            synchronized (client){
                offset = client.file.getFilePointer(); //文件指针offset
                n = client.file.read(buf, 12, 2048);
                System.out.println(getName()+" offset:"+offset);
            }
            while(n>0)
            {
                // 写入文件长度信息 n 2B
                buf[6] = (byte) ((n>>8) & 0x0FF);
                buf[7] = (byte) (n & 0x0FF);// 文件长度2B
                // 写入文件偏置信息offset 4B
                buf[8] = (byte)((offset>>24) & 0x0FF);
                buf[9] = (byte)((offset>>16) & 0x0FF);
                buf[10] = (byte)((offset>>8) & 0x0FF);
                buf[11] = (byte)(offset & 0x0FF);
                // 发送文件内容
                output.write(buf,0,12+n);
                System.out.println("read  "+ n);

                if(!bufferReader.readLine().equalsIgnoreCase("OK")) {
                    System.out.println("upload error");
                    break;
                }
                synchronized (client) {
                    offset = client.file.getFilePointer();
                    n = client.file.read(buf, 12, 2048);
                    System.out.println(getName()+" offset:"+ offset);
                }
            }
        }
        catch (IOException e) {
            e.printStackTrace();
        }
    }
}

  • 服务器端子线程(接收线程)
package com.cn.edu.cuc.计网.Filefinish;
import java.io.*;
import java.net.*;
public class FileServerThread extends Thread{

    private Socket clientSocket = null;
    public FileServer server = null;

    public FileServerThread(FileServer server,Socket s)
    {
        this.server = server;
        this.clientSocket = s;
        synchronized (server) {//加锁
            server.clientThreadCount++;
        }
    }
    public void run()
    {
        try{
            System.out.println(getName()+" client:"+clientSocket.getInetAddress().getHostAddress()+" : "+clientSocket.getPort());
            //获取输入输出流
            InputStream input = clientSocket.getInputStream();
            OutputStream output = clientSocket.getOutputStream();
            // 开辟缓冲区(文件长度不超过2^16)
            byte buf[] = new byte[65536];
            // 读取字节数
            int n = input.read(buf);
            while(n >=8) {
                if(buf[0]==0x00&&buf[1]==0x00&&buf[2]==0x00&&buf[3]==0x1F) {
                    // type 2B
                    int type = (buf[4]<<8)| buf[5];
                    //1 filename
                    if(type == 1) {
                        synchronized (server)
                        {
                            if(server.file == null) {
                                int len = (buf[6]<<8)|buf[7];// filename_len 2B
                                String filename = new String(buf,8,len);// 偏置包括标志位、类型位
                                server.createFile(filename); // 创建该文件名的文件
                            }
                            server.notifyAll();//释放锁
                        }
                        System.out.println("filename is received " + server.filename);
                        // 回复OK
                        output.write("OK\n".getBytes());//转化为比特流
                    }
                    //2 filesize
                    else if(type==2) {
                        synchronized (server) {
                            server.filesize = (((int) buf[8]& 0xFF)<<24)|(((int) buf[9] & 0xFF)<<16)|(((int) buf[10]& 0xFF)<<8)|((int) buf[11]& 0xFF);
                            server.notifyAll();//释放锁
                        }
                        System.out.println("filesize is received "+ server.filesize);
                        // 回复OK
                        output.write("OK\n".getBytes());
                    }
                    //3data
                    else {
                        // 读取数据长度2B和偏置4B
                        int len = (((int)buf[6] & 0xFF)<<8)|((int)buf[7] & 0xFF);
                        int offset= (((int) buf[8]& 0xFF)<<24)|(((int) buf[9]& 0xFF)<<16)|(((int) buf[10]& 0xFF)<<8)|((int) buf[11]& 0xFF);
                        System.out.println(getName()+ " offset: "+ offset +"\tlength:"+ len);
                        // debug长度为零跳出
                        if(len<=0) {
                            break;
                        }
                        synchronized (server)
                        {
                            // 文件不存在或 文件指针没有读到指定偏置位置,等待
                            while(server.file==null|| server.file.getFilePointer()!=offset) {
                                server.wait();
                            }
                            // 指针到了偏置位置,写入文件(去除8B前缀+4B偏置信息)
                            server.file.write(buf,12,len);
                            server.bytesCount +=len;// 更新读入的字节数
                            server.notifyAll();//释放
                        }
                        // 回复OK
                        output.write(("OK\n").getBytes());
                    }
                }
                n = input.read(buf);
            }
            clientSocket.close();
            synchronized (server)
            {
                server.clientThreadCount--;
                //最后一个线程
                if(server.clientThreadCount==0) {
                    server.file.close();
                    if(server.filesize!=server.bytesCount) {
                        System.out.println("file size error");
                    }
                    else {
                        System.out.println(server.filename + "is received successfully!");
                    }
                }
                server.notifyAll();//释放
            }
        }
        catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println(getName()+ " terminated");//线程终止
    }

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值