通信之文件传输

        我们要通信就必须要有一个服务器和多个客户端,如同打电话时的被叫与主叫,而实现二者的连接就必    须有一套规则,即通信协议。

   

    今天说的就是自定义通信协议来实现文件的传输。

     

    首先我们说一下消息传输时服务器读取消息的规则:

       1.读取消息的总长,int型数据

       2.读取消息的类型,byte型数据,1为文本聊天消息,2为文件

       3.若类型为1时,则再读取接收者的号码(int型数据),消息内容

       4.若类型为2时,则读取消息的规则为:读取接收者的号码,读取文件名,读取文件内容,保存文件

    

    具体代码:

       服务器:

    

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

public class Sever {
	//启动主函数
    public static void main(String[] args) {
    	Sever cs = new Sever();
        cs.setUpServer(1111);
    }
    /**
    * 在指定端口上启动一个服务器
    * @param port:服务器所用的端口
    */
    public void setUpServer(int port) {
    	try {
    		// 1.建立绑定在指定端口上的服务器对象
                ServerSocket server = new ServerSocket(port);
            
                while (true) { 
            	// 让服务器进处循环等待状态
                Socket client = server.accept();
                System.out.println("Incoming clieng:"+client.getRemoteSocketAddress());
                // 调用处理连接对象的方法去处理连接
                processChat(client);
            }
          } catch (Exception ef) {
        	  ef.printStackTrace();
           }
    }
     // 处理客户机进入的连接对象
     private void processChat(java.net.Socket client) {
        	try {
        		// 得到一个输出/输入流对象
                        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 (Exception ef) {
            	 ef.printStackTrace();
            }
        }
 }

    注意:

     1.将InputStream包装为DataInputStream流对象:

            可以将Socket上得到的输入流包装为DataInputStream流对象,随后,如果调用dins的readByte()               时,只会从底层的数据流中读取一个字节返回;而调用readInt()时,则从底层读取4个字节(32位),             readInt() 方法内部经过位运算实现了将读到的4个字节组成一个int型数据返回

     2.read()方法与readFully()方法的区别:

         当发送大数据量时,有可能一部分数据已发送到对方,有一部分数据还在本地的网卡缓存中,如果调

    用read()方法 可能会提前返回而没有读到足够的数据,在传大块数据(如一次传送一个较大文件时)可

    能出错,而readfully()方法一直等待,读取到数组长度的所有数据,才会返回。

      
     客户端 :

      客户端代码要发送数据时,也必须按照协议格式和顺序将组成消息的各部分数据发送。发送据时应用到      DataOutputStream对象,调用其writeInt(1)方法时,这个整数1会被作为4个字节写入到流中;但如果调用      writeByte(1)方法写入,就只会写入一个字节。

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


public class Chat {
    private DataOutputStream dous;//输出流对象
    
    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');
                    len--;
                }
         }catch(Exception ef){
        	 ef.printStackTrace(); 
         }
    }
    /**
    * 发送一条文本消息
    * @param msg:消息内容
    * @param destNum:接收者号码
    */
    private void sendTextMsg(String msg,int destNum){
    	try{
    		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(Exception ef){ 
        	ef.printStackTrace();
        }
    }
    /**
    * 发送一个文件数据包
    * @param fileName:文件绝对路径名
    * @param destNum:目标用户号码
    */
    private void sendFileMsg(String fileName,int destNum){
    	try{
    		//根据文件名创建文件对象
                File file=new File(fileName);
                //根据文件对象,构造一个输入流
                InputStream ins=new FileInputStream(file);
                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();
               //写入文件名,不足256个长度时,补\0
               writeString(dous,shortFileName,256);
               byte[] fileData=new byte[fileDataLen];
               ins.read(fileData);//读入文件数据
               dous.write(fileData);//写出到服务器的流中
               dous.flush(); //
         }catch(Exception ef){ 
        	 ef.printStackTrace(); 
        }
    }
    /**
    * 连接上服务器
    * @param ip:服务器ip
    * @param port:服务器端口
    */
    public void conn2Server(String ip, int port) {
    	 // 创建一个到服务器端的Socket对象
         try {
        	 java.net.Socket client = new java.net.Socket(ip, port);
                 // 得到输入输出流对象
                 InputStream ins = client.getInputStream();
                 OutputStream ous = client.getOutputStream();
                //将输出流包装为DataOutputStream对象
                int testCount=0;
             while(true){
                 dous=new DataOutputStream(ous);

            	 System.out.println("登录服务器成功,请选择你要发的类型(1:聊天 2:文件:");
              
                 Scanner sc=new Scanner(System.in);
                 int type=sc.nextInt();
                 if(type==1){
                     sendTextMsg("abc聊天内容"+testCount,1111);
                 }
                 if(type==2){
                     //发文件,注这个文件必须存在
                     sendFileMsg("G:\\l.txt",1111);
                 }
                 testCount++;
              }
           } catch (Exception ef) {
        	   ef.printStackTrace();
        	}
         }
         //主函数:
         public static void main(String[] args) {
        	 Chat qqc = new Chat();
        	 qqc.conn2Server("127.0.0.1", 1111);
         }
}

      

 

 

     

你可能感兴趣的:(文件传输)