İçeriğe geçmek için "Enter"a basın

Linux Üzerinde Socket Kavramı ve Kullanımı

Son güncelleme tarihi 10 Nisan 2022

IPC (Inter-process Communication) Yazı Dizisi 1

Öncelikle bir işletim sisteminde olmazsa olmaz denilebilecek, işleyişi düzenleyen, basitleştiren ve aynı işi yapan onlarca uygulamanın çalışması yerine, bir uygulama ile onlarca süreci birleştiren yapılar vardır, temel olarak bu kavrama ve uygulamalarına sistem servisleri de diyebiliriz. Linux altında sistem servisleri konusunu bu kategori üzerinde detaylandırılacağından burada sadece ismen bahsedeceğim.

Bir sistem servisi yapısı itibariyle bir uygulamadır, ancak bu uygulamayı özel kılan şey, birçok uygulama ile bağlantı kurulabilecek, işletim sistemine entegre çalışacak şekilde tasarlanmış olmasıdır.

Bir bilgisayar üzerinde çalışan uygulamaların kendisine biçilen görevi/görevleri yapabilmesi için, sahip olduğu her işlevi kendine özel, uygulama içerisinde gömülü olacak biçimde kodlayarak geliştirmek pek te akıl karı değil.
Düşünsenize Client-Server mantığında çalışacak bir uygulama yazacaksınız ve bütün Network iletişim altyapısını, protokollerini de kendiniz yazıyorsunuz, veya veritabanı ihtiyacı olan bir uygulamanız var ve MySQL benzeri bir RDBMS tasarlamaya çalışıyorsunuz, örnekler çoğaltılabilir.

Özetle, bir uygulamayı sistem genelinde çalışan uygulamalar ile birleştirmek, haberleştirmek yazılım geliştirmek için akıllıca olan yöntem ve bu yazımda bu haberleşme mekanizmasının içerdiği socket kavramı bu kavramın üst başlığı olan IPC yani Inter Process Communication (İşleçler Arası İletişim) ve bağıl birkaç terime değineceğim.


IPC (Inter-Process Communication) ana başlığı altında incelenebilecek bu kavram ile birlikte bu yazımızda iki adet socket türünden, uygulamaları ile bahsetmek istiyorum.

  1. Unix Domain Socket
  2. Network Socket
  3. Örnek Socket Uygulamaları
    1. Unix Domain Socket Uygulaması
    2. TCP Network Socket Uygulaması
    3. UDP Network Socket Uygulaması

IPC Nedir?

İşleçler Arası İletişim şeklinde dilimize çevirebiliriz, tanımı açmak gerekirse;

  • İşleç : İşletim sistemi kernelinde veya üzerinde çalışır durumdaki uygulama(lar).
  • İletişim : Yerel sistem üzerinde çalışan işleçlerin kendi arasında veya talep durumunda yerel sistemin dışında bulunan sistemler ve üzerindeki uygulamalar ile haberleşmesi, mesajlaşması, ortak kaynakları kullanabilmesi, bu kaynakları birbirine yönlendirebilmesi, koşullu şekilde tetikleme yapabilmesi.

Bu iletişimin kurulabilmesi veya ortak data kullanılabilmesi için yerel sistemdeki depolama (FILE), bellek (MEMORY), CPU gibi fiziksel kaynakların erişilebilir kılınması, veya bilgisayar ağları üzerinden ağ protokolleri (TCP, UDP, SCTP) kullanarak diğer sistemler ve bu sistemlerin fiziksel kaynaklarına erişilebilir olması gerekmekte. Bir işletim sistemi kerneli de tam olarak bize ihtiyaç duyduğumuz bu altyapıyı sağlıyor. Bahsettiğim sistem tabii ki Linux.

IPC kavramı ve alt başlıkları;

  • Unix Domain Socket
  • Socket
  • Signal, Asynchronous System Trap
  • Pipes (Named & Anonymous)
  • Message Queue
  • Shared File
  • Shared Memory
  • Memory-mapped File

IPC Yazı Dizisi 1. Yazıda Unix Domain Socket ve Network Socket kavramlarını inceleyeceğiz.

socket nedir.

Host sistem üzerindeki veya network üzerindeki herhangi bir işletim sisteminde çalışan uygulamalar/işlemler arasında, tek yönlü (bidirectional) veya çok yönlü (unidirectional) olabilecek şekilde (server/client), duruma göre tetikleme ile, komut ile, akış yönlendirme ile veri alışverişi yapılabilmesini sağlayan yapıdır socket, eğer yerel bilgisayar içerisinde bu veri değişimi yapılıyorsa unix domain socket, network üzerinden başka host sistemler ile yapılıyorsa network socket olarak adlandırılır.

Tanım içerisinde kullanılan ve bağıntılı kelimeler ve anlamları;

Not: Yazı yazma mantığı olarak; tanımlamaları anlaşılabilir olabilmesi adına genellikle Türkçe yapmaya çalışıyorum, ana çerçevede kullanımda ise elimden geldiği kadar Dünya genelindeki literatür ne ise onu kullanmayı tercih ediyorum.

Tanım sırasında terimleri tanıtırken, işlevlerin algılanabilmesi adına Türkçe kullanıyorum, bu işlevleri kullanabilmeniz için ise İngilizce şart!

  • Host Sistem
    Yerel bilgisayar sistemimiz,
  • İşletim Sistemi
    Host sistemi kullanabilmemizi sağlayan Yazılım, bu yazıda Linux İşletim Sistemi üzerinden anlatım yapılacak.
  • bidirectional
    İletişimin tek yönlü olduğunu işaret eder.
  • unidirectional
    İletişimin çok yönlü olduğunu işaret eder.
  • Domain
    İşletim Sistemi Yüklü yerel bilgisayarımız ve bu sistemin yetki alanı.
  • Process
    İşletim Sistemi ile etkileşimdeki, işletim sistemi üzerinde çalışan uygulamalar ile etkileşimdeki veya işletim sistemi üzerinde çalışan uygulamaların kendisi.
  • Server Uygulama
    Kendisine iletilen argümanlara göre uygulama özelindeki fonksiyonları ile işlemler yapıp geriye işlenmiş veri döndüren uygulama.
  • Client Uygulama
    Server Uygulamaya belirli yollarla bağlanıp, argüman-komut gönderip geri döndürülen veri değerlerine göre kendi işini yapan uygulama.
  • Network
    Bilgisayar ağları (Network) ve bu ağlar üzerindeki sistemlerin birbirleri ile iletişim kurabilmesini sağlayan protokoller (TCP, UDP, …), Internet protocol suite isimli yazımda detaylıca her birine değineceğim.
  • Data Exchange
    Veri alış-veriş işlemi, clear text veya crypted biçimde de olabilir. (başka yazıda incelenecek)
  • Command
    Client veya Server uygulamaların içerisinde kodlanmış fonksiyonlarına ulaşılabilmesi için, uygulamanın işlem yapabilmesi için terminal aracılığıyla veya uygulamanın kendi takip ettiği değerlere göre tetiklenmesiyle çalışmasını sağlayan kelime, durum, komut.

Unix Domain Socket

Temel tanımlamamızı yaptık, aklınızda bir şeyler şekillendiğini görür gibiyim. Buraya kadar algılamamız gereken en temel nokta ağ bağlantımızın olmadığı ve sadece 1 adet bilgisayar üzerinde işlem yaptığımız.

Host Sistem üzerindeki çalışır durumdaki uygulamalar birbirine nasıl bağlanır? veya birbiri ile nasıl etkileşimde olur? Yerel sistemdeyse güvenliği nasıl olacak? gibi sorular aklımızda belirdiyse eğer, doğru yoldayız demektir.

İki şeyin haberleşebilmesi için bir haberleşme kanalına ihtiyacı vardır, bu haberleşme kanalı unix domain socket için yerel sistemdeki bir dosyadır. Bu dosyanın diğer dosyalardan farkı stream (akış) özelliğinde olmasıdır. Basitçe stream az önce belirttiğimiz data iletişim kanalından geçen veridir.

Data iletişiminin yapısı client-server türünde olduğundan bağlantı açıldıktan sonra, herhangi bir taraf bağlantıyı kesmediği veya bağlantı kopmadığı sürece iletişim, data akışı devam eder.

Client-Server mimari temelde request / response (talep / yanıt) olarak nitelendirilen iletişim şeklinden ibarettir, basit bir örnekleme yapmak gerekirse senaryomuz aşağıdaki şekilde olsun;

Bulunduğunuz ortamda bildiğiniz bir konu hakkında sizden 5 kişi yardım istesin, cevap verebileceğiniz 2 yöntemden bahsedelim.

  1. Soru soran 5 kişiyi sırayla karşılarsınız, ilk kişiye cevap verdikten sonra ikinci, üçüncü … şeklinde herkese cevap verirsiniz ancak zaman kaybı fazla olacaktır.
  2. Cevabı bilen 4 kişiyi daha yanınıza aldığınızda eş zamanlı şekilde 5 kişinin tümüne cevap verebilirsiniz.

Bu senaryoda ortaya çıkan kavrama verilen isimler;

Her talep için, 1. şekilde iletişim varsa, tekrarlı (iterative) bağlantı, 2. şekilde iletişim varsa eş zamanlı (concurrent) bağlantı olarak adlandırılır.

Iterative bağlantı her seferinde bir talebi karşılarken, concurrent bağlantı aynı anda kendisine ayrılan işlem havuzu kadar cevap verebilme kabiliyetine sahiptir. Yeni karşılaşacağımız kavramın ismi multi processing ve multi threading.

  • Iterative
    Bir işlem bitmeden diğer işleme geçmeyen bağlantı türü.
  • Concurrent
    Aynı anda kendisine ayrılan adette eş zamanlı bağlantı kabul edebilen bağlantı türü.
  • Multi Processing
    Fiili olarak aynı işi yapabilen donanımlar için de kullanılan, yazılımlar için de kullanılabilecek terim, örneğimiz üzerinden gidersek cevap veren kişiden 5 adet olmas durumu veya aynı uygulamadan 5 adet çalıştırarak ayrı süreçler halinde yürütülmesi.
  • Multi Threading
    Terimi ise, bir uygulamanın aynı işi alt parçalar şeklinde kendi bünyesinde eş zamanlı gerçekleştirilebilmesi, örnek üzerinden gidince garip olacak ama teşbihte hata olmaz derler, cevap veren kişinin 5 adet ağızı olduğunu farzedelim. Uygulamanın 5 adet alt süreç başlatarak gelen talepleri eş zamanlı işleyebilmesi de örnek olarak verilebilir.
    Eş zamanlı çoklu işlem (Concurrent Multi Threaded) kavramı da temel olarak bu şekilde tanımlanabilir.

Socket uygulamaları tasarlanırken Iterative, Concurrent, Multi Processing ve Multi Threading kavramları karma şekilde kallanılarak tasarlanıp yazılabilirler.

Network Socket

Yukarıda Unix Domain Socket için yazılan her şey burada da geçerli olmakla birlikte iki socket türünü birbirinden ayıran ana özellik iletişim kanalı olarak Host sistem üzerindeki bir dosya yerine, Network donanımları ve protokollerinin kullanılmasıdır.

Hadi Biraz Program Yazalım.

Program yazmaya başlamadan önce, uygulamamızın ihtiyaçlarını belirleyip, Unix Domain Socket için un.h -> man unix ardından Network Socket için socket.h -> man socket dökümanımızı incelemek daha sağlıklı olacaktır.

Linux Terminalimizde man unix yazarak un.h (unix socket) dökümanına erişebiliriz. Bu döküman üzerinde ihtiyaç duyduğumuzdan oldukça fazlası açıklanmakta, içerisinde bir adet örneğimiz de mevcut.

NAME
       unix - sockets for local interprocess communication

SYNOPSIS
       #include <sys/socket.h>
       #include <sys/un.h>

       unix_socket = socket(AF_UNIX, type, 0);
       error = socketpair(AF_UNIX, type, 0, int *sv);

DESCRIPTION
       The AF_UNIX (also known as AF_LOCAL) socket family is used to communicate between processes on the same machine efficiently.  Traditionally, UNIX domain sockets can be either unnamed, or bound to a
       filesystem pathname (marked as being of type socket).  Linux also supports an abstract namespace which is independent of the filesystem.

...
satır 359 ->   Program source

       /*
        * File connection.h
        */

       #define SOCKET_NAME "/tmp/9Lq7BNBnBycd6nxy.socket"
       #define BUFFER_SIZE 12

       /*
        * File server.c
        */
...

Linux Terminalimizde man socket yazarak socket.h dökümanına erişebiliriz. Bu döküman üzerinde ihtiyaç duyduğumuzdan oldukça fazlası açıklanmakta, bir adet örneğe de link verilmiş mesela.

NAME
       socket - create an endpoint for communication

SYNOPSIS
       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>

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

DESCRIPTION
       socket()  creates  an  endpoint for communication and returns a file descriptor that refers to that endpoint.  The file descriptor returned by a successful call will be the lowest-numbered file descriptor not currently open for the process.

       The domain argument specifies a communication domain; this selects the protocol family which will be used for communication.  These families are defined in <sys/socket.h>.   The  formats  currently understood by the Linux kernel include:

       Name         Purpose                                    Man page
       AF_UNIX      Local communication                        unix(7)
       AF_LOCAL     Synonym for AF_UNIX
       AF_INET      IPv4 Internet protocols                    ip(7)
       AF_AX25      Amateur radio AX.25 protocol               ax25(4)
       AF_IPX       IPX - Novell protocols
       AF_APPLETALK AppleTalk                                  ddp(7)
       AF_X25       ITU-T X.25 / ISO-8208 protocol             x25(7)
       AF_INET6     IPv6 Internet protocols                    ipv6(7)
       AF_DECnet    DECet protocol sockets
       AF_KEY       Key  management protocol, originally de‐
                    veloped for usage with IPsec
       AF_NETLINK   Kernel user interface device               netlink(7)
       AF_PACKET    Low-level packet interface                 packet(7)
       AF_RDS       Reliable Datagram Sockets (RDS) protocol   rds(7)
                                                               rds-rdma(7)
...

EXAMPLES
       An example of the use of socket() is shown in getaddrinfo(3).

Bu döküman içerisinde, socket oluşturmak için kullanabileceğimiz neredeyse tüm özellikler tanımlanmış durumda. Bir uygulama için de referans gösterilmiş man getaddrinfo yazarak bu örneğe ulaşabiliyoruz ancak örnek sadece UDP için yapılmış, ben hem Local SOCKET (AF_UNIX), hem TCP hem de UDP olacak şekilde AF_INET[6] destekli yazmak istiyorum.

Örnek uygulama öncelikli olarak unix domain socket olarak çalışacak ardından network için kullanılacak olanı yazılacak, C dilinde yazalım, uygulamadan yapmasını istediğimiz şey aşağıdaki gibi olsun:

Unix Domain Socket Uygulaması

Unix domain socket uygulamamızı olduğu şekliyle kullanacağım, örnek olarak sunulan uygulama connection.h, server.c ve client.c olmak üzere 3 dosyadan oluşuyor. Uygulama client üzerinden server socket uygulamasına gönderilen sayıları toplayıp, client uygulamaya geri gönderiyor.

connection.h

#define SOCKET_NAME "/tmp/test_socket.socket"
#define BUFFER_SIZE 1024

server.c

/*
* File server.c
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include "connection.h"

int
main(int argc, char *argv[])
{
   struct sockaddr_un name;
   int down_flag = 0;
   int ret;
   int connection_socket;
   int data_socket;
   int result;
   char buffer[BUFFER_SIZE];

   /* Create local socket. */

   connection_socket = socket(AF_UNIX, SOCK_SEQPACKET, 0);
   if (connection_socket == -1) {
	   perror("socket");
	   exit(EXIT_FAILURE);
   }

   /*
	* For portability clear the whole structure, since some
	* implementations have additional (nonstandard) fields in
	* the structure.
	*/

   memset(&name, 0, sizeof(name));
			  /* Bind socket to socket name. */

   name.sun_family = AF_UNIX;
   strncpy(name.sun_path, SOCKET_NAME, sizeof(name.sun_path) - 1);

   ret = bind(connection_socket, (const struct sockaddr *) &name,
			  sizeof(name));
   if (ret == -1) {
	   perror("bind");
	   exit(EXIT_FAILURE);
   }

   /*
	* Prepare for accepting connections. The backlog size is set
	* to 20. So while one request is being processed other requests
	* can be waiting.
	*/

   ret = listen(connection_socket, 20);
   if (ret == -1) {
	   perror("listen");
	   exit(EXIT_FAILURE);
   }

   /* This is the main loop for handling connections. */

   for (;;) {

	   /* Wait for incoming connection. */

	   data_socket = accept(connection_socket, NULL, NULL);
	   if (data_socket == -1) {
		   perror("accept");
		   exit(EXIT_FAILURE);
	   }
					  result = 0;
	   for (;;) {

		   /* Wait for next data packet. */

		   ret = read(data_socket, buffer, sizeof(buffer));
		   if (ret == -1) {
			   perror("read");
			   exit(EXIT_FAILURE);
		   }

		   /* Ensure buffer is 0-terminated. */

		   buffer[sizeof(buffer) - 1] = 0;

		   /* Handle commands. */

		   if (!strncmp(buffer, "DOWN", sizeof(buffer))) {
			   down_flag = 1;
			   break;
		   }

		   if (!strncmp(buffer, "END", sizeof(buffer))) {
			   break;
		   }

		   /* Add received summand. */

		   result += atoi(buffer);
	   }

	   /* Send result. */

	   sprintf(buffer, "%d", result);
	   ret = write(data_socket, buffer, sizeof(buffer));
	   if (ret == -1) {
		   perror("write");
		   exit(EXIT_FAILURE);
	   }
					  /* Close socket. */

	   close(data_socket);

	   /* Quit on DOWN command. */

	   if (down_flag) {
		   break;
	   }
   }

   close(connection_socket);

   /* Unlink the socket. */

   unlink(SOCKET_NAME);

   exit(EXIT_SUCCESS);
}

client.c

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include "connection.h"

int
main(int argc, char *argv[])
{
   struct sockaddr_un addr;
   int ret;
   int data_socket;
   char buffer[BUFFER_SIZE];

   /* Create local socket. */

   data_socket = socket(AF_UNIX, SOCK_SEQPACKET, 0);
   if (data_socket == -1) {
	   perror("socket");
	   exit(EXIT_FAILURE);
   }

   /*
	* For portability clear the whole structure, since some
	* implementations have additional (nonstandard) fields in
	* the structure.
	*/

   memset(&addr, 0, sizeof(addr));

   /* Connect socket to socket address */

   addr.sun_family = AF_UNIX;
   strncpy(addr.sun_path, SOCKET_NAME, sizeof(addr.sun_path) - 1);

   ret = connect(data_socket, (const struct sockaddr *) &addr,
				  sizeof(addr));
   if (ret == -1) {
	   fprintf(stderr, "The server is down.\n");
	   exit(EXIT_FAILURE);
   }
			  /* Send arguments. */

   for (int i = 1; i < argc; ++i) {
	   ret = write(data_socket, argv[i], strlen(argv[i]) + 1);
	   if (ret == -1) {
		   perror("write");
		   break;
	   }
   }

   /* Request result. */

   strcpy(buffer, "END");
   ret = write(data_socket, buffer, strlen(buffer) + 1);
   if (ret == -1) {
	   perror("write");
	   exit(EXIT_FAILURE);
   }

   /* Receive result. */

   ret = read(data_socket, buffer, sizeof(buffer));
   if (ret == -1) {
	   perror("read");
	   exit(EXIT_FAILURE);
   }

   /* Ensure buffer is 0-terminated. */

   buffer[sizeof(buffer) - 1] = 0;

   printf("Result = %s\n", buffer);

   /* Close socket. */

   close(data_socket);

   exit(EXIT_SUCCESS);
}
yasin@uxn-workstation:~/test/c/C-Sockets$ gcc unix-domain-server-socket.c -o unix-domain-server
yasin@uxn-workstation:~/test/c/C-Sockets$ gcc unix-domain-client.c -o unix-domain-client
yasin@uxn-workstation:~/test/c/C-Sockets$ ./unix-domain-server 2>&1 &
[1] 51925
yasin@uxn-workstation:~/test/c/C-Sockets$ ./unix-domain-client 1 2 3 4 5 6 7 8 9 10 
Result = 55
yasin@uxn-workstation:~/test/c/C-Sockets$ ./unix-domain-client DOWN
read: Connection reset by peer
[1]+  Bitti                   ./unix-domain-server 2>&1
yasin@uxn-workstation:~/test/c/C-Sockets$ 

TCP Network Socket Uygulaması

Sıradaki, TCP ve UDP uygulamalarımız ise, Linux sistemimiz üzerinde kendisine bağlandıktan sonra gönderilen satır aynı şekilde uygulamaya bağlanan kullanıcıya geri göndersin, (ping-pong veya echo).

Kendisine client tarafından gönderilen mesajı olduğu gibi client tarafına gönderen ve TCP protokolü kullanan server-client için örnek. Manuel içerisinde yer alan örneği bir miktar modifiye etmem gerekti.

tcp-socket-server.c

#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netdb.h>

#define BUF_SIZE 500

int
main(int argc, char *argv[])
{
	struct addrinfo hints;
	struct addrinfo *result, *rp;
	int sfd, cfd, s;
	struct sockaddr_storage peer_addr;
	socklen_t peer_addr_len;
	ssize_t nread;
	char buf[BUF_SIZE];

	if (argc != 3) {
		fprintf(stderr, "Usage: %s ip %s port\n", argv[0]);
		exit(EXIT_FAILURE);
	}

	memset(&hints, 0, sizeof(hints));
	hints.ai_family = AF_UNSPEC;    /* Allow IPv4 or IPv6 */
	hints.ai_socktype = SOCK_STREAM; /* STREAM socket */
	hints.ai_flags = AI_PASSIVE;    /* For wildcard IP address */
	hints.ai_protocol = IPPROTO_TCP;          /* TCP protocol */
	hints.ai_canonname = NULL;
	hints.ai_addr = NULL;
	hints.ai_next = NULL;
   
	s = getaddrinfo(argv[1], argv[2], &hints, &result);
	if (s != 0) {
		fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s));
		exit(EXIT_FAILURE);
	}

	/* getaddrinfo() returns a list of address structures.
	  Try each address until we successfully bind(2).
	  If socket(2) (or bind(2)) fails, we (close the socket
	  and) try the next address. */

	for (rp = result; rp != NULL; rp = rp->ai_next) {
		int  reuse = 1;

		sfd = socket(rp->ai_family, rp->ai_socktype,
		   rp->ai_protocol);
		if (sfd == -1)
			continue;

		if (bind(sfd, rp->ai_addr, rp->ai_addrlen) == 0)
			break;                  /* Success */

		close(sfd);
	}

	freeaddrinfo(result);           /* No longer needed */

	if (rp == NULL) {               /* No address succeeded */
		fprintf(stderr, "Could not bind\n");
		exit(EXIT_FAILURE);
	}

	if (listen(sfd, 10) < 0) {
		fprintf(stderr, "Could not listen\n");
		exit(EXIT_FAILURE);
	}

	/* Read data and echo them back to sender */

	for (;;) {
		peer_addr_len = sizeof(peer_addr);

		cfd = accept(sfd, (struct sockaddr*) &peer_addr, &peer_addr_len);  /* accept blocks */
		if (cfd < 0) {
			continue;  /* Ignore failed request */
		}

		/* read from client */
		int i;
		
		for (i = 0; i < BUF_SIZE; i++) {
			char buf[BUF_SIZE + 1];
			memset(buf, '\0', sizeof(buf)); 
			int count = read(cfd, buf, sizeof(buf));
			
			if (count > 0) {
				char host[NI_MAXHOST], service[NI_MAXSERV];
				s = getnameinfo((struct sockaddr *) &peer_addr, 
								peer_addr_len, host, NI_MAXHOST,
								service, NI_MAXSERV, NI_NUMERICSERV);
				if (s == 0)
					printf("\nReceived %zd bytes from %s:%s the message is: %s\n", 
									count, host, service, buf);
				else
					fprintf(stderr, "getnameinfo: %s\n", gai_strerror(s));

				printf("The message sending back to the client : %s\n", buf);
				if (sendto(cfd, buf, count, 0,
						(struct sockaddr *) &peer_addr, peer_addr_len) != count)
					fprintf(stderr, "Error sending response\n");
			}
		}
		close(cfd);
	}
}

tcp-socket-client.c Server tarafıyla iletişim kurmak için örnek.

#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

#define BUF_SIZE 500

int
main(int argc, char *argv[])
{
	struct addrinfo hints;
	struct addrinfo *result, *rp;
	int sfd, s;
	size_t len;
	ssize_t nread;
	char buf[BUF_SIZE];

	if (argc < 3) {
		fprintf(stderr, "Usage: %s host port msg...\n", argv[0]);
		exit(EXIT_FAILURE);
	}

	/* Obtain address(es) matching host/port */

	memset(&hints, 0, sizeof(hints));
	hints.ai_family = AF_UNSPEC;    /* Allow IPv4 or IPv6 */
	hints.ai_socktype = SOCK_STREAM; /* STREAM socket */
	hints.ai_flags = 0;
	hints.ai_protocol = IPPROTO_TCP;          /* TCP protocol */

	s = getaddrinfo(argv[1], argv[2], &hints, &result);
	if (s != 0) {
		fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s));
		exit(EXIT_FAILURE);
	}

	/* getaddrinfo() returns a list of address structures.
	Try each address until we successfully connect(2).
	If socket(2) (or connect(2)) fails, we (close the socket
	and) try the next address. */

	for (rp = result; rp != NULL; rp = rp->ai_next) {
		sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
		if (sfd == -1)
			continue;

		if (connect(sfd, rp->ai_addr, rp->ai_addrlen) != -1)
			break;                  /* Success */

		close(sfd);
	}

	freeaddrinfo(result);           /* No longer needed */

	if (rp == NULL) {               /* No address succeeded */
		fprintf(stderr, "Could not connect\n");
		exit(EXIT_FAILURE);
	}

	/* Send remaining command-line arguments as separate
	datagrams, and read responses from server */

	for (int j = 3; j < argc; j++) {
		len = strlen(argv[j]) + 1;
		/* +1 for terminating null byte */

	if (len > BUF_SIZE) {
		fprintf(stderr,
		"Ignoring long message in argument %d\n", j);
		continue;
	}

	if (write(sfd, argv[j], len) != len) {
		fprintf(stderr, "partial/failed write\n");
		exit(EXIT_FAILURE);
	}

	nread = read(sfd, buf, BUF_SIZE);
	if (nread == -1) {
		perror("read");
		exit(EXIT_FAILURE);
	}

	printf("Received %zd bytes: %s\n", nread, buf);
	}

	exit(EXIT_SUCCESS);
}
tcp-socket

Kutu içerisine almış olduğum yerlerden de görüleceği üzere aynı uygulama hem IPv4 hem de IPv6 olarak başlatılabilmekte, bunu sağlayan ise AF_UNSPEC şeklinde adres ailesi tanımlamamızı yapmış olmamızdan kaynaklı. Yalnızca IPv4 için AF_INET, IPv6 için AF_INET6 yazılması yeterli olacaktır.

yasin@uxn-workstation:~/test/c/C-Sockets$ gcc tcp-server-socket.c -o tcp-server
yasin@uxn-workstation:~/test/c/C-Sockets$ gcc tcp-client-socket.c -o tcp-client

yasin@uxn-workstation:~/test/c/C-Sockets$ ./tcp-server ::1 8484
yasin@uxn-workstation:~/test/c/C-Sockets$ ./tcp-client ::1 8484 "Selam!"

yasin@uxn-workstation:~/test/c/C-Sockets$ ./tcp-server 0.0.0.0 8484
yasin@uxn-workstation:~/test/c/C-Sockets$ ./tcp-client 127.0.0.1 8484 "Selam!"

UDP Network Socket Uygulaması

Uygulamamız yukarıdaki TCP uygulaması ile neredeyse aynı, değişen şeyler ise, SOCK_STREAM yerine SOCK_DGRAM, IPPROTO_TCP yerine IPPROTO_UDP ve bu uygulama için listen() fonksiyonunu kullanmamıza gerek yok.

udp-socket-server.c

#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netdb.h>

#define BUF_SIZE 500

int
main(int argc, char *argv[])
{
	struct addrinfo hints;
	struct addrinfo *result, *rp;
	int sfd, s;
	struct sockaddr_storage peer_addr;
	socklen_t peer_addr_len;
	ssize_t nread;
	char buf[BUF_SIZE];

	if (argc != 3) {
		fprintf(stderr, "Usage: %s ip %s port\n", argv[0]);
		exit(EXIT_FAILURE);
	}

	memset(&hints, 0, sizeof(hints));
	hints.ai_family = AF_UNSPEC;    /* Allow IPv4 or IPv6 */
	hints.ai_socktype = SOCK_DGRAM; /* Datagram socket */
	hints.ai_flags = AI_PASSIVE;    /* For wildcard IP address */
	hints.ai_protocol = IPPROTO_UDP;/* UDP protocol */
	hints.ai_canonname = NULL;
	hints.ai_addr = NULL;
	hints.ai_next = NULL;
   
	s = getaddrinfo(argv[1], argv[2], &hints, &result);
	if (s != 0) {
		fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s));
		exit(EXIT_FAILURE);
	}

	/* getaddrinfo() returns a list of address structures.
	  Try each address until we successfully bind(2).
	  If socket(2) (or bind(2)) fails, we (close the socket
	  and) try the next address. */

	for (rp = result; rp != NULL; rp = rp->ai_next) {
		sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
		if (sfd == -1)
			continue;

		if (bind(sfd, rp->ai_addr, rp->ai_addrlen) == 0)
			break;                  /* Success */

		close(sfd);
	}

	freeaddrinfo(result);           /* No longer needed */

	if (rp == NULL) {               /* No address succeeded */
		fprintf(stderr, "Could not bind\n");
		exit(EXIT_FAILURE);
	}


   /* Read datagrams and echo them back to sender */

	for (;;) {
		peer_addr_len = sizeof(peer_addr);
		nread = recvfrom(sfd, buf, BUF_SIZE, 0,
					(struct sockaddr *) &peer_addr, &peer_addr_len);
		if (nread == -1)
			continue;               /* Ignore failed request */

		char host[NI_MAXHOST], service[NI_MAXSERV];

		s = getnameinfo((struct sockaddr *) &peer_addr,
		peer_addr_len, host, NI_MAXHOST,
		service, NI_MAXSERV, NI_NUMERICSERV);
		if (s == 0)
			printf("\nReceived %zd bytes from %s:%s the message is: %s\n",
						nread, host, service, buf);
		else
			fprintf(stderr, "getnameinfo: %s\n", gai_strerror(s));

		printf("The message sending back to the client : %s\n", buf);
		if (sendto(sfd, buf, nread, 0,
						(struct sockaddr *) &peer_addr, peer_addr_len) != nread)
		fprintf(stderr, "Error sending response\n");
	}
}

udp-socket-client.c

#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

#define BUF_SIZE 500

int
main(int argc, char *argv[])
{
	struct addrinfo hints;
	struct addrinfo *result, *rp;
	int sfd, s;
	size_t len;
	ssize_t nread;
	char buf[BUF_SIZE];

	if (argc < 3) {
		fprintf(stderr, "Usage: %s host port msg...\n", argv[0]);
		exit(EXIT_FAILURE);
	}

	/* Obtain address(es) matching host/port */

	memset(&hints, 0, sizeof(hints));
	hints.ai_family = AF_UNSPEC;    /* Allow IPv4 or IPv6 */
	hints.ai_socktype = SOCK_DGRAM; /* Datagram socket */
	hints.ai_flags = 0;
	hints.ai_protocol = 0;          /* Any protocol */

	s = getaddrinfo(argv[1], argv[2], &hints, &result);
	if (s != 0) {
		fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s));
		exit(EXIT_FAILURE);
	}

	/* getaddrinfo() returns a list of address structures.
	Try each address until we successfully connect(2).
	If socket(2) (or connect(2)) fails, we (close the socket
	and) try the next address. */

	for (rp = result; rp != NULL; rp = rp->ai_next) {
			sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
		if (sfd == -1)
			continue;

		if (connect(sfd, rp->ai_addr, rp->ai_addrlen) != -1)
			break;                  /* Success */

		close(sfd);
	}

	freeaddrinfo(result);           /* No longer needed */

	if (rp == NULL) {               /* No address succeeded */
		fprintf(stderr, "Could not connect\n");
		exit(EXIT_FAILURE);
	}

	/* Send remaining command-line arguments as separate
	datagrams, and read responses from server */

	for (int j = 3; j < argc; j++) {
		len = strlen(argv[j]) + 1;
		/* +1 for terminating null byte */

		if (len > BUF_SIZE) {
			fprintf(stderr, "Ignoring long message in argument %d\n", j);
			continue;
		}

		if (write(sfd, argv[j], len) != len) {
			fprintf(stderr, "partial/failed write\n");
			exit(EXIT_FAILURE);
		}

		nread = read(sfd, buf, BUF_SIZE);
		if (nread == -1) {
			perror("read");
			exit(EXIT_FAILURE);
		}

		printf("Received %zd bytes: %s\n", nread, buf);
	}
	exit(EXIT_SUCCESS);
}
udp-socket
yasin@uxn-workstation:~/test/c/C-Sockets$ gcc udp-server-socket.c -o udp-server
yasin@uxn-workstation:~/test/c/C-Sockets$ gcc udp-client-socket.c -o udp-client

yasin@uxn-workstation:~/test/c/C-Sockets$ ./udp-server ::1 8484
yasin@uxn-workstation:~/test/c/C-Sockets$ ./udp-client ::1 8484 "Selam!"

yasin@uxn-workstation:~/test/c/C-Sockets$ ./udp-server 0.0.0.0 8484
yasin@uxn-workstation:~/test/c/C-Sockets$ ./udp-client 127.0.0.1 8484 "Selam!"

Bu yazımda da belirttiğim gibi socket oluşturmak için temel 4 adımımız var;

  1. Create
  2. Bind
  3. Listen
  4. Accept

C ve PHP üzerinde temelde fonksiyonlar benzer işlevlere sahip olduğundan yapıyı aynen kurgulamak, uyarlamak zor olmayacaktır. Fırsat bulduğumda PHP ile socket uygulaması yazımı burada verdiğim örnekler ile geliştirebilirim veya yeni bir yazı ile ayrıca tanıtabilirim.

man komutu paha biçilemez bir komut olmakla birlikte, benim gibi giriş seviye C bilgisi olan birisi bile C ile sadece dökümanları inceleyerek ve bir miktar da google ile 3 adet server-client mantığında socket uygulaması yazabiliyor. Dökümanları incelemeniz dileğiyle IPC Yazı dizisi 1. Bölümü burada bitiriyorum.

Allah’a emanet olun.

Bir yorum

  1. […] “Socket ne ki?” diyen arkadaşlar için ise yazılarım arasında, Linux İşletim sistemi, Sistem Servisleri, Network, TCP, UDP, Client-Server İlişkisi, netcat uygulaması, Yazılım Geliştirme gibi kavramlar için tanım, açıklama, standardizasyon, geliştirme, kullanım senaryoları gibi makaleler mevcut olmalı, eğer yoksa merak etmeyin yazım aşamasındadır. Burada yukarıda sıraladığım kavramları temel olarak bildiğinizi düşünerek bu yazıyı hazırlıyorum. […]

Yorumlar kapatıldı ancak, geri izlemeler ve pingback'ler açık.