29/05/2020 - GO
Golang ile eşzamanlı bir TCP (İletim Kontrol Protokolü) istemcisi ve sunucu örneği oluşturmak için aşağıdaki basit örneği kullanabilirsiniz. Eşzamanlı bir sunucuya sahip olmanın avantajı, sunucunuzun aynı anda birden fazla istemciye (bağlantı) hizmet verebilmesidir. TCP, tasarım gereği güvenilir bir iletişim protokolüdür. TCP paket dağıtım sorunlarını tespit ettiğinde, kayıp verilerin yeniden iletilmesini talep eder, sıra dışı verileri yeniden düzenler, diğer sorunların ortaya çıkmasını azaltmak için ağ tıkanıklığını en aza indirmeye yardımcı olur. IP ağı üzerinden iletişim kuran ana bilgisayarlarda çalışan uygulamalar arasında kullanılır. Bu yazının kapsamı dışında kalan diğer ağ protokolleri gibi kendi güvenlik açıklarına sahiptir. TCP, UDP (Kullanıcı Datagram Protokolü) protokolüne kıyasla daha güvenilir ancak daha yavaştır. Bu yazının kapsamı dışında olmasına rağmen, size UPD hakkında çok kısa bir bilgi vereyim. UDP hata kontrolü, düzeltme veya paket yeniden iletimi sağlamaz, bu nedenle güvenilmez ancak hızlıdır. Video konferans, canlı akış, gerçek zamanlı uygulamalar gibi çevrimiçi oyunlar için yaygın olarak kullanılır.
package main
import (
"bufio"
"io"
"log"
"net"
"os"
"strings"
)
func main() {
con, err := net.Dial("tcp", "0.0.0.0:9999")
if err != nil {
log.Fatalln(err)
}
defer con.Close()
clientReader := bufio.NewReader(os.Stdin)
serverReader := bufio.NewReader(con)
for {
// Waiting for the client request
clientRequest, err := clientReader.ReadString('\n')
switch err {
case nil:
clientRequest := strings.TrimSpace(clientRequest)
if _, err = con.Write([]byte(clientRequest + "\n")); err != nil {
log.Printf("failed to send the client request: %v\n", err)
}
case io.EOF:
log.Println("client closed the connection")
return
default:
log.Printf("client error: %v\n", err)
return
}
// Waiting for the server response
serverResponse, err := serverReader.ReadString('\n')
switch err {
case nil:
log.Println(strings.TrimSpace(serverResponse))
case io.EOF:
log.Println("server closed the connection")
return
default:
log.Printf("server error: %v\n", err)
return
}
}
}
package main
import (
"bufio"
"io"
"log"
"net"
"strings"
)
func main() {
listener, err := net.Listen("tcp", "0.0.0.0:9999")
if err != nil {
log.Fatalln(err)
}
defer listener.Close()
for {
con, err := listener.Accept()
if err != nil {
log.Println(err)
continue
}
// If you want, you can increment a counter here and inject to handleClientRequest below as client identifier
go handleClientRequest(con)
}
}
func handleClientRequest(con net.Conn) {
defer con.Close()
clientReader := bufio.NewReader(con)
for {
// Waiting for the client request
clientRequest, err := clientReader.ReadString('\n')
switch err {
case nil:
clientRequest := strings.TrimSpace(clientRequest)
if clientRequest == ":QUIT" {
log.Println("client requested server to close the connection so closing")
return
} else {
log.Println(clientRequest)
}
case io.EOF:
log.Println("client closed the connection by terminating the process")
return
default:
log.Printf("error: %v\n", err)
return
}
// Responding to the client request
if _, err = con.Write([]byte("GOT IT!\n")); err != nil {
log.Printf("failed to respond to client: %v\n", err)
}
}
}
İstemci sunucuya bir istek gönderir. Sunucu GOT IT!
ile yanıt veriyor. İstemci :QUIT
yazıp enter tuşuna basarak bağlantıyı kesebilir. Sunucu isteği anlar ve istemci arasındaki ilgili bağlantıyı kapatacaktır. Sunucu ayrıca istemcinin bağlantıyı nasıl kesmek istediğini de anlar. Örneğin, Ctrl+C
veya Ctrl+]
'den sonra telnet> close
sinyali gibi. Test amacıyla yukarıdaki istemci dosyamız yerine Telnet
kullanmak istiyorsanız, bunu aşağıda gösterildiği gibi yapabilirsiniz. İstemci bağlantıyı kapattığında, bu sunucununda kapatılması anlamına gelmez. Açıkça kapatılmadığı sürece sunucu her zaman açıktır.
TCP sunucusu her zaman çalışır durumda olacak ve yeni bir TCP istemci bağlantısı bekleyecektir. Yeni bir istemci TCP sunucusuna her bağlandığında, yeni ve benzersiz bir TCP bağlantısı oluşturulur. Bunu, aşağıda gösterildiği gibi netstat
komutunu çalıştırarak doğrulayabilirsiniz.
# When we have two clients connected to the server. One with Telnet and another with our Go script.
$ netstat -anp TCP | grep 9999
tcp4 0 0 127.0.0.1.9999 127.0.0.1.51877 ESTABLISHED # -> Client 2: established conn
tcp4 0 0 127.0.0.1.51877 127.0.0.1.9999 ESTABLISHED # -> Server: listening Client 2 conn
tcp4 0 0 127.0.0.1.9999 127.0.0.1.51872 ESTABLISHED # -> Client 1: established conn
tcp4 0 0 127.0.0.1.51872 127.0.0.1.9999 ESTABLISHED # -> Server: listening Client 1 conn
tcp46 0 0 *.9999 *.* LISTEN # -> Server listening
# After clients close connections.
$ netstat -anp TCP | grep 9999
tcp46 0 0 *.9999 *.* LISTEN
Dikkat edilmesi gereken bir nokta, bağlantılar tamamen kaybolmadan önce birkaç saniye boyunca ESTABLISHED
durumundan TIME_WAIT
durumuna dönecektir. TIME_WAIT
sizin/istemcinizin bağlantıyı kapattığını, dolayısıyla bağlantının bir süre açık tutulmasını sağlar, böylece teslim edilmeyen tüm paketler bağlantıyı gerçekten kapatmadan önce işlenmek üzere bağlantıya teslim edilebilir. Başlangıçta bundan bahsettik.
Bir istemci oturumuna girdikten sonra, ne olacağını görmek için bir şeyler yazın. Çıkış işlemlerini de test edebilirsiniz.
$ go run -race main.go
$ go run -race main.go
$ telnet 0.0.0.0 9999