Intro:: 에코 서버 구현전에 참고하면 좋습니다!
socket() #include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
해당 소켓이 가리키는 소켓 디스크립터를 반환한다.
int
domain
: AF_UNIX(프로토콜 내부에서), AF_INET(IPv4), AF_INET6(IPv6)int
type
: SOCK_STREAM(TCP), SOCK_DGRAM(UDP), SOCK_RAW(사용자 정의)int
protocol
: 0, IPPROTO_TCP(TCP 일때), IPPROTO_UDP(UDP 일때)
bind() int bind( SOCKET sockfd, const sockaddr *my_addr, int namelen );
소켓에 IP와 포트를 할당한다.
SOCKET
sockfd
: socket()으로 구한 소켓 파일 디스크립터sockaddr *
my_addr
: IP 주소와 port번호를 저장하기 위한 변수가 있는 구조체listen() int listen(int sockfd, int backlog)
클라이언트로부터 연결 요청을 기다린다.
SOCKET
sockfd
: socket()으로 구한 소켓 파일 디스크립터int
backlog
: 동시에 연결을 시도하는 최대 클라이언트 수kqueue() #include <sys/time.h>
#include <sys/event.h>
#include <sys/types.h>
int kqueue(void);
kqueue()는 커널에 새로운 event queue를 만들고, fd를 return한다. return된 fd는 kevent()에서 이벤트를 등록, 모니터링하는데 사용된다. 이 queue는 fork로 자식 프로세스 분기 시 상속되지 않는다.
단순하게 이벤트를 감지해주는 시스템 콜이라고 생각하면 된다.
kevent()
int kevent(int kq, const struct kevent *changelist, int nchanges,
struct kevent *eventlist, int nevents, const struct timespec *timeout);
kevent 는 ident와 filter를 식별자로 삼아 kevent의 중복 등록을 막고, 해당 이벤트 발생 조건이 부합하지 않는 경우에는 kqueue에서 삭제되어 최소한의 kevent만 남아있도록 관리된다.
const struct kevent *
changelist
: kqueue에 등록될 kevent 구조체 배열이다. struct kevent {
uintptr_t ident; /* event에 대한 식별자로 fd 번호가 입력된다 */
int16_t filter; /* 커널이 event를 핸들링할 때 이용되는 필터 */
uint16_t flags; /* event를 적용시키서나, event가 return 되었을 때 사용되는 flag */
uint32_t fflags; /* filter에 따라 다르게 적용되는 flag */
intptr_t data; /* filter에 따라 다르게 적용되는 data */
void *udata; /* event와 함께 등록하여 event return시 사용할 수 있는 user-data */
};
filter 종류 EVFILT_READ
: ident의 fd를 모니터링하고, 읽을 수 있는 data 가 있다면 event returnEVFILT_WRITE
: ident의 fd를 모니터링하고, write가 가능하다면 event returnEVFILT_VNODE
, EVFILT_SIGNAL
, ….flags 종류 EV_ADD
: kqueue에 event 등록EV_ENABLE
: kevent()가 return 할 수 있도록 활성화EV_DISABLE
: kevent()가 return 못하도록 비활성화EV_DELETE
: queue에서 event 삭제etc…
int
nchanges
: changelist 배열의 크기 struct kevent *
eventlist
: 발생한 event가 return될 배열int
nevents
: eventlist 배열의 크기const struct timespec *
timeout
: timespec 구조체의 포인터를 전달, NULL을 전달할 셩우 이벤트 발생까지 block(무한정 대기)
에코 서버 구현
#include <sys/types.h>
#include <sys/event.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>
#include <iostream>
#include <map>
#include <vector>
using namespace std;
void exit_with_perror(const string& msg) {
cerr << msg << endl;
exit(EXIT_FAILURE);
}
void change_events(vector<struct kevent>& change_list, uintptr_t ident, int16_t filter,
uint16_t flags, uint32_t fflags, intptr_t data, void* udata) {
struct kevent tmp_event;
EV_SET(&tmp_event, ident, filter, flags, fflags, data, udata);// kevent 구조체 초기화 매크로
change_list.push_back(tmp_event);
}
void disconnect_client(int client_fd, map<int, string>& clients) {
cout << "client disconnected: " << client_fd << endl;
close(client_fd);
clients.erase(client_fd);
}
int main(int argc, char** argv) {
if (argc != 2) {
exit_with_perror(string(argv[0]) + " [PORT]");
}
/* 서버 소켓 초기화 & listen */
int server_socket = socket(PF_INET, SOCK_STREAM, 0);// 서버 소켓 IPv4, TCP, 0
struct sockaddr_in server_addr;
if (server_socket == -1) {
exit_with_perror("socket() eroror: " + string(strerror(errno)));
}
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;// TCP, UDP 설정
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);// 모든 ip접근 허가
server_addr.sin_port = htons(atoi(argv[1]));// 포트 설정
if (bind(server_socket, reinterpret_cast<struct sockaddr*>(&server_addr), sizeof(server_addr)) == -1 ) {
exit_with_perror("bind() eroror: " + string(strerror(errno)));
}
if (listen(server_socket, 5) == -1) {
exit_with_perror("listen() eroror: " + string(strerror(errno)));
}
fcntl(server_socket, F_SETFL, O_NONBLOCK);// 소켓 플레그를 논블로킹으로 설정
/* kqueue 초기화*/
int kq = kqueue();
if (kq == -1) {
exit_with_perror("kq() eroror: " + string(strerror(errno)));
}
map<int, string> clients;// 클라이언트 소켓 데이터
vector<struct kevent> change_list;// kevent 배열
struct kevent event_list[8];// event list 배열
/* 서버 소켓에 이벤트 추가 */
change_events(change_list, server_socket, EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, NULL);
cout << "echo server started" << endl;
/* 메인 루프 */
int new_events;
struct kevent* cur_event;
while (1) {
new_events = kevent(kq, &change_list[0], change_list.size(), event_list, 8, NULL);// change_list안의 값들은 kqueue에 등록된다.
if (new_events == -1) {
exit_with_perror("kevent() eroror: " + string(strerror(errno)));
}
change_list.clear();
cout << "감지된 이벤트 개수: " << new_events << "\n";
for (int i=0; i<new_events; i++) {
cur_event = &event_list[i];
/* 에러 이벤트 체크 */
if (cur_event->flags & EV_ERROR) {// 에러
if (cur_event->ident == server_socket) {
exit_with_perror("server socket eroror: " + string(strerror(errno)));
}
cerr << "client socket error" << endl;
disconnect_client(cur_event->ident, clients);
} else if (cur_event->filter == EVFILT_READ) {// 읽기
if (cur_event->ident == server_socket) {
/* 새로운 클라이언트 승인 */
int client_socket = accept(server_socket, NULL, NULL);
if (client_socket == -1) {
exit_with_perror("accept() eroror: " + string(strerror(errno)));
}
cout << "accept new client: " << client_socket << endl;
fcntl(client_socket, F_SETFL, O_NONBLOCK);
/* 클라 소켓 이벤트 추가 - 쓰기, 읽기 이벤트 추가 */
change_events(change_list, client_socket, EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, NULL);
change_events(change_list, client_socket, EVFILT_WRITE, EV_ADD | EV_ENABLE, 0, 0, NULL);
clients[client_socket] = "";
} else if (clients.find(cur_event->ident) != clients.end()) {
/* 클라이언트 데이터 읽어오기 */
char buf[1024];
int n = read(cur_event->ident, buf, sizeof(buf));
if (n <= 0) {
if (n < 0) {
cerr << "client read error!" << endl;
}
disconnect_client(cur_event->ident, clients);
} else {
buf[n] = '\0';
clients[cur_event->ident] += buf;
cout << "received data from " << cur_event->ident << ": " << clients[cur_event->ident] << endl;
}
}
} else if(cur_event->filter == EVFILT_WRITE) { // 쓰기
/* 클라이언트에게 데이터 송신 */
map<int, string>::iterator cur_client = clients.find(cur_event->ident);
if (cur_client != clients.end()) {
if (clients[cur_event->ident] != "") {// map에 클라이언트 데이터가 있다면
int n = write(cur_event->ident, cur_client->second.c_str(), cur_client->second.size());
if (n == -1) {
cerr << "client write error!" << "\n";
disconnect_client(cur_event->ident, clients);
} else {
cout << "server echoed to:" << cur_event->ident << ", message: " << cur_client->second << endl;
cur_client->second.clear();
}
} else {
cout << cur_event->ident << "에게 보낼 데이터가 없습니다." << endl;
}
}
}
}
sleep(1);
}
return 0;
}
에코 클라이언트 구현
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <iostream>
#define BUF_SIZE 1024
using namespace std;
void exit_with_perror(const string& msg) {
cerr << msg << endl;
exit(EXIT_FAILURE);
}
int main(int argc, char** argv) {
if (argc != 3) {
exit_with_perror(string(argv[0]) + " [IP] [PORT]");
}
/* 클라이언트 소켓 초기화 & connect */
int client_socket = socket(PF_INET, SOCK_STREAM, 0);// 클라이언트 소켓 IPv4, TCP, 0
struct sockaddr_in server_addr;
if (client_socket == -1) {
exit_with_perror("socket() eroror: " + string(strerror(errno)));
}
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;// TCP, UDP 설정
server_addr.sin_addr.s_addr = inet_addr(argv[1]);// IP
server_addr.sin_port = htons(atoi(argv[2]));// PORT
if (connect(client_socket, (const sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
exit_with_perror("connect() eroror: " + string(strerror(errno)));
}
char message[BUF_SIZE];
int len;
while (1) {
/* 메세지 송신 */
cout << "전송할 메세지를 입력: " << endl;
cin.getline(message, BUF_SIZE);
write(client_socket, message, strlen(message));
/* 메세지 수신 */
len = read(client_socket, message, BUF_SIZE - 1);
message[len] = '\0';
cout << "수신한 메세지: " << message << endl;
}
close(client_socket);
return 0;
}
번외 - 웹 브라우저와 통신 위의 에코 서버 코드와 거의 동일하고 클라이언트에게 데이터 송신하는 부분을 다음 코드로 바꿔주면 된다. 아래 코드에서는 서버가 소켓에 메시지를 적어주고 바로 통신을 끊는다.
...
} else if(cur_event->filter == EVFILT_WRITE) { // 쓰기
/* 클라이언트에게 데이터 송신 */
map<int, string>::iterator cur_client = clients.find(cur_event->ident);
if (cur_client != clients.end()) {
if (clients[cur_event->ident] != "") {
string msg = "Hello, world!!!\nhi Chrome!!!";// 브라우저에 출력하고 싶은 문구
string header = "HTTP/1.1 200 OK\r\nContent-Length: " + to_string(msg.size()) + "\r\n\r\n" + msg;
write(cur_event->ident, header.c_str(), header.size());
cout << "server responsed to:" << cur_event->ident << endl;
cur_client->second.clear();
disconnect_client(cur_event->ident, clients);
}
}
}
...
References::
Loading Comments...