node:net modülü, HTTP gibi bir uygulama protokolü olmadan doğrudan TCP ya da IPC (yerel soket) üzerinden konuşan sunucular ve istemciler yazmanızı sağlar. Kısa cevap: sunucu için net.createServer() ile bir net.Server oluşturup listen() çağırırsınız, istemci için net.connect() (diğer adıyla net.createConnection()) ile bir net.Socket açarsınız. Her bağlantı, hem okunabilir hem yazılabilir bir stream olan bir soket nesnesidir. Modül belgede Stability: 2 - Stable olarak işaretli.
Bu yazıda bir TCP sunucusunu ve istemcisini aynı betikte kurmayı, TCP'nin bir bayt akışı olduğunu ve mesaj sınırlarını sizin belirlemeniz gerektiğini, bağlantının kapanma adımlarını, EADDRINUSE ve ECONNREFUSED hatalarını, boşta kalma zaman aşımını, Windows named pipe ile Unix domain socket farkını ve BlockList ile IP engellemeyi örneklerle anlatıyoruz. Örnekler Node.js v26.13.0 ile çalıştırıldı ve her biri tek bir istek yapıp sunucuyu kapattığı için kendiliğinden sonlanır.
net modülü nerede durur?

Node.js'teki node:http modülü de altta TCP soketleri kullanır, ama size istek satırını, başlıkları ve gövdeyi ayrıştırılmış halde verir. node:net ile çalıştığınızda ise elinizde yalnızca baytlar vardır; protokolü siz tanımlarsınız. Bu, kendi satır tabanlı protokolünüzü yazmak, bir veritabanı ya da önbellek sunucusuyla düşük seviyede konuşmak, aynı makinedeki iki süreci yerel soketle bağlamak veya bir proxy yazmak için uygundur. Web uygulaması yazıyorsanız node:http ile sunucu yazısı daha doğru başlangıç noktasıdır; tarayıcıyla çift yönlü iletişim içinse WebSocket ve SSE karşılaştırmasına bakabilirsiniz.
| Sınıf veya fonksiyon | Görevi |
|---|---|
| net.createServer(options, listener) | Yeni bir TCP veya IPC sunucusu oluşturur; listener her bağlantı için çağrılır |
| server.listen(port, host) / listen(path) | TCP portunda veya IPC yolunda dinlemeye başlar |
| net.connect(port, host) / connect(path) | Sunucuya bağlanan bir istemci soketi açar |
| net.Socket | Tek bir bağlantı; Duplex stream, data/end/close/error olayları |
| net.BlockList | IP adresi, aralık ve alt ağ kurallarıyla erişim engelleme |
| net.isIP / isIPv4 / isIPv6 | Bir dizenin geçerli bir IP adresi olup olmadığını denetler |
İlk TCP sunucusu ve istemcisi
Aşağıdaki betik, gelen metni büyük harfe çevirip geri gönderen bir sunucu kuruyor ve aynı betikten ona bağlanıyor. Port olarak 0 verdik: belgeye göre port verilmez ya da 0 olursa işletim sistemi boş bir port atar, atanan portu listening olayından sonra server.address().port ile okursunuz. Bu, testlerde port çakışmasını önlemenin en basit yoludur. Host olarak da 127.0.0.1 verdik; host verilmezse sunucu IPv6 varsa ::, yoksa 0.0.0.0 adresinde, yani bütün ağ arayüzlerinde dinler.
import net from 'node:net';
const sunucu = net.createServer((soket) => {
console.log('sunucu: bağlantı geldi, uzak aile:', soket.remoteFamily);
soket.setEncoding('utf8');
soket.on('data', (veri) => {
soket.write(veri.toLocaleUpperCase('tr-TR'));
});
});
sunucu.listen(0, '127.0.0.1', () => {
const { address, family, port } = sunucu.address();
console.log('dinleniyor:', address, family, typeof port);
const istemci = net.connect({ port, host: '127.0.0.1' }, () => {
istemci.end('merhaba ışık\n'); // yaz ve yazma tarafını kapat
});
istemci.setEncoding('utf8');
istemci.on('data', (veri) => console.log('istemci aldı:', JSON.stringify(veri)));
istemci.on('close', (hadError) => {
console.log('istemci kapandı, hata var mı:', hadError);
sunucu.close(() => console.log('sunucu kapandı'));
});
});
// çıktı: dinleniyor: 127.0.0.1 IPv4 number
// çıktı: sunucu: bağlantı geldi, uzak aile: IPv4
// çıktı: istemci aldı: "MERHABA IŞIK\n"
// çıktı: istemci kapandı, hata var mı: false
// çıktı: sunucu kapandıAkışı adım adım izleyelim. listen() asenkrondur; son parametre olarak verilen fonksiyon listening olayına bağlanır ve server.address() ancak bu olaydan sonra anlamlı bir değer döndürür (öncesinde ve close() sonrasında null döner). İstemci bağlanınca sunucunun bağlantı dinleyicisi bir net.Socket alır. İstemci end() ile veri gönderip kendi yazma tarafını kapatır. Sunucu cevabı yazar; varsayılan allowHalfOpen: false ayarı yüzünden karşı taraf yazmayı bitirince sunucu soketi de bekleyen yazmaları gönderdikten sonra kendi tarafını kapatır. Sonunda istemcide close olayı gelir, biz de sunucuyu kapatırız.
server.close() ile ilgili bir ayrıntı: belgeye göre bu çağrı yeni bağlantı kabul etmeyi durdurur ama mevcut bağlantıları açık tutar; sunucu bütün bağlantılar bittiğinde gerçekten kapanır ve close olayını yayar. Geri çağırma fonksiyonu, sunucu zaten açık değilse bir Error ile çağrılır. Üretimde düzgün kapanma akışını cluster ve PM2 yazısında ele aldık.
TCP bir bayt akışıdır: mesaj sınırlarını siz belirlersiniz
TCP ile ilgili en yaygın yanılgı, karşı tarafın her write() çağrısının alıcıda bir data olayına denk geleceğini sanmaktır. Böyle bir garanti yoktur: bir mesaj birden fazla parçaya bölünebilir, birden fazla mesaj tek bir data olayında birleşebilir. Bu yüzden uygulama düzeyinde bir çerçeveleme kuralı gerekir; en basiti her mesajı satır sonu karakteriyle bitirmektir. Aşağıdaki küçük protokolde istemci TOPLA, bilinmeyen bir komut ve CIK gönderiyor; ilk mesajı bilerek iki parçaya bölüyoruz, sonraki üç mesajı tek bir write() ile yolluyoruz.
import net from 'node:net';
// TCP bir bayt akışıdır: mesaj sınırını uygulama belirler (burada satır sonu)
function satirlaraBol(soket, satirGeldi) {
let artik = '';
soket.setEncoding('utf8');
soket.on('data', (parca) => {
artik += parca;
let i;
while ((i = artik.indexOf('\n')) !== -1) {
satirGeldi(artik.slice(0, i));
artik = artik.slice(i + 1);
}
});
}
const sunucu = net.createServer((soket) => {
satirlaraBol(soket, (satir) => {
const [komut, ...arg] = satir.split(' ');
if (komut === 'TOPLA') soket.write(arg.map(Number).reduce((a, b) => a + b, 0) + '\n');
else if (komut === 'CIK') soket.end('GÜLE GÜLE\n');
else soket.write('BILINMEYEN ' + komut + '\n');
});
});
sunucu.listen(0, '127.0.0.1', () => {
const istemci = net.connect(sunucu.address().port, '127.0.0.1');
satirlaraBol(istemci, (satir) => console.log('cevap:', satir));
// Bir mesaj iki parçaya bölünmüş, iki mesaj tek write ile gidiyor
istemci.write('TOPLA 2 ');
setTimeout(() => istemci.write('3 5\nSELAM\nCIK\n'), 20);
istemci.on('close', () => sunucu.close());
});
// çıktı: cevap: 10
// çıktı: cevap: BILINMEYEN SELAM
// çıktı: cevap: GÜLE GÜLEsatirlaraBol() fonksiyonu, gelen parçaları bir tamponda biriktirip her satır sonunda tam bir mesaj çıkarıyor; parçalanma nasıl olursa olsun sonuç aynı kalıyor. setEncoding('utf8') çağrısı burada önemlidir: kodlama ayarlandığında soket, çok baytlı bir UTF-8 karakteri iki parçaya bölünmüş olarak gelse bile onu doğru birleştirir. Ham Buffer ile çalışıyorsanız bu birleştirmeyi kendiniz yapmanız gerekir. Satır tabanlı okumayı dosya ve stdin üzerinde readline yazısında göreceksiniz.
Belge data olayı için bir uyarı daha yapar: soket data yaydığında dinleyici yoksa veri kaybolur. Dinleyiciyi bağlantı kurulur kurulmaz ekleyin ya da soketi bir stream olarak pipe() veya pipeline() ile tüketin.
Yazma, backpressure ve Nagle algoritması

socket.write() bir boolean döndürür. Belgeye göre true, verinin tamamının çekirdek tamponuna aktarıldığını; false ise verinin bir kısmının ya da tamamının kullanıcı belleğinde kuyruğa alındığını gösterir. Tampon tekrar boşaldığında drain olayı yayılır. Büyük veri gönderirken false döndüğünde yazmayı durdurup drain beklemek, belleğin şişmesini önler; bu desenin ayrıntısı pipeline ve backpressure yazısında.
Belge, yeni bir TCP bağlantısında Nagle algoritmasının açık olduğunu söyler. Bu algoritma küçük paketleri biriktirip toplu gönderir; verimi artırır ama gecikmeyi yükseltebilir. Etkileşimli, küçük mesajlı protokollerde socket.setNoDelay(true) ile kapatabilir ya da sunucuyu createServer({ noDelay: true }) ile oluşturabilirsiniz. Benzer şekilde keepAlive seçeneği ve socket.setKeepAlive(), boşta duran bağlantılarda keep-alive yoklamalarını açar.
Bağlantının kapanması: end, close ve allowHalfOpen
Soket kapanırken üç olay görebilirsiniz ve her birinin anlamı farklıdır. end olayı, karşı tarafın iletimin bittiğini bildirdiği, yani sizin okuma tarafınızın sona erdiği anda gelir. error olayı bir hata oluştuğunda gelir ve belgeye göre hemen ardından close yayılır. close ise soket tamamen kapandığında gelir; aldığı hadError parametresi kapanışın bir iletim hatasından kaynaklanıp kaynaklanmadığını söyler.
allowHalfOpen: true verirseniz karşı taraf yazmayı bitirdiğinde soketiniz kendi yazma tarafını otomatik kapatmaz; istediğiniz kadar veri yazıp end() çağrısını kendiniz yaparsınız. Belge bunu yarı açık (half-closed) bağlantı olarak tanımlar: okunamaz ama hâlâ yazılabilir. socket.end() zarif kapanıştır, socket.destroy() ise soketi hemen yok eder; socket.resetAndDestroy() TCP bağlantısını RST paketiyle sonlandırır.
Sık karşılaşılan hatalar: EADDRINUSE ve ECONNREFUSED
Belge, dinleme sırasında en sık görülen hatanın EADDRINUSE olduğunu söyler: istenen port ya da yol üzerinde başka bir sunucu zaten dinliyordur. Bir istemcinin dinlemeyen bir porta bağlanmaya çalışması ise ECONNREFUSED ile sonuçlanır. Aşağıdaki örnek ikisini de üretiyor.
import net from 'node:net';
const ilk = net.createServer().listen(0, '127.0.0.1', () => {
const port = ilk.address().port;
// Aynı porta ikinci sunucu: EADDRINUSE
const ikinci = net.createServer();
ikinci.on('error', (err) => {
console.log('ikinci sunucu:', err.code, err.syscall);
ilk.close(() => {
// Artık kimse dinlemiyor: bağlanma denemesi ECONNREFUSED
const s = net.connect(port, '127.0.0.1');
s.on('error', (e) => console.log('istemci:', e.code, e.syscall));
s.on('close', (hadError) => console.log('close, hadError:', hadError));
});
});
ikinci.listen(port, '127.0.0.1');
});
// çıktı: ikinci sunucu: EADDRINUSE listen
// çıktı: istemci: ECONNREFUSED connect
// çıktı: close, hadError: trueSunucu tarafındaki hatanın syscall değeri listen, istemci tarafındakinin connect oldu. İstemcide error olayından sonra close geldi ve hadError değeri true oldu. Belge, EADDRINUSE için bir süre bekleyip server.close() ve ardından tekrar listen() çağırmayı örnek bir çözüm olarak gösterir. Bir de şunu bilin: listen() yalnızca ilk çağrı hata verdiyse ya da close() çağrıldıysa tekrar çağrılabilir; aksi halde ERR_SERVER_ALREADY_LISTEN fırlatılır. Bu hata kodlarının ayrıntılı açıklaması ve kalıcı çözüm yolları EADDRINUSE ve ECONNREFUSED yazısında.
Boşta kalma zaman aşımı
Varsayılan olarak net.Socket nesnelerinin bir zaman aşımı yoktur; bağlantı istediği kadar boşta kalabilir. socket.setTimeout(ms) belirtilen süre boyunca hiç etkinlik olmazsa bir timeout olayı yayar. Belgenin özellikle vurguladığı nokta: bu olay yalnızca bir bildirimdir, bağlantı kesilmez; end() ya da destroy() çağrısını sizin yapmanız gerekir. 0 vermek mevcut zaman aşımını kapatır.
import net from 'node:net';
const sunucu = net.createServer((soket) => {
soket.setTimeout(100); // 100 ms hareketsizlikte 'timeout' olayı
soket.on('timeout', () => {
console.log('sunucu: boşta kalma süresi doldu, timeout:', soket.timeout);
soket.end('zaman aşımı\n'); // bağlantıyı kendimiz kapatmalıyız
});
});
sunucu.listen(0, '127.0.0.1', () => {
const c = net.connect(sunucu.address().port, '127.0.0.1');
c.setEncoding('utf8');
c.on('data', (d) => console.log('istemci:', d.trim()));
c.on('end', () => console.log('istemci: karşı taraf kapattı'));
c.on('close', () => sunucu.close());
});
// çıktı: sunucu: boşta kalma süresi doldu, timeout: 100
// çıktı: istemci: zaman aşımı
// çıktı: istemci: karşı taraf kapattıBoşta kalan bağlantıları kapatmak, çok sayıda istemcinin açık soket bırakıp sunucu kaynaklarını tüketmesini engellemenin basit bir yoludur. Bağlantı sayısını sınırlamak için server.maxConnections da kullanılabilir; belgeye göre bu eşiğe ulaşıldığında yeni bağlantılar düşürülür ve sunucu bir drop olayı yayar.
IPC: Windows named pipe ve Unix domain socket

node:net aynı makinedeki süreçler arasında ağ katmanı kullanmadan konuşmak için IPC destekler: Windows'ta named pipe, diğer işletim sistemlerinde Unix domain socket. Sunucuyu port yerine bir yol ile dinletirsiniz; istemci de aynı yola bağlanır. İki platformun kuralları belgede farklı anlatılır:
- Unix: Yol bir dosya sistemi yoludur ve uzunluğu
sockaddr_un.sun_pathboyutunu (Linux'ta tipik olarak 107, macOS'ta 103 bayt) aşarsa hata fırlatılır. Soketi Node.js oluşturduysaserver.close()onu siler; ama program çökerse soket dosyası silinene kadar dosya sisteminde kalır. Linux'ta yolun başına\0eklenerek dosya sisteminde görünmeyen soyut (abstract) soket kullanılabilir. - Windows: Yol
\\?\pipe\veya\\.\pipe\altında olmalıdır. Pipe ad alanı düzdür ve pipe'lar kalıcı değildir: son referans kapanınca ya da sahibi olan süreç çıkınca silinir. JavaScript dizelerinde ters eğik çizgileri çift yazmanız gerekir.
import net from 'node:net';
import path from 'node:path';
// Adres doğrulama
console.log(net.isIP('192.168.1.10'), net.isIP('::1'), net.isIP('192.168.001.010'));
// BlockList: createServer({ blockList }) ile gelen bağlantıları reddetmek için
const engel = new net.BlockList();
engel.addAddress('203.0.113.7');
engel.addCIDR('10.0.0.0/8');
console.log(engel.check('203.0.113.7'), engel.check('10.20.30.40'), engel.check('8.8.8.8'));
// IPC: Windows'ta named pipe, diğer sistemlerde Unix domain socket
const yol = process.platform === 'win32'
? path.join('\\\\?\\pipe', 'n25-ornek')
: path.join(import.meta.dirname, 'n25-ornek.sock');
const sunucu = net.createServer((s) => s.end('ipc tamam\n'));
sunucu.listen(yol, () => {
console.log('adres türü:', typeof sunucu.address());
const c = net.connect(yol);
c.setEncoding('utf8');
c.on('data', (d) => process.stdout.write(d));
c.on('close', () => sunucu.close());
});
// çıktı: 4 6 0
// çıktı: true true false
// çıktı: adres türü: string
// çıktı: ipc tamamIPC sunucusunda server.address() bir nesne değil, yolun kendisini içeren bir dize döndürür; örnekte typeof değeri string çıktı. Aynı betiğin başındaki satırlar iki yardımcıyı da gösteriyor: net.isIP() geçerli bir IPv4 adresi için 4, IPv6 için 6, geçersiz girdi için 0 döndürür. Belgeye göre başında sıfır olan noktalı ondalık gösterim (192.168.001.010) geçersiz sayılır.
BlockList ile IP engelleme
net.BlockList, belirli IP adreslerini, aralıkları ya da alt ağları engellemek için kural listesi tutar. addAddress(), addRange() ve addSubnet() eski yöntemlerdir; belgedeki YAML kayıtlarına göre addCIDR(), addCIDRs() ve addAddresses() v26.8.0 ile geldi. addCIDR(), adres ailesini kendisi algılar: adreste iki nokta üst üste varsa IPv6, yoksa IPv4 kabul eder. check() bir adresin kurallardan birine uyup uymadığını söyler.
Listeyi net.createServer({ blockList }) seçeneğiyle verirseniz bu adreslerden gelen bağlantılar engellenir. Belge önemli bir sınırlamayı da not eder: sunucu bir ters proxy ya da NAT arkasındaysa denetlenen adres istemcinin değil proxy'nin adresidir, bu yüzden engelleme beklediğiniz gibi çalışmaz. BlockList.toJSON() ve fromJSON() ise belgede Stability: 1.2 - Release candidate olarak işaretlidir.
Sık yapılan hatalar
- Her write() çağrısının ayrı bir data olayı olduğunu sanmak: TCP mesaj sınırı tanımaz; satır sonu, uzunluk öneki gibi bir çerçeveleme kullanın.
- Sunucuyu 0.0.0.0 üzerinde gereksiz açmak: Host vermezseniz sunucu bütün arayüzlerde dinler. Yalnızca yerel erişim gerekiyorsa
127.0.0.1verin. - timeout olayının bağlantıyı kapattığını sanmak: Olay yalnızca bildirimdir;
end()veyadestroy()çağırın. - error dinleyicisi eklememek: Dinleyicisi olmayan bir
errorolayı yakalanmamış istisnaya dönüşür ve süreci sonlandırır. - write() dönüş değerini görmezden gelmek: Yavaş bir istemciye büyük veri basmak belleği doldurur;
falsedönüncedrainbekleyin. - Çöken programın bıraktığı Unix soket dosyası: Bir sonraki
listen()çağrısıEADDRINUSEverebilir; başlangıçta eski dosyayı temizlemeyi düşünün. - Ters proxy arkasında BlockList'e güvenmek: Görülen adres proxy'nin adresidir.
Sık Sorulan Sorular
net.connect ile net.createConnection farklı mı?
Hayır. Belge net.connect() fonksiyonunu net.createConnection() için bir takma ad olarak tanımlar; ikisi de aynı imzaları (seçenek nesnesi, IPC yolu ya da port ve host) kabul eder ve bir net.Socket döndürür.
Bir TCP sunucusunu worker thread'lere dağıtabilir miyim?
Belgeye göre dinleyen bir TCP net.Server, worker_threads postMessage() çağrısının transferList listesine konarak başka bir thread'e taşınabilir; yeni kabul edilmiş ve henüz okumaya başlamamış bir net.Socket de taşınabilir. Yalnızca TCP tutamaçları desteklenir. Worker kullanımı için worker_threads yazısına bakın.
Sunucuyu belirli bir süre sonra kapatmak için AbortSignal kullanabilir miyim?
server.listen({ port, signal }) biçiminde verilen bir AbortSignal iptal edildiğinde sunucu close() çağrılmış gibi kapanır. Belge, bunu uzun ömürlü bir sunucuyu kaynak temizliği amacıyla kapatmak için kullanmanın DEP0209 ile kullanımdan kaldırıldığını, iptal ve zaman aşımı için ise uygun olduğunu belirtir. Ayrıntı AbortController yazısında.
Gelen verinin hangi kodlamada olduğunu nasıl seçerim?
Varsayılan olarak data olayı Buffer verir. socket.setEncoding('utf8') çağırırsanız dize alırsınız. Gönderirken socket.write(dize, kodlama) ikinci parametreyi yalnızca dize için kullanır ve varsayılan kodlama UTF-8'dir.
Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.
İlgili içerikler: Node.js öğrenme rehberi · node:http ile sunucu · EADDRINUSE ve ECONNREFUSED · stream pipeline ve backpressure · WebSocket ve SSE