Parcel資料傳輸過程,簡要分析Binder流程
1. parcel是啥
Parcel主要是方便實現上層抽象物件或資料打包做跨程序傳輸而封裝的一個類,類名的字面意思也差不多說明了。其作用,說白了,就是將要寫入的資料,規整到一個連續的buffer記憶體中,同時記錄一些資料資訊屬性。遠端程序可接收後根據這些屬性和讀取順序來克隆還原。連續buffer記憶體方便驅動實現,同時效率也高。目前Android中,除了service_manager外[太簡單了,而且純C寫的..],都是走parcel。2.一個簡單的binder通訊例子
這裡,我們簡單的用一個dump服務的介面實現,來看一下在binder通訊過程中,資料在程序中的傳遞過程。#include <utils/Atomic.h> #include <utils/Errors.h> #include <utils/Log.h> #include <binder/IBinder.h> #include <binder/IPCThreadState.h> #include <binder/IServiceManager.h> #include <binder/ProcessState.h> #include <stdio.h> using namespace android; int main() { status_t err = NO_ERROR; const sp<IServiceManager> sm = defaultServiceManager(); sp<IBinder> ibinder = sm->getService(String16("activity")); // 這裡開始Binder傳輸,請求服務端執行dump,只有傳送資料,不需要接收資料 if (ibinder != NULL ) { // 構造兩個空的Parcel,一個是用於傳送,一個用於接受 Parcel send; Parcel reply; send.writeFileDescriptor(STDOUT_FILENO); // 寫入檔案FD. send.writeInt32(0); // dump命令的引數個數,這裡是0個,不帶引數了。 status_t err = ibinder->transact(IBinder::DUMP_TRANSACTION, send, &reply); } // 這是binder傳輸,獲取服務的descriptor, 不需要傳送資料,只需要接收資料 { Parcel send, reply; status_t err = ibinder->transact(IBinder::INTERFACE_TRANSACTION, send, &reply); if (err == NO_ERROR) { printf("Read service interface: %s\n", String8(reply.readString16()).string()); } } return 0; }
通訊過程比較簡單,將要傳遞的資料,通過Parcel的writeXXX系列函式寫入到Parcel中,然後呼叫IBinder的transact,發起遠端通訊請求,通訊的資料結果,存在Parcel物件reply中,從這個物件中讀出結果即可。 Binder通訊的詳細流程不分析了,扒一下Binder驅動和IPCThreadState,整個Binder的一次傳輸過程,可以用如下圖簡單描述一下: 通訊過程的資料傳遞,後面接著看一下。
3. 傳送時待傳輸的Parcel物件
Binder通訊基本上都是通過Parcel來傳遞資料的,呼叫Parcel.writexxx將要傳遞資料存到物件中. 看下流程。Parcel::Parcel() { LOG_ALLOC("Parcel %p: constructing", this); initState(); } .. void Parcel::initState() { LOG_ALLOC("Parcel %p: initState", this); mError = NO_ERROR; mData = 0; mDataSize = 0; mDataCapacity = 0; mDataPos = 0; ALOGV("initState Setting data size of %p to %zu", this, mDataSize); ALOGV("initState Setting data pos of %p to %zu", this, mDataPos); mObjects = NULL; mObjectsSize = 0; mObjectsCapacity = 0; mNextObjectHint = 0; mHasFds = false; mFdsKnown = true; mAllowFds = true; mOwner = NULL; }
Parcel儲存資料的記憶體緩衝[mData],是在寫入物件時(這裡基本資料型別,也可以看做物件,實現稍有差異,不過原理都一樣),根據寫入資料,自動調整的。看一下上面例子中,寫入parcel的方法: 1. send.writeFileDescriptor(STDOUT_FILENO);
時,
status_t Parcel::writeFileDescriptor(int fd, bool takeOwnership)
{
flat_binder_object obj;
obj.type = BINDER_TYPE_FD;
obj.flags = 0x7f | FLAT_BINDER_FLAG_ACCEPTS_FDS;
obj.binder = 0; /* Don't pass uninitialized stack data to a remote process */
obj.handle = fd;
obj.cookie = takeOwnership ? 1 : 0;
return writeObject(obj, true);
}
這裡傳輸fd會比較特殊。fd雖然是一個整形,但它代表的是一個檔案控制代碼,跨程序傳遞到對方程序中。程式碼也比較簡單,把fd封裝到flat_binder_object中,再通過writeObject寫入Parcel中。
status_t Parcel::writeObject(const flat_binder_object& val, bool nullMetaData)
{
const bool enoughData = (mDataPos+sizeof(val)) <= mDataCapacity;
const bool enoughObjects = mObjectsSize < mObjectsCapacity;
//首次寫,是沒有申請記憶體的, mDataCapacity是0,此條件不成立
if (enoughData && enoughObjects) {
restart_write:
*reinterpret_cast<flat_binder_object*>(mData+mDataPos) = val;
....
return finishWrite(sizeof(flat_binder_object));
}
// 記憶體不夠, 增加容量,申請到可以容納寫入程序的空間
if (!enoughData) {
const status_t err = growData(sizeof(val));
if (err != NO_ERROR) return err;
}
...
goto restart_write;
}
growData是真正申請記憶體的地方:
status_t Parcel::growData(size_t len)
{
size_t newSize = ((mDataSize+len)*3)/2;
return (newSize <= mDataSize)
? (status_t) NO_MEMORY
: continueWrite(newSize);
}
這裡新申請的記憶體,是需要的1.5倍,目的應該也就是避免頻繁grow的情況吧,下面可以看到,如果要grow的話,代價很大的!
status_t Parcel::continueWrite(size_t desired)
{
...
// 這裡的mOwner變數,是很重要的一個東西,在從Binder讀取資料的時候用到。
if (mOwner) {
...
} else if (mData) { // 第一次,mData也是NULL,如果不為NULL,要有memcpy動作了,這裡就是比較浪費
...
} else {
// This is the first data. Easy!
// 上面是原生註釋,第一次簡單,直接分配就行,第二次或以後,麻煩再要拷貝移動了。
uint8_t* data = (uint8_t*)malloc(desired);
if (!data) {
mError = NO_MEMORY;
return NO_MEMORY;
}
...
mData = data;
mDataSize = mDataPos = 0;
ALOGV("continueWrite Setting data size of %p to %zu", this, mDataSize);
ALOGV("continueWrite Setting data pos of %p to %zu", this, mDataPos);
mDataCapacity = desired;
}
return NO_ERROR;
}
分配好記憶體後,下面就是寫了, 寫入繼續見writeObject函式,分配好後goto restart_write,直接寫入資料:
-
*reinterpret_cast<flat_binder_object*>(mData+mDataPos)= val;
status_t Parcel::finishWrite(size_t len)
{
//printf("Finish write of %d\n", len);
mDataPos += len;
ALOGV("finishWrite Setting data pos of %p to %zu", this, mDataPos);
if (mDataPos > mDataSize) {
mDataSize = mDataPos;
ALOGV("finishWrite Setting data size of %p to %zu", this, mDataSize);
}
//printf("New pos=%d, size=%d\n", mDataPos, mDataSize);
return NO_ERROR;
}
只是更新一下資料指標。 2. send.writeInt32(0); 這是寫入一個int數,比較簡單。 上一步分配的記憶體容量,實際是還夠寫入一個int的,直接寫入即可。注意資料是按照資料型別對齊的。
status_t Parcel::writeInt32(int32_t val)
{
return writeAligned(val);
}
..
template<class T>
status_t Parcel::writeAligned(T val) {
COMPILE_TIME_ASSERT_FUNCTION_SCOPE(PAD_SIZE(sizeof(T)) == sizeof(T));
if ((mDataPos+sizeof(val)) <= mDataCapacity) {
restart_write:
*reinterpret_cast<T*>(mData+mDataPos) = val;
return finishWrite(sizeof(val));
}
status_t err = growData(sizeof(val));
if (err == NO_ERROR) goto restart_write;
return err;
}
以上是寫入Parcel資料的過程,蠻簡單的,記憶體容量的增長和資料大小等概念,和c++中的vector實現有些類似。 不過實際使用,可能還有一些注意的:
- fd的寫入,需要封裝成一個flat_binder_object, object還用另外一個mObjects管理,mObjects存放的是objects在mData中的偏移位置,這個也要發給驅動,這裡沒展開說。
- 正常主動通過Parcel寫入的資料,都是放在malloc分配的堆上的,這裡也就沒有特別的尺寸限制了。但並不是說對尺寸沒要求,因為資料的接收端有要求!
- 這裡Parcel只是對資料的打包類,在面向物件的抽象上,可以將可序列化的物件,flatten到一個parcel中通過binder傳遞。不過上層的物件和這裡的flat_binder_object是兩個不同概念,flat_binder_object專指binder和fd[這兩個是不能序列化的,所以要特殊處理了,binder驅動會針對做特殊處理..]。
4. binder的傳輸資料過程
具體負責binder通訊的類,是IPCThreadState。IBinder的transact,實際呼叫的就是IPCThreadState::transact Parcel是專門供binder transact過程中,打包傳遞資料使用的。瞅一下傳遞過程: 呼叫IBinder的trasact,實際方法是在IPCThreadState類中,這個類是實際負責和Binder驅動操作。 IPCThreadState類不再展開說了,基本上,就是將通訊的資料,再封在一個Parcel類中,開始傳輸:status_t IPCThreadState::transact(int32_t handle,
uint32_t code, const Parcel& data,
Parcel* reply, uint32_t flags)
...
if (err == NO_ERROR) {
...
// 將parcel組織起來,放到待寫入結構中
err = writeTransactionData(BC_TRANSACTION, flags, handle, code, data, NULL);
}
if (err != NO_ERROR) {
if (reply) reply->setError(err);
return (mLastError = err);
}
if ((flags & TF_ONE_WAY) == 0) {
...
// 如果不是非同步binder,則要等待一下,這個transaction請求傳送成功,並等待遠端端處理完的迴應
if (reply) {
err = waitForResponse(reply);
} else {
Parcel fakeReply;
// 如果不需要遠端迴應返回資料,同步呼叫也要等待binder的遠端執行完成結束,這裡通過fakeReply實現這一邏輯
err = waitForResponse(&fakeReply);
}
...
} else {
// 非同步binder,只等待transaction命令寫入成功即可
err = waitForResponse(NULL, NULL);
}
return err;
}
這裡主要是將待寫入的資料,封裝成一個binder_transaction_data的結構體,然後寫入binder驅動。binder通訊過程是在waitForResponse中實現的。
status_t IPCThreadState::waitForResponse(Parcel *reply, status_t *acquireResult)
{
while (1) {
if ((err=talkWithDriver()) < NO_ERROR) break;;
...
cmd = mIn.readInt32();
...
switch (cmd) {
case BR_TRANSACTION_COMPLETE:
if (!reply && !acquireResult) goto finish;
break;
...
case BR_REPLY:
...
goto finish;
default:
err = executeCommand(cmd);
if (err != NO_ERROR) goto finish;
break;
}
}
finish:
if (err != NO_ERROR) {
if (acquireResult) *acquireResult = err;
if (reply) reply->setError(err);
mLastError = err;
}
return err;
}
對於傳送端,通過這個函式,就將資料寫入到binder驅動中,驅動接收後會反饋BR_TRANSACTION_COMPLETE訊息,如果是非同步呼叫,直接就通訊結束了,如果是同步的,還要再talkWithDriver,等服務端傳送的呼叫結果。 5. binder接收方得到Parcel
服務端在talkWithDriver時,收到驅動的訊息,在transact呼叫的BR_TRANSACT時,會執行服務端的transact處理,響應程式碼如下:
status_t IPCThreadState::executeCommand(int32_t cmd)
{
BBinder* obj;
RefBase::weakref_type* refs;
status_t result = NO_ERROR;
...
case BR_TRANSACTION:
{
binder_transaction_data tr;
result = mIn.read(&tr, sizeof(tr));
Parcel buffer;
buffer.ipcSetDataReference(
reinterpret_cast<const uint8_t*>(tr.data.ptr.buffer), //驅動填入的buffer地址,binder驅動分配的,是binder記憶體
tr.data_size,
reinterpret_cast<const binder_size_t*>(tr.data.ptr.offsets),
tr.offsets_size/sizeof(binder_size_t), freeBuffer, this); // 釋放binder記憶體的釋放函式。
...
Parcel reply;
status_t error;
if (tr.target.ptr) {
sp<BBinder> b((BBinder*)tr.cookie);
error = b->transact(tr.code, buffer, &reply, tr.flags);
} else {
error = the_context_object->transact(tr.code, buffer, &reply, tr.flags);
}
...
}
break;
...
return result;
}
這裡主要由兩點: 1. buffer.ipcSetDataReference 這個就是客戶端呼叫時,寫過來的parcel的資料內容。parcel通過ipcSetDataReference重構出一個和客戶端一樣的內容。這個函式注意的就是傳入的buffer地址,這個是驅動分配的binder buffer地址。另外還提供了free這個buffer的方法(freeBuffer)。 2. 呼叫binder服務端的transcat。 這裡注意的是,當target.ptr為0時,實際就是和service_manager通訊了。不過service_manager自己走自己實現那套,這個是死程式碼,不可能走進來的。正常都是走b->transact,即:
status_t BBinder::transact(
uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
data.setDataPosition(0);
status_t err = NO_ERROR;
switch (code) {
case PING_TRANSACTION:
reply->writeInt32(pingBinder());
break;
default:
err = onTransact(code, data, reply, flags);
break;
}
if (reply != NULL) {
reply->setDataPosition(0);
}
return err;
}
default分支,就是服務端的onTransact執行,具體的遠端程式碼執行了。 簡單總結: 接收端也比較簡單,從驅動中拿到通訊對方的資料,執行對應的請求即可。資料來自驅動,處理完了後,Parcel的解構函式,自動會free這片記憶體。(這裡有個坑!!!,後面再說)
6. 驅動乾的事情
一些注意事項 強調Binder共享記憶體大小 強調注意釋放接收記憶體,否則Binder掛了 關於Binder的記憶體,是驅動mmap出來的,每個參與binder通訊的程序都會分配,除了service_manager外,其他程序的大小都是固定的,不到1M,定義在ProcessState.cpp中: #define BINDER_VM_SIZE ((1*1024*1024) - (4096 *2)) 特別注意的是,這裡雖然是mmap的,看樣子應該是共享記憶體,可以供跨程序使用,實際不然。binder記憶體不能跨程序使用,操作是:- 每個binder程序都需要分配,只能分配的Binder程序讀取
- 驅動負責分配和回收(回收需要使用者程序主動向驅動發命令去free),當然,驅動是核心態的,可以看到所有程序的記憶體
- 以上,通訊過程必然要有一次copy,copy動作是在驅動中做的。
- binder buffer僅供接收資料用。
int binder_thread_write(struct binder_proc *proc, struct binder_thread *thread,
binder_uintptr_t binder_buffer, size_t size,
binder_size_t *consumed)
{
uint32_t cmd;
void __user *buffer = (void __user *)(uintptr_t)binder_buffer;
void __user *ptr = buffer + *consumed;
void __user *end = buffer + size;
// 先獲取傳輸的command,確定要binder做何事。
while (ptr < end && thread->return_error == BR_OK) {
if (get_user(cmd, (uint32_t __user *)ptr))
return -EFAULT;
ptr += sizeof(uint32_t);
...
switch (cmd) {
...
case BC_TRANSACTION:
case BC_REPLY: {
struct binder_transaction_data tr;
// 從使用者態獲取transact_data
if (copy_from_user(&tr, ptr, sizeof(tr)))
return -EFAULT;
ptr += sizeof(tr);
binder_transaction(proc, thread, &tr, cmd == BC_REPLY);
break;
}
...
}
*consumed = ptr - buffer;
}
return 0;
}
處理Transaction 請求:
static void binder_transaction(struct binder_proc *proc,
struct binder_thread *thread,
struct binder_transaction_data *tr, int reply)
{
...
// 不是reply,走else
if (reply) {
...
} else {
// 查詢獲得通訊的target端。
if (tr->target.handle) {
struct binder_ref *ref;
ref = binder_get_ref(proc, tr->target.handle);...
target_node = ref->node;
} else {
target_node = binder_context_mgr_node;...
}
e->to_node = target_node->debug_id;
target_proc = target_node->proc;...
}
...
}
if (target_thread) {
e->to_thread = target_thread->pid;
target_list = &target_thread->todo;
target_wait = &target_thread->wait;
} else {
target_list = &target_proc->todo;
target_wait = &target_proc->wait;
}
e->to_proc = target_proc->pid;
// 構建transaction結構
/* TODO: reuse incoming transaction for reply */
t = kzalloc(sizeof(*t), GFP_KERNEL);...
tcomplete = kzalloc(sizeof(*tcomplete), GFP_KERNEL);...
if (!reply && !(tr->flags & TF_ONE_WAY))
t->from = thread;
else
t->from = NULL;
t->sender_euid = proc->tsk->cred->euid;
t->to_proc = target_proc;
t->to_thread = target_thread;
t->code = tr->code;
t->flags = tr->flags;
t->priority = task_nice(current);
// 分配binder buffer,消耗的是目標程序的buffer
t->buffer = binder_alloc_buf(target_proc, tr->data_size,
tr->offsets_size, !reply && (t->flags & TF_ONE_WAY));...
t->buffer->allow_user_free = 0;
t->buffer->debug_id = t->debug_id;
t->buffer->transaction = t;
t->buffer->target_node = target_node;
if (target_node)
binder_inc_node(target_node, 1, 0, NULL);
offp = (binder_size_t *)(t->buffer->data +
ALIGN(tr->data_size, sizeof(void *)));
// 這裡,將使用者態寫入的要通訊的Parcel中的資料[存在使用者malloc堆中],拷貝到核心binder buffer中
if (copy_from_user(t->buffer->data, (const void __user *)(uintptr_t)
tr->data.ptr.buffer, tr->data_size)) {...}
if (copy_from_user(offp, (const void __user *)(uintptr_t)
tr->data.ptr.offsets, tr->offsets_size)) {...}
...
// 呼叫請求給到目標程序,喚醒目標程序,看是工作
t->work.type = BINDER_WORK_TRANSACTION;
list_add_tail(&t->work.entry, target_list);
tcomplete->type = BINDER_WORK_TRANSACTION_COMPLETE;
list_add_tail(&tcomplete->entry, &thread->todo);
if (target_wait)
wake_up_interruptible(target_wait);
return;
...
}
上面就是copy的動作。Binder傳輸資料是有一次拷貝的。當Transaction開始後,喚醒目標程序,tranasction請求和資料到目標程序的路徑如下: [email protected]kernel/driver/staging/android/binder.c
executeCommand@IPCThreadState.cpp 注意:
- 目標程序啟動binder執行緒,在執行joinThreadPool後,或者binder執行緒通訊完成,空閒後,都是在驅動的binder_thread_read這個函式中wait,等待請求。
- 目標程序收到資料後,talkWithDriver收到資料,呼叫executeCommand處理資料的各種請求
7. 其他,用Parcel傳遞物件。
以上只是binder通訊,用parcel傳遞資料的過程。上述描述的主要是native層的,基本上角度是通訊的角度,如果從上層角度看,Parcel主要是方便實現上層抽象物件的跨程序傳輸,可以將本地物件,拆包後,扁平化資料流的形式,通過Binder,傳輸到遠端程序,遠端通過接收到的資料,重新構基本一樣的物件,可以做到使用者看起來好像所有物件或執行程式碼都在本地一樣。基本上,Parcel的打包解包,和序列化過程差不多:這裡基本上就是業務邏輯上層,封了一層。實現也比較簡單,就是類實現Parcelable介面,支援將物件資料寫入Parcel,通過binder傳遞到遠端程序中,再根據這些資料,在遠端重新構建出物件即可,這個理解起來比較簡單。 其他一些注意事項:
- Binder支援fd的傳遞,fd傳遞的話,fd 在本程序還要用的話,一定要dup一份,否則,在parcel在通訊完成後析構時,是要close fd的。
- Parcel中資料的釋放,會在類析構的時候,釋放記憶體。parcel通訊返回的reply,是一個指標。注意如果是用java程式碼實現的通訊邏輯的話,是直接儲存這個reply指標的,而且java是沒有析構的概念,java的Parcel類被GC,也沒有相應的釋放IPC資料庫的動作,必須在不用的時候,呼叫recycle來顯示的回收這個資料。否則很快這個binder記憶體(只有1M-8K)就耗幹了。基本上原生程式碼,看起來就是MediaPlayer的getMetaData沒有做這個事,會洩漏(好像最新版本android也沒修)。
- 從上可知,binder記憶體很小的,特別要注意不要傳遞大物件和資料,否則很容易失敗。如果資料量比較大,可以用資料庫,或者參考Bitmap的傳遞實現,用Blob。
簡單總結:
- Binder通訊,通訊發起端(BC_XXX)往binder驅動中寫入的資料,是建立在自己程序的堆中的
- Binder通訊的發起端程序的通訊資料,在binder驅動中,會拷貝到響應端的binder記憶體中,binder記憶體,除service_manager外,每個程序只有1M-8K,注意通訊傳輸資料量要小
- 響應端(BR_XXX)得到的資料,是儲存在Binder記憶體中的,使用完後,注意可能有free問題,免得洩露
相關推薦
Parcel資料傳輸過程,簡要分析Binder流程
Android Binder框架跨程序傳輸的,基本都是通過Parcel來封裝的資料[service_manager除外],這個是十分底層的實現了。對於Framework開發來說,除了瞭解RPC的抽象意義外,對底層的具體通訊細節的實現,也是要比較瞭解的,這裡簡單記錄一下RPC
大資料處理過程,業務性資料庫與分析性資料庫比較
一丶 一般情況下,資料探勘經過如下階段1,資料記錄到資料來源中,如(文字檔案,傳統的業務資料系統,和其他各種資料來源)2.這些資料經過ETL(extract,transform,load)過程儲存到資料倉庫中,如hive(這些資料倉庫並不儲存資料,只是在檔案系統上的儲存倉庫引
TCP協議三次握手和四次分手以及資料傳輸過程
1、三次握手 TCP是面向連線的,在面向連線的環境中,開始傳輸資料之前,在兩個終端之間必須先建立一個連線。建立連線同步的過錯稱為三次握手,具體過程如下: (1)當主機A想同主機B建立連線,主機A會發送SYN給主機B,初始化序列號seq
中國電信物聯網4:連線時間過長,資料傳輸消失,電信平臺顯示延遲
NB-IOT論壇: 華為NB-IOT論壇:https://developer.huawei.com/ict/forum/forum.html 移動物聯網論壇:https://open.iot.10086.cn/bbs/forum.php 電信沒有自己的論壇 一份2017年的部落格(h
第1章 1.5計算機網路概述--訪問網站資料傳輸過程
15.0.0.2主機(A)中的瀏覽器ie向網站伺服器(B)13.0.0.2請求訪問url1網頁,B將生成的網頁資料放到網絡卡快取C中,C就緒後就開始向網絡卡快取D傳送資料,D收到資料後就告訴C我收到資料1了,我要資料2。C就會將資料1從快取中清除,傳送資料2給D,同時將還沒有放進C的、要傳送的資料
計算機網路————可靠性資料傳輸過程(rdt1.0 /rdt2.0 /rdt2.1 /rdt2.2 /rdt3.0)
rdt1.0 將資料的傳輸通道理想化,視為完全可靠,不丟包,不損失bit ,在這樣的情況下,傳送端傳送資料,接收端直接接收,並不考慮丟包,超時這些問題。 該協議中,都是直接傳送,直接接收。 rdt2.0 " 在 rdt2.0 中,我們將傳輸通道視為有可能
laravel傳輸資料介面時,好用的資料傳輸方法,收藏一波!
1.養成程式碼儲存到log檔案中的習慣,方便核實資料的正確性,使得使用更方便; public function saveLogToFile($d) { $data =
Qemu-KVM虛擬機器初始化及建立過程原始碼簡要分析(一)
我們知道,Qemu-KVM實際上包括Qemu和KVM兩部分,那麼在建立以及初始化虛擬機器時,實際上也是在這兩部分進行的。 KVM實際上就是kvm核心模組,包括kvm.ko、kvm-intel.ko、kvm-amd.ko三部分,後兩部分分別對應Intel體系的
Qemu-KVM虛擬機器初始化及建立過程原始碼簡要分析(二)
前面我們講了KVM核心層建立及初始化虛擬機器的一些工作過程,現在講一下Qemu層的流程以及與KVM核心層的配合過程。 Qemu層是從vl.c中的main()函式開始的,這裡通過在程式碼中新增一些註釋的方式來進行講解,中間省略了很多不重要或者我也沒有搞
TCP資料傳輸過程
建立連線後,兩臺主機就可以相互傳輸資料了。如下圖所示: 圖1:TCP 套接字的資料交換過程 上圖給出了主機A分2次(分2個數據包)向主機B傳遞200位元組的過程。首先,主機A通過1個數據包傳送100個位元組的資料,資料包的 Seq 號設定為 1200。主機B為了確認這一
騰雲大學與美世諮詢聯合釋出資料人才報告,深入分析行業生態結構!
近日,騰雲大學與美世諮詢聯合釋出《專業資料人才教育行業生態報告》。 其中,美世諮詢中國區副總裁兼戰略客戶部總經理喬魯諾對專業資料人才市場規模與現狀做出了有關介紹;TalkingData CEO助理、騰雲大學(TalkingData University)執行校長楊慧則對
SSM原理,簡要分析(僅供個人參考)
1.作用 SSM框架是spring MVC ,spring和mybatis框架的整合,是標準的MVC模式,將整個系統劃分為表現層,controller層,service層,DAO層四層。 使用spring MVC負責請求的轉發和檢視管理,spring實現業務物件管理,my
Linux利用socket實現兩臺pc之間的資料傳輸功能,包括windows到linux,UDP實現
makefile 和TCP一樣,參見上一篇博文 客戶端和服務端主函式也和上一篇的tcp是一樣的,同樣參考上一篇博文,這裡只是修改了pub.c的檔案了 /* * pub.c * * Created on: 2016年7月14日 * Author: Admi
AllJoyn資料傳輸過程
一、概述 AllJoyn提供Methods,signals,properties作為介面成員,AllJoyn APP可以使用這些介面成員來實現資料交換。應用之間交換資料必須建立AllJoun Session,除非是使用Sessionless來發送。 傳送
微信公眾平臺開發之資料傳輸過程
當用戶發一條訊息到微信公眾號,該公眾號會返回相應的訊息給使用者,那麼這些訊息是經過什麼過程才返回到使用者的微信裡呢。 其實使用者傳送訊息開始,主要經過5個過程: 1、使用者傳送訊息傳輸到微信伺服器 2、
如何保證資料傳輸過程安全性
對稱加密:加密和解密使用同一個金鑰 特點:保證了資料的保密性。 侷限性:無法解決金鑰交換問題。 常用的演算法有:DES,3DES,AES; 公鑰加密:生成一個金鑰對(私鑰和公鑰),加密時用私鑰加密,解密時用公鑰解密 特點:解決了金鑰交換問題。 侷限性:對大的
nova建立虛擬機器過程原始碼簡要分析(一)
nova部署虛擬機器原始碼呼叫過程簡要分析,關於novaclient的程式處理流程暫時還沒有分析。後期如果有時間會進一步分析novaclient的程式執行過程,以及客戶端和服務之間的http請求響應關係。 nova/api/openstack/compute/
Apollo學習筆記(一):canbus模組與車輛底盤之間的CAN資料傳輸過程
Apollo學習筆記(一):canbus模組與車輛底盤之間的CAN資料傳輸過程 博主現在從車載自組網通道分配和多跳路由轉向了自動駕駛,沒啥經驗,想快些做出來個Demo還是得站在巨人的肩膀上才行,我選擇了Apollo,主要還是支援國產而且它的開發者套件有現成的底盤可以直接跑起來,但是apollo
通過官方API結合源碼,如何分析程序流程
Lucene 很多 core 結合 ear 關註 執行 方法 內部 通過官方API結合源碼,如何分析程序流程通過官方API找到我們關註的API的某個方法,然後把整個流程執行起來,然後在idea中,把我們關註的方法打上斷點,然後通過Step Out,從內向外一層一層分析調用鏈
通過AMS分析Binder流程(Java到Kernel)
栗子: public class MainActivity extends Activity{ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstan