使用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();
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。