数据包格式
数据包分为标志位、数据类型、数据长度和数据。
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");//线程终止
}
}
这篇博客详细介绍了基于Java实现的文件传输协议,包括客户端和服务器端的子线程设计。客户端通过发送文件名、文件大小和文件内容数据包与服务器进行交互,而服务器端负责接收并验证这些信息,确保文件完整传输。整个过程涉及到了数据包的格式定义、缓冲区的使用以及文件指针的管理。
706

被折叠的 条评论
为什么被折叠?



