এই পর্বে যা শিখবেন
- নেটওয়ার্কের চার স্তর, IP ঠিকানা, DNS, port আর client-server — দুই মেশিনের প্রোগ্রাম কীভাবে একে অপরকে খুঁজে পায়
ServerSocketআরSocketদিয়ে নিজের TCP সার্ভার ও client, সংযোগের জীবনচক্র, timeout আর সাধারণ exception- framing সমস্যা, virtual thread দিয়ে অনেক client সামলানো, আর ধীর বা দুষ্ট client থেকে সার্ভার রক্ষা
- UDP কখন, আর
java.net.http.HttpClientগভীরে — request, header, timeout, redirect, async, retry - WebSocket কী আর কেন, TLS/HTTPS-এর মূল কথা, আর AI-এর লেখা নেটওয়ার্ক কোড যাচাই
১ · দুই মেশিনের দুই প্রোগ্রাম — নেটওয়ার্ক কীভাবে স্তরে সাজানো
আপনার ফোনের bKash অ্যাপে “ব্যালেন্স দেখুন” চাপলেন। অ্যাপটি চলছে আপনার হাতের ফোনে, আর ব্যালেন্স রাখা আছে কোনো ডেটা সেন্টারের সার্ভার প্রোগ্রামে; মাঝে মোবাইল টাওয়ার, ফাইবার, রাউটার। তবু এক সেকেন্ডের কম সময়ে উত্তর আসে। এই পর্বের বিষয়: এক প্রোগ্রাম আরেক মেশিনের প্রোগ্রামের সাথে কীভাবে কথা বলে, আর Java সেই কথা বলার কোন কোন উপায় দেয়।
পুরো কাজটা একবারে করা কঠিন, তাই নেটওয়ার্ককে কয়েকটি স্তরে ভাগ করা হয়; প্রতিটি স্তর একটি ছোট দায়িত্ব নেয় আর শুধু নিচের স্তরের সেবা ব্যবহার করে। সবচেয়ে প্রচলিত ভাগ TCP/IP মডেল, যার চারটি স্তর:
| স্তর | দায়িত্ব | উদাহরণ | Java-তে |
|---|---|---|---|
| application | আসল কথার নিয়ম — কী জিজ্ঞেস, কী উত্তর | HTTP, WebSocket, DNS, SMTP | HttpClient, WebSocket |
| transport | মেশিনের ভেতরে কোন প্রোগ্রাম (port); নির্ভরযোগ্যতা আর ক্রম | TCP, UDP | Socket, ServerSocket, DatagramSocket |
| internet (IP) | কোন মেশিন; এক নেটওয়ার্ক থেকে আরেক নেটওয়ার্কে পথ খোঁজা | IPv4, IPv6 | InetAddress |
| link | একই তার বা একই Wi-Fi-র পাশের যন্ত্র পর্যন্ত পৌঁছানো | Ethernet, Wi-Fi, 4G | OS আর হার্ডওয়্যারের কাজ |
প্রতিটি স্তরের নিজের নিয়মকানুনের নাম protocol — দুই পক্ষ আগে থেকে যে ভাষা আর ক্রমে কথা বলতে রাজি। পাঠানোর সময় প্রতিটি স্তর নিজের তথ্য একটি ছোট header হিসেবে ডেটার সামনে জুড়ে দেয়; একে বলে encapsulation (OOP-এর encapsulation-এর সাথে নাম মিললেও এটা আলাদা ধারণা)। গ্রহণের সময় উল্টো ক্রমে প্রতিটি স্তর নিজের header খুলে ভেতরেরটা ওপরের স্তরকে দেয়।
কুরিয়ারে রাজশাহী থেকে চট্টগ্রামে একটি উপহার পাঠানোর কথা ভাবুন। বাক্সের ওপর প্রাপকের নাম — “করিম সাহেব, ৩ তলা” (port: বাড়ির ভেতরে কে)। কুরিয়ার অফিস বাক্সটি বস্তায় ভরে বস্তায় শহর আর এলাকার ঠিকানা লেখে (IP: কোন বাড়ি)। বস্তা ট্রাকে ডিপো থেকে ডিপোতে যায় (link: এক ধাপ থেকে পরের ধাপ); প্রতিটি ডিপো শুধু বস্তার ঠিকানা পড়ে, উপহার খোলে না। চট্টগ্রামে উল্টো ক্রমে খোলা হয়, আর বাক্স পৌঁছায় করিম সাহেবের হাতে।
- application
"TEA\n"অ্যাপের ডেটা - TCP segment
port 5000কোন প্রোগ্রাম - IP packet
203.0.113.5কোন মেশিন - link frame
Wi-Fiপরের ধাপ পর্যন্ত - রাউটার
hop → hopশুধু IP পড়ে - সার্ভার
readLine()উল্টো ক্রমে খোলা
out.println("TEA");
byte: 54 45 41 0A (T E A আর newline)
অ্যাপ শুধু byte দেয় — কোথায় যাবে, কীভাবে যাবে, জানে না
[TCP header: src port 52814 → dst port 5000, seq, ACK] + "TEA\n" 52814 = client-এর অস্থায়ী port, OS বেছে দিয়েছে TCP header-এ ক্রমিক নম্বর — হারালে আবার পাঠানো যাবে
[IP header: 192.168.0.10 → 203.0.113.5, protocol=TCP, TTL=64]
+ [TCP header + "TEA\n"]
IP জানে কোন মেশিন — port বা ভেতরের লেখা নিয়ে মাথা ঘামায় না
[Wi-Fi header: পরের যন্ত্র = বাসার রাউটার] + [IP packet] + [checksum]
link স্তর শুধু পরের লাফটুকু পৌঁছে দেয়
বাসার রাউটার → ISP → … → ডেটা সেন্টার
প্রতিটি রাউটার: frame খোলে, IP ঠিকানা দেখে, TTL এক কমায়,
নতুন frame-এ ভরে পরের দিকে পাঠায়
TCP header বা "TEA" কেউ খোলে না
frame খোলা → IP: ঠিকানা আমার? হ্যাঁ → TCP: port 5000 কার? → ক্রম মিলিয়ে, ACK পাঠিয়ে → সার্ভার প্রোগ্রামের stream-এ "TEA\n" in.readLine() → "TEA"
- অ্যাপের ডেটা। আপনার কোড
println("TEA")লেখে — ওপরে শুধু কয়েকটি byte; কোন মেশিনে যাবে বা পথে হারালে কী হবে, তা অ্যাপের ভাবনা নয়। - TCP header যোগ। OS-এর TCP অংশ সামনে header জোড়ে: কোন port থেকে কোন port-এ, আর ক্রমিক নম্বর, যা দিয়ে অন্য প্রান্ত ক্রম মেলায় আর হারানো অংশ আবার চায়। এই টুকরোর নাম segment.
- IP header যোগ। IP স্তর উৎস আর গন্তব্য মেশিনের ঠিকানা জোড়ে — এখন এটি একটি packet. TTL প্রতিটি রাউটারে এক কমে, শূন্য হলে packet ফেলে দেওয়া হয়, যাতে চিরকাল ঘুরতে না পারে।
- Link frame. Wi-Fi বা Ethernet packet-টিকে frame-এ ভরে শুধু পাশের যন্ত্র পর্যন্ত পৌঁছায়; শেষে checksum, যাতে পথে bit উল্টালে ধরা পড়ে।
- রাউটার থেকে রাউটারে। প্রতিটি রাউটার শুধু IP header পড়ে পরের লাফ ঠিক করে; ভেতরের TCP বা “TEA” তার কাছে অর্থহীন। তাই মাঝের যন্ত্র নতুন application protocol না জেনেও তা বহন করে।
- উল্টো ক্রমে ওপরে। সার্ভারে প্রতিটি স্তর নিজের header খুলে ভেতরেরটা ওপরে দেয়; TCP port দেখে প্রোগ্রাম চেনে, ক্রম মিলিয়ে ACK পাঠায়। শেষে
readLine()পায় ঠিক “TEA”.
সুফল হলো, যে স্তরে কাজ করছেন তার নিচের খুঁটিনাটি না জানলেও চলে — HTTP লেখার সময় Wi-Fi না 4G, তা জানার দরকার নেই। Java প্রতিটি স্তরের জন্য আলাদা API দেয়: নিচের দিকে socket, ওপরের দিকে HttpClient আর WebSocket.
২ · ঠিকানা — IP, DNS, port আর client-server
IP ঠিকানা — কোন মেশিন
নেটওয়ার্কে প্রতিটি যন্ত্রের একটি IP ঠিকানা থাকে। IPv4 ঠিকানা ৩২ bit — চারটি ০-২৫৫ সংখ্যা, যেমন 192.168.0.10; মোট প্রায় ৪৩০ কোটি, যা অনেক আগেই কম পড়ে গেছে। তাই এসেছে ১২৮ bit-এর IPv6, যেমন 2001:db8::1. বাসার রাউটারের পেছনের যন্ত্রগুলো সাধারণত 192.168.x.x-এর মতো ব্যক্তিগত ঠিকানা পায়।
একটি বিশেষ ঠিকানা: 127.0.0.1 (IPv6-এ ::1), নাম localhost — loopback ঠিকানা, অর্থাৎ “এই কম্পিউটারই”; এখানে পাঠানো ডেটা নেটওয়ার্ক কার্ডে যায় না। অনলাইন runner-এ বাইরের ইন্টারনেটে সংযোগ খোলা যায় না, তাই এই পর্বের চালানো যায় এমন উদাহরণ সার্ভার আর client দুটোই একই প্রোগ্রামে 127.0.0.1-এ চালায়।
DNS — নাম থেকে ঠিকানা
মানুষ abcltech.com মনে রাখে, নেটওয়ার্ক বোঝে সংখ্যা। নাম থেকে ঠিকানা খুঁজে দেয় DNS — ইন্টারনেটের ফোনবুক, যা পৃথিবীজুড়ে ছড়ানো অসংখ্য সার্ভারে ভাগ করে রাখা। Java-তে এই খোঁজার কাজ করে InetAddress:
InetAddress site = InetAddress.getByName("abcltech.com"); // DNS-কে জিজ্ঞেস — নেটওয়ার্ক লাগে
System.out.println(site.getHostAddress()); // একটি IP ঠিকানা (সময়ভেদে বদলাতে পারে)
InetAddress me = InetAddress.getLoopbackAddress(); // DNS লাগে না
System.out.println(me); // localhost/127.0.0.1নাম খুঁজে না পেলে getByName একটি UnknownHostException ছোড়ে। আর মনে রাখুন, DNS-এর উত্তর আসতে সময় লাগে, ব্যর্থও হতে পারে — তাই এটাও একটি নেটওয়ার্ক কাজ, সাধারণ method ডাকের মতো তাৎক্ষণিক নয়।
Port — মেশিনের ভেতরে কোন প্রোগ্রাম
IP ঠিকানা মেশিন পর্যন্ত পৌঁছায়, কিন্তু একটি সার্ভারে একসাথে ওয়েব সার্ভার, ডেটাবেস, মেইল সার্ভার চলতে পারে। কোনটির কাছে যাবে, তা বলে port — ০ থেকে ৬৫,৫৩৫-এর একটি সংখ্যা। ভবনের ঠিকানা যদি IP হয়, port হলো কক্ষ নম্বর। প্রথা অনুযায়ী HTTP 80, HTTPS 443, PostgreSQL 5432. Client-এর দিকের port সাধারণত OS নিজে বেছে দেয় — ephemeral port. একটি TCP সংযোগ চেনা যায় চারটি জিনিসে: দুই প্রান্তের IP আর port.
Client আর সার্ভার
বেশিরভাগ নেটওয়ার্ক প্রোগ্রাম client-server ধাঁচের। সার্ভার আগে থেকে একটি নির্দিষ্ট port-এ চালু হয়ে অপেক্ষা করে (listen); client যখন দরকার তখন সংযোগ খোলে, প্রশ্ন পাঠায়, উত্তর নেয়। দোকানদার দোকান খুলে বসে থাকেন, ক্রেতা আসেন যখন খুশি। সার্ভারকে তাই একসাথে অনেক client সামলাতে হয় — অংশ ৫-এর মূল প্রশ্ন।
৩ · TCP socket — দোকানের দাম জানার সার্ভার
TCP দুই প্রোগ্রামের মধ্যে একটি সংযোগ তৈরি করে, তারপর দুই দিকে দুটি byte-এর স্রোত দেয় — যা পাঠানো হলো, ঠিক সেই ক্রমে, কোনো byte না হারিয়ে পৌঁছাবে, নইলে সংযোগই ভেঙে যাবে আর আপনি exception পাবেন। পথে packet হারালে TCP নিজেই আবার পাঠায়, উল্টাপাল্টা এলে সাজায়, দ্রুত পাঠানো বেশি হলে গতি কমায়। আপনার কোড দেখে শুধু একটি নির্ভরযোগ্য পাইপ।
Java-তে TCP-র দুটি মূল class. সার্ভারের দিকে ServerSocket: একটি port-এ বসে নতুন সংযোগের অপেক্ষা করে। তার accept() method একটি client না আসা পর্যন্ত আটকে থাকে, এলে সেই client-এর জন্য একটি নতুন Socket ফেরত দেয়। Client-এর দিকে সরাসরি Socket. দুই দিকেই Socket-এর getInputStream() আর getOutputStream() পর্ব ৩৭-এর মতো byte stream দেয়। লেখা পাঠাতে এদের মুড়ে নিই:
- পড়তে:
InputStreamReader(byte থেকে অক্ষর, charset দিয়ে) → BufferedReader (readLine()দিয়ে পুরো লাইন)। - লিখতে:
OutputStreamWriter(অক্ষর থেকে byte) → PrintWriter (println). দ্বিতীয় argumenttrueদিলে প্রতিটিprintln-এর পর নিজে থেকেই flush হয় — না হলে লেখা buffer-এ পড়ে থাকবে, অন্য প্রান্ত অপেক্ষাই করে যাবে।
charset সবসময় স্পষ্ট করে UTF-8 দিন। Java 18 থেকে ডিফল্ট UTF-8 হলেও অন্য প্রান্ত হয়তো পুরোনো Java বা অন্য ভাষায় লেখা; “টাকা” শব্দটি দুই প্রান্তে দুই নিয়মে রূপান্তর হলে অর্থহীন চিহ্ন দেখাবে। এবার একটি ছোট মুদি দোকানের সার্ভার: client একটি পণ্যের কোড পাঠায়, সার্ভার দাম জানায়। একই প্রোগ্রাম সার্ভার চালায়, তারপর নিজেই client হয়ে প্রশ্ন করে:
import java.io.*;
import java.net.*;
import java.util.Map;
import java.util.concurrent.Executors;
import static java.nio.charset.StandardCharsets.UTF_8;
class Main {
static final Map<String, Integer> PRICE = Map.of("CHAL", 75, "DAL", 140, "TEA", 120);
static BufferedReader reader(Socket s) throws IOException { return new BufferedReader(new InputStreamReader(s.getInputStream(), UTF_8)); }
static PrintWriter writer(Socket s) throws IOException { return new PrintWriter(new OutputStreamWriter(s.getOutputStream(), UTF_8), true); }
static void serve(Socket s) { // এক client = এক virtual thread
try (s; var in = reader(s); var out = writer(s)) {
s.setSoTimeout(2_000); // ২ সেকেন্ড চুপ থাকলে বিদায়
for (String code; (code = in.readLine()) != null && code.length() <= 10; )
out.println(code + " -> " + (PRICE.containsKey(code) ? PRICE.get(code) + " টাকা" : "পাওয়া যায়নি"));
} catch (IOException e) { } // client চলে গেছে বা timeout
}
public static void main(String[] args) throws Exception {
var local = InetAddress.getLoopbackAddress(); // 127.0.0.1 — এই কম্পিউটারই
try (var pool = Executors.newVirtualThreadPerTaskExecutor(); var server = new ServerSocket(0, 50, local)) {
pool.submit(() -> { while (true) { Socket c = server.accept(); pool.submit(() -> serve(c)); } });
try (var s = new Socket()) { // এবার আমরা client
s.connect(new InetSocketAddress(local, server.getLocalPort()), 1_000);
var in = reader(s); var out = writer(s);
for (String code : new String[] {"CHAL", "TEA", "MACH"}) { out.println(code); System.out.println(in.readLine()); }
}
}
System.out.println("সার্ভার বন্ধ");
}
}আউটপুট চার লাইন: CHAL -> 75 টাকা, TEA -> 120 টাকা, MACH -> পাওয়া যায়নি, আর সার্ভার বন্ধ. এখানে সত্যিকারের TCP সংযোগ খুলেছে, শুধু দুই প্রান্তই একই মেশিনে। new ServerSocket(0, 50, local)-এ port 0 মানে “OS, একটি খালি port বেছে দাও” — সংখ্যাটি প্রতিবার আলাদা, তাই ছাপা হয়নি; ৫০ হলো backlog, অর্থাৎ accept()-এর অপেক্ষায় কতগুলো সংযোগ সারিতে দাঁড়াতে পারবে। একটি virtual thread চিরকাল accept() করে, আর প্রতিটি নতুন client-কে আরেকটি virtual thread-এ serve-এর হাতে দেয় (অংশ ৫)। Client প্রতিটি কোড একটি লাইনে পাঠিয়ে একটি লাইনের উত্তরের অপেক্ষা করে — newline-ই message-এর সীমানা (অংশ ৪)। শেষে try-with-resources উল্টো ক্রমে বন্ধ করে: আগে server — তাতে accept() exception ছুড়ে লুপ থামে — তারপর pool, যা বাকি কাজের অপেক্ষা করে। খালি catch শুধু জায়গা বাঁচাতে; আসল সার্ভারে log করুন। (অনলাইন runner ফাইলটিকে অন্য নামে compile করে, তাই Main এখানে public নয়।)
1ServerSocket server = new ServerSocket(5000); // সার্ভার2Socket conn = server.accept(); // সার্ভার3Socket s = new Socket(host, 5000); // client4out.println("TEA"); // client5String code = in.readLine(); // সার্ভার6out.println(code + " -> 120 টাকা"); // সার্ভার7s.close(); // client
| সার্ভার | |
| client | |
| তারে যা গেল |
- সার্ভার listen করে।
new ServerSocket(5000)OS-কে বলে: port 5000-এ যত সংযোগ আসবে, আমার। এখনো কোনো client নেই, কোনো ডেটাও যায়নি। - accept() — অপেক্ষা। সার্ভারের thread
accept()-এ আটকে যায়। একটি platform thread হলে পুরো OS thread ঘুমায়; virtual thread হলে নেমে যায় (পর্ব ৪২)। - Client connect — handshake. Client-এর
new Socket(host, 5000)তিনটি ছোট packet-এ সংযোগ বানায়: SYN (“কথা বলতে চাই”), SYN-ACK (“রাজি”), ACK (“ঠিক আছে”)। একে বলে three-way handshake. শেষ হলেই সার্ভারেরaccept()একটি নতুনSocketফেরত দেয় — শুধু এই client-এর জন্য।serverআগের মতোই পরের client-এর জন্য খোলা। - Client পাঠায়।
println("TEA")চারটি byte পাঠায় — শেষে newline. সার্ভারেরreadLine()newline না আসা পর্যন্ত অপেক্ষা করে, তারপর “TEA” ফেরত দেয় (newline বাদ দিয়ে)। - সার্ভার উত্তর দেয়। সার্ভার একই সংযোগে উল্টো দিকের স্রোতে উত্তর লেখে। TCP দুই দিকেই একসাথে চলে; client-এর
readLine()উত্তর পায়। সার্ভার আবারreadLine()-এ ফেরে, পরের প্রশ্নের অপেক্ষায়। - বন্ধ। Client
close()করলে একটি FIN packet যায়। সার্ভারেরreadLine()তখনnullদেয় — “স্রোত শেষ” — লুপ থামে, সার্ভারও নিজের দিক বন্ধ করে।nullনা দেখে লুপ চালালে সার্ভার মরা সংযোগ নিয়ে বসে থাকত।
৪ · সংযোগের জীবনচক্র — timeout, ব্যর্থতা আর framing
বন্ধ করা আর half-close
TCP সংযোগের দুটি দিক আলাদা। close() দুটিই বন্ধ করে। কিন্তু কখনো client বলতে চায় “আমার পাঠানো শেষ, উত্তর এখনো পড়ব” — তখন socket.shutdownOutput(): সার্ভার স্রোতের শেষ (null বা -1) দেখে, তবু উত্তর লিখতে পারে। একে বলে half-close. runner-এ পরীক্ষায় দুটি লাইন পাঠিয়ে shutdownOutput() করলে সার্ভার দুটি লাইন পড়ে স্রোতের শেষ পেয়েছে, আর তার পাঠানো “done” client ঠিকই পড়েছে।
Timeout — নেটওয়ার্ক কোডের সবচেয়ে ভোলা জিনিস
ডিফল্টে Java-র socket-এ কোনো timeout নেই। অন্য প্রান্ত চুপ থাকলে read() চিরকাল অপেক্ষা করবে। মোবাইল নেটওয়ার্কে সংযোগ মাঝপথে নীরবে মরে যাওয়া খুব সাধারণ — timeout না থাকলে আপনার thread চিরকাল একটি মরা সংযোগের পাহারায়। দুটি timeout দিন:
- connect timeout:
socket.connect(address, 1_000)— ১ সেকেন্ডে সংযোগ না হলেSocketTimeoutException. (new Socket(host, port)constructor-এ timeout দেওয়ার উপায় নেই, তাই খালিnew Socket()বানিয়ে পরেconnect.) - read timeout:
socket.setSoTimeout(2_000)— প্রতিটিreadসর্বোচ্চ ২ সেকেন্ড অপেক্ষা করবে, তারপর SocketTimeoutException. সাবধান: এটা প্রতি পড়ার সীমা, পুরো কথোপকথনের নয়। কেউ প্রতি ১.৯ সেকেন্ডে একটি করে byte পাঠালে সংযোগটি অনন্তকাল বেঁচে থাকবে (অংশ ৫-এর নিরাপত্তা)।
দুটি সাধারণ ব্যর্থতা — বার্তা চিনে রাখুন
একটি port-এ কেউ listen করছে না, অথচ সেখানে সংযোগ চাইলে কী হয়? অনলাইন runner-এ একটি port খুলে সাথে সাথে বন্ধ করে, তারপর সেখানে new Socket("127.0.0.1", port) চালালে এই stack trace আসে (মাঝের কয়েকটি লাইন বাদ):
Exception in thread "main" java.net.ConnectException: Connection refused at java.base/sun.nio.ch.Net.connect0(Native Method) at java.base/sun.nio.ch.Net.connect(Net.java:589) ... at java.base/java.net.Socket.<init>(Socket.java:324) at Main.main(prog.java:6)
ConnectException: Connection refused মানে মেশিন পর্যন্ত পৌঁছানো গেছে, কিন্তু সেই port-এ কেউ নেই — OS নিজেই “না” বলেছে। সাধারণ কারণ: সার্ভার চালু হয়নি, ভুল port, বা সার্ভার শুধু 127.0.0.1-এ বাঁধা অথচ আপনি অন্য মেশিন থেকে ডাকছেন। এর বিপরীতে timeout মানে কোনো উত্তরই আসেনি — মেশিন বন্ধ, firewall চুপচাপ packet ফেলে দিচ্ছে, বা পথ নেই। দ্বিতীয় পরীক্ষা: সার্ভার সংযোগ নেয় কিন্তু কিছু লেখে না, আর client setSoTimeout(300) দিয়ে পড়তে চায়। ফল java.net.SocketTimeoutException: Read timed out. দুটিই IOException-এর উপশ্রেণি, তাই checked — Java আপনাকে এদের কথা ভুলতে দেয় না।
Framing — TCP message চেনে না
এটি নেটওয়ার্ক প্রোগ্রামিংয়ের সবচেয়ে গুরুত্বপূর্ণ ধারণাগুলোর একটি। TCP একটি byte-এর স্রোত — একটানা পানির নলের মতো। আপনি দুবার আলাদা করে “TEA” আর “DAL” লিখলে অন্য প্রান্ত একবার read()-এ “TEADAL” পেতে পারে, অথবা প্রথমবার “TE” আর পরেরবার “ADAL”. অনলাইন runner-এ সত্যিই পরীক্ষা করা হয়েছে: দুটি আলাদা write-এর পর সার্ভারের একটি read(buf) পেয়েছে একসাথে TEADAL. লেখার সীমানা পথে হারিয়ে যায়; সীমানা ঠিক করার দায়িত্ব application-এর। একে বলে framing. দুটি প্রচলিত উপায়:
বিভাজক — newline
প্রতিটি message-এর শেষে একটি বিশেষ চিহ্ন, সাধারণত \n. readLine() ঠিক এটাই করে। সহজ, মানুষও পড়তে পারে; HTTP-র header, SMTP আর FTP-র কমান্ড এভাবে লাইনভিত্তিক। শর্ত: message-এর ভেতরে চিহ্নটি থাকতে পারবে না, থাকলে escape করতে হয়।
দৈর্ঘ্য আগে — length prefix
আগে ৪ byte-এ message-এর দৈর্ঘ্য, তারপর ঠিক ততগুলো byte. ভেতরে যেকোনো byte থাকতে পারে — ছবি, binary. গ্রহণকারী আগে দৈর্ঘ্য পড়ে, তারপর ঠিক ততটুকু পড়া শেষ না হওয়া পর্যন্ত পড়তে থাকে।
binary protocol// পাঠানো (out = DataOutputStream): আগে দৈর্ঘ্য, তারপর byte
byte[] msg = "চা-পাতা ২৫০ গ্রাম".getBytes(UTF_8); // ১৭টি char, কিন্তু ৪৫টি byte
out.writeInt(msg.length);
out.write(msg);
out.flush();
// পড়া (in = DataInputStream)
int len = in.readInt();
if (len < 0 || len > 64_000) throw new IOException("সন্দেহজনক দৈর্ঘ্য: " + len); // অবিশ্বাস!
byte[] buf = new byte[len];
in.readFully(buf); // ঠিক len byte না আসা পর্যন্ত পড়ে
String text = new String(buf, UTF_8);দৈর্ঘ্য byte-এ গোনা, অক্ষরে নয় — বাংলার প্রতিটি অক্ষর UTF-8-এ সাধারণত ৩ byte (runner-এ মাপা: ৪৫)। readFully “যতটুকু আছে” দিয়ে ফেরে না, পুরোটা পড়ে। আর দৈর্ঘ্য যাচাই বাধ্যতামূলক: দুষ্ট client 2,000,000,000 পাঠালে যাচাই ছাড়া কোড ২ GB-র array বানাতে গিয়ে সার্ভারের মেমরি শেষ করবে।
৫ · অনেক client একসাথে — virtual thread, NIO আর নিরাপত্তা
একটি thread-এ লুপে accept() করে সেখানেই client সামলালে একজন ক্রেতা কথা বলার সময় বাকিরা দরজায় দাঁড়িয়ে থাকেন। সমাধান পর্ব ৪২-এর thread-per-request ধাঁচ — এখানে প্রতি সংযোগে একটি thread. platform thread-এ কয়েক হাজার সংযোগেই সার্ভার থমকে যেত; virtual thread দিয়ে সহজ ব্লকিং কোডেই হাজারো সংযোগ:
try (var server = new ServerSocket(5000);
var pool = Executors.newVirtualThreadPerTaskExecutor()) {
while (true) {
Socket client = server.accept(); // পরের ক্রেতার অপেক্ষা
pool.submit(() -> handle(client)); // প্রতি সংযোগে নতুন একটি virtual thread
}
}- accept লুপ
server.accept()একটি virtual thread - VT-১ · রহিম
handle()readLine()-এ অপেক্ষা - VT-২ · সুমি
handle()দাম জানতে চাইল - VT-৩ · করিম
handle()নতুন সংযোগ - carrier
C1, C2core-এর সমান
accept লুপ: server.accept() … কেউ নেই
virtual thread নেমে গেছে — কোনো carrier আটকে নেই
accept() → socket-রহিম pool.submit(handle) → VT-১ তৈরি accept লুপ: সাথে সাথে আবার accept() VT-১: readLine() … রহিম এখনো টাইপ করছেন
accept() → socket-সুমি → VT-২
VT-২: "TEA" পেল → "TEA -> 120 টাকা" পাঠাল
VT-১: এখনো readLine()-এ — কেউ কাউকে আটকাচ্ছে না
accept() → socket-করিম → VT-৩
এখন ৩টি সংযোগ, ৩টি virtual thread, আর ১টি accept লুপ
১০,০০০ জন হলেও নকশা একই
OS thread (carrier): মাত্র ২টি
বেশিরভাগ virtual thread socket-এর অপেক্ষায় heap-এ
ডেটা এলে সেই VT কোনো carrier-এ উঠে কাজ সেরে আবার নামে
কোড ব্লকিং, খরচ async-এর কাছাকাছি
- শুধু দরজা। একটি thread শুধু
accept()করে; কেউ না এলে virtual thread হিসেবে নেমে অপেক্ষা করে। - প্রথম ক্রেতা। রহিমের
Socketএকটি নতুন virtual thread পায়, লুপ সাথে সাথে দরজায় ফেরে। রহিম টাইপ করতে দেরি করছেন — VT-১readLine()-এ অপেক্ষায়। - একজন ধীর, অন্যজন দ্রুত। সুমি VT-২-তে সাথে সাথে দাম পান; রহিমের ধীরতা তাঁকে একটুও দেরি করায় না।
- যত খুশি। করিমের জন্য VT-৩. pool-এর সীমা নেই (পর্ব ৪২); সীমা লাগলে
Semaphore. - নিচের যন্ত্র। OS দেখে মাত্র কয়েকটি carrier thread; socket-এর অপেক্ষায় virtual thread নেমে যায়, ডেটা এলে ওঠে। কোড ব্লকিং, খরচ event-ভিত্তিক সার্ভারের কাছাকাছি।
chat-এর মতো broadcast — ধারণা
একটি পরিবার-গ্রুপের chat ভাবুন: একজন লিখলে সবাই দেখবে। প্রতি সংযোগের thread নিজের client-এর লাইন পড়ে, আর একটি thread-safe সেটে রাখা সবার PrintWriter-এ লিখে দেয়:
static final Set<PrintWriter> clients = ConcurrentHashMap.newKeySet(); // সবার লেখার পথ
static void chat(Socket s, String name) { // প্রতি সংযোগে একটি virtual thread
try (s; var in = reader(s); var out = writer(s)) {
clients.add(out);
try {
for (String line; (line = in.readLine()) != null; )
for (PrintWriter c : clients) c.println(name + ": " + line);
} finally { clients.remove(out); } // চলে গেলে তালিকা থেকে বাদ
} catch (IOException e) { }
}দুটি আসল সমস্যা ভাবার মতো: কারও নেটওয়ার্ক ধীর হলে তাঁর socket-এ লেখা আটকে পাঠানো লুপও আটকে যেতে পারে — বড় সিস্টেমে প্রতি client-এর জন্য আলাদা পাঠানোর সারি রাখা হয়। আর PrintWriter ভুল হলে exception ছোড়ে না, শুধু checkError()-এ জানায়।
আরেক পথ — NIO selector আর event loop
Virtual thread-এর আগে হাজারো সংযোগের পথ ছিল Java 1.4-এর NIO: non-blocking SocketChannel-গুলো একটি Selector-এ নিবন্ধন করা হয়, আর একটি thread বারবার জিজ্ঞেস করে “কোন সংযোগে এখন ডেটা পড়া যাবে?” — তারপর শুধু সেগুলো সামলায়। এই event loop ধাঁচে অল্প thread লাখো সংযোগ চালায়; Netty এর ওপর বানানো, আর আজও বহু উচ্চ-ক্ষমতার সার্ভার আর driver-এর ভিত্তি। দাম কোডে: প্রতিটি সংযোগের অবস্থা আর অর্ধেক পড়া message হাতে রাখা, callback-এর শিকলে চিন্তা। নতুন সাধারণ সার্ভারে virtual thread + ব্লকিং socket প্রায়ই যথেষ্ট আর অনেক সহজ (ভাবনার প্রশ্ন ৪)।
নিরাপত্তা — client-কে বিশ্বাস করবেন না
ইন্টারনেটে খোলা সার্ভারে যে কেউ যেকোনো byte পাঠাতে পারে। নিয়মগুলো:
- আকারের সীমা: লাইন আর message-এর সর্বোচ্চ আকার ঠিক করুন।
readLine()লাইন শেষ না হওয়া পর্যন্ত সব মেমরিতে জমায় — newline ছাড়া ১ GB পাঠালে লাইন হাতে আসার আগেই মেমরি শেষ; উপরের “১০ অক্ষর” যাচাই পড়ার পরে হয়, তাই আসল সার্ভারে সীমিত পড়ার ব্যবস্থা লাগে। - ধীর client-এর বিরুদ্ধে timeout: প্রতি পড়ার timeout-এর পাশাপাশি পুরো request-এর একটি মোট সময়সীমা। খুব ধীরে byte পাঠিয়ে হাজারো সংযোগ খুলে রাখা একটি পরিচিত denial-of-service কৌশল (Slowloris নামে পরিচিত)।
- সংযোগের সংখ্যা: virtual thread সস্তা, কিন্তু প্রতিটি সংযোগের buffer আর OS-এর file descriptor নয় — মোট আর প্রতি-IP সীমা রাখুন।
- বিষয়বস্তু যাচাই: client-এর পাঠানো সবকিছু যাচাই করে তবে ব্যবহার; দাম, ব্যালেন্স বা অনুমতি ঠিক হবে সার্ভারের ডেটায়।
৬ · UDP — সংযোগ ছাড়া, নিশ্চয়তা ছাড়া, কিন্তু দ্রুত
UDP একেবারে আলাদা দর্শন। কোনো সংযোগ নেই, handshake নেই। আপনি একটি datagram — নির্দিষ্ট আকারের একটি স্বয়ংসম্পূর্ণ packet — ঠিকানাসহ ছুড়ে দেন। পৌঁছাতে পারে, না-ও পারে; দুটি পাঠালে উল্টো ক্রমে পৌঁছাতে পারে; কেউ জানাবে না। বিনিময়ে পাওয়া যায় গতি আর সরলতা, আর একটি সুবিধা: UDP-তে message-এর সীমানা থাকে — একটি পাঠানো মানে একটি পুরো datagram গ্রহণ (পৌঁছালে)।
// পাঠানো
try (var socket = new DatagramSocket()) {
byte[] data = "TEA".getBytes(UTF_8);
socket.send(new DatagramPacket(data, data.length, InetAddress.getByName("192.168.0.20"), 9876));
}
// গ্রহণ (অন্য প্রোগ্রামে)
try (var socket = new DatagramSocket(9876)) {
byte[] buf = new byte[512];
var packet = new DatagramPacket(buf, buf.length);
socket.setSoTimeout(2_000); // না এলে চিরকাল অপেক্ষা নয়
socket.receive(packet); // একটি পুরো datagram, অথবা timeout
String msg = new String(packet.getData(), 0, packet.getLength(), UTF_8);
}runner-এ 127.0.0.1-এ এই জোড়া চালালে গ্রহণকারী ঠিক TEA পায় — তবে loopback-এ হারানো প্রায় হয় না, আসল নেটওয়ার্কে হয়। UDP মানায় যেখানে দেরিতে আসা ডেটা না আসার সমান: ভিডিও কল, অনলাইন গেম, DNS-এর ছোট প্রশ্ন-উত্তর। নির্ভরযোগ্যতা লাগলে application নিজে বানায় — HTTP/3-এর নিচের QUIC এভাবে UDP-র ওপর নিজের নির্ভরযোগ্যতা গড়ে।
| বিষয় | TCP | UDP |
|---|---|---|
| সংযোগ | আগে handshake, পরে কথা | নেই — সরাসরি পাঠানো |
| নির্ভরযোগ্যতা | হারালে আবার পাঠায়, ক্রম ঠিক রাখে | হারাতে পারে, ক্রম বদলাতে পারে |
| সীমানা | byte-এর স্রোত — framing নিজে করুন | প্রতিটি datagram আলাদা message |
| Java class | Socket, ServerSocket | DatagramSocket, DatagramPacket |
| উদাহরণ | HTTP/1.1, HTTP/2, ডেটাবেস, SSH | DNS, ভিডিও কল, গেম, QUIC |
৭ · HttpClient গভীরে — request, timeout, redirect, async আর retry
বাস্তবে বেশিরভাগ Java প্রোগ্রাম নিজের TCP protocol বানায় না — অন্য সার্ভিসের HTTP API ডাকে। পর্ব ৫১-এ JDK-র ছোট HttpServer আর একটি সাধারণ GET দেখবেন; এখানে client-এর দিকটা গভীরে। java.net.http.HttpClient Java 9-এ incubator হিসেবে এসে Java 11-এ স্থায়ী হয় (JEP 321), পুরোনো কষ্টকর HttpURLConnection-এর জায়গায়। তিনটি অংশ:
HttpClient— একবার বানিয়ে বারবার ব্যবহার করুন (thread-safe; ভেতরে সংযোগ জমিয়ে রেখে পুনর্ব্যবহার করে)। এখানে সাধারণ নিয়ম: connect timeout, redirect নীতি, HTTP সংস্করণ।- HttpRequest — একটি request: URI, method, header, body, আর এই request-এর timeout. immutable, তাই আবার পাঠানো নিরাপদ।
HttpResponse<T>— status code, header, body. body কোন রূপে চাই, তা বলে BodyHandler:ofString(),ofByteArray(),ofFile(path),ofLines(), বা ফেলে দিতেdiscarding().
JSON পাঠানো — POST আর BodyPublisher
var post = HttpRequest.newBuilder(URI.create(base + "/payments"))
.header("Content-Type", "application/json") // body কোন ফরম্যাটে
.POST(HttpRequest.BodyPublishers.ofString("{\"amount\":500}"))
.build();
var res = client.send(post, HttpResponse.BodyHandlers.ofString());
System.out.println(res.statusCode() + " " + res.body());runner-এ একটি স্থানীয় সার্ভার (যা পাওয়া body আর Content-Type ফেরত পাঠায়) এর বিরুদ্ধে আউটপুট: 201 {"received":{"amount":500},"ct":"application/json"}. বাস্তবে JSON হাতে না লিখে পর্ব ৩৯-এর মতো library দিয়ে বানান।
যে ডিফল্টগুলো জানা জরুরি
| বিষয় | ডিফল্ট | কী করবেন |
|---|---|---|
| connect timeout | নেই — OS-এর দীর্ঘ সীমা পর্যন্ত | newBuilder().connectTimeout(...) |
| request timeout | নেই — উত্তরের জন্য অনন্ত অপেক্ষা | প্রতিটি request-এ .timeout(...); পার হলে HttpTimeoutException |
| redirect | NEVER — 301/302 নিজেই ফেরত আসে | .followRedirects(Redirect.NORMAL) (https থেকে http-তে নামা বাদে অনুসরণ করে) |
| HTTP সংস্করণ | HTTP_2 চেষ্টা করে | সার্ভার না পারলে নিজে থেকেই HTTP/1.1-এ নামে; res.version() দেখায় আসলে কোনটি হলো |
| status code | 4xx/5xx-এ কোনো exception নেই | statusCode() নিজে দেখুন |
শেষ সারিটি সবচেয়ে বেশি ভুলের উৎস: send শুধু নেটওয়ার্ক ব্যর্থতায় exception ছোড়ে; 500 বা 404-ও “সফল” উত্তর, body-তে হয়তো error-এর JSON. status code না দেখে body-কে ডেটা ধরলে ভুল তথ্য ছড়াবে। runner-এ দেখা গেছে, ডিফল্ট client 301 পেয়ে সেটাই ফেরত দেয়, আর Redirect.NORMAL দেওয়া client নতুন ঠিকানা থেকে আসল উত্তর আনে।
বাস্তব উদাহরণ — মুদ্রার বিনিময় হার আনা
একটি রপ্তানি প্রতিষ্ঠানের হিসাব-সফটওয়্যার প্রতিদিন সকালে ডলার-টাকার হার একটি API থেকে আনে। এই কোডটি অনলাইনে চলবে না (বাইরের নেটওয়ার্ক নেই, ঠিকানাটিও উদাহরণ):
var client = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(5))
.followRedirects(HttpClient.Redirect.NORMAL)
.build();
var req = HttpRequest.newBuilder(URI.create("https://api.example.com/v1/rates?base=USD&symbols=BDT"))
.timeout(Duration.ofSeconds(10))
.header("Accept", "application/json")
.header("Authorization", "Bearer " + System.getenv("RATES_API_KEY")) // key কোডে নয়
.GET().build();
HttpResponse<String> res = client.send(req, HttpResponse.BodyHandlers.ofString());
if (res.statusCode() != 200) throw new IOException("rate API: HTTP " + res.statusCode());
System.out.println(res.body());প্রত্যাশিত আউটপুট একটি JSON লাইন (সংখ্যা উদাহরণ মাত্র): {"base":"USD","rates":{"BDT":122.50}}. API key আসছে environment variable থেকে — কোডে বা Git-এ কখনো নয়। এবার একই ধাঁচ, অনলাইনে চালানো যায় এমনভাবে: প্রোগ্রাম নিজেই একটি স্থানীয় “হার সার্ভার” চালায়, যা প্রথম দুবার ব্যস্ত (503). client বাড়তে থাকা বিরতিতে — exponential backoff — আবার চেষ্টা করে:
import com.sun.net.httpserver.HttpServer;
import java.net.*;
import java.net.http.*;
import java.time.Duration;
import java.util.concurrent.atomic.AtomicInteger;
import static java.nio.charset.StandardCharsets.UTF_8;
class Main {
public static void main(String[] args) throws Exception {
var calls = new AtomicInteger();
var server = HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 0);
server.createContext("/rate", ex -> { // ভান: প্রথম দুবার সার্ভার ব্যস্ত
boolean busy = calls.incrementAndGet() < 3;
byte[] body = (busy ? "{\"error\":\"busy\"}" : "{\"from\":\"USD\",\"to\":\"BDT\",\"rate\":122.50}").getBytes(UTF_8);
ex.getResponseHeaders().set("Content-Type", "application/json");
ex.sendResponseHeaders(busy ? 503 : 200, body.length);
try (var out = ex.getResponseBody()) { out.write(body); }
});
server.start();
try (var client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(2)).build()) {
var req = HttpRequest.newBuilder(URI.create("http://127.0.0.1:" + server.getAddress().getPort() + "/rate?from=USD&to=BDT"))
.timeout(Duration.ofSeconds(3)).header("Accept", "application/json").GET().build();
long waitMs = 100;
for (int attempt = 1; attempt <= 4; attempt++) {
var res = client.send(req, HttpResponse.BodyHandlers.ofString(UTF_8));
System.out.println("চেষ্টা " + attempt + ": " + res.statusCode() + " " + res.version() + " " + res.body());
if (res.statusCode() == 200) break;
if (res.statusCode() < 500) throw new IllegalStateException("আবার চেষ্টা করে লাভ নেই: " + res.statusCode());
Thread.sleep(waitMs); waitMs *= 2; // exponential backoff: 100, 200 ms ...
}
} finally { server.stop(0); }
}
}আউটপুট তিন লাইন: চেষ্টা 1: 503 HTTP_1_1 {"error":"busy"}, চেষ্টা 2: 503 HTTP_1_1 {"error":"busy"}, আর চেষ্টা 3: 200 HTTP_1_1 {"from":"USD","to":"BDT","rate":122.50}. কয়েকটি জিনিস একসাথে দেখা যাচ্ছে। (১) দুই রকম timeout: client-এ connectTimeout, request-এ timeout. (২) HTTP_1_1: client ডিফল্টে HTTP/2 চায়, কিন্তু JDK-র ছোট সার্ভার শুধু HTTP/1.1 জানে — তাই client নিজে থেকেই নেমে এসেছে, কোনো error ছাড়া। (৩) status code অনুযায়ী সিদ্ধান্ত: 200 হলে থামো; 5xx সার্ভারের সাময়িক সমস্যা, তাই অপেক্ষা করে আবার; 4xx মানে আমাদের request-ই ভুল, আবার পাঠালেও একই ফল — তাই থামো। (৪) অপেক্ষা ১০০, তারপর ২০০ মিলিসেকেন্ড — প্রতিবার দ্বিগুণ, আর মোট চেষ্টা সর্বোচ্চ ৪, যাতে লুপ কখনো অনন্ত না হয়। (৫) এটা GET — idempotent, তাই আবার পাঠানো নিরাপদ (পর্ব ৫১)। (৬) Java 21 থেকে HttpClient একটি AutoCloseable, তাই try-with-resources-এ বন্ধ হয়; আর finally-তে সার্ভারও। port প্রতিবার আলাদা বলে ছাপা হয়নি।
sendAsync — অপেক্ষা না করে পাঠানো
send উত্তর না আসা পর্যন্ত thread আটকে রাখে — virtual thread-এ যা সম্পূর্ণ ঠিক আছে। আর sendAsync সাথে সাথে একটি পর্ব ৪১-এর CompletableFuture ফেরত দেয়, উত্তর এলে সেটি পূর্ণ হয়:
CompletableFuture<String> f = client.sendAsync(post, HttpResponse.BodyHandlers.ofString())
.thenApply(HttpResponse::body); // উত্তর এলে body বের করো
// … এর মধ্যে অন্য কাজ …
String body = f.get(3, TimeUnit.SECONDS); // অপেক্ষাতেও সীমাঅনেক request একসাথে পাঠিয়ে CompletableFuture.allOf(...) দিয়ে অপেক্ষা করা যায়; তবে Java 21-এ একই কাজ প্রায়ই আরও পরিষ্কার হয় virtual thread + সাধারণ send দিয়ে (পর্ব ৪২)। পার্থক্য মূলত পাঠযোগ্যতায়।
বাস্তব উদাহরণ — মোবাইল পেমেন্টের অবস্থা জানা
একটি ই-কমার্স সাইট গ্রাহককে পেমেন্ট গেটওয়েতে পাঠিয়েছে। গেটওয়ে বলে “লেনদেন প্রক্রিয়াধীন”। সাইট প্রতি কয়েক সেকেন্ডে অবস্থা জানতে চায় — কিন্তু চিরকাল নয়, আর গেটওয়ের ওপর ঝড় তুলে নয়:
String pollStatus(HttpClient client, URI statusUri) throws Exception {
Instant deadline = Instant.now().plusSeconds(30); // মোট সময়সীমা
long waitMs = 500;
while (Instant.now().isBefore(deadline)) {
var req = HttpRequest.newBuilder(statusUri).timeout(Duration.ofSeconds(5)).GET().build();
try {
var res = client.send(req, HttpResponse.BodyHandlers.ofString());
int code = res.statusCode();
if (code == 200 && !res.body().contains("\"PENDING\"")) return res.body(); // বাস্তবে JSON parse করুন
if (code >= 400 && code < 500 && code != 429) throw new IllegalStateException("HTTP " + code);
} catch (HttpTimeoutException e) { } // এই চেষ্টা ধীর — পরে আবার
Thread.sleep(waitMs + ThreadLocalRandom.current().nextLong(waitMs / 2)); // backoff + jitter
waitMs = Math.min(waitMs * 2, 8_000);
}
throw new TimeoutException("৩০ সেকেন্ডেও ফল জানা গেল না — পরে আবার দেখুন");
}তিন স্তরের সীমা: প্রতিটি request ৫ সেকেন্ড, বিরতি দ্বিগুণ হতে হতে সর্বোচ্চ ৮ সেকেন্ড, সব মিলিয়ে ৩০ সেকেন্ড। বিরতিতে একটু এলোমেলো সময় — jitter — যাতে হাজারো client একই মুহূর্তে আবার চেষ্টা করে গেটওয়েকে ডুবিয়ে না দেয়। 429 Too Many Requests মানে “ধীরে চলুন”, তাই আবার চেষ্টা চলে (Retry-After header থাকলে মানুন)। retry এখানে নিরাপদ কারণ এটা GET; “টাকা পাঠাও” POST আবার পাঠাতে পর্ব ৫১-এর idempotency key লাগবে, নইলে দুবার টাকা কাটতে পারে।
৮ · WebSocket আর TLS — সারাক্ষণ খোলা লাইন, আর নিরাপদ লাইন
WebSocket — দুই দিক থেকে যখন খুশি
HTTP প্রশ্ন-উত্তরের ভাষা: client জিজ্ঞেস না করলে সার্ভার কিছু বলতে পারে না। কিন্তু chat, শেয়ারবাজারের দাম, ক্রিকেটের লাইভ স্কোরে নতুন খবর এলেই পাঠানো দরকার। পুরোনো সমাধান polling: প্রতি কয়েক সেকেন্ডে “নতুন কিছু?” — বেশিরভাগ উত্তর “না”, তবু প্রতিবার পুরো একটি request.
WebSocket সমস্যাটা মূলে সমাধান করে। শুরু হয় একটি সাধারণ HTTP request দিয়ে, যাতে লেখা থাকে Upgrade: websocket. সার্ভার রাজি হলে উত্তর দেয় 101 Switching Protocols — এই HTTP upgrade-এর পর একই TCP সংযোগ আর HTTP থাকে না, হয়ে যায় একটি full-duplex চ্যানেল: দুই পক্ষই যখন খুশি ছোট ছোট message (frame) পাঠাতে পারে। লক্ষ করুন, WebSocket নিজেই framing করে দেয় — TCP-র ওপর message-এর সীমানা এখানে protocol-এর কাজ।
Java 11 থেকে java.net.http-এ WebSocket client আছে। সার্ভার বানাতে JDK-তে কিছু নেই — লাগে Jakarta WebSocket বা Spring-এর মতো library, তাই নিচের কোডটি অনলাইনে চালানো যাবে না (ঠিকানাটিও উদাহরণ):
var listener = new WebSocket.Listener() {
@Override
public CompletionStage<?> onText(WebSocket ws, CharSequence data, boolean last) {
System.out.println("নতুন দাম: " + data);
ws.request(1); // পরের message পেতে প্রস্তুত
return null;
}
};
WebSocket ws = HttpClient.newHttpClient().newWebSocketBuilder()
.buildAsync(URI.create("wss://prices.example.com/live"), listener)
.join(); // upgrade শেষ হওয়া পর্যন্ত অপেক্ষা
ws.sendText("{\"subscribe\":\"TEA\"}", true);সার্ভার দাম বদলালেই message পাঠালে প্রত্যাশিত আউটপুট — client কিছু জিজ্ঞেস না করেই প্রতিটি নতুন দামে একটি লাইন: নতুন দাম: {"code":"TEA","price":120}, পরে নতুন দাম: {"code":"TEA","price":125}. request(1) হলো প্রবাহ নিয়ন্ত্রণ — listener আর কতগুলো message নিতে প্রস্তুত (ডিফল্ট onOpen শুরুতে একটি চায়)। বড় message টুকরোয় এলে শেষ টুকরোয় last হয় true. wss:// মানে TLS-এর ভেতর দিয়ে WebSocket.
TLS আর HTTPS — পথে কেউ পড়তে বা বদলাতে না পারে
সাধারণ TCP-তে ডেটা খোলা চিঠির মতো যায় — পথের যেকোনো রাউটার, একই Wi-Fi-র যেকোনো যন্ত্র পড়তে পারে, এমনকি বদলেও দিতে পারে। TLS TCP-র ওপর একটি নিরাপদ স্তর বসায়, তিনটি নিশ্চয়তা দিয়ে: গোপনীয়তা (এনক্রিপশন), অখণ্ডতা (বদলালে ধরা পড়ে), আর পরিচয় — আপনি সত্যিই bank.example.com-এর সাথে কথা বলছেন, মাঝের কোনো প্রতারকের সাথে নয়। HTTPS মানে TLS-এর ভেতরে HTTP.
পরিচয়ের ভিত্তি certificate: সার্ভারের public key আর নাম, একটি certificate authority (CA)-র ডিজিটাল সইসহ। Java-র সাথে বিশ্বস্ত CA-দের তালিকা (truststore) আসে, আর HttpClient https:// ঠিকানায় নিজে থেকেই যাচাই করে — সই বৈধ কি না, মেয়াদ আছে কি না, নাম মেলে কি না। ব্যর্থ হলে SSLHandshakeException, আর একটি byte-ও পাঠানো হয় না।
certificate যাচাই কখনো বন্ধ করবেন না। পরীক্ষার সার্ভারে self-signed certificate থাকলে SSLHandshakeException আসে, আর ইন্টারনেটে (এবং AI-এর উত্তরে) প্রায়ই “সমাধান” মেলে: সব certificate মেনে নেওয়া একটি TrustManager, বা নাম যাচাই বন্ধ। এতে এনক্রিপশন থাকে, কিন্তু কার সাথে এনক্রিপ্ট করছেন তা আর জানেন না — একই ক্যাফের Wi-Fi-তে বসা যে কেউ মাঝখানে বসে সব পড়তে পারে। আর “শুধু টেস্টের জন্য” কোড প্রায়ই production-এ চলে যায়। সঠিক পথ: পরীক্ষার certificate একটি আলাদা truststore-এ যোগ করে শুধু সেটাকেই বিশ্বাস করা, বা Let's Encrypt-এর মতো আসল certificate. পর্ব ৫৩-এ Spring Security-তে নিরাপত্তা আরও আসবে।
C-র Berkeley socket API — socket(), bind(), listen(), accept(), connect() — Java-র নিচে ঠিক এটাই চলে; ServerSocket প্রথম তিনটিকে এক constructor-এ মুড়ে দেয়, আর ভুলের জন্য -1 আর errno-র বদলে exception দেয়। Python-এর socket module প্রায় হুবহু C-র মতো, আর HTTP-র জন্য Python-এ সাধারণত requests বা httpx — Java-র HttpClient-এর কাজ একই, তবে Java-রটা JDK-র ভেতরেই। গভীরে দেখতে C-র TCP socket আর Python-এর HTTP API পর্ব দেখুন।
- TCP আর UDP-র পার্থক্য? — TCP সংযোগভিত্তিক, নির্ভরযোগ্য, ক্রম-ঠিক byte-স্রোত; UDP সংযোগহীন, datagram হারাতে বা ক্রম বদলাতে পারে, কিন্তু দ্রুত ও হালকা।
ServerSocketআরSocket-এর পার্থক্য? —ServerSocketএকটি port-এ নতুন সংযোগের অপেক্ষা করে;accept()প্রতিটি client-এর জন্য একটিSocketদেয়, যা দিয়ে আসল ডেটা আদান-প্রদান হয়।- Three-way handshake কী? — TCP সংযোগ শুরুর তিনটি packet: SYN, SYN-ACK, ACK.
- “Connection refused” আর timeout-এর পার্থক্য? — refused: মেশিন পৌঁছানো গেছে কিন্তু port-এ কেউ নেই; timeout: কোনো উত্তরই আসেনি।
java.net.http.HttpClientকোন সংস্করণ থেকে? — Java 11 (JEP 321); ডিফল্টে HTTP/2 চায়, redirect অনুসরণ করে না।
৯ · AI-এর যুগে — নেটওয়ার্ক কোডের লুকানো ফাঁদ
নেটওয়ার্ক কোড পরীক্ষার সময় সব ঠিক চলে — নিজের ল্যাপটপে, দ্রুত নেটওয়ার্কে, একজন ব্যবহারকারী নিয়ে। ভুল ধরা পড়ে প্রথম ধীর মোবাইল সংযোগে, প্রথম হাজার ব্যবহারকারীতে, বা প্রথম দুষ্ট client-এ। AI-এর লেখা কোড প্রায়ই ঠিক এই “ভালো দিনের” কোড:
| AI যা করে | কেন সমস্যা | আপনি যা করবেন |
|---|---|---|
কোনো timeout নেই — new Socket(host, port), timeout ছাড়া HttpClient | মরা সংযোগে thread চিরকাল আটকে; ধীরে ধীরে সার্ভার থমকে যায় | connect, read আর মোট সময়সীমা — তিনটিই |
new InputStreamReader(in) — charset ছাড়া | পুরোনো Java বা অন্য প্ল্যাটফর্মে বাংলা ভেঙে যায় | UTF_8 সবসময় স্পষ্ট |
একটি read(buf)-কে একটি পুরো message ধরে নেওয়া | TCP সীমানা রাখে না; লোডে বা ধীর নেটওয়ার্কে message জোড়া লাগে বা ভাঙে | newline বা length-prefix framing, readFully |
| certificate ভুলে “trust all” TrustManager | মাঝখানে বসা আক্রমণের দরজা খোলা | সঠিক truststore; যাচাই কখনো বন্ধ নয় |
newFixedThreadPool(10)-এ প্রতি client, বা এক thread-এ সব client | ১১তম client অপেক্ষায়; একজন ধীর হলে সবাই আটকে | প্রতি সংযোগে virtual thread, সীমা আলাদা করে |
status code না দেখে res.body()-কে ডেটা ধরা; সব ব্যর্থতায় অন্ধ retry | error-এর JSON ডেটা হয়ে যায়; POST-এর retry-তে দ্বিগুণ লেনদেন | 2xx যাচাই; retry শুধু 5xx/429/timeout-এ, idempotent request-এ, backoff আর সীমা দিয়ে |
| client-এর পাঠানো দৈর্ঘ্য বা লাইনের আকারে কোনো সীমা নেই | এক request-এই মেমরি শেষ | সর্বোচ্চ আকার যাচাই, পড়ার আগেই |
নেটওয়ার্ক কোড লেখাতে AI-কে এমনভাবে বলুন:
AI-এর নেটওয়ার্ক কোড হাতে পেলে — যাচাইয়ের চেকলিস্ট
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন। এগুলোই আপনাকে কোড-লেখক থেকে সিস্টেম-চিন্তক বানাবে।
প্র ০১১৯৭৪ সালে Vint Cerf আর Bob Kahn যে নকশা প্রস্তাব করেন, তা কেন আজও প্রায় অপরিবর্তিত চলছে? আর ১৯৮৩ সালের ARPANET-এর “বদলের দিন” কী শিখিয়েছিল?
সমস্যাটা কী ছিল। ১৯৭০-এর দশকের শুরুতে আমেরিকায় ARPANET চলছিল, পাশাপাশি তৈরি হচ্ছিল রেডিও আর স্যাটেলাইটভিত্তিক packet নেটওয়ার্ক। প্রতিটির নিজের নিয়ম, packet-এর আকার, নির্ভরযোগ্যতা আলাদা। প্রশ্ন ছিল: এই ভিন্ন ভিন্ন নেটওয়ার্ককে কীভাবে জুড়ে একটি “নেটওয়ার্কের নেটওয়ার্ক” বানানো যায়, অথচ কাউকে নিজের ভেতরটা বদলাতে না হয়?
Cerf ও Kahn-এর উত্তর। ১৯৭৪ সালের মে মাসে IEEE Transactions on Communications-এ তাঁদের প্রবন্ধ “A Protocol for Packet Network Intercommunication” প্রকাশিত হয়। মূল ভাবনাগুলো: (১) মাঝের নেটওয়ার্ক বোকা থাকবে — শুধু packet যতটা পারে পৌঁছাবে, কোনো নিশ্চয়তা ছাড়া। (২) নির্ভরযোগ্যতা, ক্রম, পুনঃপ্রেরণ — সব দুই প্রান্তের কম্পিউটারের দায়িত্ব। (৩) gateway (আজকের রাউটার) শুধু ঠিকানা দেখে packet এক নেটওয়ার্ক থেকে আরেকটিতে দেবে। প্রথমে সবকিছু একটি protocol-এ ছিল; পরে, ১৯৭৮ সালের দিকে, একে ভাগ করা হয় — IP শুধু ঠিকানা আর পথ, TCP নির্ভরযোগ্য স্রোত। এই ভাগের কারণেই UDP সম্ভব হয়: কণ্ঠস্বরের মতো যে ডেটায় পুনঃপ্রেরণ অর্থহীন, তারা TCP এড়িয়ে সরাসরি IP-র ওপর চলতে পারে।
কেন টিকে গেল। এই “প্রান্তে বুদ্ধি, মাঝে সরলতা” নীতি (পরে end-to-end argument নামে পরিচিত) মানে নতুন অ্যাপ বানাতে কারও অনুমতি লাগে না — ওয়েব, ভিডিও কল, WebSocket সবই এসেছে মাঝের রাউটার একটুও না বদলে। অংশ ১-এর stepper-এ দেখেছেন, রাউটার আপনার “TEA” খোলেই না।
বদলের দিন। ARPANET আগে NCP নামে একটি পুরোনো protocol চালাত। ১ জানুয়ারি ১৯৮৩ ছিল নির্ধারিত দিন — এর পর NCP আর চলবে না, সব যন্ত্রকে TCP/IP-তে আসতে হবে। প্রস্তুতিতে বছরের পর বছর লেগেছিল, আর দিনটির পরেও অনেক সাইটকে কিছুদিন সমস্যায় ভুগতে হয়। শিক্ষা: একটি নেটওয়ার্কের সবাইকে একসাথে একটি protocol থেকে আরেকটিতে নেওয়া ভীষণ কঠিন। এ জন্যই IPv4 থেকে IPv6-এ যাত্রা দুই দশক পেরিয়েও চলছে পাশাপাশি, একদিনে নয়।
মূল উপলব্ধি: TCP/IP টিকে আছে কারণ এটি কম জিনিসের প্রতিশ্রুতি দেয় — মাঝের নেটওয়ার্ক শুধু চেষ্টা করে, বাকি দায়িত্ব প্রান্তের; এই সরলতাই নতুন উদ্ভাবনকে অনুমতি ছাড়া বাড়তে দিয়েছে, আর একসাথে সবাইকে বদলানোর কষ্ট দেখিয়েছে কেন protocol বদল ধীরে হয়।
প্র ০২১৯৮৩ সালের 4.2BSD-র “Berkeley socket” API প্রায় সব প্রোগ্রামিং ভাষা কেন নকল করল? Java-র socket class-গুলোতে এর ছাপ কোথায়, আর কোথায় Java আলাদা পথ নিল?
জন্ম। TCP/IP-কে একটি সাধারণ অপারেটিং সিস্টেমে আনার জন্য DARPA ক্যালিফোর্নিয়া বিশ্ববিদ্যালয়, বার্কলের Unix দলকে অর্থায়ন করে। ১৯৮৩ সালে প্রকাশিত 4.2BSD-তে TCP/IP-র সাথে আসে একটি প্রোগ্রামিং interface — socket. নকশাটি Unix-এর দর্শন মেনে চলে: একটি socket একটি file descriptor, তাই একই read আর write দিয়ে ফাইল আর নেটওয়ার্ক দুটোই পড়া-লেখা যায়। কয়েকটি ডাকেই পুরো জীবনচক্র: socket, bind, listen, accept, connect, close.
কেন সবাই নিল। BSD-র কোড সহজে পাওয়া যেত আর ব্যবহারের শর্ত উদার ছিল, তাই অনেক বাণিজ্যিক Unix আর পরে অন্য OS একই interface নেয়; Windows-এর Winsock-ও এর আদলে বানানো, আর পরে POSIX মানে এটি অন্তর্ভুক্ত হয়। একবার যখন সব OS একই ডাক দেয়, তখন প্রতিটি নতুন ভাষার সবচেয়ে সহজ পথ হলো এই ডাকগুলোর ওপর একটি পাতলা মোড়ক — Python-এর socket module প্রায় এক-এক মিলে যায়। একটি interface এভাবে চার দশক ধরে প্রায় অপরিবর্তিত থাকা সফটওয়্যারে বিরল।
Java-য় ছাপ। ServerSocket-এর constructor ভেতরে socket + bind + listen করে — দ্বিতীয় argument “backlog” নামটিই সরাসরি listen-এর argument থেকে। accept() ঠিক আগের মতো আটকে থাকে আর নতুন সংযোগ দেয়। setSoTimeout-এর “SO” এসেছে C-র socket option-এর নাম (SO_RCVTIMEO-জাতীয়) থেকে।
Java কোথায় আলাদা। (১) ভুল জানানো: C-তে প্রতিটি ডাকের পর -1 আর errno দেখতে হয়, যা ভুলে যাওয়া সহজ; Java দেয় checked exception, যা উপেক্ষা করা যায় না। (২) দুটি আলাদা class — সার্ভারের জন্য ServerSocket, সংযোগের জন্য Socket — তাই ভুল socket-এ ভুল ডাক compile-এর সময়ই ধরা পড়ে। (৩) stream-এর মোড়ক: Reader, Writer আর charset দিয়ে লেখা পাঠানো, C-র কাঁচা byte buffer-এর বদলে। (৪) বহনযোগ্যতা: একই Java কোড Linux আর Windows-এ চলে, যদিও নিচের socket-এ ছোট ছোট পার্থক্য আছে।
মূল উপলব্ধি: Berkeley socket জিতেছিল সহজলভ্যতা আর Unix-এর “সবকিছু ফাইল” দর্শনের জোরে; Java সেই একই মডেল রেখে তাকে নিরাপদ মোড়কে দিয়েছে — অর্থাৎ Java socket শেখা মানে আসলে পৃথিবীর প্রায় সব নেটওয়ার্ক কোডের ভিত্তি শেখা।
প্র ০৩Sun Microsystems-এর স্লোগান ছিল “The Network is the Computer”. Java 1.0-তেই কেন java.net ছিল, আর এই উত্তরাধিকার আজকের Java-কে কীভাবে গড়েছে?
স্লোগানের পেছনে। ১৯৮০-এর দশকে Sun-এর John Gage-এর মুখে আসা এই কথাটি কোম্পানির পরিচয় হয়ে ওঠে। Sun-এর workstation শুরু থেকেই নেটওয়ার্কে জোড়া থাকত; NFS-এর মতো প্রযুক্তি দিয়ে অন্য মেশিনের ফাইল নিজের মতো ব্যবহার করা যেত। ভাবনাটা ছিল: একটি কম্পিউটারের আসল ক্ষমতা তার নিজের বাক্সে নয়, নেটওয়ার্কে জোড়া সব সম্পদে।
Java কেন নেটওয়ার্ক-প্রথম। Java-র জন্ম Sun-এর Green প্রকল্পে, প্রথমে টিভি সেট-টপ বক্সের মতো নেটওয়ার্কে জোড়া যন্ত্রের জন্য। ১৯৯৫ সালে প্রকাশের সময় লক্ষ্য দাঁড়ায় ওয়েব: browser-এর ভেতরে নেটওয়ার্ক থেকে নামানো applet. তাই জানুয়ারি ১৯৯৬-এর Java 1.0-তেই java.net package ছিল — Socket, ServerSocket, DatagramSocket, URL, InetAddress. সে সময় C বা C++-এ নেটওয়ার্ক কোড মানে প্রতিটি OS-এর আলাদা API আর বাইরের library; Java-য় একই কোড সব প্ল্যাটফর্মে। bytecode-এর বহনযোগ্যতা আর bytecode verifier-এর নিরাপত্তা ভাবনাও এসেছিল এই কারণেই — অচেনা জায়গা থেকে নেটওয়ার্কে আসা কোড চালাতে হবে।
উত্তরাধিকার। applet হারিয়ে গেছে, কিন্তু নেটওয়ার্ক-প্রথম চিন্তা Java-কে সার্ভারের ভাষা বানিয়েছে: servlet, তারপর enterprise Java, আজ Spring Boot, Kafka, Elasticsearch-এর মতো নেটওয়ার্ক-কেন্দ্রিক সিস্টেম। JDK নিজেও এগিয়েছে — Java 1.4-এ NIO, Java 11-এ নতুন HttpClient, Java 13 আর 15-এ socket-এর ভেতরটা নতুন করে লেখা, আর Java 21-এ virtual thread, যার মূল লক্ষ্যই অনেক নেটওয়ার্ক সংযোগ সস্তায় সামলানো।
একটি সতর্কতা। নেটওয়ার্ককে “কম্পিউটারের অংশ” ভাবার একটি ফাঁদও আছে। Sun-এরই কয়েকজন প্রকৌশলীর নামে পরিচিত “distributed computing-এর ভুল ধারণা”র তালিকা মনে করিয়ে দেয়: নেটওয়ার্ক নির্ভরযোগ্য নয়, latency শূন্য নয়, ব্যান্ডউইথ অসীম নয়, নেটওয়ার্ক নিরাপদ নয়। এই পর্বের timeout, retry আর TLS — সবই এই ভুল ধারণার প্রতিকার।
মূল উপলব্ধি: Java শুরু থেকেই নেটওয়ার্কের জন্য নকশা করা ভাষা, আর সেই কারণেই সার্ভার-জগতে টিকে আছে; কিন্তু নেটওয়ার্কের ডাককে সাধারণ method ডাকের মতো ভাবলে চলবে না — সেটা ধীর, অনিশ্চিত আর আক্রমণযোগ্য।
প্র ০৪১৯৯৯ সালে Dan Kegel “C10K সমস্যা” নামে প্রশ্ন তোলেন: একটি সার্ভার কীভাবে একসাথে ১০,০০০ client সামলাবে? Java এই প্রশ্নের উত্তর কয়েক ধাপে দিয়েছে — ধাপগুলো আর প্রতিটির দাম ব্যাখ্যা করুন।
প্রশ্নটা। ১৯৯৯ সালে Dan Kegel তাঁর একটি বহুল-পঠিত ওয়েবপাতায় লেখেন: হার্ডওয়্যার তখনই ১০,০০০ সংযোগ সামলানোর মতো শক্তিশালী, কিন্তু প্রচলিত সার্ভার নকশা — প্রতি সংযোগে একটি OS process বা thread — সেখানে পৌঁছাতে পারে না। সমস্যা CPU নয়, প্রতিটি অপেক্ষমাণ সংযোগের খরচ: stack-এর মেমরি, OS-এর scheduling আর context switch. তিনি বিভিন্ন OS-এর সমাধান তুলনা করেন, যার কেন্দ্রে ছিল একটি thread-এ অনেক socket-এর ঘটনা জানার উপায় — পরে Linux-এ epoll আর BSD-তে kqueue এই প্রয়োজন থেকেই জনপ্রিয় হয়।
ধাপ ১ — thread-per-connection (Java 1.0). শুরুর Java-য় socket শুধু ব্লকিং; প্রতি সংযোগে একটি thread ছাড়া উপায় ছিল না। কোড সহজ, কিন্তু কয়েক হাজার সংযোগে দেয়াল — ঠিক C10K-এর বর্ণনা।
ধাপ ২ — NIO (Java 1.4, ২০০২). JSR 51-এর মাধ্যমে আসে channel, buffer আর Selector — OS-এর epoll/kqueue-জাতীয় সুবিধার Java মোড়ক। একটি thread হাজারো সংযোগের ঘটনা দেখতে পারে। দাম: কোড উল্টে যায়; প্রতিটি সংযোগের অবস্থা হাতে রাখা, অর্ধেক আসা message জমানো — অংশ ৪-এর framing সমস্যা এখানে আরও কঠিন, কারণ অপেক্ষা করে পড়ার উপায় নেই।
ধাপ ৩ — Netty আর event-loop framework. কাঁচা NIO এত কঠিন আর সূক্ষ্ম bug-ভরা যে ২০০০-এর দশকে Trustin Lee-র শুরু করা Netty-র মতো framework এর ওপর একটি পরিষ্কার নকশা দেয়: event loop, pipeline-এ সাজানো handler, buffer-এর পুনর্ব্যবহার। আজ বহু Java সার্ভার, gRPC-র Java সংস্করণ আর reactive framework (পর্ব ৪২-এ দেখা) এর ওপর চলে। দাম: callback-এর চিন্তা, আর event loop-এ ভুল করে একটি ব্লকিং ডাক দিলে হাজারো সংযোগ একসাথে থমকে যায়।
ধাপ ৪ — virtual thread (Java 21, ২০২৩). ধাপ ১-এর সহজ কোডে ফেরা, ধাপ ২-এর খরচে: virtual thread socket-এর অপেক্ষায় নেমে যায়, আর JDK ভেতরে ঠিক সেই epoll/kqueue-জাতীয় poller ব্যবহার করে। অর্থাৎ selector হারায়নি — শুধু আপনার কোড থেকে JDK-র ভেতরে সরে গেছে।
মূল উপলব্ধি: C10K-এর সমাধান সবসময় একই নিচের যন্ত্র — একটি thread-এ অনেক socket-এর ঘটনা জানা — কিন্তু প্রতিটি ধাপ জটিলতা কোথায় রাখে তা বদলেছে: প্রোগ্রামারের কোডে (NIO), framework-এ (Netty), শেষে JVM-এ (virtual thread).
প্র ০৫২০১১ সালে WebSocket (RFC 6455) কেন দরকার হলো? HTTP polling, long polling আর WebSocket তুলনা করুন — আর কখন WebSocket ভুল পছন্দ?
সমস্যা। ২০০০-এর দশকে ওয়েব পাতা হয়ে উঠছিল অ্যাপ — chat, ইমেইলে নতুন বার্তা, শেয়ারবাজারের দাম। কিন্তু HTTP-তে সার্ভার নিজে থেকে কিছু পাঠাতে পারে না; শুধু request-এর উত্তর দেয়।
Polling. browser প্রতি কয়েক সেকেন্ডে জিজ্ঞেস করে “নতুন কিছু?” সহজ, কিন্তু অপচয়: বেশিরভাগ উত্তর খালি, অথচ প্রতিবার কয়েকশো byte-এর header আর হয়তো নতুন TCP সংযোগ। আর খবর দেরিতে পৌঁছায় — বিরতি ৫ সেকেন্ড হলে গড়ে আড়াই সেকেন্ড দেরি। বিরতি কমালে সার্ভারের ওপর চাপ বাড়ে।
Long polling (Comet). চতুর কৌশল: client request পাঠায়, সার্ভার নতুন খবর না আসা পর্যন্ত উত্তর আটকে রাখে, এলে উত্তর দেয়; client সাথে সাথে আবার request পাঠায়। খবর প্রায় তাৎক্ষণিক পৌঁছায়, কিন্তু প্রতিটি message-এ একটি পুরো request-উত্তর চক্র, প্রক্সি আর timeout-এর নানা ঝামেলা, আর সার্ভারে প্রতিটি অপেক্ষমাণ client-এর জন্য একটি খোলা request — platform thread-এর যুগে যা ভারী ছিল। অনেকে Flash-এর মতো plugin-এর socket-ও ব্যবহার করতেন।
WebSocket. HTML5-এর কাজের অংশ হিসেবে নকশা করা হয়, আর ডিসেম্বর ২০১১-এ IETF RFC 6455 হিসেবে মান পায়। একবার HTTP upgrade, তারপর একই TCP সংযোগে দুই দিকে ছোট frame — প্রতিটির বাড়তি অংশ মাত্র কয়েক byte. HTTP-র port (80/443) দিয়েই শুরু হয় বলে বিদ্যমান firewall পেরোতে পারে। handshake-এ একটি key আর তার নির্দিষ্ট রূপান্তর মিলিয়ে নিশ্চিত হওয়া যায় যে সার্ভারটি সত্যিই WebSocket বোঝে। Java-য় সার্ভারের দিকের মান JSR 356 (Java EE 7, ২০১৩), আজ Jakarta WebSocket; client এসেছে Java 11-এর java.net.http-এ।
কখন ভুল পছন্দ। সারাক্ষণ খোলা সংযোগ মানে সার্ভারে অবস্থা: load balancer-এ বিশেষ যত্ন, সার্ভার restart-এ সবার সংযোগ ভাঙা, মোবাইলে ব্যাটারি আর নেটওয়ার্ক বদলের সময় নতুন করে জোড়া লাগানোর যুক্তি। খবর যদি শুধু সার্ভার থেকে client-এ যায় আর মাঝে মাঝে, তাহলে HTTP-র ওপর Server-Sent Events বা এমনকি সাধারণ polling সহজতর। সাধারণ প্রশ্ন-উত্তরের API-তে WebSocket দেওয়া অপ্রয়োজনীয় জটিলতা।
মূল উপলব্ধি: WebSocket এসেছিল HTTP-র “client জিজ্ঞেস না করলে সার্ভার চুপ” সীমা ভাঙতে — একবার upgrade, তারপর সস্তা দুই-দিকের frame; কিন্তু খোলা সংযোগের অবস্থা সামলানোর দাম আছে, তাই আসল দুই-দিকের, ঘন ঘন যোগাযোগেই এটি সঠিক হাতিয়ার।
অনুশীলন
বেশিরভাগ কাজে নতুন কোড লিখতে হবে না — কোন স্তরে কী ভুল, আর সার্ভার কোথায় দুর্বল, তা খুঁজে বের করাই আসল কাজ।
-
স্তর চিনুন: প্রতিটি সমস্যা কোন স্তরের, আর Java কোডে সাধারণত কী হিসেবে দেখা দেবে? (ক) ল্যাপটপের Wi-Fi কেটে গেছে। (খ) সার্ভার চালু করতে ভুলে গেছেন, client সংযোগ চাইছে। (গ) সংযোগ হয়েছে, কিন্তু ভুল পথে request যাওয়ায় সার্ভার
404দিল। (ঘ)api.example.comনামটির ঠিকানাই পাওয়া গেল না।(ক) link স্তর — packet বেরোতেই পারছে না; Java-য় সংযোগের চেষ্টায় একটি
IOExceptionবা timeout. (খ) transport — মেশিন পৌঁছানো যাচ্ছে, port-এ কেউ নেই;ConnectException: Connection refused. (গ) application — নেটওয়ার্ক ঠিক, HTTP উত্তর এসেছে;HttpClientexception দেবে না, শুধুstatusCode()হবে 404. (ঘ) নাম থেকে ঠিকানা খোঁজা (DNS) ব্যর্থ;UnknownHostException. exception-এর ধরন দেখেই অর্ধেক রোগ নির্ণয় হয়। -
Bug খুঁজুন: একজন সহকর্মীর সার্ভার:
byte[] buf = new byte[1024]; int n = in.read(buf); String order = new String(buf, 0, n, UTF_8); process(order);— প্রতিটি read একটি অর্ডার ধরে নেওয়া। অফিসের নেটওয়ার্কে সব ঠিক, কিন্তু গ্রাহকদের মোবাইল থেকে মাঝে মাঝে অর্ধেক অর্ডার বা দুটি অর্ডার জোড়া লাগা অবস্থায় আসে। কেন, আর কীভাবে ঠিক করবেন?TCP byte-স্রোত, message-এর সীমানা রাখে না; একটি
readততক্ষণে যা এসেছে তাই দেয় — অর্ধেক, পুরো, বা দুটি অর্ডার একসাথে। দ্রুত স্থির নেটওয়ার্কে bug লুকিয়ে থাকে, মোবাইলের ভাঙা ভাঙা নেটওয়ার্কে বেরিয়ে পড়ে; ১০২৪ byte-এর বড় অর্ডার কাটা পড়ত, আর বাংলা অক্ষরের ৩ byte মাঝখানে ভাগ হলে লেখাও ভাঙত। সমাধান framing: প্রতিটি অর্ডার এক লাইনে আরreadLine(), অথবা length-prefix —readInt(), দৈর্ঘ্য যাচাই, তারপরreadFully. দুই দিকে একই নিয়ম। -
নকশা করুন: একটি বাস টিকিটের অ্যাপ পেমেন্টের পর গেটওয়ে থেকে লেনদেনের অবস্থা জানতে চায়। কোন কোন timeout দেবেন, কোন status code-এ আবার চেষ্টা করবেন আর কোনটিতে থামবেন, আর মোট কতক্ষণ? “টাকা কাটো” request-টিও কি একইভাবে retry করবেন?
একটি যুক্তিসংগত নকশা: connect timeout ২-৫ সেকেন্ড, প্রতিটি request ৫-১০ সেকেন্ড, মোট সীমা ৩০-৬০ সেকেন্ড — তারপর “প্রক্রিয়াধীন, SMS-এ জানানো হবে” দেখিয়ে পেছনে খোঁজ চালানো। আবার চেষ্টা: 5xx, 429 (Retry-After মেনে), timeout বা সংযোগ ভাঙা — backoff, jitter আর চেষ্টার সীমাসহ। থামা: 400, 401, 403, 404 — আবার পাঠালেও একই ফল। অবস্থা জানা GET, তাই নিরাপদ। কিন্তু “টাকা কাটো” POST একইভাবে retry বিপজ্জনক: উত্তর হারালেও টাকা হয়তো কেটে গেছে। সেখানে idempotency key (পর্ব ৫১) লাগবে, অথবা retry-এর আগে অবস্থা জিজ্ঞেস করা।
-
সার্ভার শক্ত করুন (একমাত্র কোড লেখার কাজ): অংশ ৩-এর দামের সার্ভারে দুটি নিয়ম যোগ করুন: (ক) একটি সংযোগে সর্বোচ্চ ১০০টি প্রশ্ন, তারপর
ERR limitলিখে সংযোগ বন্ধ; (খ) একসাথে সর্বোচ্চ ১,০০০টি সংযোগ — বেশি হলে নতুন client-কেERR busyলিখে বিদায়। শুধু পরিবর্তিত অংশ লিখুন।static final Semaphore slots = new Semaphore(1_000); // একসাথে সর্বোচ্চ ১,০০০ // accept লুপে: Socket c = server.accept(); if (!slots.tryAcquire()) { // জায়গা নেই — অপেক্ষা নয় try (c; var out = writer(c)) { out.println("ERR busy"); } continue; } pool.submit(() -> { try { serve(c); } finally { slots.release(); } }); // serve-এর লুপে: int asked = 0; for (String code; (code = in.readLine()) != null && code.length() <= 10; ) { if (++asked > 100) { out.println("ERR limit"); break; } out.println(code + " -> " + (PRICE.containsKey(code) ? PRICE.get(code) + " টাকা" : "পাওয়া যায়নি")); }tryAcquire()অপেক্ষা করে না — পর্ব ৪২-এরacquire()-এর মতো নতুন client-কে সারিতে দাঁড় করানোর বদলে সাথে সাথে স্পষ্ট উত্তর দেয়, যা চাপের সময় ভালো আচরণ।release()রাখা হয়েছেfinally-তে, যাতে exception হলেও জায়গা ফেরত আসে; না হলে কয়েক দিনে সার্ভার “চিরকাল ব্যস্ত” হয়ে যেত। মনে রাখুন, এখনো বাকি আছে পুরো সংযোগের মোট সময়সীমা আর প্রতি-IP সীমা — আসল সার্ভারে সেগুলোও লাগবে।
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমJava-তে একটি TCP সার্ভার আর client-এর একটি প্রশ্ন-উত্তর — ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)
- 🖥️ সার্ভার
new ServerSocket(port)দিয়ে port খুলেaccept()-এ অপেক্ষা করে - 🤝 client সংযোগ চায়; SYN, SYN-ACK, ACK দিয়ে handshake হয়
- 🚪
accept()এই client-এর জন্য একটি নতুনSocketফেরত দেয় - 📤 client একটি লাইন লেখে, সার্ভার
readLine()-এ সেটা পায় - 📥 সার্ভার উত্তর লেখে, client সেটা পড়ে
- 👋 client
close()করে; সার্ভারেরreadLine()nullদেয়
Java-তে ServerSocket-এর accept() method কী করে?
- একটি client-কে স্বাগত বার্তা পাঠিয়ে সাথে সাথে ফেরত আসে
- কোনো client না থাকলে সাথে সাথে null ফেরত দেয়
- একটি client না আসা পর্যন্ত অপেক্ষা করে, তারপর সেই client-এর জন্য একটি নতুন Socket দেয়
- একটি নতুন port খোলে আর সেই সংখ্যা ফেরত দেয়
একটি TCP সংযোগে client পরপর দুবার আলাদা করে "TEA" আর "DAL" লিখল। সার্ভারের একটি read(buf) কী পেতে পারে?
- সবসময় ঠিক “TEA” — প্রতিটি write একটি read
- “TEADAL” একসাথে, “TE”-এর মতো আংশিক, বা অন্য যেকোনো ভাগ — সীমানার নিশ্চয়তা নেই
- “DALTEA” — উল্টো ক্রমে আসতে পারে
- একটি exception, কারণ দুটি message একসাথে পড়া নিষেধ
Java 21-এ HttpClient.newHttpClient() দিয়ে বানানো client একটি 301 redirect উত্তর পেলে কী করে?
- নিজে থেকেই নতুন ঠিকানায় গিয়ে আসল উত্তর আনে
- একটি RedirectException ছোড়ে
- একই ঠিকানায় আবার request পাঠায়
- 301 উত্তরটিই ফেরত দেয় — ডিফল্ট নীতি NEVER
Java-তে একটি socket সংযোগের চেষ্টায় java.net.ConnectException: Connection refused এলে সবচেয়ে সম্ভাব্য অর্থ কী?
- মেশিন পৌঁছানো গেছে, কিন্তু সেই port-এ কোনো প্রোগ্রাম listen করছে না
- লক্ষ্য মেশিন থেকে কোনো উত্তরই আসেনি
- DNS নামটির ঠিকানা খুঁজে পায়নি
- সার্ভারের TLS certificate অবৈধ
একটি HTTP client-এ স্বয়ংক্রিয় retry যোগ করতে হবে। নিচের কোন ক্ষেত্রে backoff দিয়ে আবার চেষ্টা করা সবচেয়ে যুক্তিসংগত ও নিরাপদ?
- একটি POST-এর উত্তরে
400 Bad Request - একটি GET-এর উত্তরে
503 Service Unavailable - একটি GET-এর উত্তরে
401 Unauthorized - idempotency key ছাড়া একটি “টাকা পাঠাও” POST-এর timeout
WebSocket সম্পর্কে কোন বক্তব্যটি সঠিক?
- এটি UDP-র ওপর বানানো, তাই message হারাতে পারে
- প্রতিটি message-এর জন্য একটি নতুন HTTP request পাঠায়
- একটি HTTP upgrade-এর পর একই TCP সংযোগে দুই পক্ষই যখন খুশি message পাঠাতে পারে
- JDK-তে WebSocket সার্ভার আর client দুটোই তৈরি আছে
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- স্তর: link → IP (কোন মেশিন) → TCP/UDP (কোন প্রোগ্রাম, port) → application (HTTP, WebSocket). পাঠানোর সময় প্রতিটি স্তর header জোড়ে, গ্রহণে খোলে। DNS নাম থেকে ঠিকানা দেয়;
127.0.0.1এই মেশিনই। - TCP socket:
ServerSocket.accept()→Socket→ stream, UTF-8 স্পষ্ট করেBufferedReader/PrintWriter; সব try-with-resources-এ।readLine()-এরnullমানে অন্য প্রান্ত বন্ধ। - timeout আর framing: connect timeout,
setSoTimeout, মোট সময়সীমা; refused মানে port-এ কেউ নেই, timeout মানে উত্তর নেই। TCP byte-স্রোত — newline বা length-prefix দিয়ে সীমানা, দৈর্ঘ্য যাচাই করে। - অনেক client: প্রতি সংযোগে virtual thread; NIO selector আর Netty একই সমস্যার event-loop সমাধান। আকার, সময় আর সংযোগের সংখ্যায় সীমা — client-কে বিশ্বাস নয়। UDP: সংযোগহীন, দ্রুত, নিশ্চয়তাহীন।
- HttpClient, WebSocket, TLS: timeout নিজে দিন, redirect ডিফল্টে NEVER, HTTP/2 চায় আর দরকারে নামে; status code দেখুন, retry শুধু idempotent request-এ backoff দিয়ে। WebSocket = HTTP upgrade-এর পর দুই-দিকের চ্যানেল। certificate যাচাই কখনো বন্ধ নয়।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ৪৪ · JVM মেমরি আর garbage collectionপরবর্তী পর্বheap, stack আর GC — আপনার object-গুলো কোথায় থাকে, কখন যায়।
- Java প্রোগ্রামিং · কোর্স হোম৫৯টি পর্বপুরো কোর্সের রোডম্যাপ — কোন পর্ব কখন।
- কম্পিউটার নেটওয়ার্কগভীরে যানIP routing, TCP-র congestion control, DNS আর আরও অনেক কিছু, পূর্ণ কোর্সে।
- C · TCP socketপাশের ট্র্যাকBerkeley socket API সরাসরি — Java-র নিচে যা চলে।
- Python · HTTP APIতুলনাএকই HTTP, অন্য ভাষায় — requests আর JSON.