소켓 통신에 대한 개념과 예제가 더 많은 리눅스 교재를 배포했습니다. 아래의 페이지에서 리눅스 교재를 받아가세요.

https://reakwon.tistory.com/233

 

리눅스 프로그래밍 note 배포

티스토리에 리눅스에 관한 내용을 두서없이 여지껏 포스팅했었데요. 저도 제 포스팅을 찾기가 어렵기도 하고 티스토리에서 코드삽입을 하게 되면 이게 일자로 쭉 쓰여져있는 x같은 현상이 생겨

reakwon.tistory.com

 

소켓(socket)

네트워크 통신을 하는 표준 방법으로 프로세스간 연결의 종점이라고 볼 수 있습니다. 기본적인 개념은 아래의 그림과 같습니다. 

위의 그림은 TCP/IP에서의 인터넷 통신을 보여줍니다. 클라이언트의 컴퓨터의 물리적 주소(MAC 주소) DD-44-EE-55-FF-66이며 논리적 주소(IP 주소)는 10.2.2.2입니다. 클라이언트는 여러가지의 프로그램을 실행시키고 있는데 그 중 어떤 프로세스는 TCP 포트번호 12345를 사용합니다. 이 클라이언트 프로세스는 물리적 주소가 11-AA-22-BB-33-CC이며 논리적 주소 10.1.1.3인 서버 컴퓨터의 포트 번호 80번을 사용하는 서버 프로세스와 연결되어 있습니다.

 

구체적으로 어떻게 통신할까요? 각 프로세스는 소켓을 통해서 통신을 하게 되는데, 소켓은 간단히 얘기해 ip주소와 포트번호를 갖고 있는 인터페이스라고 생각하면 됩니다. 소켓은 리눅스에서 파일로 다루어지며 프로세스는 이 소켓을 사용할때 파일디스크립터를 통해 사용합니다. 우리는 리눅스 파일 입출력에 대해 배울때 파일디스크립터를 사용했지요? 소켓 역시 파일디스크립터를 이용해서 읽기, 쓰기가 가능합니다.

 

소켓 통신할때 필요한 주요함수는 무엇이 있을까요? 간단히 알아보도록 합시다. 

 

1. socket(int domain, int type, int protocol)

소켓을 만드는데 바로 이 함수를 사용합니다. 소켓 역시 파일로 다루어지기 때문에 반환값은 파일디스크립터입니다. 만약 소켓을 여는데 실패했다면 -1을 리턴합니다.

 

2. connect(int fd, struct sockaddr *remote_host, socklen_t addr_length)

원격 호스트(원격 컴퓨터)와 연결하는 함수입니다. 연결된 정보는 remote_host에 저장됩니다. 성공시 0, 오류시 -1을 반환합니다.

 

3. bind(int fd, struct sockaddr *local_addr, socklen_t addr_length)

소켓을 바인딩합니다. 이렇게 생각하면 됩니다. 지금 fd로 넘겨지는 소켓과 이 프로세스와 묶는다(bind)라고 생각하시면 됩니다. 그래서 해당 프로세스는 소켓을 통해 다른 컴퓨터로부터 연결을 받아들일 수 있습니다.

 

4. listen(int fd, int backlog_queue_size)

소켓을 통해 들어오는 연결을 듣습니다. backlog_queue_size만큼 연결 요청을 큐에 넣습니다. 성공시 0, 오류시 -1을 반환합니다.

 

5. accept(int fd, sockaddr *remote_host, socklen_t *addr_length)

어떤 컴퓨터에서 이 컴퓨터로 연결할때 연결을 받아들입니다. 함수 이름이 말해주고 있죠.

연결된 원격 컴퓨터의 정보는 remote_host에 저장됩니다. 오류시에 -1을 반환합니다.

 

6. send(int fd, void* buffer, size_t n, int flags)

buffer를 소켓 파일 디스크립터인 fd로 전송합니다. 보낸 바이트수를 반환하며 실패시 -1을 반환합니다.

 

7. recv(int fd, void* buffer, size_t n, int flags)

send함수와 사용법이 거의 비슷합니다. n바이트를 buffer로 읽습니다. 성공시 받은 바이트수를 반환하며 실패시 -1을 반환합니다.

 

 

 

이제 예제를 보며 더 자세한 설명을 하도록 하죠. 다음 예제는 서버 프로그램이 클라이언트 프로그램에서 전송한 메시지를 출력해주는 소스코드입니다. 클라이언트 프로그램은 텔넷을 사용할 것이기 때문에 따로 클라이언트 프로그램 소스코드는 없습니다.

 

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define PORT 12346
#define BUF_SIZE 1024
int main(void){
        int socket_fd,accepted_fd;
        struct sockaddr_in host_addr, client_addr;
        socklen_t size;
        int recv_length;
        char buffer[BUF_SIZE];

        socket_fd=socket(PF_INET,SOCK_STREAM,0);

        host_addr.sin_family=AF_INET;
        host_addr.sin_port=htons(PORT);
        host_addr.sin_addr.s_addr=0;
        memset(&(host_addr.sin_zero),0,8);

        bind(socket_fd,(struct sockaddr *)&host_addr,sizeof(struct sockaddr));

        listen(socket_fd,3);

        while(1){
                size=sizeof(struct sockaddr_in);
                accepted_fd=accept(socket_fd,(struct sockaddr *)&client_addr,&size);

                send(accepted_fd,"Connected",10,0);
                printf("Client Info : IP %s, Port %d\n", inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port));

                recv_length=recv(accepted_fd,&buffer,BUF_SIZE,0);
                while(recv_length>0){
                        printf("From Client : %s\n",buffer);
                        recv_length=recv(accepted_fd,&buffer,BUF_SIZE,0);
                }

                close(accepted_fd);
        }
        return 0;
}

 

여기서 포트번호는 12346을 사용한다고 하겠습니다.

 

socket_fd=socket(PF_INET,SOCK_STREAM,0);

socket의 첫번째 인자는 프로토콜 체계 PF(Protocol Family)를 지정합니다. PF_INET은 인터넷 IP프로토콜 체계입니다. 사용하는 프로토콜 체계에는 여러가지가 있습니다. IP외에도 1970년대에 개발된 공중 데이터 네트워크에 대한 표준 X.25 외에도 애플토크,XEROX 네트워크 등등 있는데 우리는 IP를 사용할 것이기 때문에 PF_INET만 사용할 것입니다.

 

두번째 인자는 소켓의 타입입니다. 가장 보편적으로 사용하는 타입은 Stream과 Datagram입니다. SOCK_STREAM은 연결형, SOCK_DGRAM은 비연결형이라고 생각하면 되겠습니다.

 

세번째 인자는 프로토콜로, 일반적으로 0을 넣어주면 시스템이 자동으로 설정해줍니다.

host_addr.sin_family=AF_INET;
host_addr.sin_port=htons(PORT);
host_addr.sin_addr.s_addr=0;
memset(&(host_addr.sin_zero),0,8);

bind(socket_fd,(struct sockaddr *)&host_addr,sizeof(struct sockaddr));

 

다음은 바인드할때 구조체를 넘겨야하는데요. 이 프로세스가 사용할 소켓 fd와 컴퓨터의 IP주소, 포트와 묶는 작업이라고 보면 됩니다.

 

우리는 TCP/IP 상에서의 통신이기 때문에 IPv4용 구조체인 sockaddr_in을 사용합니다.

sin_family에는 IP용 Address Family(AF_INET)을 지정합니다. 

sin_port는 이 프로세스가 사용할 포트번호를 지정합니다.

sin_addr.s_addr에는 주소가 들어가게 되는데요. 0은 현재 컴퓨터의 주소를 자동으로 채우라는 의미입니다. 그것이 아니라면 주소를 직접 지정해주어야합니다.

 

htons?

sin_port에서 htons는 무슨 함수일까요? 이 함수의 풀 네임은 host-to-network short로 16비트 정수를 호스트 바이트 순서에서 네트워크 바이트 순서로 변환하는 함수입니다.

AF_INET 소켓 주소 구조체에서 사용되는 포트 번호와 IP주소는 빅 엔디언(Big-Endian)이라는 네트워크 바이트 순서를 따릅니다. 이것은 보통 우리가 사용하는 x86의 리틀 엔디언과는 반대의 표기법이죠. 그래서 변환없이 그대로 사용하게 되면 바이트 순서가 달라지게 되어 제대로 동작하지 않습니다.

 

이제 bind를 호출하는데 두번째 인자를 보세요. (struct sockaddr*)로 형 변환하고 있습니다. host_addr이라는 구조체 변수는 sockaddr_in이라는 구조체입니다.

bind는 TCP/IP뿐만 아니라 X.25, 애플토크 등 여러 프로토콜이 존재하기 때문에 인자로 받아야할 구조체 형이 sockaddr_in뿐만이 아닙니다. 그래서 일반화된 구조체가 필요하게 되는데 그 구조체가 sockaddr입니다.

 

sockaddr_in은 sockaddr로 형변환할 수 있습니다. 왜냐하면 구조체의 크기가 같기 때문이죠. sockaddr 구조체는 2바이트의 Address Family와 14바이트의 주소를 사용합니다. 반면 sockaddr_in의 IPv4 전용 구조체는 sa_data의 주소를 포트번호, ip주소, 기타 추가 비트를 포함하고 있죠.  우리는 일반화된 sockaddr에 sa_data에 직접 포트번호와 주소를 읽고 쓰기가 상당히 불편합니다.

그래서 더 사용하기 편한 인터넷 전용 구조체를 사용합니다. 형변환에 문제가 없게 크기를 같게 만들어 호환성에 문제가 없습니다.

 

listen(socket_fd,3);

 

그 소켓으로 들어오는 연결을 기다립니다. 마지막인자는 백로그 큐의 최대크기입니다.

while(1){
	size=sizeof(struct sockaddr_in);
	accepted_fd=accept(socket_fd,(struct sockaddr *)&client_addr,&size);

	send(accepted_fd,"Connected",10,0);
	printf("Client Info : IP %s, Port %d\n", inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port));

	recv_length=recv(accepted_fd,&buffer,BUF_SIZE,0);
	while(recv_length>0){
		printf("From Client : %s\n",buffer);
		recv_length=recv(accepted_fd,&buffer,BUF_SIZE,0);
	}

	close(accepted_fd);
}
        

 

 

 

accept를 통해서 이제 새롭게 연결된 클라이언트 전용 파일디스크립터를 얻어옵니다. 클라이언트에 대한 정보는 client_addr에 저장됩니다.

 

연결이 성공돼었다면 send를 통해서 "Connected"라는 문자열을 클라이언트 쪽으로 보냅니다.

그 후 클라이언트의 정보를 출력하지요. 두가지를 출력합니다. IP주소와 Port번호입니다.

 

inet_ntoa(struct in_addr *network_addr)

네트워크 주소를 숫자사이의 점을 찍는 IP주소로 network to acsii라는 뜻입니다. ip주소가 담긴 in_addr구조체는 32비트의 네트워크 주소를 갖고 있기 때문에 숫자사이에 점을 찍는 형태로 바꾸려면 이 함수를 사용합니다.

 

ntohs(Network-to-Host Short)

이것 역시 네트워크 바이트 순서가 빅 엔디안이고, 호스트의 바이트 순서가 리틀 엔디안일때 변환해야할때 사용합니다.

 

그 후에는 계속 recv를 통해 클라이언트에서 입력받은 메시지를 서버에서 그대로 출력해줍니다.

 

결과

서버

# gcc server.c
# ./a.out

 

클라이언트

# telnet 192.168.10.131 12348
Trying 192.168.10.131...
Connected to 192.168.10.131.
Escape character is '^]'.

Connected

 

서버

Client Info : IP 192.168.10.131, Port 43774

 

 

클라이언트

Hi
I'm Reakwon

 

서버

From Client :

From Client : Hi

From Client : I'm Reakwon

 

클라이언트에서 타이핑한 것이 서버에서 그대로 출력이 되는 것을 볼 수 있습니다.

 

이상으로 간단히 리눅스의 소켓과 그에 대한 예제를 보았습니다.

반응형
블로그 이미지

REAKWON

와나진짜

,

TCP/IP

인터넷 프로그램들이 서로 통신을 하는데 있어서 여러 프로토콜이 있습니다. 인터넷 프로토콜에서 가장 많이 사용하는 대표적인 프로토콜은 여러분들도 많이 아시다시피 IP입니다. 여기서 중요한 것은 TCP/IP는 계층이 아니라 프로토콜이라는 사실이라는 사실을 주의해주세요.


TCP/IP는 OSI7 계층과는 조금은 다른 TCP/IP의 구조적인 계층 위에서 동작합니다.


지난 번에 OSI7 계층에 대해서 알아보았는데요. TCP/IP 계층은 OSI7계층과 비교하여 어떤 점이 다른지 살펴보는 시간을 가져보도록 하지요.






OSI7 계층과는 조금은 다른 모습을 볼 수 있습니다. 보세요.

우선 계층의 수 부터가 다릅니다. OSI는 7계층인데 반해 TCP/IP 계층은 4계층이 전부라는 것을 알 수 있습니다.


이제 조금 더 세세하게 살펴보도록 하지요.




1. 네트워크 인터페이스 계층 (Network Interface Layer)

이 계층은 Node-To-Node간의 신뢰성 있는 데이터 전송을 담당하는 계층입니다. OSI7 계층의 물리 계층과 데이터링크 계층의 역할을 바로 이 계층이 담당하는 것으로 볼 수 있네요.


따라서 MAC주소가 이 계층에서 사용됩니다. MAC주소는 OSI7 계층에서 데이터링크 계층의 주소였죠?? 


네트워크 인터페이스 계층이 바로 데이터링크 계층까지 담당하니까 MAC 어드레스가 사용되는 겁니다.


혹시 랜카드라고 들어보셨나요? 바로 이거말이에요.




정확한 명칭은 NIC라고 하여 Network Interface Card입니다. 바로 이 랜카드가 있어야만 네트워크 통신을 할 수 있는데, 이름에서도 알 수 있듯이 네트워크 인터페이스 계층에서 동작하는 장비입니다.


주요 프로토콜을 무엇이 있을까요?

LAN상에서는 Ethernet, TokenRing, FDDI 등이 있으며 WAN 상에서는 X.25, Frame Relay, PPP 등이 있습니다.


2. 인터넷 계층 (Internet Layer)

OSI7계층의 네트워크 계층을 담당하는 계층입니다. OSI7 계층처럼 호스트간의 라우팅을 담당하지요. 


인터넷 계층에서 동작하는 프로토콜에는 무엇이 있을까요? 대표적인 몇가지 프로토콜을 살짝 알아보도록 합시다.


IP(Internet Protocol) : 비신뢰성, 비연결지향 데이터그램 프로토콜입니다. 

ARP(Address Resolution Protocol) : 주소변환 프로토콜입니다. IP주소를 MAC주소로 변환하는 프로토콜이지요.

RARP(Reverse ARP) : 반대로 MAC주소로 IP주소를 찾는 프로토콜입니다.

ICMP(Internet Control Message Protocol) : 상태 진단 메시지 프로토콜인데요. 이 프로토콜을 이용하는 대표적인 프로그램이 ping입니다.

IGMP(Internet Group Message Protocol) : 멀티캐스트용 프로토콜입니다.




3. 전송 계층 (Transport Layer)

OSI7 계층의 전송계층과 같습니다. 프로세스간의 신뢰성 있는 데이터 전송을 담당하는 계층입니다.


process-to-process 전송을 담당하기 위해서는 논리적 주소가 필요한데요. process가 사용하는 포트 번호를 그 논리적 주소로 사용합니다.


전송 계층에서 프로토콜은 무엇이 있을까요?


TCP (Transmission Control Protocol) : 신뢰성있는 연결지향형 프로토콜입니다. 신뢰성있다는 말은 그 페킷에 대한 오류처리나 재전송따위로 에러를 복구하는 것을 말합니다. 그때문에 TCP의 헤더에 붙는 정보가 많습니다.

UDP (User Datagram Protocol) : 비신뢰성 비연결형 프로토콜입니다. 페킷을 잃거나 오류가 있어도 대처하지 않는 것을 말합니다. 따라서 UDP헤더는 간단한 구조를 갖고 있습니다.



4. 응용 계층 (Application Layer)

사용자와 가장 가까운 계층입니다. OSI7계층의 5계층부터 7계층까지의 기능을 담당하고 있지요.

서버나 클라이언트 응용 프로그램이 이 계층에서 동작합니다. 우리가 알고 있는 브라우저나 텔넷같은 서비스가 이 계층에 동작하며, 동작하기 위해서는 전송계층의 주소, 즉 포트번호를 사용합니다.


이를테면 http는 포트번호 80번을 사용하지요.


역시 프로토콜은 무엇이 있나 살펴볼까요?


HTTP (Hyper-Text Transfer Protocol) : TCP기반의 프로토콜로 포트번호 80번을 사용합니다.

Telnet : TCP 포트번호 23번을 사용합니다. 원격 터미널을 접속할때 이 포로토콜을 사용합니다.

SSH (Secure Shell) : 텔넷과 같은 서비스는 보안에 취약합니다. 비밀번호가 암호화되지 않아 그대로 노출이 되기 때문이지요. 이것을 보완한것이 SSH입니다. 포트번호 22번을 사용합니다.

FTP(File Transfer Protocol) : 파일 전송 프로토콜입니다. 파일을 받거나 올릴때 FTP를 사용하지요. FTP는 파일을 올리거나 내려받을때 신뢰성을 중요시하기 때문에 TCP에서 동작하구요. 2개의 포트를 사용합니다. 

TCP 포트 20번은 데이터 전송을 위한 용도, TCP 포트 21번은 제어용으로 사용합니다.

SMTP (Simple Mail Transfer Protocol) : 메일 전송 프로토콜입니다. TCP 상에서 동작하며 포트는 25번을 사용합니다.

POP3 (Post Office Protocol Version3) : 메일 수신용 프로토콜입니다. 아웃룩같은 프로그램이 POP3라는 프로토콜을 사용하여 동작합니다. TCP 포트 110번을 사용합니다.

DNS (Domain Name System) : 도메인명에 대한 호스트 정보를 제공해줍니다. 기본적으로 UDP상에서 동작합니다. 기본적으로 실패하면 다시 한번 요청하면 되며 그렇게 중요한 정보가 아니기 때문이죠. 하지만 신뢰성을 요할 경우에는 TCP상에서도 동작합니다. 데이터의 길이가 길 경우같은 때 TCP 기반으로 동작할 수 있습니다.

UDP, TCP 포트 53번을 사용합니다.




이와 같이 포트번호가 특정 프로토콜이 사용해서 우리가 쓸 수 없는 포트들이 있습니다. 이런 포트들을 well-known port라고 합니다.


프로토콜 헤더 정보를 잘 읽고 분석할 수 있다면 네트워크를 더 잘 이해할 수 있을 겁니다.


따라서 다음 시간에는 헤더를 보고 무슨 정보가 있는지 살펴보는 기회를 갖도록 하겠습니다.

반응형
블로그 이미지

REAKWON

와나진짜

,