- 소켓 생성(socket) - 듣기소켓이라고 함..
- 소켓 정의(bind) - ip주소, port번호를 할당해줄수 있다
- 대기열 생성(listen) - 만약 어떠한 클라이언트가 서버를 사용하고 있다면 또 다른 클라이언트는 종료가 되겠지만 대기열이 있다면 대기열에서 기다림
- 연결수락(accept) - 대기열에 클라이언트가 있다면 크 클라이언트와의 연결을 허용한다.
- 데이터 통신 (read. write)
- 소켓 종료(close)
우선 서버의 흐름은 이렇게 된다.
클라이언트보다 좀더 복잡한 구성을 가지고 있다.
1. 소켓생성
소켓을 생성하기에앞서 헤더파일을 선언해준다.
1
2
|
#include <sys/types.h>
#include <sys/socket.h>
| cs |
헤더를 선언해준다.
함수의 원형은
1
|
int socket(int domain, int type, int protocol);
| cs |
이된다 이 부분은 헤더 내부에 이미 선언이 되었다.
각매개변수는
nt domain 어떤 영역에서 통신할래?
AF_UNIX : 시스템 내부 영역에서 프로세스와 프로세스간 통신AF_INET : 물리적으로 서로 멀리 떨어진 컴퓨터 사이 통신 일반적으로 IPv4를 이용
int type 어떤 프로토콜 사용할래?
SOCK_STREAM : tcp/ip 사용SOCK_DGRAM : UDP사용SOCKRWA : TCP/IP의 복잡함 감춤.... 먼솔.... 21장에 나온단다....
int protocol 도메인과 유형에 따라 사용할 프로토콜 결정
IPPROTO_TCP : AF_INET과 SOCK_STREAM 유형과 함께 사용IPPROTO_UDP : AF_UNIX와 SOCK_DGRAM 유형과 함께 사용
socket 함수는 int 값 반환한다. -1이면 실패, 0이면 성공.....
이 반환 값을 socket desctoptor 혹은 소켓 지정 번호라고 한다.
모든 소켓 함수들은 소켓 지정 번호를 가지고 필요한 일을 한다!!!!!! - 아 그래서 계속 sockfd를 가져다가 쓰는구나.......
socket을 이용해서 소켓을 만들어 보자
1
2
|
int sockfd; // 소켓번호를 저장 하는 변수 선언
sockfd= socket(AF_INET, SOCK_STREAM, TPPROTO); // IPv4와 tcp/ip를 이용하여 통신을 하는 소켓생성
| cs |
이렇게 소켓이 만들어 졌다.그럼 만들어진 소켓을 정의를 해보자.
2. 소켓 정의
소켓 정의는 bind함수를 이용한다 bind 함수를 사용하기 앞서서 헤더를 선언 해준다
1
2
|
#include <sys/types.h>
#include <sys/socket.h>
| cs |
bind함수의 원형은
1
|
int bind(int sockfd, (struct sockaddr *) my_addr, socklen_t addrlen);
| cs |
이 된다. 요놈도 헤더안에 쏙 선언이 되있음으로 선언을 해줄필요가 없다.
우선 매개변수를 살펴보면
int socket은 socket함수로 선언된 endpoint(듣기 소켓)이되겠다.
(struct sockaddr *) my_addr은 IP주소와 port번호를 저장 하는 변수가 들어있는 구조체가 되겠다.
socklen_t addrlen은 두번째 인자의 데이터 크기.
반환값은 성공하게 되면 0, 실패시 -1반환
tip!!
socklen_t addrlen을 통해 데이터의 크기를 인자로 넘기는 이유는 무엇일까?컴퓨터는 데이터의 크기를 모른다 아무것도 모른다. 아는게 아무것도 없다... 멍청멍청 그냥 사용자가 시키는것만 잘한다.즉 아무것도 모르는 컴퓨터에게 얼마만큼의 데이터를 읽어야 하는지 알려주어야 한다..만약 2번째 인자가 고정되있다면 굳이 인자의 크기를 넘겨줄 필요는 없다.
이제 bind를 이용하여 정의
1
2
3
4
5
6
7
8
9
10
11
12
|
struct sockaddr_in clientaddr, serveraddr, myaddr; // 구조체가 sockaddr_in으로 되있는데 AF_INET의 유형이 된다. <-> AF_UNIX는 sockaddr_un이 되겠다.->
memset(&serveraddr, 0x00, sizeof(serveraddr));
serveraddr.sin_family=AF_INET;
serveraddr.sin_addr.s_addr=htonll(INADDR_ANY);
serveraddr.sin_port=htons(12345);
state = bind(sockfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr));
if(state == -1)
{
perror("bind error : \n");
return 1;
}
| cs |
우선 clientaddr, serveraddr 클라이언트와 서버를 선언을 해준다.
여기서 구조체형이 truct sockaddr_in으로 되있는데 이것은 AF_INET의 유형이된다
만약 AF_UNIX의 유형이라면 struct sockaddr_un이 된다. 이 두개의 구조체의 내부는 다르다.
그리고 memset을 이용하여 buf를 초기화 시켜준다.
따로 초기화를 시켜주는 이유는 운영체제가 프로세스에게 메모리 공간을 할당 하고 회수하는 과정에서 회수만 할 뿐 초기화를 시키지 않기때문이다. 즉 이전 프로세스가 사용하다가 남긴 것들이 메모리에 남아 있기 떄문이다.
bind함수부분에서 두번쨰 인자를 보면 (struct sockaddr*)&serveraddr)로 되있는데
무조건 (struct sockaddr*)로 형변환을 시켜주어야 한다. 이 형태만 받아들이기 때문이다. 그리고 주고를 넘겨준다.....
...... struct sockaddr_in 과 struct sockaddr_un의 구조체 내부를 비교해 보면
우선 struct sockaddr의 구조체를 보면
1
2
3
4
5
6
7
8
9
10
11
12
13
|
struct sockadr
{
unsigned short sa_family;
char sa_data[14];
}
struct sockaddr_in
{
short sin_family;
unsigned short sin_port;
struct in_addr sin_addr; // 또 다른 구조체를 선언..... ip값을 저장할때 구조체를 두번 타고 들어가는 이유이다...
char sin_zero[8];
}
| cs |
여기서 sa_family의 값과short sin_family의 값은 같다.
이제 un을 봐보자
1
2
3
4
5
6
|
struct sockaddr_un
{
short sin_family;
char sun_path[108];
}
| cs |
in과 un이 다른 구조임을 알수 있다. 또한 un즉 네트워크 내부에서는 port값이 필요가 없음을 알수있다.
다음과 같이 코드를 짜면....
1
2
3
4
5
6
7
8
9
10
11
|
bind(struct sockaddr *sockinfo)
{
if(sockinfo -> sin_family == AF_INET)
{
}
else if(sockinfo -> sin_family == AF_UNIX)
{
}
}
| cs |
이렇게 해주면 클라이언트의 특성에 따라서 각각의 작업을 수행할 수 있다.
소켓을 정의했으니... 이제 대기열을 생성을.... 합시다.
3. 수신 대기열 생성
수신 대기열은 스택방식과 큐 방식중에 FIFO(first in first out)방식인 큐 방식을 사용한다.
먼저 들어온넘이 먼나 나가는 구조
스택은 데이터가 차곡차곡 쌓이는 구조로 먼저 들어온놈이 맨 밑에 깔려있기 때문에 가장 마지막에 나가는 구조
수신 대기열을 생성하기 위해서는
listen()함수 이용
우선 함수 원형을 보자
1
2
|
#include <sys/socket.h>
int listen(int sockfd, int backlog);
| cs |
이렇게된다 매우 간단한 구조를 지니었다.
1
|
listen(sockfd,5);
| cs |
sockfd이라는 소켓지정 번호에 수신 대기열 5개를 생성.....
4. 연결 기다리기...
지금까지 소켓을 만들었고, 그 소켓의 정의 그리고 클라이언트가 연결 요청시 기다릴 공간 수신 대기열을 생성하였다
이제 이 기다기고 있는 클라이언트의 연결요청을 받아들여야 한다.
이때
accept()함수를 이용한다.
먼저 accept()함수는 수신대기열에서 가장 앞에 있는 클라이언트의 요청을 가져오게된다(FIFO 방식이기 때문에)
또한 accept()함수는 서버프로그램에서만 사용하게 된다. 연결을 받아들이는 것이기때문에 클라이언트에서는 사용할 필요가 없다.
우선 함수의 원형을 보자.
1
2
3
4
|
#include <sys/type.h>
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t*addrlen);
| cs |
매개변수를 살펴보면
sockfd : socket함수로 생성된 듣기소켓으로, 클라이언트의 연결 요청을 받아들이는 소켓이 된다.addr : accept함수가 성공하게 되면, 연결된 클라이언트의 정보를 이 구조체에 넘겨주게 된다.addr은 연결한 클라이언트의 정보를 확인하거나 로그를 남기기 위한 목적 등으로 사용할 수 있다.addrlen : sockaddr 구조체의 크기.accept()함수가 끝나면 0이상의 값을 반환하게 된다. 반환 된 값이 '소켓 지정번호'이다.
socket()-듣기소켓 VS accept()-연결소켓 차이점은????
socket(듣기소켓)은 클라이언트가 연결을 하는지 안하는지 확인만 하게 된다.accept(연결소켓)은 실질적으로 클라이언트와 통신을 하는 부분이다.이렇게 나누어주는 이유는 연결을 학인하고, 통신을 지속하기위함이다.즉 socket()에서 클라이언트와의 연결요청을 받아들이면 클라이언트는 수신대기열로 들어가게 된다. 그럼 accept함수가가장 먼저 들어온 클라이언트와 통신을 하게 된다.
사용법을 보면..
1
2
|
struct sockaddr_in client_sockaddr
int client_sockfd;
| cs |
1
2
3
4
5
6
7
8
9
10
11
|
while(1)
{
if(client_sockfd = accpt(sockfd, (struct sockaddr *)&client_sockaddr, sizeof(struct sockaddr)==-1)
{
//에러처리 반환값이 -1이므로 함수 실패
}
else
{
// client_sockfd를 이용하여 통신을 한다.
}
}
| cs |
또한 accept는 계속적으로 통신을 하기때문에
while(1)문안에 넣어줌으로써 지속적인 통신이 가능하게 해준다.
댓글
댓글 쓰기