Gömülü Linux Sistemler - 7 : TCP/IP ve Soket (Client) Uygulaması

TCP , bir işletim sisteminde (gömülü sistem veya başka türlüsü) network arayüzü kullanılarak iki veya daha çok bilgisayar arası yapılan haberleşmelerde kullanılan yapıdır. TCP/UP internetin en temel protokollerini içeren bir paket yapısıdır. TCP, veri transferinde kritik noktaları belirtir. IP ise taşıma yolunun bulunmasını belirtir. TCP/IP protokol yapısı 5 kısımdan oluşur.

Yukarıdaki şemada görüldüğü üzere TCP/IP protokol yapısı bildiğimiz OSI modeline göre daha az sayıda katmandan oluşur. Taşıma katmanında TCP veya UDP protokollerinin headerları eklendikten sonra paket direk uygulama katmanına çıkmış olur. 

TCP/IP, paketin iletimini kontrol eder ve eksik veya bozuk bir paket karşı tarafa giderse bunun bilgisini göndericiye ulaştırır. Kontrol sağlanır. Gerekirse düzeltip gönderir.

Checksum kullandığı için veriyi düzgün doğrulamış olur.

Paket düzgün alındıysa göndericiye pozitif feedback verilir, eksiklik varsa negatif feedback gönderilir.

TCP/UDP soketleri network driver'ı kullanılarak dışarıya açılan sorgular ve iletişimler için kullanılıyor. İşletim sistemi mantığında içerdeki uygulamalar arası haberleşme ise IPC (inter process communication) ile sağlanmaktadır.

TCP soketini Layer-2 veya Layer-3'te açıp iletişim kurma durumumuzda oluşan farklılıklardan en önemlisi iletişim Layer-2'de olduğu zaman MAC header'ını bile Ethernet Frame'ine kendimiz ekliyoruz.  Yani kısacası Layer-2 olduğu zaman ethernet paketinin çerçevesini biz oluşturuyoruz. Bu da daha esnek ve manuel işlem yapmamızı sağlıyor. Bu sayede örneğin kablosuz iletişimde gönderilecek olan paketin boyutunun minimumda tutarak güç ve bant genişliği tüketiminde verimlilik sağlayabiliyoruz. Layer 2 ve Layer 3 her ikisinde de paket boyutunu kendimiz belirleyebiliyoruz.

#include<string.h>	//strlen
#include<sys/socket.h>
#include<arpa/inet.h>	//inet_addr

int main(int argc , char *argv[])
{
	int socket_desc;
	struct sockaddr_in server;
	char *message , server_reply[2000];
	
	//Create socket
	socket_desc = socket(AF_INET , SOCK_STREAM , 0);
	if (socket_desc == -1)
	{
		printf("Could not create socket");
	}
		
	server.sin_addr.s_addr = inet_addr("13.224.8.51");
	server.sin_family = AF_INET;
	server.sin_port = htons( 80 );

	//Connect to remote server
	if (connect(socket_desc , (struct sockaddr *)&server , sizeof(server)) < 0)
	{
		puts("connect error");
		return 1;
	}
	
	puts("Connected\n");
	
	//Send some data
	message = "GET / HTTP/1.1\r\n\r\n";
	if( send(socket_desc , message , strlen(message) , 0) < 0)
	{
		puts("Send failed");
		return 1;
	}
	puts("Data Send\n");
	
	//Receive a reply from the server
	if( recv(socket_desc, server_reply , 2000 , 0) < 0)
	{
		puts("recv failed");
	}
	puts("Reply received\n");
	puts(server_reply);
	
	return 0;
}

Yukarıdaki kodu inceleyelim:

sockaddr_in , erişilecek internet adresini de içeren bir structure (yapı) 'dır.  "netinet/in.h" kütüphanesi içinden çağrılır.

struct sockaddr_in server;

Bu structure'a yakından bakarsak aşağıdaki gibidir:

// IPv4 AF_INET sockets:
struct sockaddr_in {
    short            sin_family;   // e.g. AF_INET, AF_INET6
    unsigned short   sin_port;     // e.g. htons(3490)
    struct in_addr   sin_addr;     // see struct in_addr, below
    char             sin_zero[8];  // zero this if you want to
};

struct in_addr {
    unsigned long s_addr;          // load with inet_pton()
};

struct sockaddr {
    unsigned short    sa_family;    // address family, AF_xxx
    char              sa_data[14];  // 14 bytes of protocol address
};

sock_addr structure'ı sin_addr  denen bir structure üyesine sahiptir. Bu da in_addr tipinde bir structure 'dır. Kısaca bu yapıda IP adresi long formatında tutulmaktadır. in_addr yapısında tutulan s_addr değişkeni inet_pton() fonksiyonu sayesinde elde edilmiştir. Bu fonksiyon Linux 'un C fonksiyonlarındandır. IP adresi gibi noktalı sayıları değişkende (long formatında) tutabilme işine yarar.  Böylece IP adresini değişkene atayıp rahatça başka yerlerde kullanabiliriz.

inet_addr fonksiyonu , IPV4 Internet adreslerini string olarak ele alıp nümerik bir internet adresine dönüştürür. Bu değeri nümerik olarak ele almamız lazım ki işlem yapabilelim. Ama www.google.com gibi bir domain adını çevirmek için gethostbyname() fonksiyonu kullanılır. Şimdi IP adresini structre'a atadık. IP adresimiz olmazsa nereye bağlanacağımızı bilemeyiz.

server.sin_addr.s_addr = inet_addr("13.224.8.51");

Yukarıdaki satırda uzaktan erişmek istediğimiz IP adresini yazmış olduk. Kodun bu satırdan sonrası kısımlarında uygulamanın amacına yönetlik yani HTTP GET fonksiyonu kullanarak ilgili IP adresinin HTML yanıtını almış oluruz. Burada dönülen yanıtta web sayfasının içeriği yer alır. 

NOT: Soket üzerinden veri geldiğinde aslında basitçe soketteki veriyi okuma işlemi yaparız. Bu tıpkı bir dosyadan veri okumaya benzer. Soketteki veriyi okumak için read() fonksiyonuyla da bu işi yapabilriz. Bu da bir yöntemdir.

read(socket_desc, server_reply , 2000);


Yukarıdaki uzun kodun uygulamasını yaparken soketten gerekli okuma işlemimizi yaptıktan sonra soketi kapatırız. Bunun için aşağıdaki fonksiyonu kullanırız.

close(socket_desc);


Detaylı İnceleme

Yukarıdaki uygulamanın altyapısını detaylı inceleyelim biraz da. socket() fonksiyonu aslında bizim için en kritik olan yapıdır. Bu fonksiyon Lİnux'un sistem fonksiyonlarından biridir. Aşağıdaki satır soket oluşturmanın en bilinen yöntemidir. Burada dikkat edilmesi gereken nokta, ne türde bir soket bağlantısı oluşturacağımız ve OSI katmanlarından hangisini kullanacağımızdır.

NOT: Linux'ta soket açma/kapama uygulamalarında kullanılan fonksiyonlar ve parametrelerle ilgili başvurulacak olan referans kaynak , Linux'un sunduğu Soket manuelidir. Aşağıdaki linkten erişilebilir:

https://man7.org/linux/man-pages/man2/socket.2.html 



Bir soketi açarken aşağıdaki fonksiyonu kullanırız:

socket_desc = socket(AF_INET , SOCK_STREAM , 0);

Bu fonksiyonun hemen yukarıdaki linkte belirtildği üzere prototipi şu şekildedir:

int socket(int domain, int type, int protocol);

Bizim uygulamamızda kullandığımız fonksiyonda AF_INET parametresi domain olarak ele alınmıştır. Bu da IPv4 internet protokolü kullanacağımız anlamına gelir. Yani Layer-3 katmanında haberleşme yapılacaktır. 

İkinci parametre ile (type) iletişim tipi belirlenir. SOCK_STREAM ile stream bağlantısı yapılacağı belirtilir. Diğer bir seçenek SOCK_DGRAM yani datagram yayınıdır. Birincisi TCP ile ikincisi UDP ile kullanılır. Zaten üçüncü parametrenin "0" olması demek bağlantı şeklinin işletim sistemi tarafından otomatik şekilde yapılacağıdır ve SOCK_STREAM'den ötürü TCP bağlantısı kullanılacaktır (Taşıma katmanı belirlenir - Transport Layer).

Soket fonksiyonu Dosya Tanımlayıcı Tablosu'na (File Descriptor Table) bir değer döndürür ve atar. (ufak bir integer değeri 1,0,-1 gibi). Bu değer, soket fonksiyonunun başvurulduğu yerlerde kullanılır. Örneğin soket hata duruma girdiyse "-1" değeri kullanılacaktır. 

NOT: TCP soket uygulaması yaparken Unix tarafında açılan portları görebilmek için "ss -t -a"  komutu kullanılabilir.

Ayrıca kodun içerisinde aşağıdaki komut ile açılan portun uygulama çalışırken ekrana basılması ve takip edilmesi sağlanabilir. Sonuçta htons() fonksiyonu ile hex olarak verdiğimiz Port değeri unsigned short integer değerine çevrilecektir.

printf (" Açılan port : %hu " , server.sin_port);


Uygulamamızı çalıştırdığımız zaman aşağıdaki gibi bir çıktı görmemiz gerekir.

adem@ademg:~/Documents/getHTTPsocket$ ./getHTTPsocket 
Connected

Data Send

Reply received

HTTP/1.1 400 Bad Request
Server: CloudFront
Date: Sun, 01 Nov 2020 13:01:57 GMT
Content-Type: text/html
Content-Length: 915
Connection: close
X-Cache: Error from cloudfront
Via: 1.1 324a68a6c25ee50d774953f3e15a611d.cloudfront.net (CloudFront)
X-Amz-Cf-Pop: SEA19-C2
X-Amz-Cf-Id: ssHMFIzgMq77zabHFcOXDblue-SIwKZSm1uaUao0piB2bORAL-bSEg==

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<HTML><HEAD><META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=iso-8859-1">
<TITLE>ERROR: The request could not be satisfied</TITLE>
</HEAD><BODY>
<H1>400 ERROR</H1>
<H2>The request could not be satisfied.</H2>
<HR noshade size="1px">
Bad request.
We can't connect to the server for this app or website at this time. There might be too much traffic or a configuration error. Try again later, or contact the app or website owner.
<BR clear="all">
If you provide content to customers through CloudFront, you can find steps to troubleshoot and help prevent this error by reviewing the CloudFront documentation.
<BR clear="all">
<HR noshade size="1px">
<PRE>
Generated by cloudfront (CloudFront)
Request ID: ssHMFIzgMq77zabHFcOXDblue-SIwKZSm1uaUao0piB2bORAL-bSEg==
</PRE>
<ADDRESS>
</ADDRESS>
</BODY></HTML>


Kaynaklar

https://www.binarytides.com/socket-programming-c-linux-tutorial/

http://www.linuxhowtos.org/C_C++/socket.htm

https://www.tenouk.com/Module42a.html

https://man7.org/linux/man-pages/man2/socket.2.html



Yorumlar

Bu blogdaki popüler yayınlar

KV260 Kria Starter Kit Series: 3 - Petalinux Install and Boot

KV260 Kria Starter Kit Series: 1 - Power and Boot Up

KV260 Kria Starter Kit Series: 2 - Smartcam Application (Ubuntu)