소켓 프로그래밍이란. 흠.
최창원 교수님 수업을 들었어야했어ㅜㅜ
일단 중요한건 기본 개념.
소켓을 생성하고
<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);
}
최창원 교수님 수업을 들었어야했어ㅜㅜ
일단 중요한건 기본 개념.
소켓을 생성하고
<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);
}


최근 덧글