This is an old revision of the document!


Laborator 09. Gestiunea Apelurilor Telefonice folosind SIP & VoIP

Configurare

În cadrul acestui laborator este necesar un dispozitiv mobil fizic sau un emulator cu acces la microfonul sistemului de operare. De asemenea, pe dispozitivul mobil trebuie să fie instalat Google Play Services întrucât este necesară descărcarea și instalarea unei aplicații Android (CSipSimple) pentru realizarea de apeluri telefonice folosind SIP & VoIP.

Se va utiliza, la alegere, un furnizor (gratuit) de servicii SIP, accesibil ulterior înregistrării (creării unui cont):

  • GetOnSIP - are avantajul implementării serviciului WebRTC (proiect open-source - susținut de consorțiul World Wide Web (W3C) - ce pune la dispoziție un API pentru realizarea de apeluri de voce / multimedia, conversații și partajare de fișiere printr-o conexiune punct la punct între dispozitive mobile (IoT) și aplicații de tip navigator), dar dezavantajul limitării numărului de accesări într-o oră;

GetOnSIP

Pentru obținerea unui cont SIP gratuit, folosind serviciul GetOnSIP, este necesar să se furnizeze o adresă de poștă electronică, urmată de apăsarea butonului Submit.

La adresa de poștă furnizată va fi transmisă o legătură Internet, la accesarea căreia utilizatorul va fi redirectat către o pagină în care va putea finaliza procesul de înscriere, prin precizarea următoarelor informații:

  • un nume asociat contului respectiv;
  • denumirea contului (sufixată cu @getonsip.com);
  • o parolă introdusă de două ori spre a fi verificată.

În urma realizării cu succes a contului SIP gratuit, acesta va putea fi accesat, inclusiv prin interfața web, prin furnizarea denumirii contului și a parolei specificate anterior:

Pentru a putea utiliza serviciile SIP și VoIP prin interfața web, este necesar ca aplicația navigator să obțină acces la resursele hardware ale sistemului pe care îl utilizează:

  • camera web;
  • microfonul.

Secțiunea View Profile este foarte importantă, întrucât acolo sunt furnizate informațiile necesare pentru utilizarea contului SIP din contextul altor dispozitive:

  • adresa SIP;
  • numele de utilizator;
  • domeniul;
  • parola SIP → atenție!!! această valoare este autogenerată și este diferită de parola furnizată la înregistrare;
  • numele de utilizator furnizat pentru autentificare → atenție!!! valoarea acestui identificator este diferită de numele de utilizator furnizat la înregistrare (de regulă, este prefixat cu șirul de caractere getonsip_);
  • proxy-ul folosit pentru conexiuni din afara domeniului.

PBXes

Pentru obținerea unui cont SIP gratuit, folosind serviciul PBXes, este necesar se accesează legătura Internet Create your account.

Este necesar să se furnizeze mai multe informații, legate de:

  • limba folosită pentru afișarea interfeței grafice în cadrul paginii Internet;
  • contul propriu-zis (denumire - limitată la maximum 15 caractere!!! și parolă - nu sunt permise caracterele speciale);
  • date personale (nume / prenume, adresă - inclusiv oraș și cod poștal -, zona de timp, data nașterii, numărul de telefon, adresa de poștă electronică);
  • versiunea aplicației;
  • permisiunea de a permite unor moderatori independenți de a inspecta datele de configurare și fișierele de jurnalizare spre a putea oferi suport.

Ulterior înregistrării cu succes, se furnizează adresa care poate fi folosită pentru accesarea serviciilor SIP și VoIP (de regulă, numele de utilizator furnizat în cadrul înregistrării, la care se adaugă @pbxes.org).

Pentru utilizarea propriu-zisă a serviciului, este necesară definirea unei extensii (Extensions → Add Extension) de tipul SIP:

Este necesar să se furnizeze informații legate de:

  • extensia propriu-zisă (număr de extensie și denumire);
  • date pentru apelul prin Internet (URL, text, GPS - latitudine și longitudine și, opțional, o imagine);
  • parola specifică (va fi utilizată în locul parolei furnizată pentru cont).

Utilizare pe Dispozitivul Mobil

Adrese SIP pentru Testare

Un contact de tip SIP poate fi realizat folosind aplicația nativă Contacts, folosind, în mod obișnuit, opțiunea Create a New Contact.

Adresa SIP la care se plasează apelul telefonic VoIP va fi plasată în secțiunea Internet call sau SIP (dacă nu este vizibilă, se poate specifica un alt timp de câmp specific contactului prin opțiunea Add another field).

Pot fi folosite următoarele adrese de test:

  • thetestcall@getonsip.com;
  • 904@mouselike.org;
  • 301@ideasip.com;

În agenda telefonică, contactul respectiv va fi identificat printr-o pictogramă specifică (indicând faptul că acesta este de tip SIP), fiind apelat în mod implicit folosind protocolul respectiv.

Implementarea Nativă din Sistemul de Operare Android

Începând cu versiunea 2.3 (Gingerbread, API level 9), în kernelul Android a fost implementat protocolul SIP, acesta putând fi accesat în aplicația Phone, secțiunea Settings → Internet Calling → Accounts (sau Settings → Calls → Calling Accounts → SIP Accounts) și utilizat exclusiv pentru apeluri de voce. Totuși, nu sunt încă implementate toate funcționalitățile specifice (apeluri video, mesagerie instantanee).

Se accesează aplicația Phone și din meniul acesteia intrarea Settings.

Se parcurg următoarele ecrane Calls → Calling Accounts → SIP Accounts:

Pentru a se putea recepționa apeluri VoIP, este necesar să se selecteze opțiunea Receive incoming calls. O astfel de abordare poate conduce însă la un consum de energie mult mai rapid și la epuizarea bateriei.

Definirea unui cont de tip SIP se realizează prin accesarea opțiunii Add Account:

Informațiile care trebuie specificate sunt:

  • numele de utilizator;
  • parola;
  • adresa serverului care oferă serviciile SIP;
  • contul folosit pentru autentificare;
  • adresa proxy pentru accesări din afara domeniului;
  • portul pe care se realizează comunicația;
  • protocolul folosit pentru transport (TCP / UDP);
  • transmiterea de mesaje de tip keep-alive.

În situația în care contul SIP a fost creat, acesta va fi vizualizat în secțiunea SIP Accounts împreună cu starea sa (Receiving calls, Not receiving calls).

De asemenea, în secțiunea Calling accounts există posibilitatea de a preciza ce cont este folosit în mod implicit pentru realizarea de apeluri telefonice. Se recomandă selectarea opțiunii Ask first astfel încât pentru fiecare contact să se poată preciza mecanismul utilizat pentru plasarea sa.

Prin accesarea unui contact de tip SIP din agenda telefonică, se va utiliza contul specific pentru realizarea apelului telefonic.

Aplicația CSIPSimple

În cazul în care în aplicația Phone, opțiunea Internet Calling / SIP Accounts nu este disponibilă, poate fi utilizată aplicația CSipSimple din Play Store. Ulterior descărcării, utilizatorului îi sunt solicitate acordarea de permisiuni pentru ca această aplicație să poată fi instalată.

Aplicația CSipSimple pune la dispoziția utilizatorilor mai multe șabloane pentru definirea unui cont SIP. Se recomandă să se utilizeze Generic Wizards → Advanced întrucât permite definirea tuturor parametrilor necesari pentru realizarea conexiunii la serverului care găzduiește un astfel de serviciu:

Pentru contul de tip getonsip.com se precizează următorii parametri:

  • denumirea contului: informaticamobila2016@getonsip.com
  • adresa serverului care pune la dispoziție serviciile SIP: getonsip.com;
  • numele de utilizator: informaticamobila2016
  • identificatorul folosit pentru autentificare, de regulă același cu numele de utilizator, prefixat de șirul de caractere getonsip_: getonsip_informaticamobila2016;
  • parola (din secțiunea View Profile, nu cea folosită la definirea contului);
În situația în care se folosește emulatorul Genymotion, pentru a se evita transcrierea parolelor foarte lungi, se poate realiza operația de tip Copy-Paste din mașina fizică în mașina virtuală folosind operația de tip apăsare cu o durată de timp mai mare (eng. long press).
  • protocoul folosit (implicit UDP);
  • proxy-ul folosit pentru conexiuni din afara domeniului: sip.onsip.com

În secțiunea Accounts, contul SIP poate fi în orice moment activat sau dezactivat. Starea acestuia este vizibilă sub denumirea sa, culoarea folosită fiind sugestivă pentru rezultatul operației de înregistrare:

  • verde: operația a fost realizată cu succes;
  • roșu: s-a produs o eroare; eroarea de tipul Registration timeout denotă faptul că în NAT/SIP nu se translatează corect adresele interne
    • rețeaua Digi.Mobil/4G funcționează corespunzător;
    • în facultate, rețelele wireless ACS-UPB-OPEN, eduroam, Java-ED117 nu creează probleme; în schimb, rețeaua change blochează anumite porturi ceea ce împiedică comunicația cu serverul SIP.

Contactele de tip SIP sunt vizibile în meniul aplicației CSipSimple astfel încât să poată fi plasate apeluri de tip SIP / VoIP:

Pentru contul de tip PBXes există un șablon predefinit:

Se recomandă însă să se utilizeze tot un șablon general, astfel încât să poată fi controlați foarte bine toți parametrii:

  • denumirea contului: mobilecomputing
  • adresa serverului care pune la dispoziție serviciile SIP: pbxes.org;
  • numele de utilizator (se utilizează extensia): mobilecomputing-100
  • identificatorul folosit pentru autentificare, de regulă același cu numele de utilizator;
  • parola (se utilizează extensia);
  • protocoul folosit (implicit UDP);
  • proxy-ul folosit pentru conexiuni din afara domeniului: pbxes.org

Android NGN stack

Android are o stivă SIP nativă, dar nu are încă toate funcționalitățile implementate (instant messenging, video). Totuși, pentru voce, SIP este după Android 2.3 parte a sistemului, sub numele de “internet calling” în Phone/Settings.

Pentru acest laborator, se va folosi NGN (New Generation Networking) SIP stack, cu documantația pentru API de nivel înalt: https://imsdroid.googlecode.com/svn-history/r381/branches/2.0/android-ngn-stack-00.pdf

Această stivă se utilizează în modul următor: se inițializează stiva NgnEngine, și un serviciu SIP INgnSipService. Se continuă cu operația de signin(SIP Registration), apoi se pot deschide sesiuni de audio/video NgnAVSession, sau instant messenging NgnMessagingSession.

Interacțiunile sunt fie imperative:

  • INgnSipService.register()
  • NgnAVSession.makeCall()
  • NgnMessagingSession.sendTextMessage()

fie reactive, prin intermediul unor receivere care filtrează următoarele evenimente:

  • NgnRegistrationEventArgs.ACTION_REGISTRATION_EVENT primește răspunsurile de la signin (refuz, timeout, succes)
  • NgnInviteEventArgs.ACTION_INVITE_EVENT primește evenimentele legate de apel (sună, apel stabilit, apel terminat)
  • NgnMessagingEventArgs.ACTION_MESSAGING_EVENT - tratează sosirea unui mesaj IM

Activitate de Laborator

Se dorește implementarea unei aplicații Android care folosește stiva Android NGN pentru a realiza apeluri de voce precum și comunicație prin mesagerie instantanee folosind SIP.

Proiectul conține două activități:

  • VoiceCallActivity, folosit pentru operații de tip înregistrare / deînregistrare, apeluri de voce și transmiterea de coduri DTMF;
  • InstantMessagingActivity, utilizat pentru transferul de mesaje instantanee.

1. În contul Github personal, să se creeze un depozit denumit 'Laborator09'. Inițial, acesta trebuie să fie gol (nu trebuie să bifați nici adăugarea unui fișier README.md, nici a fișierului .gitignore sau a a fișierului LICENSE).

2. Să se cloneze în directorul de pe discul local conținutul depozitului la distanță de la https://www.github.com/eim2016/Laborator09.

În urma acestei operații, directorul Laborator09 va trebui să se conțină directoarele labtasks și solutions.

student@eim2016:~$ git clone https://www.github.com/eim2016/Laborator09

3. Să se încarce conținutul descărcat în cadrul depozitului 'Laborator09' de pe contul Github personal.

student@eim2016:~$ cd Laborator09
student@eim2016:~/Laborator09$ git remote add Laborator09_perfectstudent https://github.com/perfectstudent/Laborator09
student@eim2016:~/Laborator09$ git push Laborator09_perfectstudent master

4. Să se importe în mediul integrat de dezvoltare Eclipse proiectul android-ngn-stack, care va fi utilizat ca bibliotecă pentru proiectul ngnsip.

Să se importe în mediul integrat de dezvoltare Eclipse proiectul NgnSIP din directorul labtasks. Acesta trebuie să refere biblioteca android-ngn-stack: Project Properties → Android → Library → Add/.

4. În interfața Constants.java, să se actualizeaze informațiile specifice contului SIP.

<spoiler|Indicații de Rezolvare> Este necesar să se precizeze:

  • numele de utilizator: atributul USERNAME;
  • identificatorul utilizatorului: atributul IDENTITY_IMPI;
  • parola: atributul IDENTITY_PASSWORD.

Aceste informații pot fi preluate de la furnizorul de servicii SIP.

Constants.java
public interface Constants {
  final public static String USERNAME = "...";
  final public static String IDENTITY_IMPI = "...";
  final public static String IDENTITY_PASSWORD = "...";
  // ...
}

</spoiler>

Este necesar ca proiectul android-ngn-stack să fie referit ca bibliotecă și compilat ca atare, întrucât dacă se construiește o arhivă .jar pe baza sa, aceasta nu va include și bibliotecile partajate (din subdirectorul libs, având extensia .so), astfel încât la încercarea de inițializare a stivei NGN vor fi generate excepții de tipul java.lang.UnsatisfiedLinkError datorate faptului că nu pot fi încărcate bibliotecile partajate folosite.

5. În activitatea VoiceCallActivity, să se implementeze ascultători pentru butoanele Register și Unregister:

  • Register
    1. va configura motorul NGN, ai cărui parametri sunt plasați sub forma unor valori asociate unor chei (metoda configureStack());
    2. va porni motorul NGN (metoda startNgnEngine()) și va înregistra activitatea principală la serviciul SIP (metoda registerSipService())
  • Unregister va deînregistra serviciul SIP (metoda unregisterSipService()).

În ambele cazuri, în situația în care se produce o eroare, aceasta va fi jurnalizată folosind Logcat.

<spoiler|Indicații de Rezolvare>

private class RegisterButtonClickListener implements View.OnClickListener {
  @Override
  public void onClick(View view) {
    configureStack();
    if (!startNgnEngine()) {
      return;
    }
    registerSipService();
  }
}
private class UnregisterButtonClickListener implements View.OnClickListener {
  @Override
  public void onClick(View view) {
    unregisterSipService();
  }
}

</spoiler>

6. Să se activeze / dezactiveze ascultătorul pentru mesaje cu difuzare RegistrationBroadcastReceiver, folosind filtrul NgnRegistrationEventArgs.ACTION_REGISTRATION_EVENT. Acesta prelucrează mesajele legate de operațiile de înregistrare / deînregistrare, pe care le jurnalizează în Logcat, utilizatorul putând să le vizualizeze și în interfața grafică prin intermediul unor ferestre de tip Toast precum și într-un câmp text care conține starea curentă (Registered, Registration in progress, Unregistration in progress, Unregistered, Failed to register, Failed to unregster).

<spoiler|Indicații de Rezolvare> Operațiile de activare / dezactivare vor fi realizate pe metodele onCreate() respectiv onDestroy().

public void enableRegistrationBroadcastReceiver() {
  registrationBroadcastReceiver = new RegistrationBroadcastReceiver(registrationStatusTextView);
  registrationIntentFilter = new IntentFilter();
  registrationIntentFilter.addAction(NgnRegistrationEventArgs.ACTION_REGISTRATION_EVENT);
  registerReceiver(registrationBroadcastReceiver, registrationIntentFilter);
}
public void disableRegistrationBroadcastReceiver() {
  if (registrationBroadcastReceiver != null) {
    unregisterReceiver(registrationBroadcastReceiver);
    registrationBroadcastReceiver = null;
  }
}

</spoiler>

7. În aplicația Android, există un câmp text editabil în care se va preciza adresa SIP destinație, precum și două butoane care pornesc / opresc apelul.

Să se creeaze o sesiune către adresa SIP destinație:

NgnAVSession.createOutgoingSession(
  NgnEngine.getInstance().getSipService().getSipStack(),
  NgnMediaType.Audio
);

Pe baza acestei sesiuni, să se pornească apelul cu makeCall(), și să se oprească cu hangUpCall().

Să se implementeze metodele specifice, asociate claselor ascultător pentru operația de apăsare a butoanelor Make Call, respectiv Hang up Call.

<spoiler|Indicații de Rezolvare>

private class MakeCallButtonListener implements View.OnClickListener {
  @Override
  public void onClick(View view) {
    String validUri = NgnUriUtils.makeValidSipUri(SIPAddressEditText.getText().toString());
    if (validUri == null) {
      Log.e(Constants.TAG, "Invalid SIP address");
      return;
    }
    if (!ngnEngine.isStarted() || !ngnSipService.isRegistered()) {
      Log.e(Constants.TAG, "NGN Engine is not started or NGN Sip Service is not registered!");
      return;
    }
    ngnAVSession = NgnAVSession.createOutgoingSession(
      NgnEngine.getInstance().getSipService().getSipStack(),
      NgnMediaType.Audio
    );
    if (ngnAVSession.makeCall(validUri)) {
      callStatusTextView.setText(getResources().getString(R.string.calling));
      Log.d(Constants.TAG, "Call succeeded");
    } else {
      Log.d(Constants.TAG, "Call failed");
    }
  }
}
private class HangupCallButtonListener implements View.OnClickListener {
  @Override
  public void onClick(View view) {
    if (ngnAVSession != null) {
      ngnAVSession.hangUpCall();
      Log.d(Constants.TAG, "Hang Up");
    }
  }
}

</spoiler>

Sesiunea trebuie menținută ca membru al activității, deoarece este necesară și pentru alte operații.

8. Să se activeze / dezactiveze ascultătorul pentru mesaje cu difuzare CallStateBroadcastReceiver, folosind filtrul NgnInviteEventArgs.ACTION_INVITE_EVENT.

Acesta prelucrează mesajele legate de operațiile legate de apelurile de voce, pe care le jurnalizează în Logcat, utilizatorul putând să le vizualizeze și în interfața grafică prin intermediul unor ferestre de tip Toast precum și într-un câmp text care conține starea curentă (Incoming call, Call started, Call ended, Call state).

<spoiler|Indicații de Rezolvare> Operațiile de activare / dezactivare vor fi realizate pe metodele onCreate() respectiv onDestroy().

public void enableCallStateBroadcastReceiver() {
  callStateBroadcastReceiver = new CallStateBroadcastReceiver(SIPAddressEditText, callStatusTextView);
  callIntentFilter = new IntentFilter();
  callIntentFilter.addAction(NgnInviteEventArgs.ACTION_INVITE_EVENT);
  registerReceiver(callStateBroadcastReceiver, callIntentFilter);		
}
public void disableCallStateBroadcastReceiver() {
  if (callStateBroadcastReceiver != null) {
    unregisterReceiver(callStateBroadcastReceiver);
    callStateBroadcastReceiver = null;
  }
}

</spoiler>

9. Să se testeze un apel de voce către o adresă SIP de test (thetestcall@getonsip.com).

Să se analizeze conversația SIP la nivel pachet:

student@eim2016:/opt/android-sdk-linux/platform-tools$ ./adb -s 192.168.56.101:5555 shell

În consola sistemului de operare Android, se folosește utilitarul tcpdump:

root@android:/# ./tcpdump -s0 -ni eth1 -w /sdcard/DCIM/sip.pcap 'udp'
Utilitarul tcpdump se instalează în /data/bin, apoi se conferă drepturi de execuție pentru binar:
root@android:/data/bin# chmod 777 tcpdump

Se pornește apelul audio și după ce se termină mesajul, se oprește.

Programul tcpdump este terminat prin Ctrl-C.

Se obține dump-ul și se analizează folosind wireshark.

student@eim2016:/opt/android-sdk-linux/platform-tools$ ./adb -s 192.168.56.3:5555 pull /sdcard/DCIM/sip.pcap
student@eim2016:/opt/android-sdk-linux/platform-tools$ wireshark sip.pcap
  • Să se identifice operația REGISTER. Ce port se utilizează? Care este adresa serverului?
  • Să se găsească, în răspunsul de confirmare, adresele NAT prin care trece conversația, odată ce a fost acceptată cererea.
  • Să se identifice operația INVITE. Apar retransmisii?
  • Ce fel de codificare este utilizată pentru semnalul audio?
  • Ce parametri are fluxul de voce (protocol, dimensiune pachet, rata pachetelor)?
  • Ce adrese sunt folosite pentru traficul de voce și cum au fost negociate?
Pornirea monitorizării (pornirea utilitarului tcpdump) trebuie realizată anterior operației de înregistrare. Similar, oprirea monitorizării trebuie realizată ulterior operației de deînregistrare. În acest fel, pot fi surprinse toate operațiile.

10. (opțional) Pentru a trimite coduri numerice DTMF (Dual Tone Multi Frequency) se creează un buton și un câmp text editabil asociat. Transmiterea unui astfel de caracter se realizează prin intermediul metodei sendDTMF() a obiectului sesiune NgnAVSession cu valorile întregi 0-9, sau 10 pentru * și 11 pentru #. Folosind o adresa de test (thetestcall@getonsip.com sau 904@mouselike.org) să se testeze codurile și navigarea prin meniuri.

Să se implementeze metoda asociată clasei ascultător corespunzătoare operației de apăsare a butonului respectiv.

<spoiler|Indicații de Rezolvare>

private class DTMFButtonClickListener implements View.OnClickListener {
  @Override
  public void onClick(View view) {
    if (ngnAVSession != null) {
      int character = dtmfEditText.getText().toString().charAt(0);
      switch(character) {
        case '*':
          character = 10;
          break;
	case '#':
	  character = 11;
          break;
        default:
	  if (character >= '0' && character <= '9') {
            character -= '0';
          }
      }
      if (!ngnAVSession.sendDTMF(character)) {
	Log.e(Constants.TAG, "Failed to send DTMF " + character);
      } else {
	Log.d(Constants.TAG, "Succeeded to send DTMF " + character);
      }
    }			
  }
}

</spoiler>

11. Activitatea InstantMessagingActivity poate fi lansată din activitatea principală, doar ulterior operației de înregistrare. Aceasta primește ca argument, în intenția cu care este lansată în execuție, adresa SIP cu care se va desfașura sesiunea de mesagerie instantanee. Funcționalitatea este împărțită între activitate și ascultătorul pentru mesaje cu difuzare MessageBroadcastReceiver care trebuie să gestioneze evenimentele de tipul NgnMessagingEventArgs.ACTION_MESSAGING_EVENT.

<spoiler|Indicații de Rezolvare> În activitate, se creează o sesiune pentru fiecare mesaj, folosind

NgnMessagingSession instantMessagingSession = NgnMessagingSession.createOutgoingSession(
  ngnSipService.getSipStack(),
  remotePartyUri
);

Textul preluat din activitate este apoi transmis cu metoda sendTextMessage(). Nu uitați să eliberați resursele folosite de sesiune atunci când toate informațiile pe care aceasta le deține au fost preluate.

if(!instantMessagingSession.sendTextMessage(messageEditText.getText().toString())) {
  Log.e(Constants.TAG, "Failed to send message");
} else {
  String conversation = conversationTextView.getText().toString();
  conversationTextView.setText(conversation + "Me: " + messageEditText.getText().toString() + "\n");
  messageEditText.setText("");
  Log.d(Constants.TAG, "Succeeded to send message"); 
}
NgnMessagingSession.releaseSession(instantMessagingSession);

În ascultătorul de mesaje cu difuzare, se tratează doar acțiunea NgnMessagingEventArgs.ACTION_MESSAGING_EVENT, tipul evenimentului INCOMING, pentru a extrage octeții mesajului folosind metoda getPayload(). Aceștia se convertesc la șir de caractere și se afișează în fereastra care conține conversația.

if(!NgnStringUtils.equals(arguments.getContentType(), NgnContentType.T140COMMAND, true)) {
  byte[] contentBytes = arguments.getPayload();
  if(contentBytes != null && contentBytes.length > 0) {
    try {
      String content = new String(contentBytes, "UTF-8");
      String conversation = conversationTextView.getText().toString();
      conversationTextView.setText(conversation + "Others: " + content + "\n");
    } catch (UnsupportedEncodingException unsupportedEncodingException) {
      Log.i(Constants.TAG, unsupportedEncodingException.toString());
      if (Constants.DEBUG) {
	unsupportedEncodingException.printStackTrace();
      }
    }
  }
}

</spoiler>

12. Să se încarce modificările realizate în cadrul depozitului 'Laborator09' de pe contul Github personal, folosind un mesaj sugestiv.

student@eim2016:~/Laborator09$ git add *
student@eim2016:~/Laborator09$ git commit -m "implemented taks for laboratory 08"
student@eim2016:~/Laborator09$ git push Laborator09_perfectstudent master

Resurse Utile

laboratoare/laborator09.1462506429.txt.gz · Last modified: 2017/02/20 20:44 (external edit)
CC Attribution-Share Alike 4.0 International
Driven by DokuWiki Recent changes RSS feed Valid CSS Valid XHTML 1.0