Thứ Tư, 27 tháng 8, 2014

[JAIN SIP] Chương 16 : Sử dụng UPDATE request thực hiện WEBCAM

Chương 16 : Sử dụng UPDATE request thực hiện WEBCAM
Trong phần này, chúng ta sử dụng UPDATE để thực hiện tính năng webcam trong khi 2 peer đang thực hiện voice chat.
I.  Tìm hiểu UPDATE  request:
Nếu trước đó bạn đã gởi 1 INVITE request dính kèm SDP yêu cầu thực hiện tính năng voice. Sau đó, bạn muốn cập nhật lại SDP này để thực hiện thêm tính năng video.  Để làm điều này, bạn thực hiện như sau :  bạn sử dụng DIALOG (được tạo ra từ INVITE trước đó)  tạo ra 1 UPDATE request và dính kèm SDP mới được cập nhật.
“Tính năng của UPDATE đuuợc sử dụng để chỉnh sửa hay bổ sung các thông số của 1 INVITE  đang tồn tại trước đó”.
Ví dụ : -  SDP trước đó có thông số kỹ thuật về audio và video như sau:
m=audio 42000 RTP/AVP 0
m=video 0 RTP/AVP 0
(port dành cho video là 0, tức là tính năng video không thực hiện)
- Sử dụng UPDATE để thay đổi port dành cho video, để thực hiện tính năng video, nên SDP mới sẽ được thay đổi như sau:
m=audio 42000 RTP/AVP 0
m=video 52000 RTP/AVP 0
II. Cách hoạt động VIDEO trong HelloPhone :
UPDATE request chỉ được sử dụng khi tồn tại 1 DIALOG. Do đó, bắt buộc 2 peer phải thực hiện voice chat trước tiên, rồi mới sử dụng UPDATE để cập nhật video port và video format trong SDPOffer và SDPAnswer.
Trong giao diện của HelloPhone, chúng ta đặt thêm 1 button VIDEO để thực hiện tính năng video chat khi người dùng bấm vào button này, tính năng VIDEO được xử lý như sau  :
* Trường hợp 1 : chưa tồn tại ACK request
- Nếu chưa tồn tại ACK request, nghĩa là chưa tồn tại DIALOG, tính năng VIDEO không được thực hiện.
* Trường hợp 2 : đã khởi tạo RTP Session
- Nếu cả hai UA đã khởi tạo thành công RTP Session trước đó, thì chỉ cần bắt đầu và gởi video stream khi người dùng bấm vào button VIDEO.
* Trường hợp 3 : khác hai trường hợp trên   
- 1 UPDATE request được tạo ra để cập nhật lại video port và video format trong SDPOffer nếu là UAC, ngược lại là SDPAnswer nếu là UAS. Thông tin này được dính kèm trong request dưới dạng SDP.
- UA gởi UPDATE mà không cần thông qua proxy server.
- UA nhận được UPDATE sẽ hiển thị hộp thoại thông báo "UA want to chat webcam! "
+ Nếu đồng ý, UA tạo ra 200 OK response và dính kèm SDP chứa thông tin video port và video format của chính mình và đồng thời khởi tạo RTP Session.
Lưu ý: chỉ khởi tạo RTP Session để nhận được video stream từ phía người gởi mà thôi.
+ Nếu không đồng ý, UA tạo ra "406 Not Acceptable" response.
- Nếu UA nhận được 200 OK response, sẽ khởi tạo RTP Session, đồng thời bắt đầu gởi video stream từ webcam đến người nhận. Ngược lại, UA nhận được "406 Not Acceptable" response.
- Người nhận cũng có thể gởi video stream bằng cách bấm vào button VIDEO. Lúc này, HelloPhone gởi đi video stream mà không phải khởi tạo RTP Session vì đã được thực hiện trước đó rồi.
III. Giao diện dành cho HelloPhone – class HelloPhoneListener :
Chúng ta bổ sung button VIDEO để thực hiện tính năng video chat, còn button YES trước đó được đổi tên thành VOICE để phân biệt với tính năng video, code trước đó dành cho button VOICE vẫn giữ nguyên.
- Khi người dùng bấm vào button VIDEO thì gọi đến phương thức sendUpdate() của class HelloPhoneListener:
-  Để chuẩn bị cho tính năng video : chúng ta bổ sung như sau :
+ Tạo class VideoTool : tương tự như VoiceTool, nó tạo ra sự trao đổi video giữ 2 peer thông qua mạng. Chỉ khác là VideoTool, sử dụng thêm 1 Frame để hiển thị video trên đó.  
+ Cập nhật class SDPInfo : bổ sung thêm 2 biến videoPort và videoFormat.
+ Cập nhật class SdpTool : bổ sung  thêm đối tượng MediaDescription để miêu tả video. (những phần in đậm là những đoạn code được bổ sung vào SdpTool)
III.  Bổ sung code trong class HelloPhoneListener:
- Tạo 2 đối tượng của VideoTool class làm biến toàn cục:
- Sử dụng biến isInitVideo kiểu boolean, cho biết UA đã khởi tạo RTP Session hay chưa. Nếu chưa, isInitVideo = false, ngược lại isInitVideo = true
- Trong hàm dựng HelloPhoneListener(…), chúng ta khởi tạo 2 đối tượng Video
* Quá trình bắt đầu thực hiện video chat giữa 2 peer :
- Bắt đầu từ UA  bấm vào button VIDEO. Hành động này được thực hiện trong phương thức sendUpdate() :
- UA khác sẽ nhận và xử lý UPDATE request trong phương thức processRequest() :
- UA đã gởi UPDATE sẽ nhận được response phản hồi và xử lý chúng trong phương thức processResponse() :
IV: Chạy chương trình :
Tương tự như chương 15.
Lưu ý : JMF chưa hổ trợ tính năng video cho win 64bit, nhưng mình đang xài win7 64bit, nên đoạn code về video của phần này chưa thật chạy tốt trên máy mình. Mình chỉ muốn bổ sung phần video bởi yêu cầu của nhiều bạn. Nếu bị lỗi, các bạn cho mình biết để mình cập nhật lại code.  Chúc các bạn vui khỏe và hạnh phúc.
Download code tại :
http://www.mediafire.com/download/4y9j2g22bji2kcj/HelloPhone_JAINSIP.rar

Thứ Tư, 20 tháng 8, 2014

[JAIN SIP] Chương 15 : Sử dụng MESSAGE request

Chương 15 : Sử dụng MESSAGE request
Chúng phần này chúng ta sẽ bổ sung vào HelloPhone 1 Intant Messaging đơn giản có tính năng chat bằng ký tự giữa 2 peer bởi sử dụng MESSAGE request.
Do đó, chúng ta bổ sung vào HelloPhoneGUI 1 số thành phần giao diện để dễ dàng thực hiện chat giữa 2 peer :
I. Class HelloPhoneGUI :
Bổ sung các thành phần vào class HelloPhoneGUI như sau:
- txtReceivedMessages: là textArea,  hiển thị  nội dung của các MESSAGE request gởi đi và nhận được.
- txtSendMessage:  là textArea, soạn thảo nội dung 1 MESSAGE request.
- btnSend : là button, nhiệm vụ gởi MESSAGE request.
- Phương thức getSendMessage() : lấy nội dung của txtSendMessage do người dùng nhập vào.
- Phương thức appendReceivedMessages() : hiện thị ra màn hình nội dung của 1 MESSAGE khi gởi đi hoặc nhận được.
- Phương thức btnSendMessageActionPerformed(): thực hiện hành động gởi MESSAGE request, bằng cách gọi phương thức sendMessage() của class HelloPhoneListerner.
II. class HelloPhoneListener:
Bổ sung code trong class HelloPhoneListener:
- Phương thức SendMessage() : tạo ra MESSAGE request có content là nội dung được lấy từ txtSendMessage. Phương thức SendMessage() này được gọi khi người dùng nhấn vào button Send. Tạo MESSAGE request tương tự như INVITE request, bạn lưu ý những phần in đậm trong table bên dưới :
- Phương thức processRequest() : bổ sung thêm trường hợp xử lý Khi UAS nhận được  MESSAGE request, xử lý như sau:
+ Tạo “200 OK” response và gởi phản hồi đến UAC.
+ Lấy nội dung của MESSAGE request và hiển thị ra màn hình.
- Phương thức processResponse() : bổ sung thêm trường hợp xử lý khi UAC nhận được response phản hồi dành cho MESSAGE request trong trường hợp response có status code là 404, thể hiện MESSAGE đã gởi đi không thành công thì hiển thị ra màn hình, nội dung “Previous message not sent”
III. class SipProxyListener :
Proxy xử lý MESSAGE request giống như INVITE request nên chúng ta chỉ thay thế :
-  if (request.getMethod().equals(Request.INVITE)
thành
-  if (request.getMethod().equals(Request.INVITE)
                          || request.getMethod().equals(Request.MESSAGE))  
IV. Chạy chương trình :
Cũng tương tự như phần trước:
- Đầu tiên, class SipProxyGUI phải chạy trước và bấm button ON để khởi động proxy.
- Kế tiếp lần lượt chạy các UA bởi class HelloPhoneGUI.
Lưu ý : bạn có thể chạy nhiều giao diện UA nhưng chỉ chạy duy nhất 1 proxy.  

Download code :
http://www.mediafire.com/download/kxaq7h4qk7mhd32/HelloPhone_JAINSIP.rar

[JAIN SIP] Chương 14 : HelloPhone kết hợp với Proxy

Chương 14 : HelloPhone kết hợp với Proxy
Trong phần này, chúng ta sẽ xây dựng 1 Sip proxy server đơn giản , proxy này có các tính năng sau:
- Bao gồm Sip registrar và location service.
- Registrar khá đơn giản, nhiệm vụ nhận REGISTER request để đăng ký và thông tin được cập nhật vào location service.
- Chỉ thực hiện tính năng trao đổi riêng lẽ giữa hai peer.
Chúng ta bổ sung proxy vào HelloPhone project, thực hiện như sau:
-  Tạo class SipProxyGUI : giao diện đồ họa của Proxy.
- Tạo class SipProxyListener : code JAIN SIP thực hiện hoạt động của SIP Proxy.  
I. Cách hoạt động của Proxy:
Các User Agent(UA)  không trao đổi trực tiếp như các ví dụ trước đó mà phải thông qua proxy.
* UA đăng ký thông tin :
UA gởi REGISTER request đến proxy để đăng ký thông tin, proxy thực hiện như sau:
- Tạo 1 ServerTransaction mới dành REGISTER.
- Lấy AddressOfRecord (AOR) từ To header.
- Lấy ContactAddress từ Contact header.
- Xóa thông tin đăng ký trước đó trong Location service.
- Nếu REGISTER có Expires header, proxy sẽ kiểm tra REGISTER đã hết hạn chưa.
+ Nếu đã hết hạn, tạo ra "408 Request  Timeout" response gởi đến UA.
+ Chưa hết hạn,  lưu AOR và ContactAddress vào Location Service và trả về "200 OK" response đến UA.
Lưu ý:  Nếu UA không đăng ký thông tin thì không thể thực hiện cuộc gọi với UA khác.
* UAC thực hiện cuộc gọi :
UAC muốn thực hiện cuộc gọi với UAS : UAC gởi INVITE request đến proxy, proxy thực hiện như sau :
- Kiểm tra đó là INVITE hay reINVITE, dựa trên phương thức getServerTransaction() của RequestEvent trả về 1 ServerTransaction có đang tồn tại không:
+ Nếu ServerTransaction là null, đó là INVITE ,tạo ra 1 ServerTransaction mới dành cho INVITE này.
+ Nếu ServerTransaction khác null, đó là reINVITE.
- Sao chép request để tạo 1 bản sao gọi là copyRequest.
- Xóa Route header trong copyRequest.
- Thiết lập giá trị mới cho Request-URI trong copyRequest. HelloPhone của chúng ta chỉ có 1 proxy, nên đích đến kế tiếp là UAS, từ To header của copyRequest lấy ra AOR, từ AOR lấy ra ContactAddress của UAS trong location service. Nếu UAS chưa đăng ký với proxy - nghĩa là, proxy không biết được ContactAddress của UAS - proxy tạo ra "404 not found" response gởi đến UAC, với ý nghĩa "proxy không biết contactAddress của UAS"
- Giảm giá trị của MaxForwards header đi 1.
- Thêm địa chỉ Proxy vào Via header.
- Tạo ra client transaction mới để gởi copyRequest đến UAS.
- Tạo ra 1 response context mới, lưu lại các thông tin sau:
+ ServerTransaction dành cho INVITE.
+ ClientTransaction dành cho copyINVITE.
+ Lưu INVITE và copyINVITE.

* UAC hủy cuộc gọi :
UAC muốn hủy cuộc gọi, sẽ gởi CANCEL request đến proxy, proxy thực hiện như sau :
- CANCEL request dùng để kết thúc 1 INVITE request  khi chưa tồn tại ACK request, nên CANCEL và INVITE đều cùng 1 dialog. Do đó, ServerTransaction được khởi tạo bởi INVITE cũng là ServerTransaction nhận CANCEL.
- Lấy ra INVITE request đầu vào tương ứng, được lưu trong ProxyCore, tạo "487 Terminated” response để kết thúc INVITE gởi đến UAC.
- Tạo "200 OK" response để kết thúc CANCEL gởi đến UAC.
- Tạo CANCEL request mới gởi đến UAS.
* UAS từ chối cuộc gọi :
- UAS từ chối cuộc gọi, gởi "486 BUSY" response đến proxy, ngay lập tức proxy sẽ chuyển tiếp response này đến UAC.  
II. Giao diện của Proxy – SipProxyGUI class:
- Button ON : khởi động Proxy.
- Button OFF : ngưng hoạt động Proxy.
- Domain : là tên miền ( bạn cứ đặt theo bạn thích), nhưng các UA phải đăng ký đúng tên miền này (ví dụ, tên miền là niitag.com thì các UA phải là xxx@niitag.com).
- Tracer : theo dõi các giao dịch sip message giữa UAC với proxy và giữa proxy với UAS. Nếu là giao dịch giữa UAC với proxy, sip message hiển thị ở Server Transaction. Ngược lại là giao dịch giữa proxy với UAS, sip message  hiển thị ở Client Transaction.
- Location Service : hiển thị địa chỉ AddressOfRecord và ContactAddress của mỗi UA đã đăng ký với proxy.
- Proxy Core : hiển thị các Response Context được lưu giữ trên proxy.
a. Các biến của SipProxyGUI class :
- cmdON : button dùng để khởi động proxy.
- cmdOFF : button dùng để ngưng hoạt động proxy.
- lblInit : label hiển thị địa chỉ IP và port của proxy khi proxy khởi động thành công.
- txtClientTransaction : TextArea hiển thị nội dung các sip message khi có sự giao dịch giữa proxy với UAS.
- txtDomain: TextField hiển thị tên domain của proxy.
- tableLocationService : Table hiển thị địa chỉ AddressOfRecord và ContactAddress của mỗi UA.
- txtProxyPort : TextField hiển thị port của proxy.
- txtServerTransaction : TextArea hiển thị nội dung các sip message khi có sự giao dịch giữa UAC với proxy.
- tableProxyCore : Table hiển thị các response context  đang tồn tại.
- proxyListener : SipProxyListener, lấy nội dung giao dịch của SIP.
b. Các phương thức của SipProxyGUI class :
- Phương thức cmbONActionPerformed() : khi button ON được nhấn.
- Phương thức cmbOFFActionPerformed() : khi button OFF được nhấn.
- Phương thức getProxyPort() : lấy giá trị Port của proxy do user nhập vào.
- Phương thức setInit() : thiết lập giá trị cho label Init.
- Phương thức appendClientTransaction() : hiển thị nội dung các sip message được thực hiện khi có sự giao dịch giữa proxy với UAS.
- Phương thức appendServerTransaction() : hiển thị nội dung các sip message được thực hiện khi có sự giao dịch giữa UAC với proxy.
- Phương thức getDomain() : lấy giá trị Domain do người dùng đặt tên.
- Phương thức updateLocationService() : cập nhật sự hiển thị AOR và ContactAddress của 1 UA trong table Location Service.
- Phương thức removeLocationService() : xóa bỏ sự hiển thị AOR và ContactAddress của 1 UA trong table Loacation Service.
- Phương thức updateProxyCore() : cập nhật sự hiển thị 1 response context trong table ProxyCore.
- Phương thức removeResponseContext() : xóa bỏ sự hiển thị 1 response context trong table ProxyCore
III. SipProxyListener class :
Đối với UAC, proxy là UAS. Còn với UAS, proxy là UAC. Do đó, proxy cũng chỉ là 1 UA, nên SipProxyListener cũng tương tự như các SipListener khác, cũng phải kế thừa SipListener.
- Tạo các biến khởi động SIP:
- Đối với các chương trình thực tế,  Location Sevice và Proxy Core sử dụng hệ quản trị cơ sở dữ liệu để quản lý nó. Để các bạn dễ hiểu và đơn giản hóa, chúng tôi sử dụng đối tượng của HashMap để quản lý Location Service và ArrayList là ProxyCore.
+ Mỗi phần tử của HashMap locationservice : có key là AOR và dữ liệu là ContactAddress :
+ Mỗi phần tử của ArrayList proxyCore chứa đối tượng ResponseContext :
- Mỗi response context của proxy chứa các thông tin : Client Transaction , Server Transaction, Request đầu vào và Request gởi đi. Do đó, tạo class ResponseContext như sau:
- Hàm dựng SipProxyListener() : khởi động proxy khi button ON được nhấn
Lưu ý : Proxy không thể tự động tạo Dialog như UAC và UAS. Mặc định JAIN SIP tự động tạo Dialog khi UAC gởi INVITE, để tắt tính năng này ở proxy, đặt giá trị "OFF" cho thuộc tính AUTOMATIC_DIALOG_SUPPORT.
- Phương thức setOFF() : ngưng hoạt động proxy khi button OFF được nhấn
- Phương thức processRequest() : các request gởi đến Proxy do phương thức này bắt và xử lý.
- Phương thức processResponse() : các response gởi đến Proxy do phương thức này bắt và xử lý.
1. Xử lý các tình huống khi proxy nhận request và response:
a. UAC gởi REGISTER request đến proxy để đăng ký thông tin:
Phương thức  processRequest() bắt và xử lý các request khi gởi đến proxy. Do đó, chúng ta sẽ viết code xử lý khi proxy nhận được REGISTER trong phương thức này. Chúng ta thực hiện từng bước như sau:
+ Lấy địa chỉ AddressOfRecord từ ToHeader của REGISTER
+ Lấy địa chỉ ContactAddress từ ContactHeader của REGISTER
+ Xóa thông tin đăng ký trước đó trong Location Service dựa trên AOR
+ Kiểm tra REGISTER đã hết hạn chưa dựa trên ExpiresHeader .
i. Nếu đã hết hạn , tạo ra "408 Timeout" response trả về UA.
ii. Nếu chưa hết hạn, lưu thông tin vào location service và tạo “200 OK” response gởi đến UA để thể hiện sự đăng ký thành công.

b. UAC gởi INVITE request đến proxy :
Chúng ta sẽ viết code xử lý khi proxy nhận được INVITE trong phương thức processRequest(). Chúng ta thực hiện từng bước như sau:
- Kiểm tra đó là INVITE hay reINVITE, dựa trên phương thức getServerTransaction() của RequestEvent trả về 1 ServerTransaction có đang tồn tại không:
+ Nếu ServerTransaction là null, đó là INVITE ,tạo ra 1 ServerTransaction mới dành cho INVITE này.
+ Nếu ServerTransaction khác null, đó là reINVITE.
- Sao chép request để tạo 1 bản sao gọi là copyRequest.
- Xóa Route header trong copyRequest.
- Thiết lập giá trị mới cho Request-URI trong copyRequest. HelloPhone của chúng ta chỉ có 1 proxy, nên đích đến kế tiếp là UAS, từ To header của copyRequest lấy ra AOR, dựa vào AOR lấy ra ContactAddress của UAS trong location service. Nếu UAS chưa đăng ký với proxy - nghĩa là, proxy không biết được ContactAddress của UAS - proxy tạo ra "404 not found" response gởi đến UAC, với ý nghĩa "proxy không biết contactAddress của UAS"
- Giảm giá trị của MaxForwards header đi 1.
- Thêm địa chỉ Proxy vào Via header.
- Tạo ra client transaction mới để gởi copyRequest đến UAS.
- Tạo ra 1 response context mới, lưu lại các thông tin sau:
+ ServerTransaction dành cho INVITE.
+ ClientTransaction dành cho copyINVITE.
+ Lưu INVITE và copyINVITE.
public void processRequest(RequestEvent requestEvent) {
...
           // nếu request nhận được là INVITE
           if (request.getMethod().equals(Request.INVITE)
                               || request.getMethod().equals(Request.MESSAGE)) {

               // Kiểm tra đây là INVITE hay reINVITE
               ServerTransaction inviteServerTransaction = requestEvent.getServerTransaction();
               // nếu inviteServerTransaction là null, đó là INVITE, ngược lại là reINVITE
               if (inviteServerTransaction == null) {
                   // Nếu là INVITE tạo 1 ServerTransaction mới
                   inviteServerTransaction = sipProvider.getNewServerTransaction(request);
               }

               // sao chép Request mới nhận được
               Request copyRequest = (Request) request.clone();

               // xóa bỏ Route Header trong copyRequest
               RouteHeader receivedRouteHeader =
                                 (RouteHeader) request.getHeader(RouteHeader.NAME);
               SipURI receivedRouteHeaderSipURI =
                                  (SipURI) receivedRouteHeader.getAddress().getURI();
               String receivedRouteHeaderDomain = receivedRouteHeaderSipURI.getHost();
               if (receivedRouteHeaderDomain.equals(proxyIP)) {
                   copyRequest.removeFirst(RouteHeader.NAME);
               }

               // thiết lập giá trị mới cho Request-URI trong copyRequest
               ToHeader receivedToHeader = (ToHeader) request.getHeader("To");
               SipURI addressOfRecord = (SipURI) receivedToHeader.getAddress().getURI();
               if (addressOfRecord.getHost().equals(proxyGUI.getDomain())) {
                   URI contactAddress = (URI) locationservice.get(addressOfRecord);
                   if (contactAddress != null) {
                       copyRequest.setRequestURI(contactAddress);
                   }else {
                         // tạo "404 not found" response gởi trở lại UAC
                         Response responseNoFound = messageFactory.createResponse(404, request);                  
                         inviteServerTransaction.sendResponse(responseNoFound);
                        return;
                   }
               }

               // giảm giá trị của MaxForwards đi 1
               MaxForwardsHeader maxForwardsHeader =
                       (MaxForwardsHeader) copyRequest.getHeader(MaxForwardsHeader.NAME);
               maxForwardsHeader.decrementMaxForwards();

               // Thêm địa chỉ Proxy vào Via header
               ViaHeader viaHeader =
                      headerFactory.createViaHeader(proxyIP, proxyGUI.getProxyPort(), "udp", null);
               copyRequest.addFirst(viaHeader);

               // tạo ra clienttransaction mới để gởi copyRequest đến UAS
               ClientTransaction inviteClientTransaction =
                       sipProvider.getNewClientTransaction(copyRequest);
               inviteClientTransaction.sendRequest();
               proxyGUI.appendClientTransaction("send : " + copyRequest.toString());

               // tạo 1 response context mới chứa các thông tin
               ResponseContext context = new ResponseContext();
               context.clientTransaction = inviteClientTransaction;
               context.serverTransaction = inviteServerTransaction;
               context.requestIn = request;
               context.requestOut = copyRequest;

               // lưu response context mới này vào proxyCore
               proxyCore.add(context);

               // cập nhập sự hiển thị proxy core trong proxy GUI
               proxyGUI.updateProxyCore(context.requestIn.getMethod(),
                       context.serverTransaction.toString(), context.clientTransaction.toString());
           }
c.  UAC gởi CANCEL request đến proxy :
Chúng ta sẽ viết code xử lý khi proxy nhận được INVITE trong phương thức processRequest(). Chúng ta thực hiện từng bước như sau:
- CANCEL request dùng để kết thúc 1 INVITE request  khi chưa tồn tại ACK request, nên CANCEL và INVITE đều cùng 1 dialog. Do đó, ServerTransaction được khởi tạo bởi INVITE cũng là ServerTransaction nhận CANCEL.
- Lấy ra INVITE request đầu vào tương ứng, được lưu trong ProxyCore, tạo "487 Terminated” response để kết thúc INVITE gởi đến UAC.
- Tạo "200 OK" response để kết thúc CANCEL gởi đến UAC.
- Tạo CANCEL request mới gởi đến UAS.
d. UAS gởi response đến proxy :
Phương thức processResponse() xử lý các response được UAS gởi đến proxy. Do đó, chúng ta sẽ viết code xử lý các response trong phương thức này. Chúng ta thực hiện từng bước như sau:
+ Proxy không xử lý các response có là 487 và response  phản hồi của CANCEL request. Bởi vì, trong quá trình xử lý CANCEL request, proxy đã tự động gởi "487 terminated" đối với INVITE và "200 OK" đối với CANCEL, gởi đến UAC rồi. Nên những response này được gởi từ UAS không cần thiết phải gởi đến UAC nữa.
+ Sao chép và loại bỏ giá trị đầu tiên của Via header trong response
+ Nếu response có tồn tại  Client Transaction trong response context, thì Server Transaction của context này sẽ gởi copyResponse đến UAC.
+ Ngược lại, Response không tồn tại ClientTransaction, sử dụng SipProvider để gởi copyResponse này đến UAC.
IV. Bổ sung thêm một số thành phần trên giao diện HelloPhoneGUI :
Chúng ta bổ sung thêm các thành phần sau :
- txtName : là textFiled,  tên của 1 UA
- txtAOR : là textFiled, đó là Address of Record  - địa chỉ đăng ký với Proxy, bắt buộc:
+ Không được trùng với UA khác
+ Phải có @.
+ Tên domain trùng với proxy.
Trong ví dụ này, chúng tôi đặt Domain của proxy là niitag.com thì AOR phải có dạng  như sau: xxx@niitag.c om
- txtProxyServer : là textField, địa chỉ proxy.
- lblStatusRegister : là label, chứa hình ảnh hiển thị tình trạng đăng ký với proxy :
+   : chưa đăng ký (chưa bấm button Connect) , hoặc đăng ký thất bại. Trạng thái này không thể thực hiện cuộc gọi.
+   : đăng ký thành công, sẵn sàng thực hiện cuộc gọi.
- Phương thức getAddressProxy(), getName(), getAOR() là các phương thức lấy giá trị của txtProxyServer, txtName, txtAOR được dùng trong HelloPhoneListener class.
- Phương thức statusRegister() : chứa tham số kiểu boolean:
+ Nếu là true thể hiện đăng ký đã thành công, thiết lập hình ảnh đăng ký thành công cho lblStatusRegister và cho phép các button thực hiện cuộc gọi.
+ Nếu là false thể hiện đăng ký không thành công, thiết lập hình ảnh đăng ký không thành công cho lblStatusRegister.   
V. Bổ sung thêm code trong HelloPhoneListener class :
a. Bổ sung 1 :
 Bắt buộc UA phải gởi REGISTER đến proxy để đăng ký. Do đó, chúng ta tạo REGISTER này chung với hành động bấm vào button Connect để khởi động SIP, tức là gọi phương thức tạo REGISTER trong hàm dựng của HelloPhoneListener class. Bổ sung code như sau:
Tương tự như tạo REGISTER trong các ví dụ trước đây, nhưng chỉ khác là giá trị của Request-URI phải là địa chỉ proxy.
b. Bổ sung 2 :
Trong phương thức processResponse(), chúng ta xử lý trường hợp khi UA nhận được các response trả về từ proxy đối với REGISTER:
c. Bổ sung 3 :
INVITE request được thay thế địa chỉ Request-URI là địa chỉ proxy và bổ sung Route header vào INVITE. Hành động này, được thực hiện trong phương thức sendRequest() :
VI. Chạy chương trình :
- Đầu tiên, class SipProxyGUI phải chạy trước và bấm button ON để khởi động proxy.
- Kế tiếp lần lượt chạy các UA bởi class HelloPhoneGUI.
Lưu ý : bạn có thể chạy nhiều UA nhưng chỉ chạy duy nhất 1 proxy.  
Dưới đây, là 1 số hình ảnh về proxy khi thực hiện giao dịch giữa 2 UA.
Hình ảnh 2 UA :
Code đầy đủ:
http://www.mediafire.com/download/oocdimi4c834rh2/HelloPhone_JAINSIP.rar