컴퓨터 네트워크 (8), Transport Layer 2

2022. 5. 24. 20:09강의 내용 정리/컴퓨터 네트워크

728x90
반응형

Transport Layer 2

1. Socket Programming

전통적인 TCP/IP, UDP/IP를 트랜스 포트로 삼고, IP layer를 네트워크 계층으로 삼는 통신 기술


1) Socket Programming이란?

- 컴퓨터를 식별할 수 있는 IP 주소와 소프트웨어를 지칭할 수 있는 포트 번호를 활용하면 유니크한 소프트웨어를 찾아갈 수 있다. 특정 노드의 엔드포인트가 되는 것이 소켓이었다.

- 소켓은 TCP/IP의 IP address와 포트번호에 해당하는 번호이다. 이를 활용해 특정 장치의 특정 프로그램을 찾아갈 수 있다.

- 네트워크에서 각각 두개의 엔드포인트를 찾아갈 수 있다. 번호이기에 테이블 형태로 관리한다.

- TCP를 돌리기 위한 컴퓨터 메모리 공간이나 프로세스 등이 만들어져서 네트워크 소켓에 연결되어있다.

- TCP나 UDP 위에서 포트번호와 ip address에 기반한 통신 프로그램을 짜는 것이 통신 프로그램이다.

- TCP UDP IP를 사용하기에 bidirectional communication을 지원한다. 따라서 Send, Receive하는 것이 모두 가능하다. 

- 네트워크의 규모에 상관없고, 로컬 호스트에 있는 서버에 접속하는 것을 많이 볼 수 있는데 같은 컴퓨터 내에서도 소켓 프로그래밍을 사용하는 것이 가능하고, 본인의 ip address를 사용하면 된다. 이때 포트번호만 다르면 한대의 컴퓨터 안에서도 두 개의 프로그램이 통신이 가능하다. 두 개의 프로그램이 통신하는 것을 interprocess communication이라고 한다. 

 

2) Client and Server Architecture

- Socket client 소켓을 사용해 통신을 요청하는 경우  대표적으로 gmail client, 웹 브라우저 등이 될 것이다.

- Socket Server: 소켓을 사용해 통신을 요청 받는 경우 일반적으로 클라이언트보다 먼저 살아난다. 대표적으로 Web server가 이에 해당한다.

 

- TCP/UCP를 사용해 클라이언트와 서버와 통신을 할 때 1대1 관계이다. 이 때 카카오톡을 생각해볼 수 있는데, 카카오톡은 채팅방(세션)을 하나 만든다. 하지만 이때 TCP/UDP는 모든 클라이언트와 서버들이 각각 일대일로 연결되기에 요청을 받을 때마다 서버는 N번만큼 동작을 수행해야한다. TCP와 UDP의 1대 1 커뮤니케이션이라는 특징이 이에 대한 문제를 발생시킬 수 있다.

 

3) Procedures

- 서버: 서버 소프트웨어가 돌아갈 컴퓨터에서 살아난 뒤, 운영체제를 통해 소켓을 열어서 본인의 프로그램을 그 소켓에 바인드한다. 이때 서버는 지정된 포트가 있어서 그 포트 번호를 가지고 소켓을 열어서 이에 연결한다. 그리고 대기한다.(listen)

- 클라이언트: 서버에 접속하기 위해 소켓을 연다. 즉, 포트번호를 요청한다. 이는 랜덤 넘버로 받는다. TCP인 경우에는 listen하는 서버에서 연결 요청을 해야한다. (SYN) Client/server session은 모두 함께 동작한다. client는 Fin을 보내 연결 해제한다. 이 클래스를 위해 만들어진 메모리 공간을 정리한다. 서버는 이후 대기하는 상태가 된다.

 

4) Python Code

(1) Echo Server in Python

import socket

HOST = '127.0.0.1' # Standard loopback interface address (localhost)
PORT = 65432 # Port to listen on (non-privileged ports are > 1023)

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
	s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
    	print('Connected by', addr)
        while True:
        	data = conn.recv(1024)
            if not data:
            	break
            conn.sendall(data)

(2) Echo client in Python

import socket

HOST = '127.0.0.1' # Standard loopback interface address (localhost)
PORT = 65432 # Port to listen on (non-privileged ports are > 1023)

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
	s.connect((HOST, PORT))
    s.sendall(b'Hello world')
    data = s.recv(1024)
    
print('Received, repr(data))

 

4) C Code

(1) Echo Server in C

#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

/**
 * TCP Uses 2 types of sockets, the connection socket and the listen socket.
 * The Goal is to separate the connection phase from the data exchange phase.
 * */

int main(int argc, char *argv[]) {
	// port to start the server on
	int SERVER_PORT = 8877;

	// socket address used for the server
	struct sockaddr_in server_address;
	memset(&server_address, 0, sizeof(server_address));
	server_address.sin_family = AF_INET;

	// htons: host to network short: transforms a value in host byte
	// ordering format to a short value in network byte ordering format
	server_address.sin_port = htons(SERVER_PORT);

	// htonl: host to network long: same as htons but to long
	server_address.sin_addr.s_addr = htonl(INADDR_ANY);

	// create a TCP socket, creation returns -1 on failure
	int listen_sock;
	if ((listen_sock = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
		printf("could not create listen socket\n");
		return 1;
	}

	// bind it to listen to the incoming connections on the created server
	// address, will return -1 on error
	if ((bind(listen_sock, (struct sockaddr *)&server_address,
	          sizeof(server_address))) < 0) {
		printf("could not bind socket\n");
		return 1;
	}

	int wait_size = 16;  // maximum number of waiting clients, after which
	                     // dropping begins
	if (listen(listen_sock, wait_size) < 0) {
		printf("could not open socket for listening\n");
		return 1;
	}

	// socket address used to store client address
	struct sockaddr_in client_address;
	int client_address_len = 0;

	// run indefinitely
	while (true) {
		// open a new socket to transmit data per connection
		int sock;
		if ((sock =
		         accept(listen_sock, (struct sockaddr *)&client_address,
		                &client_address_len)) < 0) {
			printf("could not open a socket to accept data\n");
			return 1;
		}

		int n = 0;
		int len = 0, maxlen = 100;
		char buffer[maxlen];
		char *pbuffer = buffer;

		printf("client connected with ip address: %s\n",
		       inet_ntoa(client_address.sin_addr));

		// keep running as long as the client keeps the connection open
		while ((n = recv(sock, pbuffer, maxlen, 0)) > 0) {
			pbuffer += n;
			maxlen -= n;
			len += n;

			printf("received: '%s'\n", buffer);

			// echo received content back
			send(sock, buffer, len, 0);
		}

		close(sock);
	}

	close(listen_sock);
	return 0;
}

(2) Echo client in C

#include <arpa/inet.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>

int main() {
	const char* server_name = "localhost";
	const int server_port = 8877;

	struct sockaddr_in server_address;
	memset(&server_address, 0, sizeof(server_address));
	server_address.sin_family = AF_INET;

	// creates binary representation of server name
	// and stores it as sin_addr
	// http://beej.us/guide/bgnet/output/html/multipage/inet_ntopman.html
	inet_pton(AF_INET, server_name, &server_address.sin_addr);

	// htons: port in network order format
	server_address.sin_port = htons(server_port);

	// open a stream socket
	int sock;
	if ((sock = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
		printf("could not create socket\n");
		return 1;
	}

	// TCP is connection oriented, a reliable connection
	// **must** be established before any data is exchanged
	if (connect(sock, (struct sockaddr*)&server_address,
	            sizeof(server_address)) < 0) {
		printf("could not connect to server\n");
		return 1;
	}

	// send

	// data that will be sent to the server
	const char* data_to_send = "Gangadhar Hi Shaktimaan hai";
	send(sock, data_to_send, strlen(data_to_send), 0);

	// receive

	int n = 0;
	int len = 0, maxlen = 100;
	char buffer[maxlen];
	char* pbuffer = buffer;

	// will remain open until the server terminates the connection
	while ((n = recv(sock, pbuffer, maxlen, 0)) > 0) {
		pbuffer += n;
		maxlen -= n;
		len += n;

		buffer[len] = '\0';
		printf("received: '%s'\n", buffer);
	}

	// close the socket
	close(sock);
	return 0;
}

 

- 새로운 기술이 만들어지고 그 기술을 사용하면 우리가 원하는 end user application이나 서비스가 안정적으로 지원할 수 있도록 도울 수 있다. 이러한 경우에는 표준을 만들거나 이론적인 베이스를 통해 만든 것이 아닌 필요한 것을 개발하고 오픈소스로 공개하며 하는 경우가 많다. 이에 가장 유명하는 것이 Message Queue(MQ)이다. 4계층과 5계층 사이에 더 많은 기술을 넣기도 한다.

 

 


2. ZeroMQ

프로그램 간에 비동기적으로 메세지를 주고 받는 라이브러리


 

1) 개념

-  Notation이 다양하다.

- 서버가 존재하지 않고, 별도로 zero messageQ를 사용하지는 않는다.

- TCP와 UDP에서의 소켓 API 함수 체계를 비슷하게 해서 소켓 프로그래밍을 다룰 수 있다면 크게 이해하는데에 문제가 없도록 조절한다.

- 최근에는 오픈 소스 형태로 제공하면서 이용자가 많아졌다.

- TCP 외에 IPC, 멀티캐스트(이론적으로만 존재했었다.) 등을 지원한다.

- 보내면 받는 기본적인 기능을 뛰어넘어서 더 다양한 기능을 제공한다. 

- 빠르고 비동기적이며 작다.

- n이 3 이상일 때 사용하는 것이 좋다.

 

2) 기능

- 프로그래머들이 익숙한 소켓 API 방식을 유지하고, API보단 기능적인 차이가 존재한다.

- TCP/UDP와는 다르게 Many to Many를 지원한다.

- 패턴을 확인하여 각각에 맞는 기능을 사용하는 것이 중요하다.

 

3) 패턴

(1) Request-Reply

- Set of clients와 Set of Services가 서로 통신을 하는 것이다.

 

(2) Publish-subscribe

- 누군가가 정보를 가지고있고, 이 정보가 변경될 때마다 변경사항을 가입자들에게 뿌린다.

- 데이터가 퍼블리셔에의해 퍼블리싱되면 가입자들이 이를 받아서 처리한다.

 

(3) Push-pull(Pipeline, Fan-out/fan-in)

- 정보를 만드는 사람들과 이를 수집해서 사람들이 존재했을 때 이들간에 필요한 정보를 가져오고 이를 미뤄내면서 병렬처리가 큰 규모로 이뤄질 수 있다.

 

(4) Exclusive pair

- 그들 만의 형태로 1대1로 특정하게 사용 가능하다.

 

(5) Exapmle: Client & Worker

 

- TCP/UDP라면 각각의 관계를 프로그램으로 짜야한다.

- 왼쪽은 클라이언트가 브로커에서 요구해서, 브로커가 어디로 갈지 정해서 적합한 결과를 가져올 수 있도록 한다. 이는 직접 프로그램을 짜지 않아도 API로 제공한다. -> 3대 3을 하는 것을 브로커가 지원한다.

- 클라이언트가 요청할 때 중간에 있는 Router, Server Queue Proxy, dealer가 적합한 곳으로 이동시킨다.

- 브로커는 클라이언트와 독립적인 대등 관계이다. 이는 래빗과 다르다.

 

(6) Example: Publish & Subscribe

- Publisher는 정보를 뿌린다. 

- Sub은 본인이 Subscriber를 한다. 이에 따라 퍼블리셔의 정보가 바뀌면 모두 받을 수 있다.

- 반복문을 통해 여러번 send하는 것이 아닌 한번의 send만으로 보낼 수 있다.

- 퍼블리셔가 여러명인 경우도 존재할 수 있다. 

 

(7) Python code

import zmq
context = zmq.Context()

subscriber = context.socket(zmq.SUB)
subscriber.connect('tcp://192.168.55.112:5556')
subscriber.connect('tcp://192.168.55.201:7721')
subscriber.setsockopt(zmq.SUBSCRIBE, "NASDAQ")

publisher = context.socket(zmq.PUB)
publisher.bind('ipc://nasdaq-feed')

while True:
    message = subscriber.recv()
    publisher.send(message)

 

4) 클라우드 컴퓨팅 ZeroMQ

- 서로 다른 컴퓨터 간에서 ZeroMQ를 사용하는 것이 많다.

 

 


3. RabbitMQ

1) Rabbit MQ

- 중앙집중형 방식으로 서버가 존재한다. 메세지를 보내고자하는 게 있으면 서버가 이를 보낸다.

- 오픈 소스이며 브로커라고도 불리는 서버가 있는 형태이다.

- 비동기적이기에 메세지를 보내며 응답이 오기 전에도 작업을 할 수 있다.

- 여러 언어가 제공한다. 

- AMQP, STOMP, MQTT 방식 등이 있다.

 

2) 동작

- 통신회사에서 위와 같은 방식을 많이 사용했다.

- 기업/고객 같은 경우에는 1대 N으로 많이 보낸다.

- M대 M의 분산환경에서 다수가 통신하는 ZeroMQ와는 다르게 N이 굉장히 클 때 1대 N으로 동작하는 것이 RabbitMQ이다.

 

 


4. Book recommendation

- ZeroMQ (ORelly, 오라일리)

- TCP/IP Illustrated volume -> 유닉스

- TCP/IP Architecture, Design and Implementation in Linux -> 리눅스

- C++ network programming, boost asio C++ network programming -> C++

- Python network programming, foundations of python network programming -> Python

 

more

- Wifi, 이더넷, 블루투스 등 위에서 직접 짜야하는 경우는 c++을 사용해야한다.

- 네트워크 기능이 강화된다.

728x90
반응형