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다.
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 이라는 규격에 맞추어 흐르게 된다.
IP: 호스트(컴퓨터, 스마트 폰 등의 단말기)들을 식별할 수 있는 고유한 주소다. IP 주소가 있으면 어떤 호스트에 데이터를 보내는 지, 누가 보내는 지를 알 수 있다.
port번호 : 호스트내의 프로세스들을 식별하수 있는 번호.
처음 만들어 진(bind() 후 listen()한) 소켓은 그 이후로도 새로운 Client의 요청을 대기하기 위해 쓰임
UDP를 사용하므로 비 연결형(Connectionless) 소켓
신뢰성을 보장할 수 없다.
데이터가 순서대로 송수신될 지를 보장하지 못한다.
점대점 연결뿐만 아니라 일대다도 가능
connect()과정이 필요 없기 때문에 소켓을 생성한 후 바로 데이터 전송
따라서 주로 일 대 다 통신에 많이 쓰임
https://tjdahr25.tistory.com/43?category=1020081
API란 시스템이 어플리케이션에 제공하는 인터페이스이다.
API에는 여러 종류가 있는데 우리는 TCP/IP에서 쓰이는 다양한 API중 Sokcet에 대해 알아 볼 예정이다.
Sokcet은 TCP/IP에서 제공하는 API중 하나이다 ( TLK, XTI, Winsock, MacTCP ... )
int socket(int family(domain), int type, int protocol)
#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);
}
https://tjdahr25.tistory.com/46
TCP,UDP 차이와 예제코드
int bind(int sockfd, struct sockaddr *addr, int addr_len)
#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 */
}
// <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 */
}
#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);
}
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)
#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;
}
int listen(int sd, int backlog)
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에 전달된 주소의 변수 크기를 바이트 단위로 전달.
#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;
}