[소켓 인터페이스 함수의 종류]
• socket( ) : 소켓 파일 기술자 생성
• bind( ) : 소켓 파일 기술자를 지정된 IP 주소/포트 번호와 결합
• listen( ) : 클라이언트의 연결 요청 대기
• connect( ) : 클라이언트가 서버에 접속 요청
• accept( ) : 클라이언트의 연결 요청 수락
• send( ) : 데이터 송신(SOCK_STREAM)
• recv( ) : 데이터 수신(SOCK_STREAM)
• sendto( ) : 데이터 송신(SOCK_DGRAM)
• recvfrom( ) : 데이터 수신(SOCK_DGRAM)
• close( ) : 소켓 파일 기술자 종료
• bind(), listen(), accept() 함수 : 서버 측에서만 사용
• connect() 함수 : 클라이언트 측에서만 사용
• socket(), recv(), send(), recvfrom(), sendto(), close() 함수 : 서버와 클라이언트에서 모두 사용
ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
ㅇ int socket(int domain, int type, int protocol): 소켓 파일 기술자(sd) 생성
- domain : 소켓 종류(UNIX 도메인 or 인터넷 소켓)
- type : 통신 방식(TCP or UDP)
- protocol : 소켓에 이용할 프로토콜 (보통은 0을 지정, 이 경우 시스템이 protocol 값을 결정)
domain에 지정한 소켓의 형식과 type에 지정한 통신 방식을 지원하는 소켓을 생성
• domain에는 도메인 또는 주소 패밀리를 지정(유닉스 도메인 소켓을 생성할 경우 AF_UNIX를 지정하고, 인터넷 소켓을 생성할 경우 AF_INET을 지정)
• type에는 통신 방식에 따라 SOCK_STREAM이나 SOCK_DGRAM을 지정
성공하면 소켓 기술자(sd)를, 실패하면 –1을 반환
(함수 예시)
int sd;
sd = socket(AF_INET, SOCK_STREAM, 0);
ㅇ int bind(int sockfd, const struct sockaddr* addr, socklen_t addrlen): 소켓 파일 기술자(sd)를 지정된 IP 주소/포트 번호와 결합
- sockfd : socket() 함수가 생성한 소켓 기술자
- addr : 소켓의 이름을 표현하는 구조체
- addrlen : addr의 크기
socket() 함수가 생성한 소켓 기술자 sockfd에 sockaddr 구조체인 addr에 지정한 정보를 연결 (소켓에 이름을 할당하기)
sockaddr 구조체에 지정하는 정보는 소켓의 종류, IP 주소, 포트 번호
성공하면 0을, 실패하면 -1을 반환
(함수 예시) IP 주소를 192.168.100.1로 지정하고 포트 번호를 9000번으로 지정
int sd;
struct sockaddr_in sin;
memset((char*)&sin, '\0', sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(9000);
sin.sin_addr.s_addr = inet_addr("192.168.100.1");
memset(&(sin.sin_zero), 0, 8);
bind(sd, (struct sockaddr*)&sin, sizeof(struct sockaddr));
소켓 sockfd에서 클라이언트의 연결을 받을 준비를 마쳤음을 알림
ㅇ int listen(int sockfd, int backlog): 클라이언트의 연결 요청 대기
- sockfd : socket 함수가 생성한 소켓 기술자
- backlog : 최대 허용 클라이언트 수
접속이 가능한 클라이언트 수는 backlog에 지정
※ listen() 함수는 소켓이 SOCK_STREAM 방식으로 통신할 때만 필요
(함수 예시)
listen(sd, 10);
클라이언트가 서버에 연결을 요청할 때 사용
ㅇ int connect(int sockfd, const struct sockaddr* addr, socklen_t addrlen): 클라이언트가 서버에 접속 요청
- sockfd : socket() 함수가 생성한 소켓 기술자
- addr : 접속하려는 서버의 IP 정보
소켓 sockfd를 통해 addr에 지정한 서버에 연결을 요청
첫 번째 인자인 sockfd가 가리키는 소켓을 두 번째 인자인 addr이 가리키는 주소로 연결
※ connect() 함수는 SOCK_STREAM 방식으로 통신할 때만 필요
연결에 성공하면 0을, 실패하면 –1을 반환
(함수 예시) IP 주소가 192.168.100.1인 서버에 9000번 포트로 연결
int sd;
struct sockaddr_in sin;
memset((char*)&sin, '\0', sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(9000);
sin.sin_addr.s_addr = inet_addr("192.168.100.1");
memset(&(sin.sin_zero), 0, 8);
connect(sd, (struct sockaddr*)&sin, sizeof(struct sockaddr));
클라이언트의 연결 요청을 수락
ㅇ int accept(int sockfd, struct sockaddr* addr, socklen_t* addrlen): 클라이언트의 연결 요청 수락
- sockfd : socket() 함수가 생성한 소켓 기술자
- addr : 접속을 수락한 클라이언트의 IP 정보
- addrlen : addr 크기
서버는 accept() 함수를 사용해 소켓 sockfd를 통해 요청한 클라이언트와의 연결을 수락
수락할 때 addr에 클라이언트의 주소가 저장, addrlen에는 addr의 크기가 저장
클라이언트의 연결 요청이 오면 새로운 소켓 기술자를 리턴
서버는 이 새로운 소켓 기술자를 사용해 클라이언트와 데이터를 주고받을 수 있음
ockfd가 가리키는 소켓 기술자는 추가 연결 요청을 기다리는 데 사용
(함수 예시) 기존 소켓 기술자 sd를 통해 연결이 수락되면 새로운 기술자 new_sd가 리턴, clisin에는 클라이언트 주소가 저장
int sd, new_sd;
struct sockaddr_in sin, clisin;
new_sd = accept(sd, &clisin, &sizeof(struct sockaddr_in));
ㅇ ssize_t send(int sockfd, const void* buf, size_t len, int flags): 데이터 송신
- sockfd : socket() 함수가 생성한 소켓 기술자
- buf : 전송할 메시지를 저장한 메모리 주소
- len : 메시지의 크기
- flags : 데이터를 주고받는 방법을 지정한 플래그
소켓 sockfd를 통해 크기가 len인 메시지 buf를 flags에 지정한 방법으로 전송
send() 함수는 실제로 전송한 데이터의 바이트 수를 반환
리턴값이 지정한 크기보다 작으면 데이터를 모두 보내지 못했음을 의미
send() 함수의 리턴값이 –1이면 데이터 전송 자체를 실패했다는 의미
(함수 예시)
char* msg = "Send Test\n";
int len = strlen(msg) + 1;
if (send(sd, msg, len, 0) == -1) {
perror("send");
exit(1);
}
ㅇ ssize_t recv(int sockfd, void* buf, size_t len, int flags): 데이터 수신
- sockfd : socket() 함수가 생성한 소켓 기술자
- buf : 전송받은 메시지를 저장할 메모리 주소
- len : buf의 크기
- flags : 데이터를 주고받는 방법을 지정한 플래그
소켓 sockfd를 통해 전송받은 메시지를 크기가 len인 버퍼 buf에 저장
recv() 함수는 실제로 수신한 데이터의 바이트 수를 반환
(마지막 인자인 flags는 send() 함수에서 사용하는 인자 flags와 같음)
(함수 예시)
char buf[80];
int len, rlen;
if ((rlen = recv(sd, buf, len, 0)) == -1) {
perror("recv");
exit(1);
}
[UDP 전용]
ㅇ ssize_t sendto(int sockfd, void* buf, size_t len, int flags, const struct sockaddr* dest_addr, socklen_t addrlen): 데이터 송신
- sockfd : socket() 함수가 생성한 소켓 기술자
- buf : 전송할 메시지를 저장한 메모리 주소
- len : 메시지의 크기
- flags : 데이터를 주고받는 방법을 지정한 플래그
- dest_addr : 메시지를 받을 호스트의 주소
- addrlen : dest_addr의 크기
• sendto() 함수는 UDP로 데이터를 전송, 따라서 목적지까지 미리 경로를 설정하지 않고 데이터를 전송
• 데이터그램 기반 소켓(SOCK_DGRAM)으로 통신할 때는 listen()이나 accept() 함수를 호출하지 않음
• 첫 번째 인자인 sockfd로 지정한 소켓을 통해 buf가 가리키는 데이터를 dest_addr이 가리키는 목적지의 주소로 전송
+ send() 함수와 달리 매번 목적지 주소를 지정해야 함
sendto() 함수는 실제로 전송한 데이터의 바이트 수를 반환
(함수 예시) IP 주소가 192.168.10.1인 서버로 데이터를 전송
char* msg = "Send Test\n";
int len = strlen(msg) + 1;
struct sockaddr_in sin;
int size = sizeof(struct sockaddr_in);
memset((char*)&sin, '\0', sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(9000);
sin.sin_addr.s_addr = inet_addr("192.168.10.1");
memset(&(sin.sin_zero), 0, 8);
if (sendto(sd, msg, len, 0, (struct sockaddr*)&sin, size) == -1) {
perror("sendto");
exit(1);
}
[UDP 전용]
ㅇ ssize_t recvfrom(int sockfd, void* buf, size_t len, int flags, struct sockaddr* src_addr, socklen_t* addrlen): 데이터 수신
- sockfd : socket() 함수가 생성한 소켓 기술자
- buf : 전송받은 메시지를 저장할 메모리 주소
- len : 메시지의 크기
- flags : 데이터를 주고받는 방법을 지정한 플래그
- src_addr : 메시지를 보내는 호스트의 주소
- addrlen : src_addr의 크기
UDP로 전달된 데이터를 수신하는 데 사용, 따라서 어디에서 메시지를 보내온 것인지 주소 정보도 함께 전달받음
다섯 번째 인자인 src_addr에는 메시지를 발신한 시스템의 주소 정보가 저장
recvfrom() 함수는 실제로 읽어온 데이터의 바이트 수를 반환
(함수 예시) 서버에서 전송한 데이터를 읽어옴
char buf[80];
int len, size;
struct sockaddr_in sin;
if (recvfrom(sd, buf, len, 0, (struct sockaddr*)&sin, &size) == -1) {
perror("recvfrom");
exit(1);
}
[소켓 인터페이스 함수의 호출 순서 정리]
ㅁ서버 측
• 서버 측에서는 socket() 함수로 먼저 소켓을 생성한 후 bind() 함수를 사용해 특정 포트와 연결
• 클라이언트에서 오는 요청을 받을 준비를 마쳤다는 사실을 listen() 함수를 통해 OS에 알리고 요청을 기다림
• 클라이언트의 요청이 오면 accept() 함수로 요청을 받고 send()와 recv() 함수를 통해 데이터를 주고받음
ㅁ클라이언트 측
• socket() 함수로 소켓을 만든 뒤 connect() 함수로 서버와 연결을 요청
• 서버에서 연결 요청을 받아들이면 send()와 recv() 함수로 데이터를 주고받음
• 서버와 클라이언트 모두 close() 함수를 사용해 통신을 종료
[예제 12-6]
(예시 코드: 유닉스 도메인 소켓) "서버 측"
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define SOCKET_NAME "hbsocket"
int main() {
char buf[256];
struct sockaddr_un ser, cli;
int sd, nsd, len, clen;
// 소켓을 생성
if ((sd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
perror("socket");
exit(1);
}
memset((char*)&ser, 0, sizeof(struct sockaddr_un)); // 소켓 구조체를 초기화
ser.sun_family = AF_UNIX; // 소켓 주소 구조체에서 소켓 패밀리를 AF_UNIX로 지정
strcpy(ser.sun_path, SOCKET_NAME); // 소켓 경로명을 지정
len = sizeof(ser.sun_family) + strlen(ser.sun_path);
// bind() 함수로 소켓 기술자를 소켓 주소 구조체와 연결해 이름을 등록
if (bind(sd, (struct sockaddr*)&ser, len)) {
perror("bind");
exit(1);
}
// listen() 함수를 호출해 통신할 준비를 마쳤음을 알림
if (listen(sd, 5) < 0) {
perror("listen");
exit(1);
}
// accept() 함수를 호출해 클라이언트의 접속 요청을 수락하고 새로운 소켓 기술자를 생성해 nsd에 저장
// 클라이언트와는 nsd를 통해 통신을 하게 됨
printf("Waiting ...\n");
if ((nsd = accept(sd, (struct sockaddr*)&cli, &clen)) == -1) {
perror("accept");
exit(1);
}
// 클라이언트가 보낸 메시지를 recv() 함수로 받아서 출력
if (recv(nsd, buf, sizeof(buf), 0) == -1) {
perror("recv");
exit(1);
}
// 출력을 완료했으므로 소켓을 닫음
printf("Received Message: %s\n", buf);
close(nsd);
close(sd);
return 0;
}
(예시 코드: 유닉스 도메인 소켓) "클라이언트 측"
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define SOCKET_NAME "hbsocket"
int main() {
int sd, len;
char buf[256];
struct sockaddr_un ser;
// 소켓을 생성
if ((sd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
perror("socket");
exit(1);
}
// 소켓 주소 구조체를 초기화하고 값을 지정
memset((char*)&ser, '\0', sizeof(ser));
ser.sun_family = AF_UNIX;
strcpy(ser.sun_path, SOCKET_NAME);
len = sizeof(ser.sun_family) + strlen(ser.sun_path);
// 소켓 주소 구조체에 지정한 서버로 connect() 함수를 사용해 연결을 요청
if (connect(sd, (struct sockaddr*)&ser, len) < 0) {
perror("bind");
exit(1);
}
// 서버로 메시지를 전송
strcpy(buf, "Unix Domain Socket Test Message");
if (send(sd, buf, sizeof(buf), 0) == -1) {
perror("send");
exit(1);
}
close(sd);
return 0;
}
// 실행 결과: 클라이언트가 보낸 메시지를 서버에서 받아 출력했음을 알 수 있음
// 실행 시에는 서버를 먼저 실행
※ bind 오류 처리
[예제 12-6]을 한 번 실행한 뒤 다시 실행하면 다음과 같은 메시지가 출력됨
$ ch12_6s.out
bind: Address already in use
이는 소켓 파일이 이미 사용 중이라는 뜻
따라서 소켓 파일([예제 12-6]의 경우 hbsocket)을 미리 삭제하거나, 서버 프로그램에서 소켓을 생성(서버 프로그램인 [예제 12-6] 15행)하기 전에 다음을 호출해 파일을 지우도록 해야 함
unlink(SOCKET_NAME);
[예제 12-7]
(예시 코드: 인터넷 소켓) "서버 측"
#include <sys/socket.h>
#include <sys/un.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define PORTNUM 9000
int main() {
char buf[256];
struct sockaddr_in sin, cli;
int sd, ns, clientlen = sizeof(cli);
// socket() 함수의 인자로 AF_INET과 SOCK_STREAM을 지정해 소켓을 생성
if ((sd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror("socket");
exit(1);
}
// 서버의 IP 주소(192.168.147.129)를 지정하고 포트 번호는 9000으로 지정해 소켓 주소 구조체를 설정
memset((char*)&sin, '\0', sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(PORTNUM);
sin.sin_addr.s_addr = inet_addr("192.168.147.129");
// bind() 함수로 소켓의 이름을 정함
if (bind(sd, (struct sockaddr*)&sin, sizeof(sin))) {
perror("bind");
exit(1);
}
// listen() 함수를 호출해 접속 요청을 받을 준비를 마쳤음을 알림
if (listen(sd, 5)) {
perror("listen");
exit(1);
}
// accept() 함수로 클라이언트의 요청을 수락
if ((ns = accept(sd, (struct sockadrr*)&cli, &clientlen)) == -1) {
perror("accept");
exit(1);
}
// 접속한 클라이언트의 IP 주소를 읽어 메시지를 작성
sprintf(buf, "Your IP address is %s", inet_ntoa(cli.sin_addr));
// send() 함수로 메시지를 전송
if (send(ns, buf, strlen(buf) + 1, 0) == -1) {
perror("send");
exit(1);
}
// 사용을 마친 소켓을 모두 닫음
close(ns);
close(sd);
return 0;
}
(예시 코드: 인터넷 소켓) "클라이언트 측"
#include <sys/un.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define PORTNUM 9000
int main() {
int sd;
char buf[256];
struct sockaddr_in sin;
// 서버와 마찬가지로 소켓을 생성
if ((sd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror("socket");
exit(1);
}
// 소켓 주소 구조체를 설정, 이때 IP 주소는 서버의 주소로 설정해야 함
memset((char*)&sin, '\0', sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(PORTNUM);
sin.sin_addr.s_addr = inet_addr("192.168.147.129");
// connect() 함수로 서버에 연결을 요청
if (connect(sd, (struct sockaddr*)&sin, sizeof(sin))) {
perror("connect");
exit(1);
}
// 연결되면 서버에서 오는 메시지를 받음
if (recv(sd, buf, sizeof(buf), 0) == -1) {
perror("recv");
exit(1);
}
close(sd);
printf("From Server : %s\n", buf);
return 0;
}
// 실행 결과: 서버에서 보낸 메시지를 클라이언트에서 출력함을 알 수 있음
// (서버 프로그램을 먼저 실행한 다음 클라이언트를 실행해야 함)
참고 및 출처: 시스템 프로그래밍 리눅스&유닉스(이종원)
'Computer Science > UNIX & Linux' 카테고리의 다른 글
[UNIX/Linux] ep11-3) TCP 소켓 프로그래밍 (0) | 2024.12.08 |
---|---|
[UNIX/Linux] ep11-2+) 소켓 프로그래밍 함수 실습 (0) | 2024.12.05 |
[UNIX/Linux] ep11-1+) 소켓 프로그래밍 기초 함수 실습 (3) | 2024.12.03 |
[UNIX/Linux] ep11-1) 소켓 프로그래밍 기초 (3) | 2024.11.26 |
[UNIX/Linux] ep10-2) 세마포 (1) | 2024.11.13 |