C - 代理服务器中的 select()



我正在用c构建代理服务器,我正在尝试理解select()函数。 我已经完成了代码,以便从客户端建立连接,然后提取 Web 地址,以便可以建立另一个连接以连接到实际的 Web 服务器。 然后,代理接收该页面并将其传递回客户端。

我知道select()将允许它处理多个客户端请求,但我不明白它如何帮助(或者更确切地说是如何实现)与 Web 服务器的第二个连接。 据我了解,我将不再需要一个while循环来继续从Web服务器接收数据并将其传递回客户端。

我是否需要为 Web 服务器连接设置第二个文件描述符? 如果我正在处理两个或多个客户端请求,如何确保它们通过与 Web 服务器的正确连接链接在一起? 无论如何,我将不胜感激任何帮助。 我已经在线浏览了网络教程和其他一些教程,但几天后我仍然没有解决这个问题。

void error(const char *msg)
{
perror(msg);
exit(1);
}
void *get_in_addr(struct sockaddr *sa)
{
if (sa->sa_family == AF_INET)
return &(((struct sockaddr_in*)sa)->sin_addr);
return &(((struct sockaddr_in6*)sa)->sin6_addr);
}
string getHostString(const char *buf);
int main(int argc, char *argv[])
{   
int sockfd, newsockfd, portno;
int fdmax; //maximum file descriptor number
socklen_t clilen;
char buffer[256];
struct sockaddr_storage remoteaddr; //client address
char clientIP[INET6_ADDRSTRLEN];
//fd_set readfds, writefds, exceptfds;
fd_set masterfds, readfds;
struct timeval timeout;
int rc;
/*Set time limit. */
timeout.tv_sec = 3;
timeout.tv_usec = 0;
/*Create a descriptor set containing the sockets */
FD_ZERO(&readfds);
FD_ZERO(&masterfds);
/*FD_ZERO(&writefds);
FD_ZERO(&exceptfds);
FD_SET(newsockfd, &readfds);
rc = select(sizeof(readfds)*8, &readfds, NULL, NULL, &timeout);
if (rc==-1){
perror("select failed");
return -1;
}*/
struct sockaddr_in serv_addr, cli_addr;
int n;
if (argc < 2) {
fprintf(stderr,"ERROR, no port providedn");
exit(1);
}
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) 
error("ERROR opening socket");
bzero((char *) &serv_addr, sizeof(serv_addr));
portno = atoi(argv[1]);
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = INADDR_ANY;
serv_addr.sin_port = htons(portno);
if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) 
error("ERROR on binding");
if((listen(sockfd,5)) == -1 )
error("Server-listen() error!!!");
printf("Server-listen() is OK...n");

FD_SET(sockfd, &masterfds);
//keep track of the biggest file descriptor so far
fdmax = sockfd; //so far it's this one
for(;;){
readfds = masterfds;
if(select(fdmax+1, &readfds, NULL, NULL, NULL) == -1){
error("Server-select() error !");
}
printf("Server-select() is OK...n");
for(int i = 0; i <= fdmax; i++){
printf("i: %d sockfd %dn", i, sockfd);
if(FD_ISSET(i, &readfds)){
if(i == sockfd){ //sockfd is the listener
//following handles new connections
clilen = sizeof(cli_addr);
if((newsockfd = accept(sockfd,(struct sockaddr *) &cli_addr, &clilen)) == -1)
error("Server-accept() error!!!");
else{
printf("Server-accept() is OK...n");
FD_SET(newsockfd, &masterfds); //add to master set
if(newsockfd > fdmax) 
fdmax = newsockfd;
printf("selectserver: New connection from %s on  "
"socket %dn",
inet_ntop(remoteaddr.ss_family,
get_in_addr((struct sockaddr*)&cli_addr),
clientIP, INET6_ADDRSTRLEN),
newsockfd);
}
}
else{
//handle data from a client
//Here is where the FIRST read occurs
bzero(buffer,256);
//n = read(newsockfd,buffer,255);
n = read(i,buffer,255);
if (n < 0) error("ERROR reading from socket");
printf("Here is the message: nn%snn",buffer);
//string hoststring(buffer);
string hoststring(getHostString(buffer));
int html_port = 80;
int html_socket;
printf("Prior to struct addrinfon");
struct addrinfo hints, *res;
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
printf("Prior to getaddrinfo()n");
char *address = new char[hoststring.size() +1];
address[hoststring.size()] = 0;
memcpy(address, hoststring.c_str(), hoststring.size());
//getaddrinfo(token, (char *)html_port, &hints, &res);
getaddrinfo(address, "80", &hints, &res);
printf("We are past getaddrinfo()n");
//if (html_socket = socket(PF_INET, SOCK_STREAM, 0) < 0){
if ((html_socket = socket(res->ai_family, res->ai_socktype, res->ai_protocol)) < 0){
printf("socket connection errorn");
}
printf("We are past socket()n");
//char* address;
//address = new char[256];
//strncpy(address, "www.cs.ucr.edu", sizeof("www.cs.ucr.edu"));

printf("address: %sn", address);    
struct hostent * host;
if ((host = gethostbyname(address)) == NULL){
printf("Problem with gethostbyname()n");
}
printf("We are past gethostbyname() and about to connect.n");
if ( connect(html_socket, res->ai_addr, res->ai_addrlen) < 0)
printf("Unsuccessful completion of connect()");
int bytes_sent, bytes_recvd;
char recv_buff[1024];
bytes_sent = send(html_socket, buffer, 256, 0);
cout << "bytes_sent: " << bytes_sent << endl;
//do{
bytes_recvd = recv(html_socket, recv_buff, 1024, 0);
cout << "bytes_rcvd: " << bytes_recvd << endl;
cout << recv_buff << endl;
//FD_ZERO(&readfds);
//FD_SET(newsockfd, &writefds);
//n = write(newsockfd,"I got your messagen",20);
//n = write(newsockfd,recv_buff,bytes_recvd);
n = write(i,recv_buff,bytes_recvd);
if (n < 0) error("ERROR writing to socket");
bzero(recv_buff, 1024);
//}while(bytes_recvd !=0);
}
}
}
}
close(newsockfd);
close(sockfd);
return 0; 
}

string getHostString(const char *buf){
string hoststring(buf); 
hoststring = hoststring.substr(11);
cout << "hoststring: " << hoststring << endl;
int slashpos;
slashpos = hoststring.find("/");
int suffixendpos = hoststring.find("H") - slashpos;
string suffix = hoststring.substr(slashpos, suffixendpos);
hoststring = hoststring.substr(0, slashpos);
cout << "hoststring: " << hoststring << endl;
cout << "suffix: " << suffix << endl;
return hoststring;
}

我知道您正在尝试编写一个在循环中处理多个连接并使用select()来确定套接字描述符何时具有要读取的数据。但是,有时另一种方法实际上更容易且更具可扩展性。

您是否考虑过使用multi-process服务器,在该服务器中,在建立每个套接字连接后,您fork()一个新进程来处理请求?这样就无需担心select()的工作方式以及将请求映射到正确的套接字描述符。

请参阅此处的处理多个连接作为合理示例。

除了使用fork()之外,您还可以将线程与 POSIXpthreads库一起使用,这可能会提高您的效率。下面是一个很好的多线程 tcp/ip 服务器示例,它使用 pthreads。

你读过select(2)和select_tut(2)手册页吗?

您是否阅读了例如高级Linux编程或高级Unix编程的相关章节?

实际上,由于 c10k 问题和最大文件描述符限制为 256(或 1024),即__FD_SETSIZE),selectsyscall 已经过时了,您应该改用 poll(2) syscall。

您应该在for循环中设置readfds,就在select调用之前,使用显式FD_ZEROFD_SET(fd_set类型可以是数组,因此不能完整分配它。select系统调用可以修改它。

不要忘记使用gcc -Wall -g进行编译并使用调试器。您还可以研究现有自由软件HTTP客户端库或代理的源代码。

最新更新