ক্লায়েন্ট-সার্ভার কমিউনিকেশন আসলে কীভাবে কাজ করে (Go এবং Unix Systems-এর মাধ্যমে দেখলে)

A highly motivated and experienced full-stack developer with a proven track record of developing and deploying web applications. Skilled in a range of programming languages and frameworks, as well as database technologies. Comfortable working in a fast-paced environment and able to adapt to new technologies quickly. A team player who is also able to work independently when required.
যখন আমরা Web 2.0 থেকে Web 3.0-তে শিফটের কথা বলি, সবচেয়ে basic যে জিনিসটা বুঝতে হবে সেটা হলো ক্লায়েন্ট-সার্ভার কমিউনিকেশন আসলে কীভাবে হয়। এটা শুধু theory না — এটা হলো web, APIs, আর distributed systems-এর backbone।
যদি তুমি networking আর operating systems-এর deeper role নিয়ে curious, কীভাবে এগুলো requests আর responses handle করে, তাহলে abstractions গুলো একটু সরিয়ে নিচের layers-এ কী হচ্ছে সেটা দেখাই ভালো।
এই exploration-এর জন্য আমি Go language choose করেছি, কারণ আমি এখন এর runtime আর goroutines নিয়ে deep dive করছি। এই দুটোই key জিনিস Go-তে server applications-এ concurrency handle করার জন্য। আমরা দেখবো কীভাবে একটা simple HTTP request তোমার browser থেকে Go server-এ যায়, Unix kernel দিয়ে, আর আবার ফিরে আসে।
Step 1: Request করা
শুরুটা হয় তুমি browser-এ (Chrome) একটা URL টাইপ করার সাথে (www.example.com) অথবা কোনো API endpoint hit করার সাথে (https://medium.com/p/b98de3906ea0/edit)। সেই request network দিয়ে transmit হয় আর eventually server-এর router-এ পৌঁছায়, যেটা সেটাকে server-এর Network Interface Card (NIC)-এর কাছে forward করে।
Step 2: NIC আর OS Kernel
NIC হলো server-এর hardware component যেটা incoming network signals গুলোকে data-তে convert করে যেটা operating system process করতে পারে।
এখানে দুইটা key জিনিস হয়:
NIC request data গুলোকে RAM-এর receive buffer-এ রাখে।
এটা kernel-কে (OS-এর heart) signal করে যে নতুন data এসেছে।
এখন kernel responsibility নেয়। এটা packet-এর port number দেখে বুঝে নেয় কোন socket এই data handle করবে। প্রতিটা socket একটা file descriptor (FD)-এর সাথে associated, আর kernel এই mapping গুলো তার file descriptor table-এ maintain করে। সঠিক socket identify করার পরে, kernel data গুলো সেই socket-এর receive buffer-এ রাখে আর তার FD-কে "ready" mark করে।
এই point-এ, kernel basically সবকিছু prepare করে ফেলেছে আর অপেক্ষা করছে application-এর (আমাদের case-এ Go runtime) pick up করার জন্য।
Step 3: Go Runtime আর Goroutines Scene-এ আসে
Go runtime constantly goroutines গুলো manage করে আর OS-এর সাথে তাদের interactions handle করে। ধরো আমাদের main goroutine একটা socket-এ listen করছে এভাবে:
rw, err = l.Accept()
এখানে যা হয়:
Go runtime, netpoll mechanism দিয়ে, kernel-কে জিজ্ঞেস করে: "এই FD-তে data ready হলে আমাকে wake up করো।"
Kernel, দেখে যে FD active, signal back করে: "হ্যাঁ, এই socket-এ data waiting আছে।"
Go runtime তখন FD টাকে সেই goroutine-এর কাছে hand over করে যেটা এটার জন্য wait করছিল।
তো main goroutine request পায় আর l.Accept() call করে। এটা connection details retrieve করে আর request handle করার জন্য নতুন একটা goroutine spawn করে, আর main goroutine আবার sleep-এ চলে যায় (time.Sleep() 😴 এর মতো), পরের connection accept করার জন্য ready হয়ে।
Step 4: Go-তে Routing আর Handling
নতুন spawn হওয়া goroutine এখন request process করে। সাধারণত এটার মানে:
Request টাকে router (mux) দিয়ে pass করা যেটা figure out করে কোন handler কোন requested path-এর জন্য (/about, /home, /info, etc.)।
সঠিক handler function execute করা, যেটা response prepare করে।
handler function example:
// /hello
func helloHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello, World!")
}
// /about
func aboutHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "I'm Sifat, blogger, CP'er, Golang enthusiast")
}
Go-তে handlers সাধারণত responses গুলো write buffer-এ write করে। সেখান থেকে data send buffer-এ place হয়।
Step 5: Kernel আর NIC Response Handle করে
যখন handler send buffer-এ write করে, kernel আবার notify হয়। Kernel response data গুলো ring buffer-এ move করে, যেটা NIC constantly monitor করে।
NIC এই data pick up করে, electromagnetic signals-এ convert করে (অথবা optical/electrical medium অনুযায়ী), আর relevant protocols (TCP/IP stack) follow করে network দিয়ে transmit করে। Eventually, client — তোমার browser অথবা API consumer — response receive করে।
কিছু Abstractions যেগুলো Note করার মতো
আমি readability-র জন্য কিছু parts simplify করেছি:
DNS resolution: Request server-এ পৌঁছানোর আগেই client domain name টাকে IP address-এ resolve করে।
TCP three-way handshake: Data flow হওয়ার আগে client আর server connection establish করে।
TLS/HTTPS: Connection secure হলে, encryption আর decryption আরেকটা layer of processing add করে।
Multiplexing & scheduling: Kernel একসাথে অনেক sockets handle করে, প্রায়ই epoll/kqueue mechanisms ব্যবহার করে, আর Go runtime goroutines গুলোকে OS threads-এর উপর multiplex করে। (Kernel শুধু বসে বসে sockets গুলো একটা একটা করে check করে না — সেটা painfully slow হতো। বরং এটা efficient mechanisms যেমন epoll (Linux) অথবা kqueue (BSD/macOS) use করে runtime-কে notify করে যখন কোনো socket-এ data ready থাকে। এটা old-school polling থেকে অনেক বেশি scalable।)
কেন এটা Important
এই layers গুলো বুঝলে তোমার অনেক clear idea হবে performance bottlenecks কোথায় হতে পারে, Go-এর goroutines-এর মতো concurrency models কীভাবে OS-এর সাথে কাজ করে, আর কেন network programming powerful হওয়ার সাথে সাথে tricky ও হতে পারে।
যখন তুমি abstractions গুলো সরিয়ে নেবে, client-server communication আর "magic black box"-এর মতো মনে হবে না বরং hardware (NIC), OS kernel, আর language runtimes (আমাদের case-এ Go)-এর মধ্যে well-coordinated dance-এর মতো মনে হবে।
Credit : Sifat99

This flow diagram makes it easier to grasp the concept at a glance.
link : https://excalidraw.com/#room=c0dd4b512241aa3d300d,Ip9a-ahFyVXja7xBJT4qDQ



