基于自定义通信协议的文件传输

        要实现通信,首先我们需要一个服务器,然后需要多个客户机。主要方法是通过java.net 包下面的API。创建一个服务器ServerSocket,然后用Socket创建多个客户机。

          首先,我们的目地是创建一个简单服务器,能将客户机发来的字符串显示出来,并且再回送给客户
机——有必要解释一下:在这里,服务器指的是等待别人来连接的机器;客户机,当然就指的是主
动去连接别人的机器了,这就像打电话过程中的主叫与被叫的区分一样,一旦连结成功,就不存在
这样谁是客户机谁是服务器的区分了。

        

package HelloQQxy_0302;

import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * 简单服务器的实现
 * @author 
 *
 */
public class ChatServer {
/**
 * 在指定的端口上启动一个服务器
 */
	private void setUpServer(int port){
		try{
			//建立绑定在指定端口上的服务器对象
			ServerSocket server=new ServerSocket(port);
			
			System.out.println("服务器创建成功!"+port);
			while(true){
			//让服务器进入等待状态:阻塞状态
			Socket client=server.accept();
			//从连接对象上得到输入输出流对象
			OutputStream out=client.getOutputStream();
			InputStream ins=client.getInputStream();
			String s="hello,welcome to our chatroom\r\n";
			
			byte[] data=s.getBytes();//取得组成这个字符串的字节
			out.write(data);//用输出对象发送数据!
			out.flush();//强制输出
			int in=0;//一个一个字节读取客户机的输入函数
			while(in!=13){//如果读到的不是13,即回车字符
				in=ins.read();
				System.out.println("读到是:"+in);
			}
			System.out.println("客户机按了回车,退出:"+in);
			client.close();//关闭与客户机的连接
			}
		}
		catch(Exception ef){
			ef.printStackTrace();
		}
	}
	
	//主函数
	public static void main(String[] args){
		ChatServer cs=new ChatServer();
		cs.setUpServer(1000);
	}
}

 然后在 cmd 下输入telnet localhost 1000 这里的telnet 是连接的意思,localhost是本地的IP地址 1000是计算机的端口号,这样就实现了通信之间的连接。

<!--StartFragment -->


 然后就是,传文件。我们组自定义文件的写入格式为
  dous.writeInt(type);//文件类型
  dous.writeInt(size);//文件名长度
  
  int filesize = ins.available();//输入流取出文件的长度
  byte[] filedatalen=new byte[filesize];
  ins.read(filedatalen);
 dous.writeInt(filesize);//文件总长度
  dous.write(filename);//文件名
  dous.write(filedatalen);//文件数据组
总的来说我们只要记住一点how to write ,how to read就行
下面是具体代码
 
package ChatServer0713_xy;

import java.io.DataInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * 简单服务器实现
 * 1.根据协议,接受解析文本聊天消息
 * 2.根据协议,接受解析文件传送消息
 * @author 
 *
 */
public class ChatServer {
	//启动主函数
	public static void main(String[] args){
		ChatServer cs=new ChatServer();
		cs.setUpSever(8080);
	}
/**
 * 在指定端口上启动一个服务器
 * @param port:服务器所用的端口
 */
	private void setUpSever(int port) {
		try {
			
			//1.建立绑定在指定端口上的服务器对象
			ServerSocket server=new ServerSocket(port);
			System.out.println("服务器创建成功!"+port);
			//2.让服务器进入等待状态:阻塞状态
			//3.当有客户机连接上来时,等待方法就会返回,返回一个代表与客户机连接的对象
			while(true){//让服务器进入循环等待状态
				Socket client=server.accept();
				System.out.println("Incoming client"
						+client.getRemoteSocketAddress());
				//调用处理连接对象的方法去处理连接
				processChat(client);
			}
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	//处理客户机进入的连接对象
private void processChat(Socket client) {
            try {
            	System.out.println("hi");
            	//得到一个输入、输出流对象
				OutputStream	out=client.getOutputStream();
				InputStream ins=client.getInputStream();
				//将输入流包装为DataInputStream方便读取原始类型的流
				DataInputStream dins=new DataInputStream(ins);
				while(true){
					//开始读取数据:每一条消息,总是以一个int开头
					//1.读取消息长度,readInt()方法底层从流中读取4个字节,组成一个int
					int totalLen=dins.readInt();
					System.out.println("***********进入一条消息总长: "+totalLen);
					//2.读取消息类型表示,只读取一个字节
					byte flag=dins.readByte();
					System.out.println("消息接受的类型为:"+flag);
					//3.读取目标客户号码,一个int
					int destNum=dins.readInt();//消息头解析完毕
					System.out.println("消息接受目标用户号 是:"+destNum);
					//根据消息的类型,读取消息体部分
					if(flag==1){//类型为1,是文本聊天消息,按其规则读取
						//创建对应消息体部分字节的长度的数据
						byte[] data=new byte[totalLen-4-1-4];
						//从流中读取DATA.length个字节放入数组中
						dins.readFully(data);
						String msg=new String(data);//转换成字符串
						System.out.println("发给文本给:"+destNum+"类容是"+msg);
					}
					else if(flag==2){
						//文件数据包体解析
						System.out.println("发给文本给:"+destNum);
						byte[] data=new byte[256];
						dins.readFully(data);//读取256个字节作为文件名字
						//解析出文件名字,并除去末尾空格
						String fileName=new String(data).trim();
						System.out.println("读到的文件名字是:"+fileName);
						//余下的字节就是文件类容
						data=new byte[totalLen-4-1-4-256];//文件字节数据总长
						dins.readFully(data);//读入文件的字节
						//保存文件到当前目录下:
						FileOutputStream fous=new FileOutputStream(fileName);
						fous.write(data);
						fous.flush();
						fous.close();
						System.out.println("文件保存完成!");
					}else{
						System.out.println("收到未知数据包:"+flag);
						client.close();
					}
				}
            } catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
}
}
 下面是客户机的代码
package ChatServer0713_xy;

import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;

/**
 * 简单客户端实现
 * 1.根据协议,发送文本聊天消息
 * 2.根据协议,发送文件传送消息
 * @author 
 *
 */
public class ChatClient {
private DataOutputStream dous;//输入流对象
/**
 * 向流中写入定长字节,如果不足,补二进制0
 * @param out:要写入的流
 * @param str字符串
 * @param len要写入的长度
 */

private void writeString(DataOutputStream out,String str,int len){
	
	try {
		byte[] data=str.getBytes();
		out.write(data);
		//假设都是短,需要补0
		while(len>data.length){
			out.writeByte('\0');//补二进制0
			len--;
		}
	} catch (IOException e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	}
}
/**
 * 发送一条文本消息
 * @param msg:消息内容
 * @param destNum:接受者号码
 * 打包过程
 */
private void sendTextMsg(String msg,int destNum){
	//System.out.println("1");
	try {
	//	System.out.println("2");
		byte[] strb=msg.getBytes();//得到消息的字节数
		int totalLen=4+1+4+strb.length;
		System.out.println("发送总长度为:"+totalLen);
		dous.writeInt(totalLen);//总长
		dous.writeByte(1);//类型:1为文本消息
		dous.writeInt(destNum);//用户号
		dous.write(strb);//写入消息内容
		dous.flush();
	} catch (IOException e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	}//总长
}
/**
 * 发送一个文件数据包
 * @param fileName:文件的绝对路径名
 * @param destNum:用户号
 * @throws IOException 
 */
private void sendFileMsg(String fileName,int destNum) throws IOException{
	
	
		//根据文件名创建文件对象
		File file=new File(fileName);
		//根据文件对象,构造一个输入流
		InputStream ins=new FileInputStream(file);
		
		int type=2;
		dous.writeInt(type);
		
		byte[] filename = file.getName().getBytes();
		int size=filename.length;
		dous.writeInt(size);
		
		int filesize = ins.available();
		byte[] filedatalen=new byte[filesize];
		ins.read(filedatalen);
		dous.writeInt(filesize);
		dous.write(filename);
		dous.write(filedatalen);
		
		
		dous.flush();
		
		
		/**
		 * int fileDataLen=ins.available();//文件数据总长
		int totalLen=4+1+4+256+fileDataLen;
		dous.writeInt(totalLen);
		dous.writeByte(2);//类型是2,即文件数据包
		//写入目标用户号
		dous.writeInt(destNum);
		//文件名:得到文件的短名字
		String shortFileName=file.getName();
		writeString(dous,shortFileName,256);
		//写入文件名,不足256个长度时,补\0
		 * byte[] fileData=new byte[fileDataLen];
		ins.read(fileData);
		
		dous.write(fileData);//写出到服务器的流
		 */
	}
/**
 * 连接上服务器
 * @param ip:服务器ip
 * @param port:服务器端口
 */
public void conn2Server(String ip, int port){
	// 创建一个到服务器端的Socket对象
	try {
	Socket client = new Socket(ip, port);
	// 得到输入输出流对象
	InputStream ins = client.getInputStream();
	OutputStream ous = client.getOutputStream();
	//将输出流包装为DataOutputStream对象
	dous=new DataOutputStream(ous);
	
	int testCount=0;
	while(true){
		System.out.println("登录服务器成功,请选择你要发的类型(1:聊天 2:文件:");
		//让用户从命令行输入要发送的文件的名字
		Scanner sc=new Scanner(System.in);
		int type=sc.nextInt();
	
		if(type==1){//发文本
			sendTextMsg("abc聊天类容"+testCount,8080);
		}
		if(type==2){//发文件,祝这个文件必须存在
			sendFileMsg("F:\\a.txt",8080);
		}
		testCount++;
		}
	}
	catch (IOException e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	}
}
public static void main(String[] args) {
	ChatClient qqc = new ChatClient();
	qqc.conn2Server("localhost", 8080);
	//qqc.conn2Server("192.168.0.185", 8080);
	}
}
 其次,我们要注意
第一 ,将 InputStream 包装为DataInputStream 流对象 : 
InputStream ins = client.getInputStream();
//将输入流包装为DataInputStream方便读取原始类型的流
DataInputStream dins=new DataInputStream(ins);
 如上代码中将从Socket上得到的输入流包装成为DataInputStream流对象,随后如果调用dins的readByte()时,只会从底层的数据流中读取一个字节返回:而调用readInt()时,方法内部经过位运算  实现了将读到的  4个字节 组成一个 int型数据返回。
所以读取数据,读几个字节,是什么类型,必须按照与客户端所准守的协议执行,如果多读或少读,都会导致通信出错。
第二read()方法与readFully()方法的区别:
 
byte[] data=new byte[totalLen-4-1-4];
//从流中读取data.length个字节放入数组中
dins.readFully(data);
 此处从流中读取字节,填充字节数组时,没有使用read(要填充的数组)方法,而是调用readFully()方法,这样做更为安全。网路通信中,当发送大的数据量时,有这样一种可能:一部分数据已发送到对方,有一部分数据还在本地的网卡缓存中,如果调用read()方法,可能会提前返回而没有读到足够的数据,在传大块数据(如一次传送一个较大文件时)可能出错,而readfully()方法会一直等待,读取到数组长度的所有数据,才会返回。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值