지금 학원에서 소켓 프로그래밍을 하고 잇는데...
멀티 메신저 프로그램을 만들려고 해서..
자료를 찾앗는데.. 간단하게 소켓 프로그래밍에서 쓰는 함수가
정리 된 것이 잇어서.. 이렇게 올려요^^;; ㅋㅋ
포인트 없는 분들을 위해.. 그냥.. 글로.. ㅋㅋㅋ
================================================================================================================
1. 소켓이란 무엇인가?
소켓은 한 시스템이나 네트워크 상에서 통신을 가능케 해주는 통신 인터페이스이다.
소켓은 버클리 유닉스 버전에 의해 소개되었다. 소켓을 사용하면 운영체제의 종류에
관계 없이 서버/클라이언트 환경을 구축할수있다.
2. 소켓 사용하기
소켓을 사용하기 위해서는 소켓 어드레스 정보가 담긴 구조체를 사용해야 한다.
로컬 시스템에서 사용하기위한 소켓은 sys/un.h 에 정의된 sockaddr_un 을 사용해야
한다.
struct sockaddr_un
sa_family_t sun_family; /* 소켓 도메인(AF_UNIX) */
char sun_path[]; /* 어드레스 파일 경로 */
;
네트워크에서 사용하기 위한 소켓은 netinet/in.h 의 sockaddr_in 을 사용한다.
struct sockaddr_in
short int sin_family; /* 소켓 도메인(AF_INET) */
unsigned short int sin_port; /* 포트 번호 */
struct in_addr sin_addr; /* 인터넷 주소(IP) */
unsigned char sin_zero[8]; /* sockaddr를 위한 변수 */
;
마지막의 sin_zero 는 실제적으로 데이터를 보내는데 사용되는 데이터 타입인
sockaddr 구조체와 크기를 같게하기 위한 변수이다. 이것은 0으로 채워야 한다.
▶ socket() - 소켓 생성
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
domain은 소켓 도메인(소켓종류)인데 AF_UNIX 나 AF_INET 이 될수있다. type 은 소켓
타입으로써 TCP를 사용하여 신뢰된 통신을 하기위한 SOCK_STREAM, UDP를 사용하는
SOCK_DGRAM 이 있다. protocol은 기본적으로 0으로 해준다.
socket 함수는 소켓을 생성하여 성공하면 새로운 소켓 기술자를, 실패하면 -1를 반환
한다.
▶ bind() - 소켓 어드레스 할당
#include <sys/socket.h>
int bind(int socket, const struct sockaddr *address, size_t address_len);
bind는 address에 담겨있는 어드레스를 socket 소켓에 할당한다. 인터넷 개념과 함께
말하자면 소켓을 로컬시스템의 포트에 연결하는 작업이다. address_len 은 address의
크기이다. bind는 어드레스를 할당하여 성공하면 0을 반환하고, 실패하면 -1을 반환
하고 errno를 셋팅한다.
▶ listen() - 소켓 큐 생성
#include <sys/socket.h>
int listen(int socket, int backlog);
listen 함수는 소켓을 위한 backlog 크기의 큐를 생성해 준다. 이 큐는 socket 과
관련된 포트에 접속하는 클라이언트들이 대기하는 하며, 후에 accept 를 사용하여
클라이언트와 통신할수 있다.
listen은 큐를 생성하여 성공할경우 0을 반환하고, 실패하면 -1를 반환하고 errno를
셋팅한다.
▶ accept() - 접속 받아들이기
#include <sys/socket.h>
int accept(int socket, struct sockaddr *address, size_t *address_len);
accept 함수는 socket과 관련된 큐에서 대기하고 있는 클라이언트와 통신할수 있도록
새로운 소켓 기술자를 반환한다. 클라이언트의 정보는 address 가 가르키는 sockaddr
구조체에 저장된다. address_len은 address의 길이를 지정한다. 이 accept는 소켓의
큐에 접속되어있는 클라이언트가 없다면, 클라이언트가 접속을 수행할때까지 대기
(방지) 될것이다. 함수는 성공하면 새로운 기술자를 반환하고 실패하면 -1를 반환한다.
▶ connect() - 접속하기
#include <sys/socket.h>
int connect(int socket, const struct sockaddr *address, size_t address_len);
connect 함수는 socket 을 address가 가르키는 주소에 접속 시킨다. address_len은
address의 크기이다. connect로 접속되고 나면 서버/클라이언트 관계가 형성되고
read, write 또는 send, recv 등으로 데이터를 주고 받을수 있다.
connect 함수는 성공하면 0을 반환하고 실패하면 -1를 반환한다.
▶ 소켓 닫기
소켓을 닫기 위해서는 일반 파일 기술자를 닫는 close 함수를 사용하면 된다.
close는 소켓이 전송되지 않는 데이터를 가지거나 아직 접속이 닫혀있지 않은
클라이언트가 있다면 방지된다. 소켓을 닫는 함수에는 shutdown 이라는 함수가 있다.
#include <unistd.h>
int shutdown(int sockfd, int how);
how에는 닫는 방법이 들어간다.
0 - 더이상의 수신 금지
1 - 더이상의 송신 금지
2 - 더이상의 송수신 금지(close()와 같은 경우)
shutdown은 에러가 나면 -1를 반환한다.
▶ 호스트와 네트워크 바이트 순서
소켓을 사용하는 컴퓨터는 여러가지가 있다. 그 시스템 중에는 메모리에 1-2-3-4
순으로 저장하는 시스템이 있지만, 4-3-2-1 방식으로 저장하는 시스템이 있다. 서버와
클라이언와 통신을 하기위해서는 서버의 같은 포트를 사용하여 통신을 해야 하는데
서버와 클라이언트의 바이트 순서가 달르게 되면 서버 소켓에서는 1574가 주어지지만
클라이언트에서 1574 포트를 접속하려할때 바이트 순서가 달라 9734 포트로 접속
할수도 있다. 이런 사태를 방지하기 위해 소켓에서는 네트워크 바이트 순서와 호스트
바이트 순서를 서로 변환 시킬수있는 함수를 지원한다. 다음은 변환 함수들이다
#include <netinet/in.h>
unsigned long int htonl(unsigned long int hostlong);
unsigned short int htons(unsigned short int hostshort);
unsigned long int ntohl(unsigned long int netlong);
unsigned short int ntohs(unsigned short int netshort);
함수는 함수이름 그대로 해석하면 된다
htons()--"Host to Network Short"
htonl()--"Host to Network Long"
ntohs()--"Network to Host Short"
ntohl()--"Network to Host Long"
서버에 접속하거나 대기하려 할때는 sockaddr_in 구조체의 주소 멤버에 htonl로
호스트 바이트 순서에서 네트워크 바이트 순서로 바꾼 주소값을 넣고, sin_port에는
htons로 변환한 포트 값을 넣으면 될것이다. 또, 네트워크 순서로 변환되어져 있는
값은 ntohs나 ntohl 로 호스트 바이트 순서로 변환하면 될것이다.
▶ 네트워크 정보(IP, 포트, 서비스)
점으로 구성된 아이피를 호스트/네트워크 바이트 순서로 바꾸기 위해서 다음 함수를
사용한다.
#include <arpa/inet.h>
unsigned long inet_addr(const char *cp);
char *inet_ntoa(struct in_addr in);
inet_addr 함수는 점으로 이루어진 아이피 문자열을 unsigned long(NBO)로 변환
해준다. inet_ntoa 함수는 그 반대로 NBO로 이루어진 아이피를 아스키 형식의 문자열로
해준다.
IP 어드레스에 대한 정보를 알기 위해서는 다음과 같은 함수를 사용한다.
#include <netdb.h>
struct hostent *gethostbyaddr(const void *addr, size_t len, int type);
struct hostent *gethostbyname(const char *name);
위 두 함수로 부터 반환되는 hostent 구조체는 다음과 같다.
struct hostent
char *h_name; /* 호스트의 공식적인 이름 */
char **h_aliases; /* 호스트의 별명으로서 NULL 로 끝맺음된다 */
int h_addrtype; /* 주소의 종류, 보통 AF_INET */
int h_length; /* 주소의 바이트 수 */
char **h_addr_list; /* 0으로 끝나는 네트워크 주소들, NBO 구성 */
;
#define h_addr h_addr_list[0] /* h_addr_list 속의 첫번째 주소 */
로컬 시스템의 호스트 네임을 알아보기 위해서는 gethostname 함수를 사용한다.
#include <unistd.h>
int gethostname(char *name, int namelength);
gethostname은 namelength 길이의 호스트 네임을 name이 가르키는 공간에 넣는다.
성공하면 0을 반환하고, 실패하면 -1를 반환한다.
때로는 몇가지 서비스에 대한 정보를 알아보아야 할때도 있을 것이다.
#include <netdb.h>
struct servent *getservbyname(const char *name, const char *proto);
struct servent *getsevbyport(int port, const char *proto);
proto는 SOCK_STREAM 를 위한 tcp나 SOCK_DGRAM 을 위한 udp가 될수있다. 두번째
함수의 port 는 NBO이어야 한다. 두 함수에서 반환되는 정보를 갖는 servent
구조체는 다음 멤버를 가진다.
struct servent
char *s_name; /* 서비스 이름 */
char **s_aliases; /* 별칭의 목록 (선택적인 이름) */
int s_port; /* IP 포트 번호 */
char *s_proto; /* 일반적으로 "tcp"나 "udp"인 서비스 형태 */
;
▶ select() - 동시에 파일 기술자 검사하기
select 함수는 동시에 여러개의 파일 기술자를 읽기, 쓰기 행동이 있는지 알아볼수
있다. 또, 그 파일 기술자에서 읽을수 잇는 데이터나 쓸 데이터가 있을때까지
프로그램이 방지 되게 해준다.
#include <sys/types.h>
#include <sys/time.h>
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds,
struct timeval *timeout);
nfds는 테스트할 파일기술자의 숫자인데, 보통 테스트할 최대 파일 기술자 +1 이다.
readfds 는 읽을 데이터가 있는지 테스트할 파일기술자 모음이고, writefds 는
쓸수 있는지, errrorfds는 에러조건을 가지는지 테스트할 파일 기술자 모음
이다. 그리고 마지막 timeout는 timeval 구조체에 대한 포인터 인데, select는
이 timeval 시간 만큼 파일 기술자에 대해 어떠한 행동이 이루어 질때까지 기다릴
것이다. select 함수는 파일 기술자 모음에 있는 파일 기술자중에 어떠한 기술자가
읽기나, 쓰기나, 에러를 가진다면 그 기술자가 변경되었음을 가르키도록 변경하고
리턴한다. 성공하면 파일기술자 모음의 전체 갯수를 반환하고 실패하면 -1를 반환한다.
또, timeout 동안 기달려도 파일기술자들에게 아무 반응이 없다면 0을 반환한다.
timeval 구조체는 다음 멤버를 가진다.
#include <sys/types.h>
struct timeval
time_t tv_sec; /* 초 단위 */
long tv_usec; /* 밀리 초 단위 */
;
파일 기술자 모음에 파일기술자를 추가하거나 지우거나 반응이 있는지 검사하기 위해
다음 매크로들을 사용한다.
#include <sys/types.h>
#include <sys/time.h>
void FD_ZERO(fd_set *fdset); /* 파일 기술자 모음을 0으로 초기화 한다 */
void FD_CLR(int fd, fd_set *fdset); /* fdset 파일기술자 모음에서 fd를 지운다 */
void FD_SET(int fd, fd_set *fdset); /* fdset 파일기술자 모음에 fd를 추가한다 */
int FD_ISSET(int fd, fd_set *fdset); /* fd가 fdset 파일기술자 모음의 요소라면
0이 아닌값을 반환한다 */
파일기술자가 읽기/쓰기/에러 의 하나에 반응이 있었는지 알아 볼려면 FD_ISSET을
사용하여 해당 파일기술자가 반응이 잇엇는지 체크하면 된다. 반응이 있었다면
FD_ISSET은 1을 반환하고 반응이 없었다면 0을 반환한다.
기본기부터 착실히 배우시네요..
종종 보면 MFC에서 사용하는 CSOCKET CASYNCSOCKET부터 시작해서 하시는분들 있는데
이럴대는 소켓에 대해서 이해가 부족하게 되는거 같습니다.
또 유닉스나 리눅스에서는 사용을 못하고요..ㅋㅋ
역시 기초가 튼튼한게 중요한가봐요~~~
최근에 SUN 11과 윈도우와 통신하는걸 만들었는데 그때 참 여러가지 고민을 했는데 말이지요~
동기로 할건지 비동기로할건지부터 블럭킹해결방안, 트래픽 처리, LSB와 MSB간의 호환등등;;
지나고 나니 추억이네요 이것도.ㅋ