使用Java完成Socket文件傳輸方式
Java完成Socket文件傳輸
TCP協(xié)議的Socket文件傳輸
分別使用三個(gè)類(TCPFileUpload_Server服務(wù)器端、TCPFileUpload_Client客戶端、StreamUtils工具類)完成圖片的傳輸。
同樣先運(yùn)行服務(wù)器端文件,再運(yùn)行客戶端文件
import java.io.*; import java.net.ServerSocket; import java.net.Socket; /** * 服務(wù)器端 */ public class TCPFileUpload_Server { public static void main(String[] args) throws Exception { //思路 //在本機(jī) 的8888端口監(jiān)聽, 等待連接 ServerSocket serverSocket = new ServerSocket(8888); System.out.println("服務(wù)器端,監(jiān)聽8888端口,等待連接"); //當(dāng)沒有客戶端連接8888端口時(shí),程序會(huì) 阻塞, 等待連接 //如果有客戶端連接,則會(huì)返回Socket對(duì)象,程序繼續(xù) Socket socket = serverSocket.accept(); //通過socket.getInputStream() 讀取客戶端寫入到數(shù)據(jù)通道的數(shù)據(jù), 并轉(zhuǎn)換成字節(jié)數(shù)組 BufferedInputStream bufferedInputStream = new BufferedInputStream(socket.getInputStream()); byte[] bytes = StreamUtils.streamToByteArray(bufferedInputStream); //寫入指定路徑 BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream("src\\G.jpg")); bufferedOutputStream.write(bytes); //關(guān)閉IO流 bufferedOutputStream.close(); //向客戶端回復(fù)收到圖片 BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); bufferedWriter.write("收到圖片!"); bufferedWriter.flush(); socket.shutdownOutput(); //關(guān)閉所有流 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 { //連接服務(wù)端 (ip , 端口) //解讀:連接本機(jī)的 8888端口, 如果連接成功,返回Socket對(duì)象 Socket socket = new Socket(InetAddress.getLocalHost(), 8888); System.out.println("客戶端 連接端口:" + socket.getPort()); //創(chuàng)建讀取磁盤文件IO流 BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream("src/P.jpg")); //獲取字節(jié)數(shù)組 byte[] bytes = StreamUtils.streamToByteArray(bufferedInputStream); //通過Socket獲取到輸出流,將bytes發(fā)送到服務(wù)端 BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(socket.getOutputStream()); bufferedOutputStream.write(bytes); //關(guān)閉流對(duì)象,socket,刷新,添加終止符 bufferedInputStream.close(); bufferedOutputStream.flush(); socket.shutdownOutput(); //接受回復(fù)消息 //此處可調(diào)用Utils的方法 // String s = ""; // while ((s = bufferedReader.readLine()) != null) // System.out.println(s); System.out.println(StreamUtils.streamToString(socket.getInputStream())); //關(guān)閉所有流 bufferedOutputStream.close(); //socket的包裝流不要過早關(guān)閉 socket.close(); } }
import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.io.InputStreamReader; /** * 此類用于演示關(guān)于流的讀寫方法 */ public class StreamUtils { /** * 功能:將輸入流轉(zhuǎn)換成byte[] * * @param is 輸入流 * @return byte數(shù)組 * @throws Exception IO流異常 */ public static byte[] streamToByteArray(InputStream is) throws Exception { ByteArrayOutputStream bos = new ByteArrayOutputStream();//創(chuàng)建輸出流對(duì)象 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轉(zhuǎn)換成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) { //當(dāng)讀取到 null時(shí),就表示結(jié)束 builder.append(line + "\r\n"); } return builder.toString(); } }
Java Socket數(shù)據(jù)傳輸基礎(chǔ)以及優(yōu)化
學(xué)到Java的TCP的Socket傳輸數(shù)據(jù)有些錯(cuò)誤和心得在此記下
UDP和TCP
UDP:無連接通信協(xié)議,數(shù)據(jù)的發(fā)送端和接收端不用構(gòu)建邏輯連接(發(fā)送時(shí)發(fā)送端無需確認(rèn)接收端的存在,接收端無需返回相映給發(fā)送端)
- UDP協(xié)議資源消耗小,通信效率高,通常用于音頻、視頻接收和普通數(shù)據(jù)的傳輸
- 但是又因?yàn)閁DP面向無連接性,不能保證數(shù)據(jù)的完整性,因此在傳輸重要數(shù)據(jù)的時(shí)候不推薦使用UDP協(xié)議
TCP:在發(fā)送數(shù)據(jù)的準(zhǔn)備階段,客戶端與服務(wù)器之間的三次交互,以保證連接的可靠性
- 第一次握手:客戶端向服務(wù)器發(fā)出連接請(qǐng)求
- 第二次握手:服務(wù)器通知客戶端收到了連接請(qǐng)求
- 第三次握手:客戶端再次向服務(wù)器發(fā)送確認(rèn)信息,確認(rèn)連接
- TCP的傳輸安全性高于UDP,下載文件和瀏覽網(wǎng)頁等使用的都是TCP
TCP的socket通信
- 一種情況:客戶端發(fā)送信息和接收信息需要輸入輸出流兩個(gè)流,同理服務(wù)器也是這樣,為了避免生成許許多多的流對(duì)象,所以可以利用socket中自帶的輸入輸出流進(jìn)行數(shù)據(jù)交互
Socket使用方法
1.客戶端構(gòu)造函數(shù)
Socket socket = new Socket(ip,port);
注解: 因?yàn)門CP是邏輯連接式的傳輸,所以客戶端需要得知服務(wù)器的ip和端口
使用getOutputStream() 方法獲得輸出流
OutputStream cos = socket.getOutputStream(); cos.write("你好服務(wù)器".getBytes());
注解:write使用字節(jié)輸入輸出,需要經(jīng)過 字符–字節(jié)–字符的轉(zhuǎn)換,轉(zhuǎn)換的方法 getBytes()
, new String(buf,0,len)
,如果想要直接輸出可以使用打印流 printStream
使用getInputStream方法獲得輸入流
InputStream cis = socket.getInputStream() //設(shè)置一個(gè)緩沖數(shù)組 temp數(shù)組大小大于所接收的數(shù)據(jù)量 byte[] temp = new byte[1024]; int len = cis.read(temp); //將所接守的字節(jié)轉(zhuǎn)換為字符串 這里使用 temp.toString()會(huì)有亂碼 System.out.println(new String(temp,0,len));
注意最后要釋放資源
socket.close();
2.服務(wù)器
服務(wù)器首先要?jiǎng)?chuàng)建ServerSocket設(shè)置端口號(hào)
ServerSocket server = new ServerSocket(port:8888);
注解:注意當(dāng)編寫循環(huán)響應(yīng)時(shí)server的定義需要在while(true)循環(huán)之外,不然會(huì)顯示端口被占用的情況
Exception in thread "main" java.net.BindException: Address already in use: NET_Bind
然后使用accept()方法返回Socket對(duì)象
Socket socket = server.accept();
accept()
方法具有阻塞作用,后面的實(shí)例會(huì)提到
通過accept()獲得socket對(duì)象后,后面的操作方式與客戶端的socket其實(shí)是一致的了
//獲得流對(duì)象 InputStream sis = socket.getInputStream(); OutputStream sos = socket.getOutputStream(); //打印從客戶端獲得的數(shù)據(jù) byte[] temp = new btye[1024]; sis.read(temp); System.out.println(new String(temp)); //對(duì)客戶端發(fā)出返回信息 sos.write("你也好,客戶端".getBytes());
注意最后要釋放資源
socket.close(); server.close();
總的應(yīng)用代碼,圖片傳輸
客戶端
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(); } }
服務(wù)器
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{ //先把a(bǔ)ccept響應(yīng)放到前面 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類也可以當(dāng)作路徑名稱 //FileOutPutStream 會(huì)自動(dòng)生成空文件 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("已經(jīng)收到".getBytes()); socket.close(); fos.close(); //重點(diǎn):要將資源全部釋放,不然會(huì)占用線程或者端口 }catch(IOException e) { System.out.println(e); } } }).start(); } //server.close(); } }
代碼中需要注意的點(diǎn)
1.服務(wù)器ServerSocket需要放到while外部,while的目的是能夠隨時(shí)響應(yīng)客戶端的請(qǐng)求
2.fileName 使用了
String fileName = "itcast" + System.currentTimeMillis() + new Random().nextInt(9999);
這也就是為什么從網(wǎng)上下載的圖片有一大堆數(shù)字名字的原因
3.利用了多線程重寫Runnable中的 run
方法,運(yùn)用了匿名內(nèi)部類,大大提高了服務(wù)器的響應(yīng)速度
4.這里涉及之前提到的一個(gè)容易出bug的問題,server.accept()
的阻塞作用,因?yàn)闀?huì)服務(wù)器會(huì)不斷的while循環(huán)就會(huì)開啟很多線程,如果
FileOutStream fos = new FileOutStream(...)
運(yùn)行先于server.accept()
,就會(huì)每產(chǎn)生一個(gè)線程就創(chuàng)建一個(gè)空文件 我就是犯了這個(gè)錯(cuò)誤導(dǎo)致電腦多了幾萬個(gè)帶.jpg的空文件
5.服務(wù)器和客戶端要同時(shí)運(yùn)作時(shí)需要知道自己的ip地址,可以在運(yùn)行的 cmd
中 輸入 ipconfig
進(jìn)行查詢
6.Runnable 中的 run
方法并不能自動(dòng)拋出異常,只能手動(dòng) try catch
詳見上方代碼
7.還有一個(gè)阻塞問題就是,當(dāng)客戶端讀取結(jié)束后傳輸給服務(wù)器,但是服務(wù)器并不知道讀取結(jié)束就會(huì)導(dǎo)致客戶端和服務(wù)器的同時(shí)阻塞,這時(shí)需要 Socket
中的 shutdownOutput()
方法告訴服務(wù)器已經(jīng)讀取完畢
while((len = fis.read(bytes))!=-1) { cos.write(bytes,0,len); } socket.shutdownOutput();
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
java實(shí)現(xiàn)ATM機(jī)系統(tǒng)(2.0版)
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)ATM機(jī)系統(tǒng)2.0版,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-03-03使用Springboot打成jar包thymeleaf的問題
這篇文章主要介紹了使用Springboot打成jar包thymeleaf的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11Spring Security基于HttpRequest配置權(quán)限示例詳解
這篇文章主要介紹了Spring Security基于HttpRequest配置權(quán)限示例詳解,我們?cè)谂渲弥信渲玫膗rl被封裝成RequestMatcher,而hasRole被封裝成AuthorityAuthorizationManager,本文結(jié)合示例代碼講解的非常詳細(xì),需要的朋友可以參考下2024-03-03Java中Double除保留后小數(shù)位的幾種方法(小結(jié))
這篇文章主要介紹了Java中Double保留后小數(shù)位的幾種方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-07-07springboot配置http跳轉(zhuǎn)https的過程
SSL是為網(wǎng)絡(luò)通信提供安全以及保證數(shù)據(jù)完整性的的一種安全協(xié)議,SSL在網(wǎng)絡(luò)傳輸層對(duì)網(wǎng)絡(luò)連接進(jìn)行加密,這篇文章主要介紹了springboot配置http跳轉(zhuǎn)https的過程,需要的朋友可以參考下2023-04-04詳解OpenCV For Java環(huán)境搭建與功能演示
這篇文章主要介紹了x詳解OpenCV For Java環(huán)境搭建與功能演示,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-04-04