네트워크 프로그래밍 CH1. 소켓의 이해

Alpha, Orderly·2023년 3월 7일
0

네트워크 프로그래밍이란

소켓 프로그래밍
  • 네트워크로 연결된 둘 이상 컴퓨터 사이 데이터 송수신 프로그램 작성법

소켓

  • 네트워크/인터넷 연결 도구
  • OS에 의해 제공되는 소프트웨어
  • 프로그래머에게 데이터 송수신에 대한 추상적 접근 부여

1. 소켓 생성하기

  • socket 함수를 호출해 생성
#include <sys/socket.h>  - Library

int socket(int domain, int type, int protocol); - Function call

성공시 File descriptor, 실패시 -1을 반환.
  • domain : 인터넷 통신인지 프로세스내 통신인지 여부를 설정합니다.
    • PF_INET : IPv4 프로토콜 사용
    • PF_INET6 : IPv6 프로토콜 사용
    • PF_LOCAL : 동일 시스템 프로세스끼리 통신
    • PF_PACKET : Low-level socket 사용
    • PF_IPX : IPX 노벨 프로토콜 사용
  • type : 데이터의 전송 형태를 지정합니다.
    • SOCK_STREAM : TCP 프로토콜 이용
    • SOCK_DGRAM : UDP 프로토콜 이용
  • protocol 자리
    • IPPROTO_TCP : TCP
    • IPPRPTO_UDP : UDP
  • 를 넣을수도 있지만, 앞의 2개를 정확하게 이미 작성했을 경우 0을 넣어도 됩니다.

2. 소켓 주소 할당 및 연결

  • 주소는 IP와 포트번호로 구성된다.
  • 서버에서만 실행한다.
#include <sys/socket.h> - Library

int bind(int sockfd, struct sockaddr* myaddr, socklen_t addrlen) - Function call
- 소켓 주소에 포인터

성공시 0, 실패시 -1 반환
  • socketfd : 소켓의 File descriptor
  • myaddr : 소켓의 주소
  • addrlen : myaddr 구조체의 크기

두번째 인자인 sockaddr은 sockaddr_in에 값을 넣은 뒤 const struct sockaddr* 로 타입변환해 사용하면 됩니다.

3. 소켓 연결 요청 가능하게

  • 소켓이 외부로부터 연결 가능하게 한다.
  • TCP에서 사용한다.
#include <sys/socket.h> - Library

int listen(int sockfd, int backlog); - Function call

성공시 0, 실패시 -1 반환.
  • sockfd : 소켓의 File Descsriptor
  • backlog : 소켓 대기 큐의 사이즈

4. 소켓 연결 수락

  • 외부 연결 요청으로부터의 응답
  • 수락 되어야 데이터의 양방향 송수신이 가능해짐
#include <sys/socket.h>

int accept(int sockfd, struct sockaddr* addr, socklen_t* addrlen)
- 소켓 주소에 포인터

성공시 Client File descriptor, 실패시 -1 반환.
  • sockfd : 소켓 File Descriptor
  • addr : 연결 요청을 한 클라이언트의 주소정보
  • addrlen : addr의 길이
  • 연결 수락 될때까지 여기서 코드가 blocking 됨.
  • 연결 완료 시 클라이언트와 송수신하기 위한 File descriptor를 받는다.
  • 여기서 두번째 인자는 client의 주소를 받기위함이므로 const면 안된다!
  • struct sockaddr* 로 타입변환해 사용하면 된다.
  • 세번째 인자는 unsigned int 타입으로 받아온 주소의 길이를 받아온다고 생각하면 된다, 받아와야 하기에 포인터로!

소켓 연결 요청

  • 소켓에 연결을 요청한다.
  • 클라이언트에서 소켓 생성 이후 바로 실행한다.
#include <sys/socket.h>

int connect(int sockfd, struct sockaddr* serv_addr, socklen_t addrlen);
- 소켓 주소에 포인터

성공시 0, 실패시 -1 반환
  • sockfd : 소켓의 File Descriptor
  • serv_addr : 소켓 서버의 주소
  • addrlen : serv_addr의 길이

톺아보기

  • sockaddr_in
    • sa_family가 AF_INET인 경우 주소를 표현하기위해 사용하는 구조체이다.
  • memset을 사용하기 위해선 string.h나 memory.h를 include한다!
struct sockaddr_in {
	short    sin_family;          // 주소 체계: AF_INET
	u_short  sin_port;            // 16 비트 포트 번호, network byte order
	struct   in_addr  sin_addr;   // 32 비트 IP 주소
	char     sin_zero[8];         // 전체 크기를 16 비트로 맞추기 위한 dummy
};

struct  in_addr {
	u_long  s_addr;     // 32비트 IP 주소를 저장 할 구조체, network byte order
};
  • 구조체의 크기는 sizeof 함수를 사용한다.

코드 보기

  • 클라이언트보다 서버를 먼저 실행한다!
hello_server.c
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[]="Hello World!";
	
	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;
    // IPv4
    
	serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);
    //  INADDR_ANY는 자동으로 이 컴퓨터에 존재하는 랜카드 중 사용가능한 랜카드의 IP주소를 사용하라는 의미이다.
    // htonl은 SOCKADDR_IN 구조체에 저장되기 전에 32비트로 처리되는 IP Address를 
    // Network-Byte-Order로 변환하는데 주로 사용된다.

	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;
}
hello_client.c
int main(int argc, char* argv[])
{
	int sock;
	struct sockaddr_in serv_addr;
	char message[30];
	int str_len;
	
	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!");
	
	str_len=read(sock, message, sizeof(message)-1);
	if(str_len==-1)
		error_handling("read() error!");
	
	printf("Message from server: %s \n", message);  
	close(sock);
	return 0;
}

입출력

저수준 파일 입출력

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

파일 디스크립터

  • OS가 만든 파일 구분용 숫자
  • 저수준 입출력시 파일 디스크립터가 필요하다.

미리 정해진 파일 디스크립터

0 - STDIO
1 - STDOUT
2 - STDERR

파일 열기

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int open(const char* path, int flag);

성공시 File descriptor, 실패시 -1 반환
  • path : 파일 경로
  • flag
    • O_CREAT : 필요시 파일생성
    • O_TRUNC : 기존 데이터 전부 삭제
    • O_APPEND : 기존 데이터 보존하고 뒤에 이어서 저장
    • O_RDONLY : 읽기전용
    • O_WDONLY : 쓰기전용
    • O_RDWR : 읽기, 쓰기

파일 닫기

#include <unistd.h>

int close(int fd);

성공시 0, 실패시 -1 반환
  • fd : 파일 디스크립터
  • 소켓 사용후 이를 통해 닫아야 메모리 leak 방지 가능

파일 쓰기

#include <unistd.h>

ssize_t write(int fd, const void* bf, size_t nbytes);

성공시 전달한 바이트 수, 실패시 -1 반환
  • fd : 파일 디스크립터
  • buf : 전송할 데이터의 포인터 주소
  • nbytes : 전송할 데이터의 크기 바이트

파일에 저장된 데이터 읽기

#include <unistd.h>

ssize_t read(int fd, void* buf, size_t nbytes);

성공시 수신한 바이트 수, 실패시 -1
  • fd : 파일 디스크립터
  • buf : 수신할 데이터를 저장할 버퍼 주소
  • nbytes : 수신할 최대 크기 바이트
  • 뭔가를 읽어올때까지 코드가 blocking 됨

윈도우

  • 거의 동일하다.
profile
만능 컴덕후 겸 번지 팬

0개의 댓글