TCP客户/服务器程序实例——回射服务器
目录
客户/服务器程序源码
POSIX信号处理
POSIX信号语义
处理SIGCHLD信号
处理僵死进程
处理被中断的系统调用
wait和waitpid函数
wait和waitpid函数的区别
网络编程可能会遇到的三种情况
TCP程序小结
数据格式
回射输入行这样一个客户/服务器程序是一个虽然简单然而却很有效的网络应用程序的例子。实现任何客户/服务器网络应用所需的所有基本步骤可通过本例子阐明。若想把本例子扩充成你自己的应用程序,你只需修改服务器对于来自客户的输入的处理过程。
TCP回射服务器程序:main函数
/* tcpserv01.c */
#include <sys/socket.h>
#include <strings.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h> int
main(int argc, char **argv)
{
int listenfd, connfd;
pid_t childpid;
socklen_t clilen;
struct sockaddr_in cliaddr, servaddr; if((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
perror("socket");
exit(1);
} bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(9877); if(bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)
{
perror("bind");
exit(1);
}
if(listen(listenfd, 5) < 0)
{
perror("listen");
exit(1);
} for(;;)
{
clilen = sizeof(cliaddr);
if((connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &clilen)) < 0)
{
perror("accept");
exit(1);
} if((childpid = fork()) < 0)
{
perror("fork");
exit(1);
}
else if(childpid == 0) /* child process */
{
if(close(listenfd) < 0) /* close listening socket */
{
perror("child close");
exit(1);
}
str_echo(connfd); /* process the request */
exit(0); }
if(close(connfd) < 0) /* parent close connected socket */
{
perror("parent close");
exit(1);
}
} }
TCP回射服务器程序:str_echo函数
/* str_echo.c */
#include <stdio.h>
#include <stdlib.h>
#include <errno.h> void
str_echo(int sockfd)
{
ssize_t n;
char buf[4096];
again:
while((n = read(sockfd, buf, 4096)) > 0)
writen(sockfd, buf, n); if(n < 0 && errno == EINTR)
goto again;
else if(n < 0)
{
perror("read");
exit(1);
} }
TCP回射客户程序:main函数
/* tcpcli01.c */
#include <stdio.h>
#include <strings.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h> int
main(int argc, char **argv)
{
int sockfd;
struct sockaddr_in servaddr; if(argc != 2)
{
printf("usage: tcpcli <IPaddress> ");
exit(0);
} if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
perror("socket");
exit(1);
} bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(9877);
if(inet_pton(AF_INET, argv[1], &servaddr.sin_addr) < 0)
{
perror("inet_pton");
exit(1);
} if(connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)
{
perror("connect");
exit(1);
} str_cli(stdin, sockfd); /* do it all */ exit(0);
}
TCP回射客户程序:str_cli函数
/* str_cli.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h> void
str_cli(FILE *fp, int sockfd)
{
char sendline[4096], recvline[4096]; while(fgets(sendline, 4096, fp) != NULL)
{
writen(sockfd, sendline, strlen(sendline)); if(readline(sockfd, recvline, 4096) == 0)
{
printf("str_cli: server terminated prematurely");
exit(0);
}
fputs(recvline, stdout);
}
}
服务器和客户都要调用的自定义函数:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h> ssize_t /* read "n" bytes from a descriptor. */
readn(int fd, void *vptr, size_t n)
{
size_t nleft;
ssize_t nread;
char *ptr; ptr = vptr;
nleft = n;
while(nleft > 0)
{
if((nread = read(fd, ptr, nleft)) < 0)
{
if(errno == EINTR)
nread = 0; /* and call read() again */
else
return(-1);
}
else if(nread == 0)
break; /* EOF */ nleft -= nread;
ptr += nread;
}
return(n - nleft); /* return >= 0 */
} ssize_t /* write n bytes to a descriptor */
writen(int fd, const void *vptr, size_t n)
{
size_t nleft;
ssize_t nwritten;
const char *ptr; ptr = vptr;
nleft = n;
while(nleft > 0)
{
if((nwritten = write(fd, ptr, nleft)) <= 0)
{
if(nwritten < 0 && errno == EINTR)
nwritten = 0; /* and call write again */
else
return(-1); /* error */
} nleft -= nwritten;
ptr += nwritten;
}
return(n - nwritten);
} ssize_t
readline(int fd, void *vptr, size_t maxlen)
{
ssize_t n, rc;
char c, *ptr; ptr = vptr;
for(n = 1; n < maxlen; n++)
{
again:
if((rc = read(fd, &c, 1)) == 1)
{
*ptr++ = c;
if(c == '\n')
break; /* newline is stored, like fgets() */
}
else if(rc == 0)
{
*ptr = 0;
return(n - 1); /* EOF, n - 1 bytes were read */
}
else
{
if(errno == EINTR)
goto again;
return(-1); /* error, errno set by read() */
}
} *ptr = 0; /* null terminate like fgets() */
return(n);
}
正常启动:
首先,我们在主机Linux上后台启动服务器。
服务器启动后,它调用socket、bind、listen和accept,并阻塞于accept调用。(我们还没有启动客户。)在启动客户之前,我们运行netstat程序来检查服务器监听套接口的状态。netstat -a
这个输出正是我们所期望的:套接口处于LISTEN状态,它有通配的本地IP地址,本地端口号为9877(这正是我们所配置的端口号)。netstat用星号“*”来表示一个为0的IP地址(INADDR_ANY,通配地址)或为0的端口号。
我们接着在同一个主机上启动客户,并指定服务器主机的IP地址为127.0.0.1(回馈地址)。当然我们也可以指定该地址为 该主机的普通(非回馈)IP地址。
客户调用socket和connect,后者引起TCP的三路握手过程。当三路握手完成后,客户中的connect和服务器中的accept均返回,连接于是建立。
服务器父进程再次调用accept并阻塞,等待下一个客户连接。
我们特意在同一个主机上运行客户和服务器,因为这是试验客户/服务器应用程序的最简单方法。既然我们是在同一个主机上运行客户和服务器,netstat对于所建立的TCP连接给出两行输出(下图红色框内)。
第一个ESTABLISHED行对应于服务器子进程的套接口,因为它的本地端口号是9877;第二个ESTABLISHED行对应于客户进程的套接口,因为它的本地端口号是54076. 要是我们在不同的主机上运行客户和服务器,那么客户主机就只输出客户进程的套接口,服务器主机也只输出两个服务器进程(一个父进程、一个子进程)的套接口。
正常终止:
至此连接已经建立,我们在客户的标准输入中不论键入什么,都回射到它的标准输出中。
注:<Ctrl-D>是我们的终端EOF字符
此时如果立即执行netstat命令,我们将看到如下结果:
当前连接的客户端(它的本地端口号为60779)进入了TIME_WAIT状态,而监听服务器仍在等待另一个客户连接。
在服务器子进程终止时,给父进程发送一个SIGCHILD信号。这一点在本例中发生了,但是我们没有在代码中捕获该信号,而 该信号的缺省行为是被忽略。既然父进程未加处理,子进程于是进入僵死状态(http://www.cnblogs.com/nufangrensheng/p/3509618.html)。我们可以使用ps命令验证这一点。
子进程的状态现在是Z(表示僵死)。父进程的状态现在是S(表示为等待某种资源而睡眠)。
我们必须清理僵死进程,这就涉及到UNIX信号的处理。
POSIX信号处理
信号(signal)就是通知某个进程发生了某个事件,有时也称为软件中断(software interrupt)。信号通常是异步发生的,也就是说进程预先不知道信号准确发生的时刻。
信号可以:
(1)由一个进程发给另一个进程(或自身)。
(2)由内核发给某个进程。
SIGCHILD信号就是由内核在任何一个进程终止时发给它的父进程的一个信号。
每个信号都有一个与之关联的处置(disposition),也称为行为(action)。我们调用sigaction函数(http://www.cnblogs.com/nufangrensheng/p/3515945.html)或signal函数(http://www.cnblogs.com/nufangrensheng/p/3514547.html)来设定一个信号的处置,并有三种选择。
(1)我们可以提供一个函数,它将在特定信号发生的任何时刻被调用。这样的函数称为信号处理函数(signal handler),这种行为称为捕获(catching)信号。有两个信号不能被捕获,它们是SIGKILL和SIGSTOP。信号处理函数由信号值这个单一的整数参数来调用,且没有返回值,其函数原型如下:
void handler( int signo );
对于大多数信号来说,调用sigaction函数并指定信号发生时所调用的函数就是捕获信号所要做的全部工作。不过,SIGIO、SIGPOLL和SIGURG这些个别信号还要求捕获它们的进程做些额外工作。
(2)我们可以把某个信号的处置设定为SIG_IGN来忽略(ignore)它。SIGKILL和SIGSTOP这两个信号不能被忽略。
(3)我们可以把某个信号的处置设定为SIG_DFL来启用它的缺省(default)处置。缺省处置通常是在收到信号后终止进程,其中某些信号还在当前工作目录产生一个进程的核心映像(core image,也称为内存映像)。另有个别的缺省处理是忽略:SIGCHLD和SIGURG(带外数据到达时发送)就是缺省处置为忽略的两个信号。
POSIX信号语义
我们把符合POSIX的系统上的信号处理总结如下:
(1)一旦安装了信号处理函数,它便一直安装着(较早期的系统是每执行一次就将其拆除)。
(2)在一个信号处理函数运行期间,正被递交的信号是阻塞的。而且,安装处理函数时在传递给sigaction函数的sa_mask信号集中指定的任何额外信号也被阻塞。
(3)如果一个信号在被阻塞期间产生了一次或多次,那么该信号被解阻塞之后通常只递交一次,也就是说UNIX信号缺省是不排队的。
(4)利用siagprocmask函数(http://www.cnblogs.com/nufangrensheng/p/3515257.html)选择性地阻塞或解阻塞一组信号是可能的。这使得我们可以做到在一段临界区代码执行期间,防止捕获某些信号,以此保护这段代码。
处理SIGCHILD信号
设置僵死(zombie)状态的目的是维护子进程的信息,以便父进程在以后某个时候获取。这些信息包括子进程的进程ID、终止状态以及资源利用信息(CPU时间、内存使用量等等)。如果一个进程终止,而该进程有子进程处于僵死状态,那么它的所有僵死子进程的父进程ID将被重置为1(init进程)。继承这些子进程的init进程将清理它们(也就是说init进程将wait它们,从而去除它们的僵死状态)。有些UNIX系统在ps命令输出的COMMAND栏以<defunct>指明僵死进程。
处理僵死进程
我们显然不愿意留存僵死进程。它们占用内核中的空间,最终可能导致我们耗尽进程资源。无论何时我们fork子进程都得wait它们,以防它们变成僵死进程。为此我们建立一个俘获SIGCHLD信号的信号处理函数,在函数体中我们调用wait。通过在TCP回射服务器程序:main函数中的listen调用之后增加如下函数调用:
signal(SIGCHLD, sig_chld);
这样我们就建立了该信号处理函数。(这必须在fork第一个子进程之前完成,并且只做一次。) 我们接着定义名为sig_chld的这个信号处理函数,如下:
void
sig_chld(int signo)
{
pid_t pid;
int stat; pid = wait(&stat);
printf("child %d terminatted\n", pid);
return;
}
处理僵死进程的可移植方法就是捕获SIGCHLD,并调用wait或waitpid。
新的问题是:在某些系统上(这些 系统标准C函数库中提供的signal函数不会致使内核自动重启被中断的系统调用),SIGCHLD信号被捕获并处理后,慢系统调用accept会返回一个EINTR错误(被中断的系统调用).
处理被中断的系统调用
慢系统调用(slow system call)是指那些可能永远阻塞的系统调用(调用有可能永远无法返回)。多数网络支持函数都属于这一类。
适用于慢系统调用的基本规则是:当阻塞于某个慢系统调用的一个进程捕获某个信号且相应信号处理函数返回时,该系统调用可能返回一个EINTR错误。有些内核自动重启某些被中断的系统调用。
为了处理被中断的accept,我们把对accept的调用从for循环开始修改如下:
for(;;)
{
clilen = sizeof(cliaddr);
if((connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &clilen)) < 0)
{
if(errno == EINTR)
continue; /* back to for() */
else
{
perror("accept error");
exit(1);
}
}
}
这段代码所做的事情就是自己重启被中断的系统调用。对于accept以及诸如read、write、select和open之类函数来说,这是合适的。不过有一个函数我们不能重启:connect。如果该函数返回EINTR,我们就不能再次调用它,否则将立即返回一个错误。当connect被一个捕获的信号中断而且不自动重启时,我们必须调用select来等待连接完成。
wait和waitpid函数
#include <sys/wait.h> pid_t wait(int *statloc); pid_t waitpid(pid_t pid, int *statloc, int options); 二者均返回:若成功则返回已终止子进程的进程ID,若出错则返回-1
函数wait和waitpid均返回两个值:函数返回值是已终止子进程ID号,子进程的终止状态(一个整数)则通过statloc指针返回。我们可以调用三个宏来检查终止状态,并辨别子进程是正常终止、由某个信号杀死还是仅仅由作业控制停止而已(http://www.cnblogs.com/nufangrensheng/p/3510101.html)。
如果调用wait的进程没有已终止的子进程,不过有一个或多个子进程仍在执行,那么wait将阻塞到现有子进程第一个终止为止。
waitpid函数对于等待哪个进程以及是否阻塞给了我们更多的控制。首先,pid参数允许我们指定想等待的进程ID,值-1表示等待第一个终止的子进程。其次,options参数允许我们指定附加选项。最常用的选项是WNOHANG,它告知内核在没有已终止子进程时不要阻塞。
函数wait和waipid的区别
为了说明wait和waitpid的区别,我们试想如下情况:
当客户终止时,所有打开的描述符字由内核自动关闭,且所有5个连接基本在同一时刻终止。这就引发了5个FIN,每个连接一个,它们反过来使服务器的5个子进程基本在同一时刻终止。这又导致差不多在同一时刻递交5个SIGCHLD信号给父进程。
如果我们调用函数wait来处理已终止的子进程,那么只会捕获到一个SIGCHLD信号。也就是说,其他的4个子进程仍然作为僵死进程存在着。
所以,建立一个信号处理函数并在其中调用wait并不足以防止出现僵死进程。本问题在于:所有5个信号都在信号处理函数执行之前产生,而信号处理函数只执行一次,因为UNIX信号一般是不排队的。
正确的解决办法是调用waitpid而不是wait。如下所示给出了正确处理SIGCHLD的sig_chld函数的版本。这个版本管用的原因在于:我们在一个循环内调用waitpid,以获取所有已终止子进程的状态。我们必须指定WNOHANG选项,它告知waitpid在有尚未终止的子进程在运行时不要阻塞。我们不能在循环内调用wait,因为没有办法防止wait在尚有未终止的子进程在运行时阻塞。
void
sig_chld(int signo)
{
pid_t pid;
int stat; while((pid = waitpid(-1, &stat, WNOHANG)) > 0)
printf("child %d terminated\n", pid);
return;
}
服务器的最终版本
/* tcpserv01.c */
#include <sys/socket.h>
#include <strings.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h> int
main(int argc, char **argv)
{
int listenfd, connfd;
pid_t childpid;
socklen_t clilen;
struct sockaddr_in cliaddr, servaddr;
void sig_chld(int); if((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
perror("socket");
exit(1);
} bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(9877); if(bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)
{
perror("bind");
exit(1);
}
if(listen(listenfd, 5) < 0)
{
perror("listen");
exit(1);
} signal(SIGCHLD, sig_chld); /* must call waitpid() */
for(;;)
{
clilen = sizeof(cliaddr);
if((connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &clilen)) < 0)
{
if(errno == EINTR)
continue; /* back to for */
else
{
perror("accept");
exit(1);
}
} if((childpid = fork()) < 0)
{
perror("fork");
exit(1);
}
else if(childpid == 0) /* child process */
{
if(close(listenfd) < 0) /* close listening socket */
{
perror("child close");
exit(1);
}
str_echo(connfd); /* process the request */
exit(0); }
if(close(connfd) < 0) /* parent close connected socket */
{
perror("parent close");
exit(1);
}
} }
在网络编程时可能会遇到的三种情况:
(1)当fork子进程时,必须捕获SIGCHLD信号。
(2)当捕获信号时,必须处理被中断的系统调用。
(3)SIGCHLD的信号处理函数必须正确编写,应使用waitpid函数以免留下僵死进程。
TCP程序实例小结:
在客户和服务器可以彼此通信之前,每一端都得指定连接的套接口对:本地IP地址、本地端口、远地IP地址、远地端口。在下图中我们以粗体圆点标出了这四个值。该图处于客户端的角度。远地IP地址和远地端口必须在客户端调用connect时指定,而两个本地值通常就由内核作为connect的一部分来选定。客户也可在调用connect之前,通过调用bind来指定其中一个或全部两个本地值,不过这么做不常见。
客户可以在连接建立后通过调用getsockname获取由内核指定的两个本地值。
下图中标出了同样的四个值,不过是处于服务器的角度。
本地端口(服务器众所周知的端口)由bind指定。bind调用中指定的本地IP地址通常是通配IP地址,尽管服务器也可以指定一个非通配的IP地址来限定接收目标为某个特定本地接口的连接。如果服务器在一个多宿主机上绑定通配IP地址,那么它可以在连接建立后通过调用getsockname来确定本地IP地址。两个远地值则由accept调用返回给服务器。如果另外一个程序由调用accept的服务器通过调用exec来执行,则这个新程序可以在必要时调用getpeername来确定客户的IP地址和端口号。
数据格式
例子:在客户和服务器之间 传递文本串
修改我们的服务器程序,它仍然从客户读入一行文本,不过新的服务器期望该文本行包含由空格分开的两个整数,服务器将返回这两个整数的和。我们的客户和服务器程序的main函数仍保持不变,str_cli函数也保持不变,所有修改都在str_echo函数中,如下所示:
#include <stdio.h>
#include <sys/types.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h> void
str_echo(int sockfd)
{
long arg1, arg2;
ssize_t n;
char line[4096]; for(;;)
{
if((n = readline(sockfd, line, 4096)) == 0)
return; /* connection closed by other end */
if(sscanf(line, "%ld%ld", &arg1, &arg2) == 2)
snprintf(line, sizeof(line), "%ld\n", arg1 + arg2);
else
snprintf(line, sizeof(line), "input error\n"); n = strlen(line);
writen(sockfd, line, n);
}
}
我们调用sscanf把文本串中的两个参数转换为长整数,然后调用snprintf把结果转换为文本串。
不论客户和服务器主机的字节序如何,这个新的客户和服务器对都工作的很好。
例子:在客户与服务器之间传递二进制结构
现在我们 把客户和服务器程序修改为穿越套接口传递二进制值而不是文本串。
我们的客户和服务器程序的main函数无需改动。另外我们给两个参数定义了一个结构,给结果定义了另一个结构。
#ifndef _COMMON_H
#define _COMMON_H struct args
{
long arg1;
long arg2;
};
struct result
{
long sum;
}; #endif
/* str_cli09.c */
#include "common.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h> void
str_cli(FILE *fp, int sockfd)
{
char sendline[4096], recvline[4096];
struct args args;
struct result result; while(fgets(sendline, 4096, fp) != NULL)
{
if(sscanf(sendline, "%ld%ld", &args.arg1, &args.arg2) != 2)
{
printf("invalid input: %s", sendline);
continue;
}
writen(sockfd, &args, sizeof(args)); if(readn(sockfd, &result, sizeof(result)) == 0)
{
printf("str_cli: server terminated prematurely");
exit(1);
} printf("%ld\n", result.sum);
}
}
/* str_echo09.c */
#include <stdio.h>
#include "common.h"
#include <stdlib.h>
#include <errno.h> void
str_echo(int sockfd)
{
ssize_t n;
struct args args;
struct result result; for(;;)
{
if((n = readn(sockfd, &args, sizeof(args))) == 0)
return;
result.sum = args.arg1 + args.arg2;
writen(sockfd, &result, sizeof(result));
} }
如果我们在具有相同体系结构的两个主机上运行我们的客户和服务器程序,那么什么问题都没有。但是如果在具有不同体系结构的两个主机上运行同样的客户和服务器程序(例如服务器运行在大端系统,而客户运行在小端系统上),那就无法工作了。
本例子实际上存在三个潜在的问题:
(1)不同的实现以不同的格式存储二进制数。(大端和小端)
(2)不同的实现在存储相同的C数据类型上可能存在差异。(32位系统和64位系统)
(3)不同的实现给结构打包的方式存在差异,这取决于各种数据类型所用的位数以及机器的对齐限制。
因此,穿越套接口传送二进制结构绝不是明智的选择。
解决这种数据格式问题有两个常用的方法:
(1)把所有的数值数据作为文本串来传递。当然这里假设客户和服务器主机具有相同的字符集。
(2)显示定义所支持数据类型的二进制格式(位数、大端或小端),并以这样的格式在客户和服务器之间传递所有数据。
TCP客户/服务器程序实例——回射服务器的更多相关文章
- 【Unix网络编程】 chapter5 TCP客户,服务器程序实例
chapter5 5.1 概述 5.2 TCP回射服务器程序:main函数 int main(int argc, char **argv) { int listenfd,connfd; pid_t c ...
- TCP回射服务器程序:str_echo函数
str_echo函数执行处理每个客户的服务: 从客户读入数据,并把它们回射给客户 读入缓冲区并回射其中内容: read函数从套接字读入数据,writen函数把其中内容回射给客户 如果客户关闭连接,那么 ...
- 服务器编程入门(10)TCP回射服务器实现 - 并发
问题聚焦: 在前面我们大概浏览了一下服务器编程需要掌握的一些知识和技术,以及架构思想. 实践,才是检验真理的唯一标准..从这节起我们将在这些技术的基础上,一步步实现以及完善一个服 ...
- TCP回射服务器修订版(ubuntu 18.04)
一.需求 把https://www.cnblogs.com/soldierback/p/10673345.html中的TCP回射服务器程序重写成使用select来处理任意个客户的单进程 程序,而不是为 ...
- 【Unix网络编程】chapter5TCP回射服务器程序
chapter5 5.1 概述 5.2 TCP回射服务器程序:main函数 int main(int argc, char **argv) { int listenfd,connfd; pid_t ...
- Linux下select的用法--实现一个简单的回射服务器程序
1.先看man手册 SYNOPSIS /* According to POSIX.1-2001 */ #include <sys/select.h> / ...
- 第十九篇:不为客户连接创建子进程的并发回射服务器(select实现)
前言 在此前,我已经介绍了一种并发回射服务器实现.它通过调用fork函数为每个客户请求创建一个子进程.同时,我还为此服务器添加了自动消除僵尸子进程的机制.现在请想想,在客户量非常大的情况下,这种为每个 ...
- 不为客户连接创建子进程的并发回射服务器( select实现 )
前言 在此前,我已经介绍了一种并发回射服务器实现( 点此进入 ).它通过调用fork函数为每个客户请求创建一个子进程.同时,我还为此服务器添加了自动消除僵尸子进程的机制.现在请想想,在客户量非常大的情 ...
- 第二十篇:不为客户连接创建子进程的并发回射服务器(poll实现)
前言 在上文中,我使用select函数实现了不为客户连接创建子进程的并发回射服务器( 点此进入 ).但其中有个细节确实有点麻烦,那就是还得设置一个client数组用来标记select监听描述符集中被设 ...
随机推荐
- 安卓 自定义ViewGroup
韩梦飞沙 韩亚飞 313134555@qq.com yue31313 han_meng_fei_sha 指定的 布局参数 在计算时候方法 中 计算 所有 子视图的 宽高, 然后根据这些 计算出 ...
- [NC13B]贝伦卡斯泰露/[51Nod1400]序列分解
[NC13B]贝伦卡斯泰露/[51Nod1400]序列分解 题目大意: 给定\(A_{1\sim n}(n\le40)\),问是否能将\(A\)分解成两个相同的子序列? 思路: 折半搜索.时间复杂度\ ...
- 使用Spring配置shiro时,自定义Realm中属性无法使用注解注入解决办法
先来看问题 纠结了几个小时终于找到了问题所在,因为shiro的realm属于Filter,简单说就是初始化realm时,spring还未加载相关业务Bean,那么解决办法就是将springmvc ...
- 降压转换器 (Buck)
降压转换器 (Buck) 切换式降压转换器 (Buck) 能提供高效率.高度弹性.高压降比.高负载能力的降压转换. 多数降压转换器 (Buck) 包含上桥 MOSFET 和同步整流 MOSFET,根据 ...
- 三款工作流引擎比较:WWF、netBPM 和 ccflow
下面将对目前比较主流的三款工作流进行介绍和比较,然后通过三款流程引擎分别设计一个较典型的流程来给大家分别演示这三款创建流程的过程.这三款工作流程引擎分别是 Windows Workflow Found ...
- redis.conf配置解释
daemonize:如果需要在后台运行,把该项改为yespidfile:配置多个pid的地址,默认在/var/run/redis.pidbind:绑定ip,设置后只接受来自该ip的请求port:监听端 ...
- DCI:DCI学习总结
备注 之前把DCI的Role和四色原型的Role给弄混了,本文也不会比较这两种Role的区别(后面有机会再说),这里简单的记录一下对DCI的理解. 参考文章:http://www.cnblogs.co ...
- C++ Primer 学习笔记_91_用于大型程序的工具 --命名空间
用于大型程序的工具 --命名空间 引言: 在一个给定作用域中定义的每一个名字在该作用域中必须是唯一的,对庞大.复杂的应用程序而言,这个要求可能难以满足.这样的应用程序的全局作用域中一般有很多名字定义. ...
- drupal7整合Discuz康盛UC用户中心ucenter,ucuser模块
drupal7整合Discuz康盛UC用户中心ucenter,ucuser模块Drupal7整合UC用户心的模块,ucenter,康盛这个用户中心我就不多说了哈.参考了以前不知在哪里下载的一个drup ...
- XML 简单介绍
先附上一张XML 大概图:详解见博客内容. 一.定义 XML(EXtensible Markup Language) :可扩展标记语言. 设计的用途:用来描述,存储,传输数据信息. 二.特色 1.单纯 ...