어떻게 간단한 웹 서버를 실현합니까 (3)
우선 select의 함수 원형과 자주 사용하는 매크로를 살펴보겠습니다.
#include
int select(int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds, struct timeval* timeout);
FD_ZERO(fd_set *fdset); // fdset
FD_SET(int fd, fd_set* fdset); // fdset
fd FD_CLR(int fd, fd_set* fdset); // fdset
fd int FD_ISSET(int fd, fd_set* fdset); // fdset fd
먼저 select 함수 원형을 보면 nfds는 감청된 파일 설명자의 총수를 지정합니다. 그 값은 보통 모든 파일 설명자의 최대 값에 1을 더하고 다음 세 개의 fd_set* 형식의 매개 변수는 읽을 수 있고 쓸 수 있으며 이상한 이벤트에 대응하는 파일 설명자 집합을 가리키며, 마지막 매개 변수는 미초급 타이머입니다. select가 이 시간을 막은 후에 계속 실행되고, 0이면 바로 되돌아오고, NULL이면 계속 막힙니다.
관찰을 통해 fd_set 구조체의 원형은 하나의 성형 수조만 포함하고 이 수조의 모든 부분은 하나의 파일 묘사부호를 표시하기 때문에 select는 가장 감시할 수 있는 파일 묘사부호의 제한을 가진다.뒤에 있는 매크로는 fd_에 대한 단순화를 위한 것이다set 비트 조작.select 함수가 성공했을 때 준비된 파일 설명자의 총수를 되돌려줍니다. 시간 초과 시간 내에 파일 설명자가 준비되지 않았다면 select는 0을 되돌려줍니다. select가 막히는 동안 프로그램이 신호를 받으면 select는 -1을 EINTR로 되돌려줍니다.
select는 어떤 상황에서 파일 설명자에 읽을 수 있거나 쓸 수 있거나 이상한 상황이 생겼다고 생각합니까?우선, socket이 다음과 같은 상태에 있으면 읽을 수 있다고 생각할 수 있다. 1) socket 내부 핵 수신 버퍼의 바이트 수가 낮은 수위 표시보다 크거나 같을 때 우리는 이 socket을 막힘 없이 읽을 수 있고 읽기 동작의 반환 값이 0보다 크다.2) socket의 쌍방이 연결을 닫고 읽기 동작이 0으로 되돌아옵니다.3) socket에 새로운 요청이 있는 것을 감청한다.4) socket에서 처리되지 않은 오류가 있습니다.다음 상태는 socket을 쓸 수 있다고 생각할 수 있습니다. 1) socket 내부 핵 전송 버퍼의 사용 가능한 바이트 수가 낮은 수위 표기보다 크거나 같을 때 우리는 이 socket을 막힘 없이 쓸 수 있고 쓰기 동작의 반환 값이 0보다 큽니다.2) socket의 쓰기 작업이 닫히고 쓰기 작업이 닫힌 socket에 쓰기 작업이 실행되면 SIGPIPE 신호가 발생합니다.3) socket 비저지connect 연결이 성공하거나 실패하거나(시간 초과)4) socket에서 처리되지 않은 오류가 있습니다.이상 상황은 단지 한 가지가 있는데 바로 대외 데이터가 생겼다는 것이다.
하나의 예는 select 프로그램이 어떻게 쓰는지, 그리고 select가 일반 데이터와 대외 데이터를 동시에 처리하는지 살펴보는 것이다.
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
int main(int argc, char** argv) {
if(argc <= 2) {
printf("usage: %s ip_address port_number
", basename(argv[0]));
return 1;
}
const char* ip = argv[1];
int port = atoi(argv[2]);
int ret = 0;
struct sockaddr_in address;
bzero(&address, sizeof(address));
address.sin_family = AF_INET;
address.sin_port = htons(port);
inet_pton(AF_INET, ip, &address.sin_addr);
int listenfd = socket(AF_INET, SOCK_STREAM, 0);
assert(listenfd >= 0);
ret = bind(listenfd, (struct sockaddr*)&address, sizeof(address));
assert(ret != -1);
ret = listen(listenfd, 5);
assert(ret != -1);
struct sockaddr_in client_address;
socklen_t client_addrlength = sizeof(client_address);
int connfd = accept(listenfd, (struct sockaddr*)&client_address, &client_addrlength);
if(connfd < 0) {
printf("errno is: %d
", errno);
}
char buf[1024];
fd_set read_fds;
fd_set exception_fds;
FD_ZERO(&read_fds);
FD_ZERO(&exception_fds);
while(1) {
memset(buf, 0, sizeof(buf));
FD_SET(connfd, &read_fds);
FD_SET(connfd, &exception_fds);
ret = select(connfd + 1, &read_fds, NULL, &exception_fds, NULL);
if(ret < 0) {
printf("selection failure
");
break;
}
if(FD_ISSET(connfd, &read_fds)) {
ret = recv(connfd, buf, sizeof(buf), 0);
if(ret <= 0) break;
printf("get %d bytes of normal data: %s
", ret, buf);
}
memset(buf, 0, sizeof(buf));
if(FD_ISSET(connfd, &exception_fds)) {
ret = recv(connfd, buf, sizeof(buf), MSG_OOB);
if(ret <= 0) break;
printf("get %d bytes of oob data: %s
", ret, buf);
}
}
close(connfd);
close(listenfd);
return 0;
}
epoll은 Linux 특유의 I/O 복용 함수로 그 실현은 select,poll과 큰 차이가 있다.epoll은 사용자가 관심 있는 파일 묘사부호의 이벤트를 내부 핵에 있는 이벤트 테이블에 놓아서 select와poll처럼 호출할 때마다 파일 묘사부호나 이벤트 집합을 반복해서 전송할 필요가 없습니다. 그러나 epoll은 내부 핵에 있는 이 이벤트 테이블을 표시하기 위해 추가 파일 묘사부호가 필요합니다.poll과 달리 epoll은 이벤트를 감지하면 모든 준비 시간을 내부 시간표에서 이벤트가 가리키는 수조로 복사하여 응용 프로그램 검색 준비 파일 설명자의 효율을 크게 향상시키고 O(n)의 시간 복잡도에서 O(1)로 낮춘다.epoll의 몇 가지 함수를 살펴보겠습니다.
#include
int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event* event);
int epoll_wait(int epfd, struct epoll_event* events, int maxevents, int timeout);
epoll의 함수size 매개 변수를 만드는 것은 현재 쓸모가 없습니다. 단지 내부 핵에 이벤트 테이블이 얼마나 필요한지 알려 줄 뿐입니다.조작 epoll의 함수에서op 매개 변수는 조작 형식을 지정합니다. 모두 등록, 수정, 삭제 세 가지가 있습니다. 이벤트 매개 변수는 이벤트를 설명합니다.
epoll은 파일 설명자 작업에 두 가지 모드가 있습니다.
LT 및 ET 모드
LT(Level Triggered, 레벨 트리거): LT 모드는 epoll의 기본 작업 모드이자 select와poll의 작업 모드입니다. LT 모드에서 epoll은 효율이 비교적 높은poll에 해당합니다.LT 모드의 파일 설명자를 사용하여 epoll_wait에서 이벤트 발생을 감지하고 이 이벤트를 응용 프로그램에 알리면 프로그램이 이 이벤트를 즉시 처리하지 않고 다음 호출 epoll_wait는, epoll_wait는 이 이벤트를 응용 프로그램에 알립니다.ET(Edge Triggered, 테두리 터치): epoll_ 호출 시ctl, 매개 변수 이벤트에 EPOLLET 이벤트를 등록할 때 epoll은 ET 모드로 이 파일 설명자를 조작합니다. ET 모드는 epoll의 효율적인 작업 모드입니다.ET 모드를 사용하는 파일 설명자의 경우 epoll_wait에서 이벤트가 발생했음을 감지하고 이 알림 프로그램을 실행하면 프로그램은 이 이벤트를 즉시 처리해야 합니다. 왜냐하면 후속 epoll_wait 호출은 이 이벤트를 프로그램에 알리지 않습니다.ET 모드는 epoll 이벤트가 촉발되는 횟수를 낮추어 LT 모드보다 효율이 높다.
using namespace std;
#define MAX_EVENT_NUMBER 1024
#define BUFFER_SIZE 10
//
int setnonblocking(int fd) {
int old_option = fcntl(fd, F_GETFL);
int new_option = old_option | O_NONBLOCK;
fcntl(fd, F_SETFL, new_option);
return old_option;
}
// epoll
void addfd(int epollfd, int fd, bool enable_et) {
epoll_event event;
event.data.fd = fd;
event.events = EPOLLIN;
if(enable_et) event.events |= EPOLLET;
epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event);
setnonblocking(fd);
}
void lt(epoll_event* events, int number, int epollfd, int listenfd) {
char buf[BUFFER_SIZE];
for(int i = 0; i < number; i ++) {
int sockfd = events[i].data.fd;
if(sockfd == listenfd) {
struct sockaddr_in client_address;
socklen_t client_addrlength = sizeof(client_address);
int connfd = accept(listenfd, (struct sockaddr*)&client_address, &client_addrlength);
addfd(epollfd, connfd, false);
}
else if(events[i].events & EPOLLIN) {
printf("event trigger once
");
memset(buf, 0, sizeof(buf));
int ret = recv(sockfd, buf, BUFFER_SIZE, 0);
if(ret <= 0) {
close(sockfd);
continue;
}
printf("get %d bytes of content: %s
", ret, buf);
}
else printf("something else happened
");
}
}
void et(epoll_event* events, int number, int epollfd, int listenfd) {
char buf[BUFFER_SIZE];
for(int i = 0; i < number; i ++) {
int sockfd = events[i].data.fd;
if(sockfd == listenfd) {
struct sockaddr_in client_address;
socklen_t client_addrlength = sizeof(client_address);
int connfd = accept(listenfd, (struct sockaddr*)&client_address, &client_addrlength);
addfd(epollfd, connfd, true);
}
else if(events[i].events & EPOLLIN) {
// ,
printf("event trigger once
");
while(1) {
memset(buf, 0, sizeof(buf));
int ret = recv(sockfd, buf, BUFFER_SIZE, 0);
if(ret < 0) {
// I/O,
if((errno == EAGAIN) || (errno == EWOULDBLOCK)) {
printf("read later
");
break;
}
close(sockfd);
break;
}
else if(ret == 0) close(sockfd);
else printf("get %d bytes of content: %s
", ret, buf);
}
}
else printf("something else happened
");
}
}
int main(int argc, char** argv) {
if(argc <= 2) {
printf("usage: %s ip_address port_number
", basename(argv[0]));
return 1;
}
const char* ip = argv[1];
int port = atoi(argv[2]);
int ret = 0;
struct sockaddr_in address;
bzero(&address, sizeof(address));
address.sin_family = AF_INET;
address.sin_port = htons(port);
inet_pton(AF_INET, ip, &address.sin_addr);
int listenfd = socket(AF_INET, SOCK_STREAM, 0);
assert(listenfd >= 0);
ret = bind(listenfd, (struct sockaddr*)&address, sizeof(address));
assert(ret != -1);
ret = listen(listenfd, 5);
assert(ret != -1);
epoll_event events[MAX_EVENT_NUMBER];
int epollfd = epoll_create(5);
assert(epollfd != -1);
addfd(epollfd, listenfd, true);
while(1) {
int ret = epoll_wait(epollfd, events, MAX_EVENT_NUMBER, -1);
if(ret < 0) {
printf("epoll failure
");
break;
}
et(events, ret, epollfd, listenfd);
}
close(listenfd);
return 0;
}
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
다양한 언어의 JSONJSON은 Javascript 표기법을 사용하여 데이터 구조를 레이아웃하는 데이터 형식입니다. 그러나 Javascript가 코드에서 이러한 구조를 나타낼 수 있는 유일한 언어는 아닙니다. 저는 일반적으로 '객체'{}...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.