我也不知道为毛我插代码会自动置顶,简直不能忍!!!
最近开始了艰苦卓绝暗无天日搞不死人不罢休的集训,也是希望经过一个暑假的努力可以进一步朝大神大牛大学霸的目标前进,从今天开始也要保持三天一篇技术博客的效率开始加油啦
话不多说
从最基础的开始
自定义协议来实现JAVA的消息文件传输(其实只要是基于字节,什么都能传)。
首先来说协议,嗯,听起来高大上?TCP/IP协议?HTTP协议?OSI协议?镜像加速P2P?哈哈哈哈,当一个问题你也不会的时候,就丢出一堆更高端的东西,显得高大上嘛
其实这里的协议说白了就是一套规则,至于后面的由于太高端我慢慢看完书慢慢写。。。
消息传输规则
第一个发送 消息类型 int型 1表示消息 2表示文件
发送消息时,先发送消息长度(int),在发送消息内容字节数组(byte[])
发送文件时,先发送文件名长度(int)文件内容长度(int)文件名字节数组(byte[])文件字节数组(byte[])
以上其实就是我们所谓的高大上的协议OTZ,
这里要说明一下,我们发送消息或者文件时是基于字节的,就是说我们的数据最小单位都是一个一个的byte,这样的好处是完全不用理会什么编码或者文件类型什么的,我们传输的都是基本的数据,只要按规则即协议传输,就不会出错
相反,如果按字符传输,就是说是一个字符一个字符地传输的话,会有很多坏处,比如
1.没有任何安全性可言,所有消息文件内容都会被解析出来过一遍。
2.(对于不同的编码模式,GBK和UTF-8会有完全不同的结果,)总之就是一堆乱码烦死你。
具体实现过程呢
服务器类 新建一个ServerSocket,并获取连接的Socket的输入流,然后通过IO向外输出消息或者文件
客户机类 根据主机的地址和端口新建一个Socket,然后通过DataOutputStream向主机输出消息流或者文件流
服务器代码
package cn.SSheng.Server;
import java.io.DataInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
/**
* 简单服务器类
* @author SSheng
*/
public class ServerManager {
//主函数 实例化对象调用方法
public static void main(String[] argsp){
new ServerManager().setupServer(8080);
}
//根据参数建立相应端口号的服务器
public void setupServer(int port){
while(true){
try {
//新建一个ServerSocket
ServerSocket server = new ServerSocket(port);
System.out.println("建立服务成功");
//等待客户机连入,程序在连入前进入阻塞状态
Socket client = server.accept();
System.out.println("连接成功");
//得到客户机的输入流
InputStream input = client.getInputStream();
//为了方便数据处理,建立DataInputStream
DataInputStream datainput = new DataInputStream(input);
//调用处理消息方法 = =。 方法名过于脑残请无视
Communicate(datainput);
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 处理消息输入的方法
* @param datainput 客户机的数据输入流
*
* 所有的异常什么的全部丢出去不管啦好麻烦
* @throws IOException
* @throws InterruptedException
*/
public void Communicate(DataInputStream datainput) throws IOException, InterruptedException{
//根据自己定的协议,第一个先读消息类型
int type = datainput.readInt();
System.out.println(type);
//类型1 消息
if(type == 1){
System.out.println("消息输入");
//读入消息字节长度
int length = datainput.readInt();
//新建一个等于消息长度的字节数组
byte[] msg = new byte[length];
//读入消息内容
//datainput.read(msg);
datainput.readFully(msg);
//输出消息内容
System.out.println(new String(msg));
}
//类型2 文件
else if(type == 2){
//读取文件名的长度,
int fileNameLength = datainput.readInt();
//读取文件内容长度
int datalength = datainput.readInt();
//根据文件名长度建立文件名数组,并读入相应字节的内容
byte[] fileName = new byte[fileNameLength];
datainput.readFully(fileName);
//根据文件内容长度建立文件内容数组,并读入相应字节的内容
byte[] file = new byte[datalength];
datainput.readFully(file);
//新建一个文件输出流向磁盘写文件
FileOutputStream f = new FileOutputStream("F:\\"+new String(fileName));
//把所有文件字节内容写出
f.write(file);
//刷新此输出流并强制写出所有缓冲的输出字节
f.flush();
System.out.println("文件输出成功");
//关闭文件输出流
f.close();
}
}
}
客户机代码
package cn.SSheng.Client;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
/**
* 简单客户机的实现
* @author SSheng
*
*/
public class Client {
//数据输出流对象
DataOutputStream dataoutput;
//主函数 新建客户机对象并调用连接方法,连接方法里调用传输方法
public static void main(String[] args){
new Client().connect("127.0.0.1",8080);
}
/**
* 连接服务器的方法
* @param ip 服务器IP地址
* @param port 服务所在服务器端口号
*/
public void connect(String ip,int port){
try{
//新建一个Socket,连接到服务器的IP 和端口
Socket client = new Socket(ip,port);
//获取客户机Socket对象的输出流对象
OutputStream output = client.getOutputStream();
//为方便数据输出,调用JAVA库根据输出流对象新建一个数据输出流对象
dataoutput = new DataOutputStream(output);
//向客户机发送的两个方法
sendMsg("这是个坑",dataoutput);
writeFile("D:\\Cool Edit Pro\\cep2unin.exe",dataoutput);
//关闭客户机
client.close();
}catch(Exception e){
e.printStackTrace();
}
}
/**
* 发送消息的方法
* @param msg 要发送的消息
* @param output 数据输出流对象
* @throws Exception 异常什么的你懂的 丢出去就好
*/
public void sendMsg(String msg,DataOutputStream output) throws Exception{
//根据协议
int type = 1;
byte[] message = msg.getBytes();
int msgLength = message.length;
//发送消息类型,即1
output.writeInt(type);
//发送消息字节长度
output.writeInt(msgLength);
//发送消息内容
output.write(message);
}
/**
* 传输文件的方法
* @param path 文件路径,绝对路径
* @param output 数据输出流
* @throws Exception 你懂的
*/
public void writeFile(String path,DataOutputStream output) throws Exception{
//消息类型
int type = 2;
//根据路径建立文件对象
File f = new File(path);
//文件对象建立文件输入流好把文件读入进内存
FileInputStream finput = new FileInputStream(f);
//调用available方法,会返回文件流指针所指位置到文件结尾的字节个数,即文件字节长度
int fileLength = finput.available();
//调用getName方法,会返回绝对路径最后面的名字,也就是文件的名字(如:warcraft.exe)
byte[] fileName = f.getName().getBytes();
//文件名字的字节个数
int fileNameLength = fileName.length;
//根据文件长度建立文件内容的byte数组
byte[] data = new byte[fileLength];
//从文件输入流中把文件读入
finput.read(data);
//分别按协议的顺序 把消息类型(2,文件名长度,文件内容长度,文件名,文件内容输出
output.writeInt(type);
output.writeInt(fileNameLength);
output.writeInt(fileLength);
output.write(fileName);
output.write(data);
//刷新此输出流并强制写出所有缓冲的输出字节
output.flush();
//关闭文件输入流
finput.close();
}
}
最后说几个我犯过的错误吧
1.如果用输入输出流,即InputStream,OutputStream你会发现对象相应的读写方法都只有基于byte的,而如果你想读一个int,即四个字节,那么恭喜请去自己写一个位运算的方法进行转换,但是!!!JAVA怎么会让这张事情发生呢,所以一定要把输入输出流封装到数据输入输出流中,即DataInputStream DataOutputStream,然后你就会发现相应对象就有了温柔又贴心的int,byte,long,short等等等等的方法
2.得到的输入流会有一个read()方法和一个readFully()方法。readFully()方法会等数组所有数据都读取完毕后才会返回。
以下摘自某大神博客:http://xuyi1994.iteye.com/blog/2091742网路通信中,当发送大的数据量时,有这样一种可能:一部分数据已发送到对方,有一部分数据还在本地的网卡缓存中,如果调用read()方法,可能会提前返回而没有读到足够的数据,在传大块数据(如一次传送一个较大文件时)可能出错,而readfully()方法会一直等待,读取到数组长度的所有数据,才会返回。
3.在新建文件输出流时注意!
FileOutputStream f = new FileOutputStream("F:\\"+new String(fileName));这行代码,fileName还记得么,我们传输时是以字节数组传输的,如果你直接"F:\\"+fileName,那么恭喜,fileName会返回数组首地址,你得到的文件名就是一堆数据地址代码,所以要调用String的构造函数传入字节数组来构造字符串,然后创建文件
哦对了 至今还有一个问题没有弄懂, 我们在做通信时当Socket连接到ServerSocket后开始写数据,当我把所有数据都写进网络以后,需要保证Socket保持连接状态知道ServerSocket接收完数据呢?还是只要传输进网络然后就可以关闭Socket了? 如果有看到的大神求指教 么么哒。
/**
求知对我们而言最根本的,也许不是为了让我们变得更加高级,对人生报更多奢望——而是为了让我们更自由。只不过要想真正在知识里看到自由,需要恒久的努力与谦卑,以及不管懂了多少依然充满了对这个世界的好奇心。
我早有觉悟。
*/