티스토리 뷰
☞ connect, listen, accept, getpeername, send, recv
<sys/socket.h>
int connect(int socket, struct sockaddr *addr, socklen_t length);
int listen(int socket, int n);
int accept(int socket, struct sockaddr *addr, socklen_t *lenptr);
int getpeername(int socket, struct sockaddr *addr, socklen_t *lenptr);
ssize_t send(int socket, const void *buffer, size_t size, int flags);
ssize_t recv(int socket, void *buffer, size_t size, int flags);
연결 기반 소켓은 서버측에서는 소켓을 생성하고 연결을 기다리면 클라이언트에서 연결을 요청하여 연결이 이루어진다음 데이터를 송수신 하게 됩니다.
listen() 함수는 소켓을 서버 소켓으로 만들어 줍니다. 서버 소켓은 실제 데이터를 송수신하는데 사용하지는 않고 새로운 연결을 받아들이는 역할만을 수행합니다.
listen() 인수로 n에 연결 대기할 수 있는 큐의 길이를 지정합니다. n 길이 만큼의 큐가 꽉찬 상태에서 새로운 연결을 시도하면 해당 클라이언트는 ECONNREFUSED 에러로 실패하게 됩니다.
성공하면 0, 실패하면 -1을 리턴합니다. 서버 소켓의 다음 단계는 accept()입니다. 클라이언트의 연결 요청을 정상적으로 받아들인 경우에는 새로운 소켓을 리턴하면서 *addr과 *lenptr에 클라이언트의 정보가 저장되고 실패한 경우에는 -1을 리턴합니다.
만약 클라이언트의 요청이 없는 상태에서 accept()를 호출하면 nonblocking 모드가 아닌 경우에는 연결 요청이 있을 때까지 대기하게 되므로 통상 accept()앞에 select()를 통해서 연결 요청이 있는지 확인한 다음 accept()를 수행합니다.
통상 여러 클라이언트의 동시 접속을 서비스 서버의 경우에는 accept() 이후 fork()등을 통해서 클라이언트 소켓 처리를 분할하고 서버 소켓은 계속적으로 accept()를 수행해야만 연결 대기 큐가 차서 클라이언트 연결이 실패하는 경우를 최소화 할 수 있습니다.
☞ 예제1
...... context->accept_socket = accept(nlsd, context->sa_server, &context->sa_server_len); if (context->accept_socket == INVALID_SOCKET) { rv = apr_get_netos_error(); if ( rv == APR_FROM_OS_ERROR(WSAECONNRESET) || rv == APR_FROM_OS_ERROR(WSAEINPROGRESS) || rv == APR_FROM_OS_ERROR(WSAEWOULDBLOCK) ) { ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, ap_server_conf, APLOGNO(00343) "accept() failed, retrying."); continue; } /* A more serious error than 'retry', log it */ ap_log_error(APLOG_MARK, APLOG_WARNING, rv, ap_server_conf, APLOGNO(00344) "accept() failed."); if ( rv == APR_FROM_OS_ERROR(WSAEMFILE) || rv == APR_FROM_OS_ERROR(WSAENOBUFS) ) { /* Hopefully a temporary condition in the provider? */ Sleep(100); ++err_count; if (err_count > MAX_ACCEPTEX_ERR_COUNT) { ap_log_error(APLOG_MARK, APLOG_ERR, rv, ap_server_conf, APLOGNO(00345) "Child: Encountered too many accept() " "resource faults, aborting."); break; } continue; } break; } WSAEventSelect(context->accept_socket, 0, 0); err_count = 0; context->sa_server_len = sizeof(context->buff) / 2; if (getsockname(context->accept_socket, context->sa_server, &context->sa_server_len) == SOCKET_ERROR) { ap_log_error(APLOG_MARK, APLOG_WARNING, apr_get_netos_error(), ap_server_conf, APLOGNO(00346) "getsockname failed"); continue; } if ((getpeername(context->accept_socket, context->sa_client, &context->sa_client_len)) == SOCKET_ERROR) { ap_log_error(APLOG_MARK, APLOG_WARNING, apr_get_netos_error(), ap_server_conf, APLOGNO(00347) "getpeername failed"); memset(&context->sa_client, '\0', sizeof(context->sa_client)); } ......
위의 코드는 아파치 웹서버 코드의 일부입니다. 참조할 점은 accept()함수 호출 결과가 실패인 경우 심각도를 세밀하게 따져서 재시도와 중단을 판단하고 있는 점입니다.
getpeername()은 연결된 클라이언트 소켓의 정보를 인수로 전달한 addr에 저장하고 그 길이를 lenptr에 전달합니다. 성공하면 0 실패면 -1을 리턴합니다.
listen(), accept(), getpeername()를 서버측에서 사용했다면 클라이언트에서는 소켓을 생성한 다음 connect()로 연결을 요청하면 됩니다.
☞ 예제2
...... struct sockaddr_un shard_sock_addr; char port_name[BROKER_PATH_MAX]; ut_get_proxy_port_name (port_name, shm_appl->broker_name, as_info->proxy_id, BROKER_PATH_MAX); if (port_name == NULL) { return (INVALID_SOCKET); } if ((fd = socket (AF_UNIX, SOCK_STREAM, 0)) < 0) return (INVALID_SOCKET); memset (&shard_sock_addr, 0, sizeof (shard_sock_addr)); shard_sock_addr.sun_family = AF_UNIX; strncpy (shard_sock_addr.sun_path, port_name, sizeof (shard_sock_addr.sun_path) - 1); len = sizeof (shard_sock_addr.sun_len) + sizeof (shard_sock_addr.sun_family) + strlen (shard_sock_addr.sun_path) + 1; shard_sock_addr.sun_len = len; if (connect (fd, (struct sockaddr *) &shard_sock_addr, len) < 0) { CLOSE_SOCKET (fd); return (INVALID_SOCKET); } ......
위의 코드는 유닉스 소켓으로 클라이언트 접속을 시도하는 큐브리드 DBMS 코드의 일부입니다.
인터넷 네임 스페이스라면 connect()이전에 sockaddr_in의 sin_port과 sin_addr에 IP주소와 포트를 입력하면 됩니다.
소켓 연결이 된 상태라면 소켓에 대하여 파일 입출력처럼 read(), write()를 사용하여 데이터를 송신 및 수신 할 수 있습니다.
단, 소켓 통신 특유의 옵션을 사용해야 한다면 flags를 추가하여 recv(), send() 함수를 사용할 수 있습니다. 사용할 수 있는 옵션은 다음과 같습니다.
- MSG_OOB : 일반 메시지보다 우선 순위가 높은 Out-of-band 데이터 송수신
- MSG_PEEK : 데이터를 입력 큐에 둔 상태에서 참조
- MSG_DONTROUTE : 메시지에 라우팅 정보를 포함하지 않은 상태로 전송
소켓에 read(), write(), recv(), send()를 수행하면 성공하면 전송된 바이트수를 리턴하고 실패하면 -1을 리턴합니다.
'C | C++' 카테고리의 다른 글
터미널 IO (0) | 2018.06.29 |
---|---|
데이터 그램 소켓 사용하기 (0) | 2018.06.17 |
소켓 생성 및 닫기 (0) | 2018.06.17 |
소켓 바이트 오더 변환 (0) | 2018.06.17 |
인터넷 네임 스페이스 (0) | 2018.06.17 |