소켓 프로그래밍 - 서버! 코딩/개발

소켓 프로그래밍이란. 흠.
최창원 교수님 수업을 들었어야했어ㅜㅜ

일단 중요한건 기본 개념.


소켓을 생성하고


<sys/types.h>
<sys/socket.h>
int socket(int domain, int type, int protocol)

프로토콜 체계를 설정하고, 전송타입을 정하고, 특정 프로토콜을 지정한다.
ex) socket(PF_INET, SOCK_STREAM, 0)

첫번째 인자 : ip 버전4 프로토콜을 사용하자
두번째 인자 : 스트림통신 즉 TCP를 이용하겠다는것(연결지향적)
세번째 인자 : TCP/UDP를 정해주는 건데 이미 첫번째 두번째 인자를 통해서 당연히 IPPROTO_TCP가 됐다. 그러니 0으로 해도 오케.


소켓에 서버의 주소 정보를 할당한다


<sys/type.h>
<sys/socket.h>
int bind(int sockfd, struct sockaddr *myaddr, int addrlen);

소켓의 디스크립터를 지정하고, 주소 정보를 지니고 있는 포인터인자를 전달. 그리고 주소 정보 구조체의 길이를 지정한다.
ex) bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr);

첫번째 인자 : 소켓 생성뒤 얻은 디스크립터다. 리눅스에서는 파일이나 소켓이나 같은 형태로 취급하니깐 그냥 디스크립터를 받아온다.
두번째 인자 : serv_addr 이라는 구조체는 서버의 여러가지 정보를 저장하고 있다.(IP, port, IPversion)
    serv_addr.sin_family = AF_INET;    //addr family --> AF_INET : internet family --IPv4를 쓰고
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);    //ip addr, Host TO Network Long --서버 ip 지정
    serv_addr.sin_port = htons(atoi(argv[1]));    //Host TO Network Short --서버 포트 지정
 
    여기에 보면 htonl(), htons() 함수가 있는데 이건, 호스트 바이트를 네트워크 바이트로 변환시켜 주기 위한 것.
    123456789로 보내면 987654321 처럼 받은 순서대로 인식하기 때문에.
    네트워크 바이트 순서는 Big-Endian 방식만을 사용하기로 했다. 따라서 호환성을 위한 함수.
세번째 인자 : 주소 정보 구조체의 길이.


귀 귀울이기 - 연결 요청 대기 상태

<sys/type.h>
int listen(int s, int backlog);

이 단계까지 진행이 되어야 클라이언트가 연결을 요청하는 함수 connect를 호출해도 오류가 발생하지 않는다.

첫번째 인자: 클라이언트의 연결을 받을 소켓의 디스크립터. 이건 당연히 서버소켓이겠지
두번째 인자: 연결 요청 Queue의 크기를 나타낸다


연결 요청 수락하기


<sys/type.h>
<sys/socket.h>
int accept(int s, struct sockaddr *addr, int *addrlen);

연결 요청을 수락한다는 것은 요청한 클라이언트와 데이터를 주고 받을 수 있는 상태가 되는 것을 의미한다.

첫번째 인자: 서버 소켓의 디스크립터를 인자로 전달
두번째 인자: 연결 요청을 수락할 클라이언트의 주소 정보를 저장
세번째 인자: 클라이언트 addr포인터가 가리키는 구조체의 크기를 저장하고 있는 변수의 포인터.

accept 함수는 호출 성공 시 내부적으로 클라이언트와의 데이터 입/출력을 하기 위해 사용될 소켓을 생성하고, 그 소켓의 디스크립터를 리턴해주기 때문에, 즉 알아서 소켓을 생성해주기 때문에 우리가 직접 생성해주지 않아도 된다.
이 의미는 서버소켓은 계속해서 다른 클라이언트의 연결을 위해 사용되고, 새로 생성된 소켓이 클라이언트와 연결에 이용된다는 것을 나타낸다.


데이터 송수신

<unistd.h>
int read(int fd, void* buf, int count)
int write(int fd, void* buf, int count);

리눅스에서는 소켓이든 파일이든, 다시말해서 socket의 디스크립터든 file의 open으로 얻은 디스크립터든 간에 상관없이, 모두 파일로 취급하므로 그냥 디스크립터.
따라서 read, write로 읽어버리면 된다.

ex)read(clnt_sock, readBuf, sizeof(readBuf));
    white(clnt_sock, blackList, sizeof(blackList));

첫번째 인자: 읽거나 쓸 대상의 디스크립터
두번째 인자: 읽어 올 내용을 저장하는 버퍼/ 보낼 내용을 담고있는 버퍼
세번째 인자: 받거나 보낼 버퍼의 크기




서버 프로그램 예제

클라이언트가 접속하여 regex, black 등의 문자열을 보내오면 알맞은 data를 클라이언트 쪽으로 보내주는 예제 프로그램이다. 이제 서버는 대충 구현했으나, 멀티쓰레드 기반으로 튜닝이 필요하겠네. -_-

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <fcntl.h>

void error_handling(char* message);

int main(int argc, char** argv)
{    
    int i;
    int serv_sock, clnt_sock;
    struct sockaddr_in serv_addr;
    struct sockaddr_in clnt_addr;
    int clnt_addr_size;
    FILE *fp;
    char buf[100];
    char blackList[10000][17];
    char readBuf[100];
    
    //버퍼 초기화
    memset(readBuf, 0x00, sizeof(readBuf));
    memset(buf, 0x00, sizeof(buf));
    memset(blackList, 0x00, sizeof(blackList));

    //black list 파일 오픈
    if((fp = fopen("./rule", "r"))== NULL)
        printf("file open failed\n");

    for(i=0; i<10000; i++)
    {
        if(feof(fp))
            break;
        //파일을 읽어서 blackList에 저장    
        fgets(buf, sizeof(buf), fp);
        strcpy(blackList[i], buf);
    }

    if(argc != 2)
    {
        printf("Usage : %s <port>\n", argv[0]);
        exit(1);
    }

    //서버 소켓 생성
    serv_sock = socket(PF_INET, SOCK_STREAM, 0);
    if(serv_sock == -1)
        error_handling("socket() error");

    memset(&serv_addr, 0, sizeof(serv_addr));

    //소켓에 IP, 포트 등을 지정
    serv_addr.sin_family = AF_INET;    //addr family --> AF_INET : internet family
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);    //ip addr, Host TO Network Long
    serv_addr.sin_port = htons(atoi(argv[1]));    //Host TO Network Short

    //소켓에 주소 할당. serv_addr로 오는 신호는 이 소켓으로 들어와라
    if(bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1)
        error_handling("bind() error");
    
    printf("리슨 전\n");
    
    //대기
    if(listen(serv_sock, 5) == -1)
        error_handling("listen() error");
    
    clnt_addr_size = sizeof(clnt_addr);
    
    //연결수락, 현재의 위치에서 대기타고 있어!!
    clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size);

    if(clnt_sock == -1)
        error_handling("accept() error");
        
    while(1) {    
        read(clnt_sock, readBuf, sizeof(readBuf));
    
        if(strcmp(readBuf, "black\r\n") == 0)
            write(clnt_sock, blackList, sizeof(blackList));    //데이터 전송
        else if(strcmp(readBuf, "regex\r\n") == 0)
            write(clnt_sock, "아직없다\n", sizeof("아직없다\n"));
        else
            write(clnt_sock, "똑바로입력해라ㅜㅜ\n", sizeof("똑바로입력해라ㅜㅜ\n"));

        //클라이언트로 부터 수신한 데이터 버퍼 초기화
        memset(readBuf, 0x00, sizeof(readBuf));
    }

    close(clnt_sock);
    
    fclose(fp);
    
    return 0;
}

void error_handling(char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

트랙백

이 글과 관련된 글 쓰기 (트랙백 보내기)
TrackbackURL : http://zzang2k.egloos.com/tb/1596431 [도움말]

덧글

댓글 입력 영역

라이프로그