承接上文: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) + ;
select(maxfdp1, &rset, NULL, NULL, NULL); /* socket is readable */
if (FD_ISSET(sockfd, &rset)) {
memset(recvline, , sizeof(recvline));
if (read(sockfd, recvline, MAXLINE) == ) {
printf("server terminated prematurely\n");
exit();
}
fputs(recvline, stdout);
}
/* input is readable */
if (FD_ISSET(fileno(fp), &rset)) {
memset(sendline, , 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, )) < ) {
perror("socket error");
exit();
} bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(); /* daytime server */ if ( bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < ) {
perror("bind error");
exit();
} if ( listen(listenfd, LISTENQ) < ) {
perror("listen error");
exit();
} int maxfd = listenfd;
int maxi = -;
int nready, client[FD_SETSIZE];
int i;
for (i = ; i < FD_SETSIZE; i++) {
client[i] = -;
}
fd_set rset, allset;
FD_ZERO(&allset);
FD_SET(listenfd, &allset); for ( ; ; ) {
rset = allset;
nready = select(maxfd + , &rset, NULL, NULL, NULL);
// new client connection
if (FD_ISSET(listenfd, &rset)) {
clilen = sizeof(cliaddr);
connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &clilen); for (i = ; i < FD_SETSIZE; i++) {
if (client[i] < ) {
client[i] = connfd;
break;
}
}
if (i == FD_SETSIZE) {
printf("too many clients\n");
exit();
}
FD_SET(connfd, &allset);
if (connfd > maxfd)
maxfd = connfd;
if (i > maxi)
maxi = i;
if (--nready <= )
continue;
}
// check all clients for data
for (i = ; i < FD_SETSIZE; i++) {
if ( (sockfd = client[i]) < )
continue;
if (FD_ISSET(sockfd, &rset)) {
memset(buf, , sizeof(buf));
ssize_t n;
if ( (n = read(sockfd, buf, MAXLINE)) == ) {
close(sockfd);
FD_CLR(sockfd, &allset);
client[i] = -;
} else {
write(sockfd, buf, n);
} if (--nready <= )
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, )) < ) {
perror("socket error");
exit();
} bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(); /* daytime server */ if ( bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < ) {
perror("bind error");
exit();
} if ( listen(listenfd, LISTENQ) < ) {
perror("listen error");
exit();
} struct pollfd client[OPEN_MAX];
client[].fd = listenfd;
client[].events = POLLRDNORM;
int i, maxi, nready;
for (i = ; i < OPEN_MAX; i++) {
client[i].fd = -;
}
maxi = ; for ( ; ; ) {
nready = poll(client, maxi + , -);
// new client connection
if (client[].revents & POLLRDNORM) {
clilen = sizeof(cliaddr);
connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &clilen); for (i = ; i < OPEN_MAX; i++) {
if (client[i].fd < ) {
client[i].fd = connfd;
break;
}
}
if (i == OPEN_MAX) {
printf("too many clients\n");
exit();
} client[i].events = POLLRDNORM;
if (i > maxi)
maxi = i;
if (--nready <= )
continue;
} // check all clients for data
for (i = ; i <= maxi; i++) {
if ( (sockfd = client[i].fd) < )
continue;
if (client[i].revents & (POLLRDNORM | POLLERR)) {
memset(buf, , sizeof(buf));
ssize_t n;
if ( (n = read(sockfd, buf, MAXLINE)) < ) {
if (errno == ECONNRESET) {
printf("connection reset by client\n");
close(sockfd);
client[i].fd = -;
} else {
perror("read error");
exit();
}
} else if (n == ){
printf("connection closed by client\n");
close(sockfd);
client[i].fd = -;
} else {
write(sockfd, buf, n);
} if (--nready <= )
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 */
};

最后附上一张图吧:

参考文章:

也谈IO模型

socket编程之并发回射服务器2的更多相关文章

  1. socket编程之并发回射服务器3

    在socket编程之并发回射服务器一文中,服务器采用多进程的方式实现并发,本文采用多线程的方式实现并发. 多线程相关API: // Compile and link with -pthread int ...

  2. socket编程之并发回射服务器

    使用到的函数: // 子进程返回0,父进程返回子进程ID,出错返回-1 pid_t fork(void); pid_t wait(int *wstatus); // 最常用的option是WNOHAN ...

  3. socket编程之时间回射服务器

    使用到的函数: // 返回值:读到的字节数,若已到文件尾,返回0:若出错,返回-1 ssize_t read(int fd, void *buf, size_t nbytes); // 返回值:若成功 ...

  4. 第二十篇:不为客户连接创建子进程的并发回射服务器(poll实现)

    前言 在上文中,我使用select函数实现了不为客户连接创建子进程的并发回射服务器( 点此进入 ).但其中有个细节确实有点麻烦,那就是还得设置一个client数组用来标记select监听描述符集中被设 ...

  5. 不为客户连接创建子进程的并发回射服务器( poll实现 )

    前言 在上文中,我使用select函数实现了不为客户连接创建子进程的并发回射服务器( 点此进入 ).但其中有个细节确实有点麻烦,那就是还得设置一个client数组用来标记select监听描述符集中被设 ...

  6. 第十九篇:不为客户连接创建子进程的并发回射服务器(select实现)

    前言 在此前,我已经介绍了一种并发回射服务器实现.它通过调用fork函数为每个客户请求创建一个子进程.同时,我还为此服务器添加了自动消除僵尸子进程的机制.现在请想想,在客户量非常大的情况下,这种为每个 ...

  7. 不为客户连接创建子进程的并发回射服务器( select实现 )

    前言 在此前,我已经介绍了一种并发回射服务器实现( 点此进入 ).它通过调用fork函数为每个客户请求创建一个子进程.同时,我还为此服务器添加了自动消除僵尸子进程的机制.现在请想想,在客户量非常大的情 ...

  8. 【Unix网络编程】chapter5TCP回射服务器程序

    chapter5  5.1 概述 5.2 TCP回射服务器程序:main函数 int main(int argc, char **argv) { int listenfd,connfd; pid_t ...

  9. 服务器编程入门(11)TCP并发回射服务器实现 - 单线程select实现

    问题聚焦: 当客户端阻塞于从标准输入接收数据时,将读取不到别的途径发过来的必要信息,如TCP发过来的FIN标志. 因此,进程需要内核一旦发现进程指定的一个或多个IO条件就绪(即输入已准备好被读取,或者 ...

随机推荐

  1. "二号标题"组件:<h2> —— 快应用组件库H-UI

     <import name="h2" src="../Common/ui/h-ui/text/c_h2"></import> < ...

  2. idea打包报错Cleaning up unclosed ZipFile for archive D:\m2\commons-beanutils\commons-beanutils\1.9.2\...

    关于idea的打包报错:Cleaning up unclosed ZipFile for archive D:\m2\commons-beanutils\commons-beanutils\1.9.2 ...

  3. 萌新带你开车上p站(Ⅳ)

    本文作者:萌新 前情回顾: 萌新带你开车上p站(一) 萌新带你开车上p站(二) 萌新带你开车上P站(三) 回顾一下前篇,我们开始新的内容吧 0x12 登录后看源码 通读程序,逻辑是这样子的: 输入6个 ...

  4. pgsql中的事务隔离

    pgsql中的事务隔离级别 前言 事物隔离级别 在各个级别上被禁止出现的现象是 脏读 不可重复读 幻读 序列化异常 读已提交隔离级别 可重复读隔离级别 可序列化隔离级别 摘录 pgsql中的事务隔离级 ...

  5. win10+ubuntu双系统修复ubuntu启动引导

    因为windows是不能引导linux的,而每次win10升级或恢复都会将linux的启动引导覆盖掉,导致无法进入linux, 所以一直就禁止了win10更新.这几天win10出了点小毛病,所以就狠下 ...

  6. CentOS8.1.1911正式发布!

    前阵子,CentOS官方宣布:CentOS8.1.1911正式发布!已经安装CentOS8.0的朋友,可以执行yum update更新(笔者更新了2次),体验下新版本!如是新安装,可以从官方网站下载h ...

  7. 知识点二:HTTP超文本文件传输协议

    HTTP超文本传输协议概念: http1.1之前采用非持续链接服务器在建立连接上开销较大,http1.1之后默认采用持续连接,并有超时设置 http协议:超文本文件传输协议,用于传输文本文件,请求的方 ...

  8. HTTPoxy漏洞(CVE-2016-5385)复现记录

    漏洞介绍: httpoxy是cgi中的一个环境变量:而服务器和CGI程序之间通信,一般是通过进程的环境变量和管道. CGI介绍 CGI 目前由 NCSA 维护,NCSA 定义 CGI 如下:CGI(C ...

  9. 【题解】P2602 数字计数 - 数位dp

    P2602 [ZJOI2010]数字计数 题目描述 给定两个正整数 \(a\) 和 \(b\) ,求在 \([a,b]\) 中的所有整数中,每个数码(digit)各出现了多少次. 输入格式 输入文件中 ...

  10. ES6系列-什么是ES6?新手应该怎么理解

    ECMAScript 是什么 很多初学者都很困惑,ECMAScript是什么?它跟JavaScript有什么关系? 大家注意到了吗?从题目中我们就可以看出来了,ECMAScript是JavaScrip ...