webserve:socket

YP J·2022년 11월 19일
0

webserve

목록 보기
2/3

https://medium.com/@su_bak/term-socket%EC%9D%B4%EB%9E%80-7ca7963617ff

https://on1ystar.github.io/socket%20programming/2021/03/16/socket-1/

소켓이란?

소켓은 프로세스 간 통신을 하기 위한 api다.

  • 그림 설명
    • OSI 7 계층의 어플리케이션 계층(application layer)에 존재하는 네트워크 응용 프로그램들은 데이터를 송수신 하기 위해 소켓을 거쳐 전송 계층(trasport layer)의 통신 망으로 전달함으로써 데이터를 송수신 하게 된다.
    • 따라서 소켓은 그 사이에 위치하고 있으며, 응용 프로그램에서 TCP/IP를 이용하는 인터페이스 역할을 한다.
  • 네트워크 상에서 동작하는 프로그램간 통신의 종착점(Endpoint)이다.
    • 논리적인 의미로 컴퓨터 네트워크를 경유하는 프로세스 간 통신(Inter-Process Communication, IPC)의 종착점(end-point)

Endpoint: IP Address 와 Port 번호의 조합을 뜻하며 최종 목적지를 나타낸다.
(여기서 최종목적지는 사용자의 디바이스 또는 서버가 될수 있다.)

  • 네트워크를 이용해 데이터를 송수신 하고 싶은 프로그램들은 소켓을 거쳐야 한다.

  • 즉 프로그램이 네트워크에서 데이터를 통신할수 있도록 연결해주는 연결부 라고 할수있다.

  • 테이터를 통신할수 있도록 해주는 연결부이기 때문에

  • 통신할 두 프로그램 (Client , Server)모두에 소켓이 생성 되어야함.

  • Server는 특정 포트와 연결된 소켓(Server소켓)을 가지고 컴퓨터 위에서 동작하게 되는데

  • 이 Server는 소켓을 통해 Client측 소켓의 연결 요청이 있을때 까지 기다리고 있다 (listening한다)

  • Client 소켓에서 연결요청을 하면 (올바른 포트로 들어오면)

  • Server 소켓이 허락을 해서 통신을 할 수있도록 연결(Connection) 되는것.

  • *연결이 되면 Server 소켓은 새로운 소켓(연결부)을 하나 더 만드는데요.

  • 왜냐하면 연결된 소켓은 연결된 Client 소켓과 통신을 해야하는데 새로 들어오는 Client 연결 요청을 받아야 하므로 새로운 소켓을 하나 더 생성하게 됩니다.

  • 만약 port 번호가 틀리거나 Server가 listening 중이 아니라면 Server와 Client의 소켓이 연결되지 않아 Server와 Client는 통신을 할 수 없게 됩니다


소켓이란?::5-tuple

  • 통신을 통해 전달되는 모든 데이터 포맷은 5-tuple 이라는 규격에 맞추어 흐르게 된다.

    1. 프로토콜 (Protocol)
    1. 호스트 IP 주소 (source IP address)
    1. 호스트 port 번호 (source port nunber)
    1. 목적지 IP 주소 (destination IP address)
    1. 목적지 port 번호 (destination port number)

      IP: 호스트(컴퓨터, 스마트 폰 등의 단말기)들을 식별할 수 있는 고유한 주소다. IP 주소가 있으면 어떤 호스트에 데이터를 보내는 지, 누가 보내는 지를 알 수 있다.
      port번호 : 호스트내의 프로세스들을 식별하수 있는 번호.


소켓이란?::socket 통신의 특징

    1. server-client 구조.
    • TCP/UDP 위에서 동작하므로 Server-client 통신 구조를 갖춘다.
    • 처음에 데이터를 보내는 쪽이 client가 되고, 받는 쪽이 server가 된다.
    1. 양방향 통신
    • socket은 한 쪽에서 데이터를 보내고 반대 편에서 이를 수신한 뒤 연결이 끊어지는게 아니라 양 쪽에서 실시간으로 데이터를 송수신할 수 있다.
    • 따라서 실시간 스트리밍이나 채팅에 주로 유용하게 사용된다.
    1. 프로그래밍 언어나 운영체제에 종속적
    • socket은 TCP/IP 표준이 아니라 네트워크 프로그래밍 인터페이스다 따라서 운영체제마다, 프로그래밍 언어마다 소켓api구현라이브러리가 다르다.

소켓이란?::소켓의 종류, 통신흐름

종류 1. Stream sockets - TCP(Transmission Control Protocol)

  • TCP 를 사용하므로 연결 지향형 (Connection-oriented)소켓
  • 신뢰성보장
  • 데이터가 순서대로 송수신됨.
  • point-to-point 연결

흐름 flow

Server

  1. socket(): 소켓 생성(TCP는 stream)
  2. bind(): 사용할 IP address와 Port number 등록
  3. listen(): 연결 되지 않은 소켓을 요청 수신 대기 모드로 전환(Block 상태)

Client

  1. socket(): 마찬가지로 소켓 생성(TCP는 stream)
  2. connect(): Client에서 Server와 연결하기 위해 소켓과 목적지 IP address, Port number 지정 (Block 상태)

Server

  • accept() : client의 요청 수락 후 실질적인 소켓 연결 → 통신을 위한 새로운 소켓 생성

    처음 만들어 진(bind() 후 listen()한) 소켓은 그 이후로도 새로운 Client의 요청을 대기하기 위해 쓰임

Server-Client

  • send(), recv(): Client는 처음에 생성한 소켓으로, Server는 새로 반환(생성)된 소켓으로 client와 server간에 데이터 송수신
  • close(): 소켓을 닫음

종류 2. Datagran sockets-UDP(User Datagram Protocol)

  • UDP를 사용하므로 비 연결형(Connectionless) 소켓

  • 신뢰성을 보장할 수 없다.

  • 데이터가 순서대로 송수신될 지를 보장하지 못한다.

  • 점대점 연결뿐만 아니라 일대다도 가능

  • connect()과정이 필요 없기 때문에 소켓을 생성한 후 바로 데이터 전송

  • 따라서 주로 일 대 다 통신에 많이 쓰임


https://tjdahr25.tistory.com/43?category=1020081

TCP 소켓 프로그래밍

API

  • API란 시스템이 어플리케이션에 제공하는 인터페이스이다.

  • API에는 여러 종류가 있는데 우리는 TCP/IP에서 쓰이는 다양한 API중 Sokcet에 대해 알아 볼 예정이다.

  • Sokcet은 TCP/IP에서 제공하는 API중 하나이다 ( TLK, XTI, Winsock, MacTCP ... )

  • TCP/IP 에서 제공하는 Socket대한 함수들의 헤더 파일은 <sys/type.h> <sys/socket.h> 헤더파일에 존재한다.

1. socket()

int socket(int family(domain), int type, int protocol)
  • family : 어떤 프로토콜 family 를 사용할 건지
  • type : 해당 family중 어떤 type을 사용할 건지
    • SOCK_STREAM : TCP
    • SOCK_DGRAM : UDP
  • protocol : 해당 type중에서 어떤 protocol을 사용할 건지.

  • socket()은 system call로 반환값은 socket descriptor 이다.
  • 소켓 생성에 실패하면 -1 반환한다.
#include <sys/types.h>
#include <sys/socket.h>

int sockfd;

if ((sockfd = socket(PF_INET, SOCK_STREAM, 0) < 0 ) 
{
	//IPv4 에서 TCP 사용 
    perror( "socket error");
    exit(1);
}

  • 소켓은 5개의 componet 와 관련있다.
    • 프로토콜, 호스트 IP port , 목적지 ipport
    1. protocol
    • 어떤 프로토콜을 사용할건지
    • socket()함수의 argument로 어떤 프로토콜을 사용할 건지 알려준다.
    1. Source's address and port number
    • 보내는곳의 주소와 포트넘버
    • socket()함수는 socket descriptor를 반환하는데, 이 socket descriptor와 source의 주소, 포트넘버를 bind()함수로 연관시킨다
    1. Destination's address and port number
    • 받는곳의 주소와 포트넘버
    • TCP(연결지향) 에서는 connect()함수로 socket descriptor와 des의 주소 , 포트넘버를 묶는다
    • UDP(비연결지향) 에서는 sendto()함수의 인자로 목적지의 주소와 포트넘버만 넣어서 보낸다.

https://tjdahr25.tistory.com/46
TCP,UDP 차이와 예제코드

2. bind()

  • 소켓에 주소를 할당해주기위한 system call이다.
  • 보통 Server 쪽에서 하고 Client에선 하지 않는다
    • ?? clinet에선 무작위로 port number 할당 ??
int bind(int sockfd, struct sockaddr *addr, int addr_len)
  • int sockfd : 생성 소켓(socket())에서 반환된 descriptor
  • struct sockaddr *addr : socket의 구조체 포인터
    • 자신의 address와 port number가 들어있다.
  • int addr_len : 구조체 길이
  • 성공시 0 , 실패시 -1

  • 소켓의 구조체
#define <netinet/in.h>

struct sockaddr 
{
  u_char sa_len; /* length : used in kernel */
  u_short sa_family; /* address family */
  char sa_data[14]; /* address */
}
  • 위의 sockadrr을 인터넷에서 쓰기위해서 그리고 sa_dat[14]를 좀더 세분화해서 나눈 sockaddr_in 도 있다,
// <netinet/in.h> 여기의 헤더파일에 구조체가 정의되어 있다.

struct sockaddr_in 
{
  u_char sin_len; /* length */
  u_short sin_family; /* AF_INET */
  u_short sin_port; /* port number */        //2byte
  struct in_addr sin_addr; /* IP addess */   //4byte
  char sin_zero[8]; /* unused */             //8byte
}
struct in_addr 
{
	u_long s_addr; /* 32 bit IP address */
}

  • sa_data 14 byte짜리를 sin_port(2byte) + sin_addr(4byte) + sin_zero(8byte) 로 나눈것.
  • 사실 두개는 같지만 sa_data를 나누어 필요한 6byte에만 포트넘버와 주소를 넣고 나머진 0 으로 채운다.
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h> // 주소 변환 기능을 사용할때 
#define MYPORT 50000  // 나의 포트넘버

int sockfd;

struct sockaddr_in my_addr;  //socket 구조체 선언

if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) < 0) 
{  //소켓 생성
	perror(“socket error”); 
    exit (1); 
}

memset(&my_addr, 0, sizeof(my_addr));   //socket 구조체 초기화

my_addr.sin_family = AF_INET;     //address family
                                  //PF_INET과도 같지만 정확히는 AF_INET                               
// https://www.bangseongbeom.com/af-inet-vs-pf-inet.html 차이 
my_addr.sin_port = htons(MYPORT); 
/*
나의 포트넘버를 htons()를 통해 변환
htons는 host에서 사용하는 number를 
network에서 사용가능하도록short하게 변환함
*/
                                  
my_addr.sin_addr.s_addr = htonl(INADDR_ANY); 
/* 
INADDR_ANY는 현재 나의 주소 반환
htonl은 host에서 사용하는 주소를 
network에서 사용가능하도록
long하게 변환함
*/
// hrons 와 htonl이 있는 이유는 Byte odering(big endian, little endian)때문에 있다.
// 네트워크상과 호스트상의 endian방식이 달라서 맞춰줘야한다.
if (bind(sockfd, (struct sockaddr *)&my_addr, sizeof(my_addr)) < 0) 
{
	perror(“bind error” ); 
    exit(1);
}

3. Connect()

  • Client 는 connect 함수를 이용해서 연결 요청한다.

  • 연결 소켓을 반환하는 accept 함수와는 달리, connect 함수는 연결 성공 여부만 반환 한다. > https://codingfarm.tistory.com/539 이분 블로그 이해가 안된다.

  • https://recipes4dev.tistory.com/153

    • connect() API는 "IP주소"와 "포트 번호"로 식별되는 대상(Target)으로 연결 요청을 보냅니다.

    • connect() API는 블럭(Block) 방식으로 동작합니다.

    • 즉, 연결 요청에 대한 결과(성공, 거절, 시간 초과 등)가 결정되기 전에는 connect()의 실행이 끝나지 않는다는 것이죠.

    • 그러므로 connect() API가 실행되지마자 실행 결과와 관계없이 무조건 바로 리턴될 것이라 가정해선 안됩니다.

    • connect() API 호출이 성공하면, 이제 send() / recv API를 통해 데이터를 주고 받을 수 있습니다.

  • Server가 bind를 한뒤에 Server는 connect 해야한다. (서버에 연결 요청)

  • 성공시 0 실패시 -1 리턴

  • 3-handshaking 과정을 통한 커넥션을 요청하는 것이기 때문에 TCP에 사용된다.

int connect(int sd, struct sockaddr *addr, int addr_len)
  • addr 은 서버의 address이다.
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define DEST_IP “10.12.110.57”
#define DEST_PORT 23

int sockfd;
struct sockaddr_in dest_addr;
if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) < 0) 
{
	/* error */ 
}

memset(&dest_addr, 0, sizeof(dest_addr));
dest_addr.sin_family = AF_INET;
dest_addr.sin_port = htons(DEST_PORT);
dest_addr.sin_addr.s_addr = inet_addr(DEST_IP);

if (connect(sockfd, (struct sockaddr *)&dest_addr, sizeof(dest_addr)) !=0 ) 
{
	close(sockfd);
	return -1;
}

4. listen()

  • 서버가 bind 한뒤에 client로부터 request를 기다리는 과정이다.
  • 성공시 0, 실패시 -1
  • connection request를 기다리는 함수이기 때문에 TCP 에서만 사용된다.
int listen(int sd, int backlog)
  • sd: 바인딩 할때 사용했던 socket descriptor 이다.
  • ?? backlog: connection할때 queue 에 몇개까지의 request를 pending상태로 놓을것이지에 대한 사이즈 ?? ,
    • 보통 5로 놓고 사용, 구현에 따라 맞지않는경우도 많다 ???
    • server가 listen하고 있을때는 계속 여러개를 listen할수 있지만 서버가 다른 작업을 하고 있을때 그 listen 요청을 큐에 5개 까직 넣어놓는다.는 개념?

5. accept()

  • listen 상태에 있는 서버가 client 로부터 connection을 받아들이는 함수
  • TCP 에서만 사용
  • 성공시 새로운 socket descriptor 반환하고 실패시 -1 반환
  • 왜 새로운 얘냐???
  • TCP는 1:1 통신을 지원하는데 한 서버가 클라이언트 1과 통신한 후에 클라이언트 2와 새로 통신하는 것을 구별하기 위함이다??

https://recipes4dev.tistory.com/153

  • 다시 한번, listen() API가 클라이언트의 연결 요청을 확인하고 문제없이 리턴한다고 해서, 클라이언트와의 연결 과정이 모두 완료되는 것은 아닙니다.

  • 아직 실질적인 소켓 연결(Connection)을 수립하는 절차가 남아 있죠.

  • 최종적으로 연결 요청을 받아들이는 역할을 수행하는 것은 accept() API 입니다.

  • 그런데 주의할 점은 최종적으로 데이터 통신을 위해 연결되는 소켓이, 앞서 bind() 또는 listen() API에서 사용한 서버 소켓(Server Socket)이 아니다.

  • 클라이언트 소켓(Client Socket)과 연결(Connection)이 만들어지는 소켓(Socket)은 앞서 사용한 서버 소켓(Server Socket)이 아니라,

  • accept API 내부에서 새로 만들어지는 소켓(Socket)입니다.

  • 앞에서도 언급했지만, 서버 소켓(Server Socket)의 핵심 역할은 클라이언트의 연결 요청을 수신하는 것입니다.

  • 이를 위해 bind() 및 listen()을 통해 소켓에 포트 번호를 바인딩하고 요청 대기 큐를 생성하여 클라이언트의 요청을 대기하였죠.

  • 그리고 accept() API에서, 데이터 송수신을 위한 새로운 소켓(Socket)을 만들고 서버 소켓의 대기 큐에 쌓여있는 첫 번째 연결 요청을 매핑시킵니다.

  • 여기까지, 하나의 연결 요청을 처리하기 위한 서버 소켓의 역할은 끝났습니다.

  • 서버 소켓의 입장에서 남은 일은, 또 다른 연결 요청을 처리하기 위해 다시 대기(listen)하거나, 서버 소켓(Socket)을 닫는(close) 것 뿐이죠.

  • 실질적인 데이터 송수신은 accept API에서 생성된, 연결(Connection)이 수립(Established)된 소켓(Socket)을 통해 처리됩니다.

int accept(int sd, struct sockaddr *addr, int *addrlen);

addr: 클라이언트의 주소정보
addrlen: addr에 전달된 주소의 변수 크기를 바이트 단위로 전달.


  • 지금까지 의 과정
  • SYN이 왔다는 거는 client가 connection요청을 보냈다는 것.
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define MYPORT 50000
int main()
{
    int sockfd, new_fd;
    struct sockaddr_in my_addr, client_addr;
    int sin_size;
    sockfd = socket(PF_INET, SOCK_STREAM, 0);
    memset(&my_addr, 0, sizeof(my_addr));
    my_addr.sin_family = AF_INET;
    my_addr.sin_port = htons(MYPORT);
    my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    bind(sockfd, (struct sockaddr *)&my_addr, sizeof(my_addr));
    listen(sockfd, 5);
    sin_size = sizeof(client_addr);
    new_fd = accept(sockfd, (struct sockaddr *)&client_addr, &sin_size);
    return 0;
}
  • 지금까지의 과정은 데이터를 전송하기전 연결을 완료하기 위한 과정이였다.
#include <memory.h>
/* socket()
 * int socket(int family, int type, int protocol)
 * family:  어떤 프로토콜 family를 사용할건지
 *  - PF_INET : Internet protocol (TCP/IP)
 *  - PF_INET6 : IPv6
 *  - PF_LOCAL : for local communication
 *  - PF_UNIX : UNIX system internal protocol
 * type: 해당 family중에 어떤 type을 사용할건지
 *  - SOCK_STREAM : TCP // SOCK_DGRAM : UDP
 *  protocol: 해당 type중 어떤 protocol을 사용할건지. 0 은 디폴트
 *
 *  socket()은 시스템콜 함수로 반환값은 socket descroptor 값이다. 실패시 -1 반환
 */

/* bin() : 소켓에 주소를 할당해주기 위한 시스템콜
 * 보통 server쪽에서 하고 client 에선 하지않는다(client에선 무작위로 port number할당
 * int bin(int sockfd, struct sockaddr *addr, int addr_len)
 * sockfd : 생성 소켓에서 반환된값
 * *addr : socket 의 구조체 포인터 (자신의 address와 port number가 들어있음)
 * addr_len: 구조체의 길이
 * 성공시 0 , 실패시 -1 리턴
 */

#define MYPORT 5000
#include <iostream>
int main()
{
	int sockfd, new_fd;
	struct sockaddr_in my_addr, client_addr;
	int sin_size;
	if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) < 0)
	{
		perror("socket error");
		return 1;
	}
	memset(&my_addr, 0,sizeof(my_addr));
	my_addr.sin_family=AF_INET;
	my_addr.sin_port = htons(MYPORT);
	my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
	bind(sockfd,(struct sockaddr *)&my_addr, sizeof(my_addr));
	listen(sockfd, 5);
	sin_size = sizeof(client_addr);
	socklen_t sin_sizef = sin_size;
	new_fd = accept(sockfd, (struct sockaddr *)&client_addr, &sin_sizef);
	return 0;
}
profile
be pro

0개의 댓글