寫(xiě)在前面
還是畢業(yè)設(shè)計(jì)的事兒停做,還是發(fā)送圖片的事兒晤愧。前面已經(jīng)用了python實(shí)現(xiàn)圖片的發(fā)送,但是很蛋疼的是蛉腌,上位機(jī)用的是vb.net來(lái)進(jìn)行開(kāi)發(fā)官份,python的在圖片發(fā)送和接收又變得不好使了。實(shí)驗(yàn)了若干種方法烙丛,下面記錄一下舅巷。
目標(biāo):在vb.net的環(huán)境下和Android之間使用socket進(jìn)行圖片的傳輸。
實(shí)現(xiàn):自定義一個(gè)簡(jiǎn)單的傳輸協(xié)議河咽,將圖片用字節(jié)流的方式發(fā)送和接收钠右。
環(huán)境:在vb.net和Android上已經(jīng)試驗(yàn)成功,但是本文用java進(jìn)行演示(原因是:1.懶得連接Android了忘蟹;2.實(shí)在很討厭vb的語(yǔ)言....)飒房。邏輯是一樣的搁凸,只是具體的語(yǔ)法會(huì)有些少出入。
原理介紹
這次狠毯,在具體的實(shí)現(xiàn)之前护糖,先扒一下原理。網(wǎng)絡(luò)傳輸嚼松,使用的時(shí)socket嫡良,這是一個(gè)建立在tcp協(xié)議上的通信機(jī)制。所以献酗,有必要了解一下什么是tcp協(xié)議寝受,什么是socket。
TCP / IP 協(xié)議
Transmission Control Protocol/Internet Protocol的簡(jiǎn)寫(xiě)罕偎,中譯名為傳輸控制協(xié)議/因特網(wǎng)互聯(lián)協(xié)議很澄,又名網(wǎng)絡(luò)通訊協(xié)議,是Internet最基本的協(xié)議锨亏、Internet國(guó)際互聯(lián)網(wǎng)絡(luò)的基礎(chǔ)痴怨,由網(wǎng)絡(luò)層的IP協(xié)議和傳輸層的TCP協(xié)議組成。TCP/IP 定義了電子設(shè)備如何連入因特網(wǎng)器予,以及數(shù)據(jù)如何在它們之間傳輸?shù)臉?biāo)準(zhǔn)浪藻。協(xié)議采用了4層的層級(jí)結(jié)構(gòu),如下圖所示乾翔,每一層都呼叫它的下一層所提供的協(xié)議來(lái)完成自己的需求爱葵。(來(lái)自百度百科)
通俗而言:IP協(xié)議是給因特網(wǎng)的每一臺(tái)聯(lián)網(wǎng)設(shè)備規(guī)定一個(gè)地址,以便能夠在因特上上定位到該設(shè)備反浓。TCP負(fù)責(zé)發(fā)現(xiàn)傳輸?shù)膯?wèn)題萌丈,一有問(wèn)題就發(fā)出信號(hào),要求重新傳輸雷则,直到所有數(shù)據(jù)安全正確地傳輸?shù)侥康牡亍?/p>
socket
我們知道辆雾,程序在設(shè)備當(dāng)中運(yùn)行時(shí)是以進(jìn)程的形式被操作系統(tǒng)管理的。在本地月劈,兩個(gè)進(jìn)程之間要通信度迂,首先必須知道的是它們的PID。在網(wǎng)絡(luò)上猜揪,依靠PID來(lái)識(shí)別不同的進(jìn)程惭墓,顯然是不科學(xué)的,因?yàn)樗鼈儠?huì)重復(fù)而姐。從上面腊凶,我們了解到每個(gè)連接到因特網(wǎng)的設(shè)備的ip都是唯一的。所以,我們可以借助ip再加上一個(gè)端口號(hào)钧萍,來(lái)進(jìn)行進(jìn)程的唯一識(shí)別褐缠。當(dāng)網(wǎng)絡(luò)進(jìn)程可以唯一識(shí)別之后,就可以使用socket進(jìn)行通信了划煮。
socket送丰,翻譯成中文是“套接字”缔俄。它是應(yīng)用層和傳輸層之間的一個(gè)抽象層弛秋,它把TCP/IP層復(fù)雜的操作抽象為幾個(gè)簡(jiǎn)單的接口供應(yīng)用層調(diào)用已實(shí)現(xiàn)進(jìn)程在網(wǎng)絡(luò)中通信,如下圖所示俐载。socket源起于UNIX蟹略,在一切皆是文件的哲學(xué)思想下,socket是“打開(kāi)-讀寫(xiě)-關(guān)閉”模式的實(shí)現(xiàn)遏佣,服務(wù)端和客戶端共同維護(hù)一個(gè)“文件”挖炬,在連接建立之后,雙方都可以對(duì)文件進(jìn)行讀寫(xiě)操作状婶;通訊結(jié)束時(shí)意敛,關(guān)閉該文件。
具體實(shí)現(xiàn)
有了上面的原理膛虫,我們可以著手來(lái)實(shí)現(xiàn)以下了草姻。以下出現(xiàn)的代碼,只是為了演示稍刀,真實(shí)開(kāi)發(fā)環(huán)境下不要這樣寫(xiě)撩独。
Java提供了socket的相關(guān)API,我們不需要知道怎么寫(xiě)socket的底層账月,通過(guò)調(diào)用API就可以建立連接了综膀。在建立連接之前,我們需要自己定義一個(gè)用于發(fā)送圖片的簡(jiǎn)單的通信協(xié)議局齿。我自己隨便定義了一個(gè)剧劝,如下圖中間部分所示。
通信協(xié)議抓歼,顧名思義讥此,是用來(lái)規(guī)定通信過(guò)程的一種約定。只有服務(wù)端和客戶端都遵循同一個(gè)協(xié)議锭部,才可以完成有效的通信暂论。就想上圖表示的一樣,我規(guī)定了如下的內(nèi)容:
- 每次發(fā)送的數(shù)據(jù)包長(zhǎng)度為1029個(gè)字節(jié)(數(shù)據(jù)包是一個(gè)有符號(hào)的字節(jié)數(shù)組拌禾,下標(biāo)從0開(kāi)始到1028結(jié)束)
- 第一個(gè)字節(jié)是標(biāo)志位取胎,該位為1時(shí),表示后面還有數(shù)據(jù);該位為2時(shí)闻蛀,表示這是最后一包數(shù)據(jù)
- 數(shù)據(jù)包的[1 : 4]表示該數(shù)據(jù)包的有效數(shù)據(jù)長(zhǎng)度匪傍,因?yàn)閿?shù)據(jù)包最大可以存放1024字節(jié)的數(shù)據(jù),所以[1 : 4]每一位分別表示“個(gè)十百千”位上的數(shù)字觉痛。除去最后一包數(shù)據(jù)役衡,其他包都應(yīng)該是放滿的,最后一包的數(shù)據(jù)則很大可能都不滿1024(配合流的read方法可以避免接收冗余數(shù)據(jù))薪棒。
有了上面的約定手蝎,我們就可以寫(xiě)代碼了:
// SocketTest.java 下面代碼的運(yùn)行環(huán)境是Mac intellij14
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
/**
* Created by eric_lai on 2016/3/31.
*/
public class SocketTest {
// 服務(wù)端, 用來(lái)接收?qǐng)D片
public static void ServerSocket() {
// 調(diào)試標(biāo)志
String TAG = "ServerSocket: ";
// 服務(wù)端socket
ServerSocket serverSocket = null;
// 端口號(hào)
int port = 9998;
// 緩沖區(qū)
byte[] buffer = new byte[1029];
int len = buffer.length;
// 實(shí)際長(zhǎng)度
int dataLen = 0;
// 文件輸出流
FileOutputStream fileOutputStream = null;
// client輸入流
InputStream clientInputStream = null;
// 文件路徑
File file = new File("res/image2.jpg");
try {
fileOutputStream = new FileOutputStream(file);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
try {
// 綁定端口號(hào)
serverSocket = new ServerSocket(port);
// 堵塞線程, 等待連接
System.out.println(TAG + "waitting for connection ...");
Socket client = serverSocket.accept();
System.out.println(TAG + "connection has been set up");
// 獲取client輸入流
clientInputStream = client.getInputStream();
// 讀取數(shù)據(jù)
byte[] lenInByte = new byte[4];
clientInputStream.read(buffer, 0, len);
byte flag = buffer[0];
System.arraycopy(buffer, 1, lenInByte, 0, lenInByte.length);
dataLen = ChangeByte2Int(lenInByte);
System.out.println(TAG + "begin to receive image ...");
while (true) {
if (fileOutputStream != null) {
if (flag == 1) {
// 寫(xiě)入文件
fileOutputStream.write(buffer, 5, dataLen);
// 讀取數(shù)據(jù)
clientInputStream.read(buffer, 0, len);
flag = buffer[0];
System.arraycopy(buffer, 1, lenInByte, 0, lenInByte.length);
dataLen = ChangeByte2Int(lenInByte);
} else {
// 寫(xiě)入文件
fileOutputStream.write(buffer, 5, dataLen);
break;
}
}
}
System.out.println(TAG + "image successfully received ...");
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fileOutputStream != null) {
fileOutputStream.close();
}
if (clientInputStream != null) {
clientInputStream.close();
}
if (serverSocket != null) {
serverSocket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void ClientSocket() {
// 調(diào)試標(biāo)志
String TAG = "ClientSocket: ";
// 客戶端socket
Socket clientSocket = null;
// 服務(wù)器地址
String ip = "127.0.0.1";
// 端口號(hào)
int port = 9998;
// 緩沖區(qū)
byte[] buffer = new byte[1029];
int len = buffer.length;
byte[] fullData = {0, 1, 0, 2, 4};
// 實(shí)際長(zhǎng)度
int realLen = 0;
// 文件末尾標(biāo)志
int cf = 0;
// 文件輸入流
FileInputStream fileInputStream = null;
// client輸出流
OutputStream clientOutputStream = null;
// 文件路徑
File file = new File("res/image1.jpg");
try {
// 建立socket
System.out.println(TAG+"set up connection to the server ...");
clientSocket = new Socket(ip, port);
// 獲取文件輸入流(讀取要發(fā)送的圖片)
fileInputStream = new FileInputStream(file);
// 獲取socket輸出流(發(fā)送數(shù)據(jù)包)
clientOutputStream = clientSocket.getOutputStream();
} catch (IOException e) {
e.printStackTrace();
}
if (clientOutputStream != null) {
try {
System.out.println(TAG+"begin to send the image ...");
while (cf != -1) {
// 讀取數(shù)據(jù)放到數(shù)據(jù)區(qū)域
cf = fileInputStream.read(buffer, 5, len - 5);
if (cf == -1) {
// 將長(zhǎng)度轉(zhuǎn)為byte[]
byte[] by = ChangeInt2Byte(realLen);
// 將長(zhǎng)度數(shù)據(jù)整合到數(shù)據(jù)包里面
System.arraycopy(by, 0, buffer, 0, by.length);
// 標(biāo)志位置2
buffer[0] = 2;
// 發(fā)送數(shù)據(jù)
clientOutputStream.write(buffer, 0, realLen + 5);
}else {
// 緩存實(shí)際的數(shù)據(jù)長(zhǎng)度
realLen = cf;
System.arraycopy(fullData, 0, buffer, 0, fullData.length);
// 標(biāo)志位置1
buffer[0] = 1;
// 發(fā)送數(shù)據(jù)
clientOutputStream.write(buffer, 0, len);
}
}
System.out.println(TAG+"image has been sent ...");
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static int ChangeByte2Int(byte[] bytes) {
int result = 0;
int[] a = {1000, 100, 10, 1};
for (int i = 0, j = 0; i < bytes.length; i++, j++) {
int k = (int) bytes[i];
result += k * a[j];
}
return result;
}
public static byte[] ChangeInt2Byte(int len) {
byte[] bytes = new byte[5];
byte[] bytesReturn = new byte[5];
int result = 0;
int i;
int j = 1;
int z = 4;
int k = 1;
for (i = 4; i >= 0; i--) {
result = len % 10;
bytes[i] = (byte) result;
len /= 10;
}
for (; j < bytes.length; j++) {
bytesReturn[k] = bytes[j];
k++;
}
return bytesReturn;
}
public static void PrintBytes(byte[] bytes) {
int len = bytes.length;
for (byte b : bytes) {
System.out.print(b + " ");
}
}
}
// main.java
import java.net.Socket;
import java.util.Timer;
public class Main {
public static void main(String[] args) {
beginTest();
}
private static void beginTest() {
Thread server = new Thread(new Runnable() {
@Override
public void run() {
SocketTest.ServerSocket();
}
});
Thread client = new Thread(new Runnable() {
@Override
public void run() {
SocketTest.ClientSocket();
}
});
server.start();
// 確保服務(wù)端先啟動(dòng)
try {
server.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
client.start();
}
}
運(yùn)行上述代碼后,結(jié)果如下:
注意俐芯,需要自己準(zhǔn)備圖片棵介!