2008年3月15日 星期六

SIP Intro --- 第二部 (part 3)

S

IP Intro 第二部,竟然拖到part 3,一定要在此節結束掉! SIP Intro 第二部 part 3將專心於各個例子的解釋與流程說明。<此篇篇幅可能會比以前的還要長一些,為了有完整概念以及與之後的章節作銜接、延續,就請大家耐心看下去吧...>

未看過SIP 第一部、第二部(part 1,2)的請至下面連結觀看...
SIP Intro --- 第一部
SIP Intro --- 第二部(part 1)
SIP Intro --- 第二部(part 2)


先用圖回顧一下例子,再接著說明第二個message。(若已遺忘,就往回複習一下吧!)

此例子的message flow...
   Alice                     Bob
| |
| INVITE F1 |
|----------------------->|
| 180 Ringing F2 |
|<-----------------------|
| |
| 200 OK F3 |
|<-----------------------|
| ACK F4 |
|----------------------->|
| Both Way RTP Media |
|<======================>|
| |
| BYE F5 |
|<-----------------------|
| 200 OK F6 |
|----------------------->|
| |


第一個message...

F1 INVITE Alice -> Bob

INVITE sip:bob@biloxi.example.com SIP/2.0
Via: SIP/2.0/TCP client.atlanta.example.com:5060;branch=z9hG4bK74bf9
Max-Forwards: 70
From: Alice ;tag=9fxced76sl
To: Bob
Call-ID: 3848276298220188511@atlanta.example.com
CSeq: 1 INVITE
Contact:
Content-Type: application/sdp
Content-Length: 151
v=0
o=alice 2890844526 2890844526 IN IP4 client.atlanta.example.com
s=-
c=IN IP4 192.0.2.101
t=0 0
m=audio 49172 RTP/AVP 0
a=rtpmap:0 PCMU/8000


再來就是第二個message

F2 180 Ringing Bob -> Alice
SIP/2.0 180 Ringing
Via: SIP/2.0/TCP client.atlanta.example.com:5060;branch=z9hG4bK74bf9
;received=192.0.2.101
From: Alice ;tag=9fxced76sl
To: Bob ;tag=8321234356
Call-ID: 3848276298220188511@atlanta.example.com
CSeq: 1 INVITE
Contact:
Content-Length: 0



第二個message,我可以看到To這個header field多加入了tag這個parameter(UAS產生並放入),以及Contact裡的SIP URI也改成Bob了,其用意在part 2也有說明,相信大家都知道,所以我們就可以看下一個message了!

F3 200 OK Bob -> Alice

SIP/2.0 200 OK
Via: SIP/2.0/TCP client.atlanta.example.com:5060;branch=z9hG4bK74bf9
;received=192.0.2.101
From: Alice ;tag=9fxced76sl
To: Bob ;tag=8321234356
Call-ID: 3848276298220188511@atlanta.example.com
CSeq: 1 INVITE
Contact:
Content-Type: application/sdp
Content-Length: 147


這個message是Bob決定接受Alice的通訊要求後,回覆給Alice的message。在這可以注意到一點,From與To的SIP URI都沒有變,因為這是對於F1這個message(CSeq:1 INVITE)所做的回應,所以不會改變。再來看第四個message。

F4 ACK Alice -> Bob

ACK sip:bob@client.biloxi.example.com SIP/2.0
Via: SIP/2.0/TCP client.atlanta.example.com:5060;branch=z9hG4bK74bd5
Max-Forwards: 70
From: Alice ;tag=9fxced76sl
To: Bob ;tag=8321234356
Call-ID: 3848276298220188511@atlanta.example.com
CSeq: 1 ACK
Content-Length: 0


到這裡應該沒什麼問題。
如此一來RTP(Real-time Transport Protocol)streams就建立完了,雙方就開始通話。通話一段時間後,假設Bob要結束通訊,這時就會送出下面的message。

F5 BYE Bob -> Alice

BYE sip:alice@client.atlanta.example.com SIP/2.0
Via: SIP/2.0/TCP client.biloxi.example.com:5060;branch=z9hG4bKnashds7
Max-Forwards: 70
From: Bob ;tag=8321234356
To: Alice ;tag=9fxced76sl
Call-ID: 3848276298220188511@atlanta.example.com
CSeq: 1 BYE
Content-Length: 0


這裡要注意到CSeq的值仍為1,因為Bob與Alice是各自maintain自己的CSeq,所以對Bob來說,BYE是第一個request,所以值才看起來沒有變。Call-ID仍是不變,這沒問題,同一通Call,其Call-ID是不能變的,另外注意到的是From與To已經改變,因為是Bob發出的,所以From就填上Bob的SIP URI,To就填上了Alice的SIP URI(當然對應的tag也要變動囉)。(繼續看下去)

F6 200 OK Alice -> Bob

SIP/2.0 200 OK
Via: SIP/2.0/TCP client.biloxi.example.com:5060;branch=z9hG4bKnashds7
;received=192.0.2.201
From: Bob ;tag=8321234356
To: Alice ;tag=9fxced76sl
Call-ID: 3848276298220188511@atlanta.example.com
CSeq: 1 BYE
Content-Length: 0


看了這個message,大家一定有點熟悉,是的!跟之前的Bob傳給Alice的200 ok的message很像,差別只在於From與To對調而已。

到此,我們就完成了從最簡單的角度(兩個User Agent)去看一個session建立的流程,以及其流程中交換的資訊。以此為基礎,以下的例子就可以更容易上手了!(不囉嗦! 繼續下個例子)


     Alice        Proxy 1        Proxy 2          Bob
| | | |
| INVITE F1 | | |
|------------->| | |
| 407 F2 | | |
|<-------------| | |
| ACK F3 | | |
|------------->| | |
| INVITE F4 | | |
|------------->| INVITE F5 | |
| 100 F6 |------------->| INVITE F7 |
|<-------------| 100 F8 |------------->|
| |<-------------| |
| | | 180 F9 |
| | 180 F10 |<-------------|
| 180 F11 |<-------------| |
|<-------------| | 200 F12 |
| | 200 F13 |<-------------|
| 200 F14 |<-------------| |
|<-------------| | |
| ACK F15 | | |
|------------->| ACK F16 | |
| |------------->| ACK F17 |
| | |------------->|
| Both Way RTP Media |
|<==========================================>|
| | | BYE F18 |
| | BYE F19 |<-------------|
| BYE F20 |<-------------| |
|<-------------| | |
| 200 F21 | | |
|------------->| 00 F22 | |
| |------------->| 200 F23 |
| | |------------->|
| | | |



這個例子看似很龐大,但卻很簡單!其實只要之前的基礎都打好了,在看這個例子時,就只是多了兩個proxy server插在中間而已,實際去看這兩個proxy的行為,就會發現他們只是在做封包的forward而已。

OK,我想大家應該可以接受這樣的concept,有了這樣的concept,我想就可以更深入一點! 看看F1~F4這四個封包在做什麼(我想大家應該有發現這個地方不太一樣)。以下我先把前五個SIP message放在下面,以免佔篇幅!

F1 INVITE Alice -> Proxy 1
INVITE sip:bob@biloxi.example.com SIP/2.0
Via: SIP/2.0/TCP client.atlanta.example.com:5060;branch=z9hG4bK74b43
Max-Forwards: 70
Route:
From: Alice ;tag=9fxced76sl
To: Bob
Call-ID: 3848276298220188511@atlanta.example.com
CSeq: 1 INVITE
Contact:
Content-Type: application/sdp
Content-Length: 151

v=0
o=alice 2890844526 2890844526 IN IP4 client.atlanta.example.com
s=-
c=IN IP4 192.0.2.101
t=0 0
m=audio 49172 RTP/AVP 0
a=rtpmap:0 PCMU/8000



F2 407 Proxy Authorization Required Proxy 1 -> Alice
SIP/2.0 407 Proxy Authorization Required
Via: SIP/2.0/TCP client.atlanta.example.com:5060;branch=z9hG4bK74b43
;received=192.0.2.101
From: Alice ;tag=9fxced76sl
To: Bob ;tag=3flal12sf
Call-ID: 3848276298220188511@atlanta.example.com
CSeq: 1 INVITE
Proxy-Authenticate: Digest realm="atlanta.example.com", qop="auth",
nonce="f84f1cec41e6cbe5aea9c8e88d359",
opaque="", stale=FALSE, algorithm=MD5
Content-Length: 0



F3 ACK Alice -> Proxy 1
ACK sip:bob@biloxi.example.com SIP/2.0
Via: SIP/2.0/TCP client.atlanta.example.com:5060;branch=z9hG4bK74b43
Max-Forwards: 70
From: Alice ;tag=9fxced76sl
To: Bob ;tag=3flal12sf
Call-ID: 3848276298220188511@atlanta.example.com
CSeq: 1 ACK
Content-Length: 0



F4 INVITE Alice -> Proxy 1
INVITE sip:bob@biloxi.example.com SIP/2.0
Via: SIP/2.0/TCP client.atlanta.example.com:5060;branch=z9hG4bK74bf9
Max-Forwards: 70
Route:
From: Alice ;tag=9fxced76sl
To: Bob
Call-ID: 3848276298220188511@atlanta.example.com
CSeq: 2 INVITE
Contact:
Proxy-Authorization: Digest username="alice",
realm="atlanta.example.com",
nonce="wf84f1ceczx41ae6cbe5aea9c8e88d359", opaque="",
uri="sip:bob@biloxi.example.com",
response="42ce3cef44b22f50c6a6071bc8"
Content-Type: application/sdp
Content-Length: 151

v=0
o=alice 2890844526 2890844526 IN IP4 client.atlanta.example.com
s=-
c=IN IP4 192.0.2.101
t=0 0
m=audio 49172 RTP/AVP 0
a=rtpmap:0 PCMU/8000



F5 INVITE Proxy 1 -> Proxy 2
INVITE sip:bob@biloxi.example.com SIP/2.0
Via: SIP/2.0/TCP ss1.atlanta.example.com:5060;branch=z9hG4bK2d4790.1
Via: SIP/2.0/TCP client.atlanta.example.com:5060;branch=z9hG4bK74bf9
;received=192.0.2.101
Max-Forwards: 69
Record-Route:
From: Alice ;tag=9fxced76sl
To: Bob
Call-ID: 3848276298220188511@atlanta.example.com
CSeq: 2 INVITE
Contact:
Content-Type: application/sdp
Content-Length: 151

v=0
o=alice 2890844526 2890844526 IN IP4 client.atlanta.example.com
s=-
c=IN IP4 192.0.2.101
t=0 0
m=audio 49172 RTP/AVP 0
a=rtpmap:0 PCMU/8000


我想F1這個message應該不會有疑問,問題在於F2這個message,為何proxy要送407給Alice? 原因在於這個proxy有Authorization的機制,也就是說若proxy接收到的message沒有包含Authentication的資訊(包含有Proxy-Authenticate這個header field),proxy server就會回覆一個407的message給UAC。

再來UAC就要會回一個ACK (F3)給proxy server,然後就要開始處理Authentication(目前講的都是Authorization的機制,定義於RFC2617)。

在F2這個message的Proxy-Authenticate這個header field裡,proxy server會放入兩個數值(realm,nonce),realm是他的名子,nonce是隨機產生出來的的數值。

這時UAC就要依照一個不可逆的演算法(這裡是用MD5),把realm、nonce、URI與密碼當作參數套入演算法,得出一個數值,稱之唯response,此時realm、nonce、uri與response會當作Proxy-Authorization的parameters,注意UAC並沒有把密碼也送出去給proxy server(當然proxy server也沒有把密碼傳送給UAC!),因為proxy server存有此UAC的密碼,如此才有達到認證的機制。

如同F4這個message,UAC把那四個parameters放在Proxy-Authorization這個header field裡,連同先前的INVITE的內容一起送給proxy server (F4)。

這裡要注意的是CSeq的內容,已經變為CSeq: 2 INVITE,如同先前說明的,這次的INVITE對於Alice來說已經是第二次了,所以sequence number一定要加一,否則proxy server不會裡會這個packet,因為proxy會當成是重送的packet,而不會把他當成是有Authorization的packet。 (其他header field的內容大家可以一一驗證,如此會更熟悉SIP message裡的資訊)


說了一大堆,來看看流程圖吧~ 會更清楚一點! (為了簡化一點,我把ACK message拿掉)



這樣應該有概念囉~

接下來其他的SIP message就大同小異了,如下


F6 100 Trying Proxy 1 -> Alice
SIP/2.0 100 Trying
Via: SIP/2.0/TCP client.atlanta.example.com:5060;branch=z9hG4bK74bf9
;received=192.0.2.101
From: Alice ;tag=9fxced76sl
To: Bob
Call-ID: 3848276298220188511@atlanta.example.com
CSeq: 2 INVITE
Content-Length: 0



F7 INVITE Proxy 2 -> Bob
INVITE sip:bob@client.biloxi.example.com SIP/2.0
Via: SIP/2.0/TCP ss2.biloxi.example.com:5060;branch=z9hG4bK721e4.1
Via: SIP/2.0/TCP ss1.atlanta.example.com:5060;branch=z9hG4bK2d4790.1
;received=192.0.2.111
Via: SIP/2.0/TCP client.atlanta.example.com:5060;branch=z9hG4bK74bf9
;received=192.0.2.101
Max-Forwards: 68
Record-Route: ,

From: Alice ;tag=9fxced76sl
To: Bob
Call-ID: 3848276298220188511@atlanta.example.com
CSeq: 2 INVITE
Contact:
Content-Type: application/sdp
Content-Length: 151

v=0
o=alice 2890844526 2890844526 IN IP4 client.atlanta.example.com
s=-
c=IN IP4 192.0.2.101
t=0 0
m=audio 49172 RTP/AVP 0
a=rtpmap:0 PCMU/8000



F8 100 Trying Proxy 2 -> Proxy 1
SIP/2.0 100 Trying
Via: SIP/2.0/TCP ss1.atlanta.example.com:5060;branch=z9hG4bK2d4790.1
;received=192.0.2.111
Via: SIP/2.0/TCP client.atlanta.example.com:5060;branch=z9hG4bK74bf9
;received=192.0.2.101
From: Alice ;tag=9fxced76sl
To: Bob
Call-ID: 3848276298220188511@atlanta.example.com
CSeq: 2 INVITE
Content-Length: 0



F9 180 Ringing Bob -> Proxy 2
SIP/2.0 180 Ringing
Via: SIP/2.0/TCP ss2.biloxi.example.com:5060;branch=z9hG4bK721e4.1
;received=192.0.2.222
Via: SIP/2.0/TCP ss1.atlanta.example.com:5060;branch=z9hG4bK2d4790.1
;received=192.0.2.111
Via: SIP/2.0/TCP client.atlanta.example.com:5060;branch=z9hG4bK74bf9
;received=192.0.2.101
Record-Route: ,

From: Alice ;tag=9fxced76sl
To: Bob ;tag=314159
Call-ID: 3848276298220188511@atlanta.example.com
Contact:
CSeq: 2 INVITE
Content-Length: 0



F10 180 Ringing Proxy 2 -> Proxy 1
SIP/2.0 180 Ringing
Via: SIP/2.0/TCP ss1.atlanta.example.com:5060;branch=z9hG4bK2d4790.1
;received=192.0.2.111
Via: SIP/2.0/TCP client.atlanta.example.com:5060;branch=z9hG4bK74bf9
;received=192.0.2.101
Record-Route: ,

From: Alice ;tag=9fxced76sl
To: Bob ;tag=314159
Call-ID: 3848276298220188511@atlanta.example.com
Contact:
CSeq: 2 INVITE
Content-Length: 0



F11 180 Ringing Proxy 1 -> Alice
SIP/2.0 180 Ringing
Via: SIP/2.0/TCP client.atlanta.example.com:5060;branch=z9hG4bK74bf9
;received=192.0.2.101
Record-Route: ,

From: Alice ;tag=9fxced76sl
To: Bob ;tag=314159
Call-ID: 3848276298220188511@atlanta.example.com
Contact:
CSeq: 2 INVITE
Content-Length: 0



F12 200 OK Bob -> Proxy 2
SIP/2.0 200 OK
Via: SIP/2.0/TCP ss2.biloxi.example.com:5060;branch=z9hG4bK721e4.1
;received=192.0.2.222
Via: SIP/2.0/TCP ss1.atlanta.example.com:5060;branch=z9hG4bK2d4790.1
;received=192.0.2.111
Via: SIP/2.0/TCP client.atlanta.example.com:5060;branch=z9hG4bK74bf9
;received=192.0.2.101
Record-Route: ,

From: Alice ;tag=9fxced76sl
To: Bob ;tag=314159
Call-ID: 3848276298220188511@atlanta.example.com
CSeq: 2 INVITE
Contact:
Content-Type: application/sdp
Content-Length: 147

v=0
o=bob 2890844527 2890844527 IN IP4 client.biloxi.example.com
s=-
c=IN IP4 192.0.2.201
t=0 0
m=audio 3456 RTP/AVP 0
a=rtpmap:0 PCMU/8000



F13 200 OK Proxy 2 -> Proxy 1
SIP/2.0 200 OK
Via: SIP/2.0/TCP ss1.atlanta.example.com:5060;branch=z9hG4bK2d4790.1
;received=192.0.2.111
Via: SIP/2.0/TCP client.atlanta.example.com:5060;branch=z9hG4bK74bf9
;received=192.0.2.101
Record-Route: ,

From: Alice ;tag=9fxced76sl
To: Bob ;tag=314159
Call-ID: 3848276298220188511@atlanta.example.com
CSeq: 2 INVITE
Contact:
Content-Type: application/sdp
Content-Length: 147

v=0
o=bob 2890844527 2890844527 IN IP4 client.biloxi.example.com
s=-
c=IN IP4 192.0.2.201
t=0 0
m=audio 3456 RTP/AVP 0
a=rtpmap:0 PCMU/8000



F14 200 OK Proxy 1 -> Alice
SIP/2.0 200 OK
Via: SIP/2.0/TCP client.atlanta.example.com:5060;branch=z9hG4bK74bf9
;received=192.0.2.101
Record-Route: ,

From: Alice ;tag=9fxced76sl
To: Bob ;tag=314159
Call-ID: 3848276298220188511@atlanta.example.com
CSeq: 2 INVITE
Contact:
Content-Type: application/sdp
Content-Length: 147

v=0
o=bob 2890844527 2890844527 IN IP4 client.biloxi.example.com
s=-
c=IN IP4 192.0.2.201
t=0 0
m=audio 3456 RTP/AVP 0
a=rtpmap:0 PCMU/8000



F15 ACK Alice -> Proxy 1
ACK sip:bob@client.biloxi.example.com SIP/2.0
Via: SIP/2.0/TCP client.atlanta.example.com:5060;branch=z9hG4bK74b76
Max-Forwards: 70
Route: ,

From: Alice ;tag=9fxced76sl
To: Bob ;tag=314159
Call-ID: 3848276298220188511@atlanta.example.com
CSeq: 2 ACK
Content-Length: 0



F16 ACK Proxy 1 -> Proxy 2
ACK sip:bob@client.biloxi.example.com SIP/2.0
Via: SIP/2.0/TCP ss1.atlanta.example.com:5060;branch=z9hG4bK2d4790.1
Via: SIP/2.0/TCP client.atlanta.example.com:5060;branch=z9hG4bK74b76
;received=192.0.2.101
Max-Forwards: 69
Route:
From: Alice ;tag=9fxced76sl
To: Bob ;tag=314159
Call-ID: 3848276298220188511@atlanta.example.com
CSeq: 2 ACK
Content-Length: 0



F17 ACK Proxy 2 -> Bob
ACK sip:bob@client.biloxi.example.com SIP/2.0
Via: SIP/2.0/TCP ss2.biloxi.example.com:5060;branch=z9hG4bK721e4.1
Via: SIP/2.0/TCP ss1.atlanta.example.com:5060;branch=z9hG4bK2d4790.1
;received=192.0.2.111
Via: SIP/2.0/TCP client.atlanta.example.com:5060;branch=z9hG4bK74b76
;received=192.0.2.101
Max-Forwards: 68
From: Alice ;tag=9fxced76sl
To: Bob ;tag=314159
Call-ID: 3848276298220188511@atlanta.example.com
CSeq: 2 ACK
Content-Length: 0


這邊就開始RTP streams了,再來也是ㄧ樣,Bob結束通訊~

F18 BYE Bob -> Proxy 2
BYE sip:alice@client.atlanta.example.com SIP/2.0
Via: SIP/2.0/TCP client.biloxi.example.com:5060;branch=z9hG4bKnashds7
Max-Forwards: 70
Route: ,

From: Bob ;tag=314159
To: Alice ;tag=9fxced76sl
Call-ID: 3848276298220188511@atlanta.example.com
CSeq: 1 BYE
Content-Length: 0



F19 BYE Proxy 2 -> Proxy 1
BYE sip:alice@client.atlanta.example.com SIP/2.0
Via: SIP/2.0/TCP ss2.biloxi.example.com:5060;branch=z9hG4bK721e4.1
Via: SIP/2.0/TCP client.biloxi.example.com:5060;branch=z9hG4bKnashds7
;received=192.0.2.201
Max-Forwards: 69
Route:
From: Bob ;tag=314159
To: Alice ;tag=9fxced76sl
Call-ID: 3848276298220188511@atlanta.example.com
CSeq: 1 BYE
Content-Length: 0



F20 BYE Proxy 1 -> Alice
BYE sip:alice@client.atlanta.example.com SIP/2.0
Via: SIP/2.0/TCP ss1.atlanta.example.com:5060;branch=z9hG4bK2d4790.1
Via: SIP/2.0/TCP ss2.biloxi.example.com:5060;branch=z9hG4bK721e4.1
;received=192.0.2.222
Via: SIP/2.0/TCP client.biloxi.example.com:5060;branch=z9hG4bKnashds7
;received=192.0.2.201
Max-Forwards: 68
From: Bob ;tag=314159
To: Alice ;tag=9fxced76sl
Call-ID: 3848276298220188511@atlanta.example.com
CSeq: 1 BYE
Content-Length: 0



F21 200 OK Alice -> Proxy 1
SIP/2.0 200 OK
Via: SIP/2.0/TCP ss1.atlanta.example.com:5060;branch=z9hG4bK2d4790.1
;received=192.0.2.111
Via: SIP/2.0/TCP ss2.biloxi.example.com:5060;branch=z9hG4bK721e4.1
;received=192.0.2.222
Via: SIP/2.0/TCP client.biloxi.example.com:5060;branch=z9hG4bKnashds7
;received=192.0.2.201
From: Bob ;tag=314159
To: Alice ;tag=9fxced76sl
Call-ID: 3848276298220188511@atlanta.example.com
CSeq: 1 BYE
Content-Length: 0



F22 200 OK Proxy 1 -> Proxy 2
SIP/2.0 200 OK
Via: SIP/2.0/TCP ss2.biloxi.example.com:5060;branch=z9hG4bK721e4.1
;received=192.0.2.222
Via: SIP/2.0/TCP client.biloxi.example.com:5060;branch=z9hG4bKnashds7
;received=192.0.2.101
From: Bob ;tag=314159
To: Alice ;tag=9fxced76sl
Call-ID: 3848276298220188511@atlanta.example.com
CSeq: 1 BYE
Content-Length: 0



F23 200 OK Proxy 2 -> Bob
SIP/2.0 200 OK
Via: SIP/2.0/TCP client.biloxi.example.com:5060;branch=z9hG4bKnashds7
;received=192.0.2.201
From: Bob ;tag=314159
To: Alice ;tag=9fxced76sl
Call-ID: 3848276298220188511@atlanta.example.com
CSeq: 1 BYE
Content-Length: 0



相信大家看到最後,已經覺得有點無聊了吧! 沒錯! 當妳覺得無聊時,大概就表示你已經了解他下一部會要做什麼事情了! (如果是看不懂而無聊...=.=,那可能要麻煩回去看一下前面幾部的文章囉~~ >__<)

或許大家有個疑問,萬一Bob所在的proxy也要authentication呢? 很簡單! 只要proxy 1把proxy 2回覆的407 message回給Alice,讓Alice在做一次上面說的流程,把對proxy 2的認證資訊再append進去即可(也就是說會有兩個Proxy-Authorization資訊。 (這個例子就部列舉了,有興趣的參考RFC3665吧~)

OK,SIP Intro 第二部到此結束!

再來會講什麼呢?
在有了一二部的基礎後,我想該是了解Registration的機制了。為何? 因為UA一定要跟server註冊,才能讓對方找的到,也才會有便利性、移動性、位置定位的功能。因為Registration跟目前所講的不十分相關,架構也不太相同,但卻需要一點基礎,所以可以跳脫出來講這部分。

<本來想在第二部講的...但不想在第二部拖太長...所以最後還是狠下心,把Registration拉出來,另開一部做說明>

Part 3 end.

1 則留言:

路人甲 提到...

太好了 十分感謝你精闢的講解
可惜沒辦法邊看你文字說明 邊看上面的message
如果可以就更棒了
再次感謝

搜尋此網誌