[소켓 #01] 네트워크 프로그래밍과 소켓이란 무엇인가

이석환·2023년 4월 12일

Socket Programming

목록 보기
2/18

1. 네트워크 프로그래밍과 소켓에 대한 이해

네트워크 프로그래밍이란 ?

  • 소켓을 가지고 프로그래밍하기 때문에 소켓 프로그래밍이라고도 한다.
  • 네트워크로 연결된 둘 이상의 컴퓨터 사이에서의 데이터를 통신하는 프로그램

소켓이란 ?

  • 네트워크(인터넷)의 연결 도구
  • 운영체제에서 제공이 되며 프로그래머는 데이터 송수신에 대한 HW, SW에 대해 세부적인 신경을 쓰지 않아도 된다.

2. Server 소켓 생성 (전화기 만들기)

소켓은 간단히 말하면 전화기와 비교 할 수 있다.
중요한 점은 전화를 거는 용도의 소켓과 전화를 받는 용도의 소켓의 생성방법에는 차이가 있다.

  • 소켓은 socket 함수의 호출을 통해서 생성된다
#inlcude <sys/socket.h>
int socket(int domain, int type, int protocol);
// 성공 시 파일 디스크럽터, 실패시 -1 반환

소켓의 생성은 전화기를 사는 것과 같은 행동

3. 소켓의 주소 할당 및 연결 (전화번호 부여)

전화기에 전화번호가 있듯이 소켓에도 주소 정보가 할당된다.
소켓의 주소 정보는 IP와 PORT 번호로 구성이 된다.

#include <sys/socket.h>
int bind(int sockfd, struct sockaddr *myaddr, socklen_t addrlen);
// 성공 시 파일 디스크럽터, 실패시 -1 반환
// sockfd - 파일 디스크럽터

4. 연결 요청이 가능한 상태 만들기 (전화기의 연결)

연결 요청이 가능한 상태란 무엇인가 ?
소켓은 양방소통이 되는 휴대폰과 다르게 보내는 소켓과 받는 소켓이 나뉘어 진다.
연결 요청이 가능한 상태의 소켓이란 걸려오는 전화를 받을 수 있는 상태로 비유할 수 있다.
전화를 거는 용도의 소켓은 해당 상태가 필요 없다.
걸려오는 전화를 받을 용도의 소켓에서 필요한 상태이다.

#include <sys/socket.h>
int listen(int sockfd, int backlog);
//성공 시 0 반환, 실패 시 -1 반환
소켓에 할당된 IP와 PORT 번호로 연결요청이 가능한 상태가 된다.

5. 연결 요청을 수락하기 (수화기를 드는 상황)

연결 요청을 수락하는 상태
걸려오는 전화에 대해서 수락의 의미로 수화기를 드는 것에 비유할 수 있다.
연결 요청이 수락되어야 데이터의 송수신이 가능.
수락된 이후에는 데이터의 양방향 송수신이 가능하다.

#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
// 성공 시 파일 디스크럽터, 실패 시 -1 반환
// accept 함수 호출 이후에는 데이터의 송수신이 가능하다.
// 단, 연결 요청이 있을 때에만 accept 함수가 반환을 한다.
// 해당 함수가 실행되고 나면 연결 요청이 가능한 상태로 변경된다.

정리하기
1. 소켓의 생성 - socket()
2. IP와 PORT 번호 할당 - bind()
3. 연결 요청 가능 상태로 변경 - listen()
4. 연결 요청에 대한 수락 - accept()

  • 이렇게 생성된 소켓은 server 소켓이라고 하며 client 소켓보다 먼저 실행되어야 한다.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>

void error_handling(char *message);

int main(int argc, char *argv[])
{
	int serv_sock;
	int clnt_sock;

	struct sockaddr_in serv_addr;
	struct sockaddr_in clnt_addr;
	socklen_t clnt_addr_size;

	char message[]="2020118198, LeeSeokhwan";
	
	if(argc!=2){
		printf("Usage : %s <port>\n", argv[0]);
		exit(1);
	}
	
	serv_sock=socket(PF_INET, SOCK_STREAM, 0);
	if(serv_sock == -1)
		error_handling("socket() error");
	
	memset(&serv_addr, 0, sizeof(serv_addr));
	serv_addr.sin_family=AF_INET;
	serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);
	serv_addr.sin_port=htons(atoi(argv[1]));
	
	if(bind(serv_sock, (struct sockaddr*) &serv_addr, sizeof(serv_addr))==-1)
		error_handling("bind() error"); 
	
	if(listen(serv_sock, 5)==-1)
		error_handling("listen() error");
	
	clnt_addr_size=sizeof(clnt_addr);  
	clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_addr,&clnt_addr_size);
	if(clnt_sock==-1)
		error_handling("accept() error");  
	
	write(clnt_sock, message, sizeof(message));
	close(clnt_sock);
	close(serv_sock);
	return 0;
}

void error_handling(char *message)
{
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}

6. Client Socket 생성 - 연결을 요청하는 소켓 (전화를 거는 상황)

  • 연결을 요청하는 소켓의 구현
    전화를 거는 상황에 비유할 수 있다.
    Server와 마찬가지로 일단 socket()을 호출하여 생성해준다. (server와 동일, 과정 생략)
#include <sys/socket.h>
int connect(int sockfd, struct sockaddr *serv_addr, sockeln_t addrlen);
// 성공 시 0, 실패 시 -1 반환
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>

void error_handling(char *message);

int main(int argc, char* argv[])
{
	int sock;
	struct sockaddr_in serv_addr;
	char message[30];
	int str_len=0;
	int idx=0, read_len=0;
	
	if(argc!=3){
		printf("Usage : %s <IP> <port>\n", argv[0]);
		exit(1);
	}
	
	sock=socket(PF_INET, SOCK_STREAM, 0);
	if(sock == -1)
		error_handling("socket() error");
	
	memset(&serv_addr, 0, sizeof(serv_addr));
	serv_addr.sin_family=AF_INET;
	serv_addr.sin_addr.s_addr=inet_addr(argv[1]);
	serv_addr.sin_port=htons(atoi(argv[2]));
		
	if(connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr))==-1) 
		error_handling("connect() error!");

	while(read_len=read(sock, &message[idx++], 1))
	{
		if(read_len==-1)
			error_handling("read() error!");
		
		str_len+=read_len;
	}

	printf("Message from server: %s \n", message);
	printf("Function read call count: %d \n", str_len);
	close(sock);
	return 0;
}

void error_handling(char *message)
{
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}

7. Linux에서의 Socket

  • 저 수준 파일 입출력
    ANSI의 표준 함수가 아닌 운영체제가 제공하는 함수 기반의 파일 입출력
    표준이 아니기 때문에 OS에 대한 호환성이 없다.
    리눅스는 소켓도 파일로 간주하기 때문에 저 수준 파일 입출력 함수를 기반으로 소켓 기반의 데이터 송수신이 가능하다.

  • 파일 디스크립터
    운영체제가 만든 파일(그리고 소켓)을 구분하기 위한 일종의 숫자
    저 수준 파일 입출력 함수는 입출력을 목적으로 파일 디스크립터를 요구한다.
    저 수준 파일 입출력 함수에게 소켓의 파일 디스크럽터를 전달하면, 소켓을 대상으로 입출력을 진행한다.

  • 실행결과를 통해서 소켓과 파일에 일련의 파일 디스크립터 정수 값이 할당됨을 알 수 있음.
    결론 : Linux는 파일과 소켓을 동일하게 간주한다.

Git : https://github.com/im2sh/Socket_Programming/tree/main/lab01
출처 : 윤성우의 열혈 TCP/IP 소켓 프로그래밍

profile
반갑습니다.

0개의 댓글