在只需要发送聊天消息时,我们只需要定义基于字符的协议就基本可以满足需求。但是,当同时需要传送文本消息与文件消息时,这样的基于字符的协议显然是不能满足要求了。因为,在发送的文件中,什么样的字符都是可能存在的,如果定义基于字符的协议,那么接收文件的一方,在解析文件时,就有可能出现错误。而基于字节的协议就可以很好的解决这个问题。
既然是自定义的协议,说明协议的定义具有很强的灵活性,程序员可以根据自己的程序的需要,自定义通信协议。只要也只有严格遵守相同的协议,客户端与服务器之间才可以正常的通信。这就好比玩游戏,只有玩家遵守同一套游戏规则,游戏才能有序正常的进行。
在我的程序中设计文本消息及文件消息的发送,他们的协议分别是:
文本消息:
消息总长 + 消息类型 + 消息内容长度 + 消息内容
其中:消息总长为一个int型占4个字节,消息类型为整数1占4个字节,消息内容长度为一个int型占4个字节,消息内容为一个byte型数组;
文件消息:
消息总长 + 消息类型 + 文件名长度 + 文件内容长度 + 文件名 + 文件内容
其中:消息总长为一个int型占4个字节,消息类型为整数2占4个字节,文件名长度为一个int型占4个字节,文件内容长度为一个int型占4个字节,文件名为一个byte型数组,文件内容为一个byte型数组;
实现通行的原则及关键只有一个,那就是通信双方,即服务器及客户端严格遵守协议;
实现技术细节:
1.将输入流包装成DataInputStream、输出流包装成DataOutputStream方便读取原始类型流
将从Socket上得到的输入流包装为DataInputStream流对象后,如果调用DataInputStream对象的readByte()方法时,只会从底层的数据流中读取一个字节返回,而调用readInt()时,则从底层读取4个字节(32位),readInt()方法内部经过为运算实现了将读到的4个字节组成一个int型数据返回。
2.read()方法与readFully()方法的区别
byte[] content = new byte[contentSize];
// 读取文件内容
dis.readFully(content);
此处从流中读取字节,填充字节数组时,没有使用read(要填充的数组)的方法,而是调用readFully()方法,这样做更安全。在网络通信中,当发送大的数据粮食,有这样一种可能:一部分数据已发送到对方,有一部份还在本地的网卡缓存中,如果调用read()方法
,可能会提前返回而没有读取到足够的数据,在传送大块数据(如一次传送一个较大文件时)可能会出错,而readFully()方法会一直等待,直到读取到的数组长度的所有数据后,才会返回。
服务器示例代码:
package 文件传输;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
private InputStream ins = null;
private OutputStream ous = null;
private DataInputStream dis = null;
private DataOutputStream dos = null;
public Server()
{
try
{
ServerSocket server = new ServerSocket(8080);
System.out.println("Server create success!");
Socket client = server.accept();
System.out.println("Client connect success!");
ins = client.getInputStream();
ous = client.getOutputStream();
dis = new DataInputStream(ins);
dos = new DataOutputStream(ous);
// 发送消息
// sendMsg("可以啦服务器->客户端");
// 发送文件
// sendFile("D://","abc.txt");
while(true)
{
int totalLen = dis.readInt();
System.out.println(totalLen);
int type = dis.readInt();
// 文本消息
if(type == 1)
{
// 读取消息内容长度
int len = dis.readInt();
byte[] msg = new byte[len];
// 读取消息内容
dis.readFully(msg);
String message = new String(msg);
System.out.println(message);
}
// 文件消息
if(type == 2)
{
// 读取文件名长度
int nameSize = dis.readInt();
// 读取文件内容长度
int contentSize = dis.readInt();
System.out.println("文件名长度"+nameSize+"~~~文件内容长度"+contentSize);
byte[] fileName = new byte[nameSize];
dis.read(fileName);
// 读取文件名
String name = new String(fileName);
System.out.println("文件名:" + name);
byte[] content = new byte[contentSize];
// 读取文件内容
dis.readFully(content);
File file = new File("F:/" + name);
file.createNewFile();
FileOutputStream fos = new FileOutputStream(file.getAbsolutePath(),true);
fos.write(content);
fos.flush();
fos.close();
dis.close();
}
}
} catch (IOException e)
{
e.printStackTrace();
}
}
// 发送消息
public void sendMsg(String str)
{
byte[] data = str.getBytes();
// 消息总长度
int totalLen = 4 + 4 + 4 + data.length ;
try {
dos.writeInt(totalLen);
dos.writeByte(1);
dos.writeInt(data.length);
dos.write(data);
dos.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
public void sendFile(String path, String name)
{
try
{
FileInputStream fis = new FileInputStream(path+name);
try
{
// 获取文件内容总字节数
int contentLen = fis.available();
byte[] content = new byte[contentLen];
// 将文件内容读取到文件输入流中
fis.read(content);
// 获取文件名字节长度
int nameLen = name.getBytes().length;
byte[] nameArry = name.getBytes();
// 获取总长度
int totalLen = 4 + 4 + 4 + nameLen + contentLen;
dos.writeInt(totalLen);
dos.writeInt(2);
dos.writeInt(nameLen);
dos.writeInt(contentLen);
dos.write(nameArry);
dos.write(content);
dos.close();
fis.close();
} catch (IOException e)
{
e.printStackTrace();
}
} catch (FileNotFoundException e)
{
e.printStackTrace();
}
}
// private void writeString (DataOutputStream out, String str,int len)
// {
// byte[] data = str.getBytes();
// try {
// out.write(data);
// } catch (IOException e1)
// {
// e1.printStackTrace();
// }
// while(len > data.length)
// {
// try
// {
// out.write('\0');
// len--;
// } catch (IOException e)
// {
// e.printStackTrace();
// }
// }
//
// }
public static void main(String[] args)
{
new Server();
}
}
客户端示例代码:
package 文件传输;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Scanner;
public class Client {
private InputStream ins;
private OutputStream ous;
private DataInputStream dis = null;
private DataOutputStream dos = null;
public Client(String str)
{
try
{
Socket client = new Socket(str, 8080);
ins = client.getInputStream();
ous = client.getOutputStream();
dis = new DataInputStream(ins);
dos = new DataOutputStream(ous);
// 实例化一个文本扫描器对象
Scanner scn = new Scanner(System.in);
System.out.println("请选择你要发送的消息类型:1 文本消息 2 文件消息");
// 从控制台读取一个int型数据
int a = scn.nextInt();
if(a == 1)
{
System.out.println("请输入你要发送的消息内容:");
String tempStr = scn.next();
System.out.println(tempStr);
// 发送文本信息
sendMsg(tempStr);
System.out.println("消息发送成功");
}
else if(a == 2)
{
System.out.println("请输入你要发送的文件名字:");
String tempStr = scn.next();
System.out.println(tempStr);
// 发送文件
sendFile("D://",tempStr);
System.out.println("文件发送成功");
}
System.out.println(a);
// 从控制台读取一个字符串
// 消息解析
// while(true)
// {
// int totalLen = dis.readInt();
// System.out.println(totalLen);
// int type = dis.readInt();
// // 文本消息
// if(type == 1)
// {
// // 读取消息内容长度
// int len = dis.readInt();
// byte[] msg = new byte[len];
// // 读取消息内容
// dis.read(msg);
// String message = new String(msg);
// System.out.println(message);
// }
//
// // 文件消息
// if(type == 2)
// {
// // 读取文件名长度
// int nameSize = dis.readInt();
// // 读取文件内容长度
// int contentSize = dis.readInt();
//
// System.out.println("文件名长度"+nameSize+"~~~文件内容长度"+contentSize);
//
// byte[] fileName = new byte[nameSize];
// dis.read(fileName);
// // 读取文件名
// String name = new String(fileName);
// System.out.println("文件名:" + name);
//
// byte[] content = new byte[contentSize];
// // 读取文件内容
// dis.readFully(content);
//
// File file = new File("F:/" + name);
// file.createNewFile();
// FileOutputStream fos = new FileOutputStream(file.getAbsolutePath(),true);
// fos.write(content);
// fos.flush();
//
// fos.close();
// dis.close();
// }
//
// }
} catch (UnknownHostException e)
{
e.printStackTrace();
} catch (IOException e)
{
e.printStackTrace();
}
}
// 发送消息
public void sendMsg(String str)
{
byte[] data = str.getBytes();
int totalLen = 4 + 4 + 4 + data.length;
try
{
dos.writeInt(totalLen);
dos.writeInt(1);
dos.writeInt(data.length);
dos.write(data);
dos.flush();
} catch (IOException e)
{
e.printStackTrace();
}
}
// 发送文件
public void sendFile(String path, String name)
{
try
{
// 文件输入流
FileInputStream fis = new FileInputStream(path+name);
try
{
// 获取文件内容总字节数
int contentLen = fis.available();
byte[] content = new byte[contentLen];
// 将文件内容读取到文件输入流中
fis.read(content);
// 获取文件名字节长度
int nameLen = name.getBytes().length;
byte[] nameArry = name.getBytes();
// 获取总长度
int totalLen = 4 + 4 + 4 + nameLen + contentLen;
dos.writeInt(totalLen);
dos.writeInt(2);
dos.writeInt(nameLen);
dos.writeInt(contentLen);
dos.write(nameArry);
dos.write(content);
dos.close();
fis.close();
} catch (IOException e)
{
e.printStackTrace();
}
} catch (FileNotFoundException e)
{
e.printStackTrace();
}
}
public static void main(String[] args)
{
new Client("localhost");
}
}
分享到:
相关推荐
8. **协议设计**:文件传输可能需要自定义简单的协议,以标识文件开始、结束、数据块以及错误处理等信息。例如,可以使用特定的起始和结束标志符,或者在数据块中包含长度信息。 9. **效率优化**:为了提高传输效率...
在VB中,可以使用`MSWinsock`控件创建TCP或UDP套接字,传输二进制数据(如字节数组)或文本数据(数值可转换为字符串)。 5. **文件共享(File Sharing)** 简单的方法是通过读写文件来共享数据。一个程序写入文件,...
标题中的“聊天传送文件抓取屏幕”是一个基于网络通信的项目,它允许用户在聊天过程中发送文件并捕获屏幕图像。这个项目展示了如何利用Socket编程技术实现这些高级功能,这在许多即时通讯软件中是非常常见的。 1. *...
2. 网络协议:设计一套自定义的文件传输协议,包含文件列表、文件分块、校验和等信息,确保数据完整性和传输可靠性。 3. 用户界面:创建友好的用户界面,显示文件传输进度、聊天窗口等,提供直观的操作体验。 4. ...
Java zip压缩包查看程序,应用弹出文件选择框,选择ZIP格式的压缩文件,可以像Winrar软件一样查看压缩文件内部的文件及文件夹,源码截图如上所示。 Java 数字签名、数字证书生成源码 2个目标文件 摘要:JAVA源码,...
文件流(FileInputStream和FileOutputStream)用于与本地文件系统交互,而字节流(如InputStream和OutputStream)和字符流(如Reader和Writer)则用于处理二进制和文本数据。在传输文件时,我们通常会使用...
Java zip压缩包查看程序,应用弹出文件选择框,选择ZIP格式的压缩文件,可以像Winrar软件一样查看压缩文件内部的文件及文件夹,源码截图如上所示。 Java 数字签名、数字证书生成源码 2个目标文件 摘要:JAVA源码...
Java zip压缩包查看程序,应用弹出文件选择框,选择ZIP格式的压缩文件,可以像Winrar软件一样查看压缩文件内部的文件及文件夹,源码截图如上所示。 Java 数字签名、数字证书生成源码 2个目标文件 摘要:JAVA源码...
Java zip压缩包查看程序,应用弹出文件选择框,选择ZIP格式的压缩文件,可以像Winrar软件一样查看压缩文件内部的文件及文件夹,源码截图如上所示。 Java 数字签名、数字证书生成源码 2个目标文件 摘要:JAVA源码...
Applet钢琴模拟程序java源码 2个目标文件,提供基本的音乐编辑功能。编辑音乐软件的朋友,这款实例会对你... //增加信息 …… Java实现的点对点短消息发送协议(smpp)开发包源码 70个目标文件,如题。 Java实现的放大...
Applet钢琴模拟程序java源码 2个目标文件,提供基本的音乐编辑功能。编辑音乐软件的朋友,这款实例会对你... //增加信息 …… Java实现的点对点短消息发送协议(smpp)开发包源码 70个目标文件,如题。 Java实现的放大...
【计算机应用基础学习指南】 1. 电子计算机的起源:世界上第一台电子计算机ENIAC是在1946年诞生的,它标志着计算机...28. Excel数据库排序:排序是基于单元格中的数据进行的,可以按照字母顺序或自定义顺序进行排序。
`InputStream` 类和 `OutputStream` 类:主要用于处理字节流而非字符流,不适合文本文件的行处理。 - C. `FileReader` 类和 `FileWriter` 类:虽然也可以用来读写文本文件,但没有内置按行读写的方法,不如使用缓冲...
而字符流主要用于文本数据的处理,Reader的常见子类有FileReader和BufferedReader,FileReader直接从文件读取字符,BufferedReader则增加了缓冲功能,提高了读取效率。Writer的子类包括FileWriter和BufferedWriter,...
• 修正了通过 SFTP 下载时 0 字节文件没有适当关闭的问题 • 现在你能够使用 Ctrl+L 在本地与远端视图之间切换 FlashFXP v3.8 BETA - (3.7.5 build 1297) • 修正了应用某些示意图样式时传送示意图绘制的问题 • ...
由于没有文件传送功能,这个例子可能只关注基本的文本通信,对于更复杂的应用场景,如文件传输,可能需要自定义协议来处理数据的分块、校验和重传等问题。 总的来说,了解和掌握UDP协议以及如何使用C#进行UDP编程,...
- `-w:windowsize`:自定义传送缓冲区的大小,默认为4096字节。 FTP命令包括: - `!`:退出FTP子系统,返回到命令提示符。 - `?`或`help`:显示FTP命令帮助。 - `append`:追加文件到远程服务器,如`append local...