使用Java完成Socket文件传输方式
作者:牛言牛语
Java完成Socket文件传输
TCP协议的Socket文件传输
分别使用三个类(TCPFileUpload_Server服务器端、TCPFileUpload_Client客户端、StreamUtils工具类)完成图片的传输。
同样先运行服务器端文件,再运行客户端文件
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
/**
* 服务器端
*/
public class TCPFileUpload_Server {
public static void main(String[] args) throws Exception {
//思路
//在本机 的8888端口监听, 等待连接
ServerSocket serverSocket = new ServerSocket(8888);
System.out.println("服务器端,监听8888端口,等待连接");
//当没有客户端连接8888端口时,程序会 阻塞, 等待连接
//如果有客户端连接,则会返回Socket对象,程序继续
Socket socket = serverSocket.accept();
//通过socket.getInputStream() 读取客户端写入到数据通道的数据, 并转换成字节数组
BufferedInputStream bufferedInputStream = new BufferedInputStream(socket.getInputStream());
byte[] bytes = StreamUtils.streamToByteArray(bufferedInputStream);
//写入指定路径
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream("src\\G.jpg"));
bufferedOutputStream.write(bytes);
//关闭IO流
bufferedOutputStream.close();
//向客户端回复收到图片
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
bufferedWriter.write("收到图片!");
bufferedWriter.flush();
socket.shutdownOutput();
//关闭所有流
bufferedWriter.close();
bufferedInputStream.close();
socket.close();
serverSocket.close();
}
}import java.io.*;
import java.net.InetAddress;
import java.net.Socket;
/**
* 客户端
*/
public class TCPFileUpload_Client {
public static void main(String[] args) throws Exception {
//连接服务端 (ip , 端口)
//解读:连接本机的 8888端口, 如果连接成功,返回Socket对象
Socket socket = new Socket(InetAddress.getLocalHost(), 8888);
System.out.println("客户端 连接端口:" + socket.getPort());
//创建读取磁盘文件IO流
BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream("src/P.jpg"));
//获取字节数组
byte[] bytes = StreamUtils.streamToByteArray(bufferedInputStream);
//通过Socket获取到输出流,将bytes发送到服务端
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(socket.getOutputStream());
bufferedOutputStream.write(bytes);
//关闭流对象,socket,刷新,添加终止符
bufferedInputStream.close();
bufferedOutputStream.flush();
socket.shutdownOutput();
//接受回复消息
//此处可调用Utils的方法
// String s = "";
// while ((s = bufferedReader.readLine()) != null)
// System.out.println(s);
System.out.println(StreamUtils.streamToString(socket.getInputStream()));
//关闭所有流
bufferedOutputStream.close(); //socket的包装流不要过早关闭
socket.close();
}
}import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
/**
* 此类用于演示关于流的读写方法
*/
public class StreamUtils {
/**
* 功能:将输入流转换成byte[]
*
* @param is 输入流
* @return byte数组
* @throws Exception IO流异常
*/
public static byte[] streamToByteArray(InputStream is) throws Exception {
ByteArrayOutputStream bos = new ByteArrayOutputStream();//创建输出流对象
byte[] b = new byte[1024];
int len;
while ((len = is.read(b)) != -1) {
bos.write(b, 0, len);
}
byte[] array = bos.toByteArray();
bos.close();
return array;
}
/**
* 功能:将InputStream转换成String
*
* @param is 输入流
* @return 字符串
* @throws Exception IO流异常
*/
public static String streamToString(InputStream is) throws Exception {
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
StringBuilder builder = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) { //当读取到 null时,就表示结束
builder.append(line + "\r\n");
}
return builder.toString();
}
}Java Socket数据传输基础以及优化
学到Java的TCP的Socket传输数据有些错误和心得在此记下
UDP和TCP
UDP:无连接通信协议,数据的发送端和接收端不用构建逻辑连接(发送时发送端无需确认接收端的存在,接收端无需返回相映给发送端)
- UDP协议资源消耗小,通信效率高,通常用于音频、视频接收和普通数据的传输
- 但是又因为UDP面向无连接性,不能保证数据的完整性,因此在传输重要数据的时候不推荐使用UDP协议
TCP:在发送数据的准备阶段,客户端与服务器之间的三次交互,以保证连接的可靠性
- 第一次握手:客户端向服务器发出连接请求
- 第二次握手:服务器通知客户端收到了连接请求
- 第三次握手:客户端再次向服务器发送确认信息,确认连接
- TCP的传输安全性高于UDP,下载文件和浏览网页等使用的都是TCP
TCP的socket通信
- 一种情况:客户端发送信息和接收信息需要输入输出流两个流,同理服务器也是这样,为了避免生成许许多多的流对象,所以可以利用socket中自带的输入输出流进行数据交互
Socket使用方法
1.客户端构造函数
Socket socket = new Socket(ip,port);
注解: 因为TCP是逻辑连接式的传输,所以客户端需要得知服务器的ip和端口
使用getOutputStream() 方法获得输出流
OutputStream cos = socket.getOutputStream();
cos.write("你好服务器".getBytes()); 注解:write使用字节输入输出,需要经过 字符–字节–字符的转换,转换的方法 getBytes() , new String(buf,0,len) ,如果想要直接输出可以使用打印流 printStream
使用getInputStream方法获得输入流
InputStream cis = socket.getInputStream() //设置一个缓冲数组 temp数组大小大于所接收的数据量 byte[] temp = new byte[1024]; int len = cis.read(temp); //将所接守的字节转换为字符串 这里使用 temp.toString()会有乱码 System.out.println(new String(temp,0,len));
注意最后要释放资源
socket.close();
2.服务器
服务器首先要创建ServerSocket设置端口号
ServerSocket server = new ServerSocket(port:8888);
注解:注意当编写循环响应时server的定义需要在while(true)循环之外,不然会显示端口被占用的情况
Exception in thread "main" java.net.BindException: Address already in use: NET_Bind
然后使用accept()方法返回Socket对象
Socket socket = server.accept();
accept() 方法具有阻塞作用,后面的实例会提到
通过accept()获得socket对象后,后面的操作方式与客户端的socket其实是一致的了
//获得流对象
InputStream sis = socket.getInputStream();
OutputStream sos = socket.getOutputStream();
//打印从客户端获得的数据
byte[] temp = new btye[1024];
sis.read(temp);
System.out.println(new String(temp));
//对客户端发出返回信息
sos.write("你也好,客户端".getBytes());注意最后要释放资源
socket.close(); server.close();
总的应用代码,图片传输
客户端
import java.io.*;
import java.net.*;
public class TCPClient {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("C:\\Users\\子陌\\Pictures\\1.jpg");
Socket socket = new Socket("192.168.1.8",8888);
OutputStream cos = socket.getOutputStream();
InputStream cis = socket.getInputStream();
int len =0;
byte[] bytes = new byte[1024];
while((len = fis.read(bytes))!=-1){
cos.write(bytes,0,len);
}
socket.shutdownOutput();
byte[] bytes1 = new byte[1024];
int len1=0;
len1 =cis.read(bytes1);
System.out.println(new String(bytes1));
socket.close();
fis.close();
}
}服务器
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Random;
public class TCPSever {
public static void main(String[] args) throws IOException {
//这里ServerSocket要放到外面
ServerSocket server = new ServerSocket(8888);
while(true) {
new Thread(new Runnable() {
@Override
public void run() {
try{
//先把accept响应放到前面
Socket socket = server.accept();
String fileName = "itcast" + System.currentTimeMillis() + new Random().nextInt();
File file = new File("D:\\TCPUpLoad");
if (!file.exists()) {
file.mkdirs();
}
FileOutputStream fos = new FileOutputStream(file + "\\" + fileName + ".jpg");//这里file既是File类也可以当作路径名称
//FileOutPutStream 会自动生成空文件
InputStream sis = socket.getInputStream();
OutputStream sos = socket.getOutputStream();
int len = 0;
byte[] bytes = new byte[1024];
while ((len = sis.read(bytes)) != -1) {
fos.write(bytes, 0, len);
}
sos.write("已经收到".getBytes());
socket.close();
fos.close();
//重点:要将资源全部释放,不然会占用线程或者端口
}catch(IOException e)
{
System.out.println(e);
}
}
}).start();
}
//server.close();
}
}代码中需要注意的点
1.服务器ServerSocket需要放到while外部,while的目的是能够随时响应客户端的请求
2.fileName 使用了
String fileName = "itcast" + System.currentTimeMillis() + new Random().nextInt(9999);
这也就是为什么从网上下载的图片有一大堆数字名字的原因
3.利用了多线程重写Runnable中的 run 方法,运用了匿名内部类,大大提高了服务器的响应速度
4.这里涉及之前提到的一个容易出bug的问题,server.accept() 的阻塞作用,因为会服务器会不断的while循环就会开启很多线程,如果
FileOutStream fos = new FileOutStream(...)
运行先于server.accept(),就会每产生一个线程就创建一个空文件 我就是犯了这个错误导致电脑多了几万个带.jpg的空文件
5.服务器和客户端要同时运作时需要知道自己的ip地址,可以在运行的 cmd 中 输入 ipconfig 进行查询
6.Runnable 中的 run 方法并不能自动抛出异常,只能手动 try catch 详见上方代码
7.还有一个阻塞问题就是,当客户端读取结束后传输给服务器,但是服务器并不知道读取结束就会导致客户端和服务器的同时阻塞,这时需要 Socket 中的 shutdownOutput() 方法告诉服务器已经读取完毕
while((len = fis.read(bytes))!=-1)
{
cos.write(bytes,0,len);
}
socket.shutdownOutput();总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。
