소켓이란 무엇인가? 소켓의 정의는 다음과 같다.
Socket
네트워크 소켓은 컴퓨터 네트워크를 경유하는 프로세스 간 통신의 종착점이다.
즉, 프로그램이 네트워크에서 데이터를 통신할 수 있도록 연결해주는 연결부이다. 이러한 소켓을 이용하여 통신하는 것을 소켓 통신 한다고 한다.
그러면 소켓통신 하는 과정은 어떻게 되는가? 이에 대한 답변은 다음 그림과 같다.
그림 1의 과정을 서버와 클라이언트로 나누어서 순서를 보면 다음과 같다.
[Server]
1. socket() 함수를 사용하여 서버의 소켓을 연다
2. 열어놓은 소켓에 서버의 IP, PORT 를 바인딩 한다.
3. listen() 함수를 사용하여 메시지를 받을 수 있는 상태로 전환한다.
4. 클라이언트가 보낸 connect request를 받고 클라이언트와 통신하기 위한 파일 디스크립터를 생성한다.
5. send() 를 통해서 메시지를 주거나 recv()를 통해서 메시지를 받는다.
6. 열어놓은 소켓을 닫는다.
[Client]
1. socket() 함수를 사용하여 클라이언트의 소켓을 연다.
2. 서버에 connect request 를 전송한다.
3. 메시지를 보내거나 받는다.
4. 소켓을 닫는다.
위와 같은 과정으로 socket 통신을 하는데 우리는 socket을 구별해야 한다. 이때 socket을 구별하는 방법은 세가지가 존재한다. 이에 대한 표는 다음과 같다.
Domain | definition | address 정의 방법 |
---|---|---|
Unix domain socket | AF_UNIX | filepath |
IPv4 Internet domain socket | AF_INET | IPv4 주소 + Port 번호 |
IPv6 Internet domain socket | AF_INET6 | IPv6 주소 + Port 번호 |
socket 통신 하는데 데이터를 주고받는 방식은 다음과 같다.
Socket type | definition | 특징 |
---|---|---|
Stream | SOCK_STREAM | Connect-orient, byte stream, reliable, 양방향 |
Datagram | SOCK_DGRAM | Connectionless, unreliable, 양방향 |
위 표를 보면 socket 통신 하는데 데이터를 주고받는 방식이 두개이다. 그리고 [그림 1]의 캡션을 보면 (SOCK_STREAM 일 경우)
라는 문구를 명시해 놓았다. 그러면 Datagram 통신일때는 통신 과정이 다른가? 결론은 [그림 1] 과정에서 몇가지 생략을 한것이 SOCK_DGRAM 일 경우의 통신 과정이다. 이에 대한 그림은 다음과 같다.
[Server]
1. socket() 함수를 사용하여 서버의 소켓을 연다
2. 열어놓은 소켓에 서버의 IP, PORT 를 바인딩 한다.
3. send() 를 통해서 메시지를 주거나 recv()를 통해서 메시지를 받는다.
4. 열어놓은 소켓을 닫는다.
[Client]
1. socket() 함수를 사용하여 클라이언트의 소켓을 연다.
2. 메시지를 보내거나 받는다.
3. 소켓을 닫는다.
struct sockaddr {
u_short sa_family; /* 주소체계를 구분하기 위한 변수, 2 bytes, u_short는 unsigned short를 의미 */
char sa_data[14]; /* 실제 주소를 저장하기 위한 변수. 14 bytes*/
}
struct sockaddr_in{
short sin_family; /* 주소 체계: AF_INET */
u_short sin_port; /* 포트 번호를 의미, 범위는 0 ~ 65535, 2 bytes */
struct in_addr sin_addr; /* 호스트 IP 주소 */
char sin_zero[8]; /* 8 bytes dummy data. 반드시 모두 0으로 채워져 있어야 한다. 이유는 sockaddr 구조체와 크기를 일치시키기 위함이다. */
}
struct in_addr{
u_long s_addr; /* 32비트 IP 주소를 저장할 변수 */
}
struct sockaddr_in6{
sa_family_t sin6_family; /* AF_INET6 */
in_port_t sin6_port; /* IPv6 포트를 저장, ntohs() 또는 htons()로 조작하는 것이 좋음 */
uint32_t sin6_flowinfo; /* IPv6 헤더와 연관된 트래픽 클래스와 플로루 레이블을 포함 */
struct in6_addr sin6_addr; /* 16 bytes IPv6 주소를 저장하는 변수 */
uint32_t sin6_scope_id; /* sin6_addr의 주소 범위에 따라 달라지는 식별자를 포함할 수 있다. */
}
struct in6_addr{
unsigned char s6_addr[16]; /* IPv6 address store */
}
struct sockaddr_un{
sa_family_t sun_family; /* AF_UNIX */
char sun_path[UNIX_PATH_MAX] /* 파일 시스템 경로 지정. NULL로 끝나는 문자열이여야 한다. 경로의 최대 길이는 NULL terminator를 포함해서 108 bytes 이다. */
}
<sys/types.h>
, <sys/socket.h>
int socket(int domain, int type, int protocol);
int domain
: 프로토콜 family를 지정해주는 변수AF_UNIX
(프로토콜 내부), AF_INET
(IPv4), AF_INET6
(IPv6)int type
: 어떤 타입의 프로토콜을 사용할 것인지 설정SOCK_STREAM
(TCP), SOCK_DGRAM
(UDP), SOCK_RAW
(사용자 정의)int protocal
: 어떤 프로토콜의 값을 결정하는 것0
, IPPROTO_TCP
(TCP), IPPROTO_UDP
(UDP)1
<sys/types.h>
, <sys/socket.h>
int bind(int sockfd, struct sockaddr *myaddr, socklen_t addrlen);
int sockfd
: 소켓 식별자 또는 소켓 디스크립터struct sockaddr *myaddr
: sockaddr
포인터socklen_t addrlen
: myaddr
구조체 크기1
0
<sys/socket.h>
int listen(int sock, int backlog);
int sock
: 연결요청 대기상태에 두고자 하는 소켓의 파일 디스크립터 전달. 이 함수의 인자로 전달된 디스크립터의 소켓이 서버 소켓이 된다.int backlog
: 연결요청 대시 큐(queue)의 크기정보 전달.1
0
<sys/types.h>
, <sys/socket.h>
int accept(int sockfd, struct sockaddr* addr, socklent_t *addrlen);
int sockfd
: 연결을 기다리는 소켓 디스크립터.struct sockaddr* addr
: 받아들인 클라이언트 주소 및 포트 정보가 저장될 구조체 주소값.socklen_t *addrlen
: sockaddr
구조체의 길이가 저장된 변수의 주소값1
<sys/types.h>
, <sys/socket.h>
int connect(int socket, const struct sockaddr *address, socklen_t address_len);
int socket
: 클라이언트 소켓의 파일 디스크립터.const struct sockaddr *address
: 연결 요청을 보낼 서버의 주소 정보를 지닌 구조체 변수의 포인터.socklen_t address_len
: 포인터가 가리키는 주소 정보 구조체 변수의 크기.1
0
<sys/socket.h>
ssize_t sendto(int socket, const void *buffer, size_t length, int flags, const struct sockaddr *dest_addr, socklen_t dest_len);
int socket
: 소켓 디스크립터const void *buffer
: 전송할 데이터size_t length
: 데이터의 바이트 단위 길이int flags
: 전송을 위한 옵션const struct sockaddr *dest_addr
: 목적지 주소 정보socklen_t dest_len
: 목적지 주소 정보 크기1
, 아래의 내용은 상세 errno의 대한 내용이다.[EACCES] The SO_BROADCAST option is not set on the socket and a broadcast address is given as the destination.
[EAGAIN] The socket is marked non-blocking and the requested operation would block.
[EBADF] An invalid descriptor is specified.
[ECONNRESET] A connection is forcibly closed by a peer.
[EFAULT] An invalid user space address is specified for a parameter.
[EHOSTUNREACH] The destination address specifies an unreachable host.
[EINTR] A signal interrupts the system call before any data is transmitted.
[EMSGSIZE] The socket requires that message be sent atomically, and the size of the message to be sent makes this impossible. IOV_MAX.
[ENETDOWN] The local network interface used to reach the destination is down.
[ENETUNREACH] No route to the network is present.
[ENOBUFS] The system is unable to allocate an internal buffer. The operation may succeed when buffers become available.
[ENOBUFS] The output queue for a network interface is full. This generally indicates that the interface has stopped sending, but may be caused by transient congestion.
[ENOTSOCK] The argument socket is not a socket.
[EOPNOTSUPP] socket does not support (some of) the option(s) specified in flags.
[EPIPE] The socket is shut down for writing or the socket is connection-mode and is no longer connected. In the latter case, and if the socket is of type SOCK_STREAM, the SIGPIPE signal is generated to the calling thread.
[EADDRNOTAVAIL] The specified address is not available or no longer available on this machine.
int flags
의 옵션들<sys/socket.h>
ssize_t recvfrom(int socket, void *restrict buffer, size_t length, int flags, struct sockaddr *restrict address, socklen_t *restrict address_len);
int socket
: 소켓 디스크립터void *restrict buffer
: 수신한 데이터를 저장할 버퍼size_t length
: 읽을 데이터 크기int flags
: 읽을 데이터 유형 또는 읽는 방법에 대한 옵션struct sockaddr *restrict address
: 접속한 상대 시스템의 socket 주소 정보를 저장할 버퍼socklen_t *restrict address_len
address
의 크기를 설정한 후에 호출한다.address
의 크기가 저장된다.1
, 아래 내용은 errno에 대한 내용이다.* EAGAIN or EWOULDBLOCK : time out이 발생하였거나 socket에 non-blocking이 설정된 경우
* EBADF : sockfd가 유효하지 않는 descriptor
* ECONNREFUSED : network상에서 접속 거부된 경우
* EFAULT : 읽을 데이터를 저장할 buf가 유효하지 않은 메모리인 경우
* EINTR : signal이 발생하여 interrupted 된 경우
* EINVAL : 파라미터의 값이 유효하지 않은 경우
* ENOMEM : 데이터 수신을 위한 메모리 할당이 되지 않은 경우 (recvmsg(2)호출시)
* ENOTCONN : connect(2), accept(2)가 호출되지 않은 상태인 경우
* ENOTSOCK : sockfd가 socket descriptor가 아닌 일반 파일인 경우
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<sys/un.h>
#define QUEUE_DEFAULT_NUM 5
#define BUF_MAX_LEN 4096
static void print_help(const char *progname)
{
printf("Usage: %s (s|c Message) (stream|datagram) (filepath)\n", progname);
}
static int setting_sock_type(const char *TYPE, int *out)
{
if(!strcmp(TYPE, "stream")){
/* stream type */
*out = SOCK_STREAM;
} else if(!strcmp(TYPE, "datagram")){
/* datagram type */
*out = SOCK_DGRAM;
} else {
printf("[ERROR] setting_sock_type - input wrong socket type\n ");
return -1;
}
return 0;
}
static void setting_sock_filepath(const char *PATH, char *out)
{
memset((void *)out, 0, sizeof(out));
strcpy(out, PATH);
}
static int partial_message_recv(int fd, void *buf, size_t size, int flags)
{
int written = 0;
int ret = 1;
while(ret){
ret = recv(fd, (char *)buf + written , size - written, flags);
if(ret == -1){
perror("recv()");
return ret;
}
written += ret;
}
return 0;
}
static int partial_message_send(int fd, void *buf, size_t size, int flags)
{
int written = 0;
int ret;
while(written < size){
ret = send(fd, (char *)buf + written , size - written, flags);
if(ret == -1){
return ret;
}
written += ret;
}
printf("send data: %s\n", (char *)buf);
return 0;
}
static int do_recv_data(const int socket_type, const char *filepath)
{
int server_fd;
int client_fd;
char buf[BUF_MAX_LEN];
memset((void *)buf, 0, sizeof(buf));
struct sockaddr_un addr;
server_fd = socket(AF_UNIX, (int)socket_type, 0);
if(server_fd == -1){
perror("socket()");
return -1;
}
memset((void *)&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, filepath, sizeof(addr.sun_path)-1);
if(bind(server_fd, (const struct sockaddr *)&addr, sizeof(addr)) == -1){
perror("bind()");
close(server_fd);
return -1;
}
if(socket_type == SOCK_STREAM){
listen(server_fd, QUEUE_DEFAULT_NUM);
client_fd = accept(server_fd, NULL, NULL);
if(partial_message_recv(client_fd, (void *)buf, sizeof(buf), 0) == -1){
close(server_fd);
close(client_fd);
return -1;
}
printf("Server : Client said %s\n", buf);
close(server_fd);
close(client_fd);
}
else if(socket_type == SOCK_DGRAM){
if(recvfrom(server_fd, (void *)buf, sizeof(buf), 0, NULL, NULL) == -1){
perror("recvfrom()");
close(server_fd);
return -1;
}
printf("Server : Client said %s\n", buf);
close(server_fd);
} else {
printf("[ERROR] do_recv_data - wrong socket_type\n");
return -1;
}
return 0;
}
static int do_send_data(const int socket_type, const char *filepath, const char *message)
{
int client_fd;
struct sockaddr_un addr;
client_fd = socket(AF_UNIX, (int)socket_type, 0);
if(client_fd == -1){
perror("socket()");
return -1;
}
memset((void *)&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, filepath, sizeof(addr.sun_path)-1);
if(socket_type == SOCK_STREAM){
if(connect(client_fd, (const struct sockaddr *)&addr, sizeof(addr)) == -1){
perror("connect()");
close(client_fd);
return -1;
}
if(partial_message_send(client_fd, (void *)message, sizeof(message), 0) == -1){
close(client_fd);
return -1;
}
close(client_fd);
}
else if(socket_type == SOCK_DGRAM){
if(sendto(client_fd, (void *)message, sizeof(message), 0, (struct sockaddr *)&addr, sizeof(addr)) == -1){
perror("sendto()");
close(client_fd);
return -1;
}
close(client_fd);
} else {
printf("[ERROR] do_recv_data - wrong socket_type\n");
return -1;
}
return 0;
}
int main(int argc, char **argv)
{
int SOCK_TYPE;
char filepath[1024];
if(argc < 4){
print_help(argv[0]);
return -1;
}
if(!strcmp(argv[1], "s")){
/* Server */
if(setting_sock_type(argv[2], &SOCK_TYPE) == -1){
goto main_err;
}
setting_sock_filepath(argv[3], filepath);
if(do_recv_data((const int)SOCK_TYPE, (const char *)filepath) == -1){
goto main_err;
}
} else if(!strcmp(argv[1], "c")){
/* Client */
if(argc < 5){
print_help(argv[0]);
return -1;
}
// char message[1024];
// memset((void *)message, 0, sizeof(message));
// strncpy(message, (const char *)argv[2], sizeof(argv[2]));
if(setting_sock_type(argv[3], &SOCK_TYPE) == -1){
goto main_err;
}
setting_sock_filepath(argv[4], filepath);
if(do_send_data((const int)SOCK_TYPE, (const char *)filepath, (const char *)argv[2]) == -1){
goto main_err;
}
} else {
goto main_err;
}
return 0;
main_err:
print_help(argv[0]);
return -1;
}
./main (s|c Message) (stream|datagram) (file path)
안녕하세요.
리눅스 프로그래밍에 대한 깊이 있는 내용 잘 보았습니다.
메세지큐, 통신, IPC 에 대해서 글 작성하실 때 참고하신 자료가 있을까요?
저자님처럼 깊게 배워보고 싶습니다.