socket编程之并发回射服务器2
承接上文:socket编程之并发回射服务器
为了让服务器进程的终止一经发生,客户端就能检测到,客户端需要能够同时处理两个描述符:套接字和用户输入。
可以使用select达到这一目的:
void str_cli(FILE *fp, int sockfd) { int maxfdp1; fd_set rset; char sendline[MAXLINE], recvline[MAXLINE]; FD_ZERO(&rset); for (;;) { FD_SET(fileno(fp), &rset); FD_SET(sockfd, &rset); maxfdp1 = max(fileno(fp), sockfd) + 1; select(maxfdp1, &rset, NULL, NULL, NULL); /* socket is readable */ if (FD_ISSET(sockfd, &rset)) { memset(recvline, 0, sizeof(recvline)); if (read(sockfd, recvline, MAXLINE) == 0) { printf("server terminated prematurely\n"); exit(1); } fputs(recvline, stdout); } /* input is readable */ if (FD_ISSET(fileno(fp), &rset)) { memset(sendline, 0, sizeof(sendline)); if (fgets(sendline, MAXLINE, fp) == NULL) { return; } write(sockfd, sendline, strlen(sendline)); } } }
函数声明如下:
// 返回:若有就绪描述符则为其数目,若超时则为0,若出错则为-1 int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
之前的服务器程序通过多进程的方式来处理并发连接,我们也可以使用select对其进行改造:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <sys/select.h> #define MAXLINE 4096 #define LISTENQ 10 int main(int argc, char **argv) { int listenfd, connfd, sockfd; char buf[MAXLINE]; socklen_t clilen; struct sockaddr_in servaddr, cliaddr; if ( (listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror("socket error"); exit(1); } bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(13); /* daytime server */ if ( bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0) { perror("bind error"); exit(1); } if ( listen(listenfd, LISTENQ) < 0) { perror("listen error"); exit(1); } int maxfd = listenfd; int maxi = -1; int nready, client[FD_SETSIZE]; int i; for (i = 0; i < FD_SETSIZE; i++) { client[i] = -1; } fd_set rset, allset; FD_ZERO(&allset); FD_SET(listenfd, &allset); for ( ; ; ) { rset = allset; nready = select(maxfd + 1, &rset, NULL, NULL, NULL); // new client connection if (FD_ISSET(listenfd, &rset)) { clilen = sizeof(cliaddr); connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &clilen); for (i = 0; i < FD_SETSIZE; i++) { if (client[i] < 0) { client[i] = connfd; break; } } if (i == FD_SETSIZE) { printf("too many clients\n"); exit(1); } FD_SET(connfd, &allset); if (connfd > maxfd) maxfd = connfd; if (i > maxi) maxi = i; if (--nready <= 0) continue; } // check all clients for data for (i = 0; i < FD_SETSIZE; i++) { if ( (sockfd = client[i]) < 0) continue; if (FD_ISSET(sockfd, &rset)) { memset(buf, 0, sizeof(buf)); ssize_t n; if ( (n = read(sockfd, buf, MAXLINE)) == 0) { close(sockfd); FD_CLR(sockfd, &allset); client[i] = -1; } else { write(sockfd, buf, n); } if (--nready <= 0) break; } } } }
当然了,也可以使用poll对其进行改造:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <sys/poll.h> #include <sys/errno.h> #define MAXLINE 4096 #define LISTENQ 10 #define OPEN_MAX 1024 int main(int argc, char **argv) { int listenfd, connfd, sockfd; char buf[MAXLINE]; socklen_t clilen; struct sockaddr_in servaddr, cliaddr; if ( (listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror("socket error"); exit(1); } bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(13); /* daytime server */ if ( bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0) { perror("bind error"); exit(1); } if ( listen(listenfd, LISTENQ) < 0) { perror("listen error"); exit(1); } struct pollfd client[OPEN_MAX]; client[0].fd = listenfd; client[0].events = POLLRDNORM; int i, maxi, nready; for (i = 1; i < OPEN_MAX; i++) { client[i].fd = -1; } maxi = 0; for ( ; ; ) { nready = poll(client, maxi + 1, -1); // new client connection if (client[0].revents & POLLRDNORM) { clilen = sizeof(cliaddr); connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &clilen); for (i = 1; i < OPEN_MAX; i++) { if (client[i].fd < 0) { client[i].fd = connfd; break; } } if (i == OPEN_MAX) { printf("too many clients\n"); exit(1); } client[i].events = POLLRDNORM; if (i > maxi) maxi = i; if (--nready <= 0) continue; } // check all clients for data for (i = 1; i <= maxi; i++) { if ( (sockfd = client[i].fd) < 0) continue; if (client[i].revents & (POLLRDNORM | POLLERR)) { memset(buf, 0, sizeof(buf)); ssize_t n; if ( (n = read(sockfd, buf, MAXLINE)) < 0) { if (errno == ECONNRESET) { printf("connection reset by client\n"); close(sockfd); client[i].fd = -1; } else { perror("read error"); exit(1); } } else if (n == 0){ printf("connection closed by client\n"); close(sockfd); client[i].fd = -1; } else { write(sockfd, buf, n); } if (--nready <= 0) break; } } } }
函数声明如下:
/* * 返回:若有就绪描述符则为其数目,若超时则为0,若出错则为-1 * nfds:结构数组fds中元素个数 */ int poll(struct pollfd *fds, nfds_t nfds, int timeout);
struct pollfd { int fd; /* file descriptor */ short events; /* requested events */ short revents; /* returned events */ };
最后附上一张图吧:
参考文章: