1. Giriş
Interaktiv proses — bu, təkcə özü nəsə edən deyil, sizinlə «danışmağınızı» gözləyən proqramdır. O, istifadəçinin girişini qəbul edir və cavab verir.
Klassik nümunələr — Python, bash və ya PowerShell kimi interpretatorlardır. Onlar səbirlə əmrləri gözləyir, icra edir və nəticəni göstərirlər. Başqa nümunələr də var: kalkulyator kimi interaktiv utilitlər, konsol verilənlər bazaları (psql, sqlite3) və hətta vi və nano kimi mətn redaktorları. Bəzən adi skript də interaktiv ola bilər — icra zamanı sizdən parametrlər və ya cavablar istəyirsə.
Belə prosesləri Java-dan işə salmaq — parkda gəzinti deyil. Burada sadəcə «əmr ver və nəticəni al» kifayət etmir. Həqiqi dialoq qurmaq lazımdır: məlumatları vaxtında göndərmək və cavabları oxumaq — sanki canlı söhbətdəki kimi.
Təsəvvür edin ki, dostunuza messencerdə yazırsınız. Mesaj göndərdiniz, cavab gözlədiniz, sonra yenə yazdınız. Axınlarla işi düzgün qursanız, Java ilə kənar prosesin qarşılıqlı əlaqəsi təxminən belə görünür.
İkitərəfli mübadilənin təşkili
Hər bir prosesin üç «əlaqə kanalı» var: giriş (stdin), çıxış (stdout) və səhvlər (stderr). Birincisi ilə prosesə nəsə ötürə, ikincisi ilə nəticəni ala, üçüncüsü ilə isə səhv mesajlarını oxuya bilərsiniz.
Əsas odur ki, prosesin «donmasına» yol verməyəsiniz. Yalnız adi çıxışı oxuyub səhv axınını görməzdən gəlsəniz, proses sanki şikayətlərinə diqqət yetirməyinizi gözləyirmiş kimi ilişib qala bilər. Əks istiqamətdə də elədir: girişə nəsə göndərir, amma cavabları oxumursunuzsa, proses yığılan məlumatı hara versin deyə gözləyib qala bilər.
Ona görə də interaktiv proseslərlə işləyərkən ikitərəfli mübadiləni dəstəkləmək vacibdir — normal söhbətdə olduğu kimi, hər iki tərəf dinləyir və cavab verir, boşluğa danışmır.
Interaktiv mübadilə sxemi
+---------------------+
| Java proqramı |
+---------------------+
| ^
v |
stdin stdout/stderr
| ^
+---------------------+
| Xarici proses |
+---------------------+
- Java prosesin giriş axınına (stdin) yazır.
- Java prosesin stdout və stderr-indən oxuyur.
- Bunların hamısı eyni vaxtda baş verə bilər!
2. Praktika: kənar proseslə interaktiv mübadilə
Gəlin praktik nümunəyə baxaq: kənar prosesi (məsələn, Python interpretatoru və ya sadə echo-skript) işə salacağıq, ona sətirlər göndərəcəyik və cavabları oxuyacağıq.
Nümunə 1: Girişi gözləyən Python skriptinin işə salınması
Əvvəlcə sadə Python skripti yaradaq (adını echo_bot.py qoyaq):
# echo_bot.py
while True:
try:
line = input()
if line == "exit":
print("Bye!")
break
print("Echo:", line)
except EOFError:
break
Bu skript girişi gözləyir və hər sətrə "Echo: ..." ilə cavab verir. "exit" daxil etsəniz — işi bitirir.
Bu skripti Java-dan necə işə salmaq və onunla «danışmaq» olar?
1. Prosesi işə salırıq
ProcessBuilder builder = new ProcessBuilder("python", "echo_bot.py");
Process process = builder.start();
2. Ünsiyyət üçün axınları hazırlayırıq
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(process.getOutputStream()));
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
3. Dialoqu reallaşdırırıq
// Sətiri prosesə göndəririk
writer.write("Hello, process!\n");
writer.flush();
// Cavabı oxuyuruq
String response = reader.readLine();
System.out.println("Prosesin cavabı: " + response);
4. Lazım olduğu qədər təkrarlayırıq
Bunu sadə dövr kimi tərtib edək ki, istifadəçi konsola yaza bilsin, Java isə bunu prosesə göndərsin və cavabı çıxarsın.
Tam nümunə: kənar proseslə Java söhbəti
import java.io.*;
public class InteractiveProcessDemo {
public static void main(String[] args) throws IOException {
ProcessBuilder builder = new ProcessBuilder("python", "echo_bot.py");
Process process = builder.start();
// Proseslə ünsiyyət üçün axınlar
BufferedWriter toProcess = new BufferedWriter(new OutputStreamWriter(process.getOutputStream()));
BufferedReader fromProcess = new BufferedReader(new InputStreamReader(process.getInputStream()));
BufferedReader userInput = new BufferedReader(new InputStreamReader(System.in));
System.out.println("Prosesə göndərmək üçün sətirlər daxil edin (çıxış üçün exit):");
while (true) {
// İstifadəçidən sətir oxuyuruq
String line = userInput.readLine();
if (line == null) break;
// Onu prosesə göndəririk
toProcess.write(line + "\n");
toProcess.flush();
// Prosesin cavabını oxuyuruq
String response = fromProcess.readLine();
System.out.println("Prosesin cavabı: " + response);
if ("exit".equals(line)) break;
}
// Prosesi yekunlaşdırırıq
process.destroy();
}
}
Koda şərhlər:
- Üç axından istifadə edirik: istifadəçidən oxumaq üçün (System.in), prosesə yazmaq üçün (process.getOutputStream()) və cavabı oxumaq üçün (process.getInputStream()).
- Prosesə hər sətir göndərdikdən sonra dərhal readLine() vasitəsilə cavabı oxuyuruq.
- İstifadəçi "exit" daxil edərsə, proqram dövrü bitirir və prosesi məhv edir (process.destroy()).
3. Niyə eyni vaxtda oxumaq və yazmaq vacibdir?
Real tapşırıqlarda proses çoxlu məlumat çıxara və bəzən ardıcıl bir neçə dəfə giriş tələb edə bilər. Prosesin çıxışını vaxtında oxumasanız, onun daxili buferi dolub proses «asılı» qala bilər — kimsə mesajlarını oxusun deyə gözləyər. Oxşar şəkildə, giriş göndərməsəniz, proses də gözləyəcək və «donub» qala bilər.
Deadlock: hər şey ilişib qaldıqda
Deadlock — iki prosesin (və ya axın və prosesin) bir-birini gözlədiyi və heç kimin işi davam etdirə bilmədiyi vəziyyətdir.
Deadlock nümunəsi:
- Java prosesin stdout-a nəsə yazmasını gözləyir.
- Proses Java-nın ona stdin-ə nəsə yazmasını gözləyir.
- Hər ikisi gözləyir — heç kim işləmir.
Həll: oxu və yazma üçün ayrı axınlar
Deadlock-dan qaçmaq üçün tez-tez prosesin stdout və stderr-ini eyni vaxtda oxumaq üçün ayrı axınlar (Thread) istifadə olunur. Məsələn, iki axın yarada bilərsiniz: biri stdout-u, digəri — stderr-i oxuyur, əsas axın isə stdin-ə yazır.
Ayrı stderr axını ilə minimal nümunə
// stderr-i ayrı bir axında oxuyuruq
Thread errorThread = new Thread(() -> {
try (BufferedReader errorReader = new BufferedReader(
new InputStreamReader(process.getErrorStream()))) {
String line;
while ((line = errorReader.readLine()) != null) {
System.err.println("stderr: " + line);
}
} catch (IOException e) {
e.printStackTrace();
}
});
errorThread.start();
4. Praktika: kalkulyatorla interaktiv mübadilə
Həmişə Python olmaya bilər. Gəlin demək olar ki, hər yerdə olan standart əmri işə salaq — məsələn, komanda sətrində kalkulyator. Linux/Mac-də bu, bc (basic calculator) ola bilər, Windows-da — cmd və ya powershell.
Nümunə: bc ilə interaktiv qarşılıqlı əlaqə (Linux/Mac)
ProcessBuilder builder = new ProcessBuilder("bc");
Process process = builder.start();
BufferedWriter toProcess = new BufferedWriter(new OutputStreamWriter(process.getOutputStream()));
BufferedReader fromProcess = new BufferedReader(new InputStreamReader(process.getInputStream()));
toProcess.write("2 + 2\n");
toProcess.flush();
String result = fromProcess.readLine();
System.out.println("Kalkulyatorun cavabı: " + result);
toProcess.write("quit\n");
toProcess.flush();
process.destroy();
Windows-da: cmd-i işə salıb ona əmrlər vermək olar, amma bu, bir az çətin olacaq (başqa sintaksis tələb edir). Adətən interaktivliyi nümayiş etdirmək üçün hər iki platformada mövcud olan utilitlərdən istifadə olunur və ya kiçik skriptlər yazılır.
5. Problemlər və tələlər
Axınların buferləşdirilməsi
Bəzən proses məlumatı dərhal çıxarmır, onu daxili buferdə yığır və yalnız bufer dolanda və ya yeni sətir simvolu ("\n") alanda «verir». Nəticədə proses artıq cavabı «hazırlasa» da, siz onu uzun müddət görməyə bilərsiniz.
Məsləhət:
- Prosesin stdin-inə yazarkən sətirləri həmişə "\n" simvolu ilə bitirin.
- Öz skriptlərinizi yazırsınızsa, hər çıxışdan sonra flush() istifadə edin.
Axınlar düzgün təşkil olunmadıqda deadlock
Əgər stderr oxunmursa və proses ora çoxlu məlumat yazırsa — kimsə səhvləri oxuyana qədər proses ilişib qala bilər.
Məsləhət:
Həmişə prosesin həm stdout, həm də stderr-ini oxuyun, üstünlükcə ayrı axınlarda.
Proses başa çatıb — siz isə hələ də yazırsınız
Proses artıq başa çatıbsa, siz isə hələ də girişə yazırsınızsa — IOException alacaqsınız: "Stream closed".
7. Best practices: interaktiv qarşılıqlı əlaqə
Paralel oxumaq üçün ExecutorService istifadəsi
Çıxış və səhvləri oxumaq üçün axınları əllə yaratmamaq üçün ExecutorService-dən (məsələn, iki axınlı hovuz) istifadə etmək rahatdır:
import java.util.concurrent.*;
ExecutorService executor = Executors.newFixedThreadPool(2);
executor.submit(() -> {
try (BufferedReader out = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
String line;
while ((line = out.readLine()) != null) {
System.out.println("stdout: " + line);
}
} catch (IOException e) {
e.printStackTrace();
}
});
executor.submit(() -> {
try (BufferedReader err = new BufferedReader(new InputStreamReader(process.getErrorStream()))) {
String line;
while ((line = err.readLine()) != null) {
System.err.println("stderr: " + line);
}
} catch (IOException e) {
e.printStackTrace();
}
});
Axınların düzgün bağlanması
Proseslə iş bitdikdən sonra bütün axınları (stdin, stdout, stderr) mütləq bağlayın ki, resurs sızmaları olmasın.
toProcess.close();
fromProcess.close();
process.destroy();
executor.shutdown();
8. Yekun nümunə: kənar proseslə interaktiv mübadilə
Gəlin hamısını bir yerə yığaq. Aşağıdakı nümunə istifadəçi ilə kənar proses arasında (məsələn, Python skripti və ya kalkulyator) interaktiv «söhbət» kimi işləyir.
import java.io.*;
import java.util.concurrent.*;
public class InteractiveProcessUniversal {
public static void main(String[] args) throws IOException {
// Komandanızı əvəz edin — məsələn, "python echo_bot.py" və ya "bc"
ProcessBuilder builder = new ProcessBuilder("python", "echo_bot.py");
Process process = builder.start();
ExecutorService executor = Executors.newFixedThreadPool(2);
// Prosesin stdout-unun oxunması
executor.submit(() -> {
try (BufferedReader out = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
String line;
while ((line = out.readLine()) != null) {
System.out.println("[process] " + line);
}
} catch (IOException e) {
e.printStackTrace();
}
});
// Prosesin stderr-nin oxunması
executor.submit(() -> {
try (BufferedReader err = new BufferedReader(new InputStreamReader(process.getErrorStream()))) {
String line;
while ((line = err.readLine()) != null) {
System.err.println("[process-err] " + line);
}
} catch (IOException e) {
e.printStackTrace();
}
});
// Əsas axın: prosesin stdin-ə yazır
try (BufferedWriter toProcess = new BufferedWriter(new OutputStreamWriter(process.getOutputStream()));
BufferedReader userInput = new BufferedReader(new InputStreamReader(System.in))) {
System.out.println("Proses üçün sətirlər daxil edin (çıxış üçün exit):");
String line;
while ((line = userInput.readLine()) != null) {
toProcess.write(line + "\n");
toProcess.flush();
if ("exit".equals(line)) break;
}
}
process.destroy();
executor.shutdown();
}
}
9. Interaktiv iş zamanı tipik səhvlər
Xəta №1: Prosesin stderr-i oxunmur. Əgər proses çoxlu səhv çıxışı yazır, amma stderr oxunmursa, proses ilişib qala bilər. Hətta səhv olmayacağına əminsinizsə belə — stderr-i oxuyun!
Xəta №2: Axınlar bağlanmır. Prosesin axınları bağlanmasa, resurs sızmaları yarana və bəzən prosesin tamamlanması bloklana bilər.
Xəta №3: Platformaya görə komanda fərqləri. Əmr və onların sintaksisi Windows və Unix-oxşar sistemlər arasında fərqlənir. OS-i yoxlayın və uyğun komandanı seçin.
Xəta №4: Proqram «asılıb» — çıxış flush olunmur. Axına yazdıqdan sonra flush() çağırmağı unutsaq, məlumat buferdə «ilişib» qala və prosesə çatmaya bilər.
Xəta №5: Kənar prosesin çıxışının buferləşdirilməsi. Bəzən proses kifayət qədər simvol yığmayınca və ya "\n" almayınca məlumatı çıxarmır. Öz skriptlərinizi yazırsınızsa — flush() istifadə edin.
Xəta №6: Giriş/çıxışı eyni vaxtda gözləməkdən doğan deadlock. Əsas axın prosesin çıxışını gözləyir, proses isə girişi — hər ikisi «asılır». Oxuma/yazma üçün ayrı axınlardan istifadə edin.
Xəta №7: Bitmiş prosesə yazarkən «Stream closed» istisnası. Prosesə stdin-ə yazmazdan əvvəl onun hələ «canlı» olub-olmadığını yoxlayın. Səhv halında IOException alacaqsınız: "Stream closed".
GO TO FULL VERSION