기본 콘텐츠로 건너뛰기

TCP/IP소켓 프로그래밍 socket-소캣생성, bind-소캣 정의 listen수신 대기열 생성 accept 연결 기다리기

  • 소켓 생성(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, 0x00sizeof(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)문안에 넣어줌으로써 지속적인 통신이 가능하게 해준다.


댓글

이 블로그의 인기 게시물

[git] git log 확인하기

git log를 통해서 커밋 이력과 해당 커밋에서 어떤 작업이 있었는지에 대해 조회를 할 수 있다. 우선 git에서의 주요 명령어부터 알아보겠다. $ git push [branch name] $ git pull [branch name] 여기서 branch name은 로컬일 경우 해당 브런치 이름만 적으면 되지만 깃허브 원격 저장소로 연결을 원할 경우는 해당 브런치 이름 앞에 꼭 origin을 붙이도록 한다. $ git brnch [branch name] $ git checkout [branch name] branch일경우 해당 브런치를 생성을 한다. 여기서 현재의 브런치를 기준으로 브런치를 따는것이다. checkout은 브런치를 바꾸는 것이다.(HEAD~[숫자]를 이용하면 해당 커밋으로 움직일수 있다.. 아니면 해당 커밋 번호를 통해 직접 옮기는것도 가능하다.) -> 해당 커밋으로 옮기는 것일뿐 실질적으로 바뀌는 것은 없다. 해당 커밋으로 완전히 되돌리려면 reset이라는 명령어를 써야한다. 처음 checkout을 쓰면 매우 신기하게 느껴진다. 막 폴더가 생겼다가 지워졌다가 ㅋㅋㅋㅋㅋ  master 브런치에서는 ht.html파일이 존재하지만 a브런치에서는 존재하지않는다. checkout 으로 변경을 하면 D 로 명시를 해준다.  $ git log 해당 브런치의 커밋 내역을 보여준다. a 브런치의 커밋 내역들이다. (머지 테스트를 하느라 커밋 내용이 거의 비슷하다 ㅋㅋ) master 브런치의 커밋 내역들이다. 커밋 번호, 사용자, 날짜, 내용순으로 등장을 한다. 이건 단순히 지금까지의 내역을 훑어보기 좋다. 좀더 세밀한 내용을 봐보자. $ git log --stat --stat을 붙이면 기존의 로그에서 간략하게 어떤 파일에서

[kali linux] sqlmap - post요청 injection 시도

아래 내용은 직접 테스트 서버를 구축하여 테스트 함을 알립니다.  실 서버에 사용하여 얻는 불이익에는 책임을 지지 않음을 알립니다. sqlmap을 이용하여 get요청이 아닌 post요청에 대해서 injection공격을 시도하자. 뚀한 다양한 플래그를 이용하여 DB 취약점 테스트를 진행을 해보려고 한다. 서버  OS : windows 7 64bit Web server : X Server engine : node.js Framework : expresss Use modules : mysql Address : 172.30.1.30 Open port : 6000번 공격자 OS : kali linux 64bit use tools : sqlmap Address : 172.30.1.57 우선 서버측 부터  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 var  express  =  require( 'express' ); var  app  =  express(); var  mysql  =  require( 'mysql' ); var  ccc  =  mysql.createConnection({     host: '127.0.0.1' ,     user: 'root' ,     post: '3306' ,     password: '*********' ,     database: 'test' }) app.post(

[git] pull을 하여 최신코드를 내려받자

보면 먼가 로고가 다르게 뜨는것을 확인을 할 수가있다. C:\Users\mung\Desktop\etc\study\python-gene>git checkout remotes/origin/master Note: checking out 'remotes/origin/master'. You are in 'detached HEAD' state. You can look around, make experimental changes and commit them, and you can discard any commits you make in this state without impacting any branches by performing another checkout. If you want to create a new branch to retain commits you create, you may do so (now or later) by using -b with the checkout command again. Example:   git checkout -b HEAD is now at 29e282a... fetch test C:\Users\mung\Desktop\etc\study\python-gene>git branch * (HEAD detached at origin/master)   master   test1   test2 깃이 잘 쓰면 참 좋은놈인데 어지간히 쓰기가 까다롭다. 처음에 깃을 푸시 성공하는데만 한달정도 걸렸던걸로 기억이 난다.. ㅋㅋㅋ 여담으로  깃 프로필을 가면 아래사진 처럼 보인다. 기여도에 따라서 초록색으로 작은 박스가 채워지는데 저걸 잔디라고 표현을 한다고 합니다 ㅎ 저 사진은 제 깃 기여도 사진입니당 ㅋㅋㅋㅋ 다시 본론으로 돌아와서 ㅋㅋ pull을 하면 깃에 최신 소