导读
为了避免大家无法理解这一章的内容,我们还是有必要理一理我们在做什么。
首先,我们完成了一个简单的,可以反射内容的服务器,也就是说客户端发送了什么,服务端就返回什么。
虽然功能很简单,但是我们醉翁之意不在酒,我们试图想要解释的是,一个健壮的服务器,需要考虑到什么东西?
首先出现的问题就是僵尸进程,我们引入了信号处理函数提供处理接口,我们使用非阻塞的waitpid来解决信号丢失的问题。
然后因为我们要处理信号,被阻塞的系统调用(accept)就会被中断,我们需要提供机制来允许重新调用accept,这就要求我们需要判断错误是否是EINTR来恰当的重启。
在完成上述的步骤之后,我们的服务器已经能够正确的处理僵尸进程了,但是是否就是完备的呢,我们开始考虑到网络中可能出现的各种情况。
这些情况包括客户端在accept之前请求终止连接,或者客户端向收到RST的套接字发送数据,当然更多的出现在服务端,包括服务端进程关闭,主机崩溃,主机崩溃后重启,主机正常关闭,我们指出了每一种情况下可能需要考虑的问题并且提供了一定的解决检查思路
代码(带注释)
// server
#include "../unp.h"
int main(int argc, char **argv)
{
int listenfd,connfd;
pid_t child_pid;
socklen_t clilen;
struct sockaddr_in cliaddr,servaddr;
listenfd = socket(AF_INET, SOCK_STREAM, 0);//表明我们生成的套接字是IPv4TCP协议
// 下面主要是配置server address
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_port = htons(8080);// 监听套接字端口应该是周知端口,这个端口是自行定的
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); //表示我们随便IP地址
servaddr.sin_family = AF_INET; //使用IPv4
bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); //将套接字和指定地址绑定
listen(listenfd, 1024); // 这里1024表示的是backlog的长度,是自行指定的
for (;;)
{
clilen=sizeof(cliaddr);
connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &clilen); //这里是阻塞监听,cliaddr和clilen其实我们没有用到,我们可以用NULL代替
if ((child_pid=fork())== 0)
{
close(listenfd);
str_echo(connfd);
exit(0); //这里因为我们子进程会被关闭,所以不需要显式close
}
close(connfd);
}
}
void str_echo(int fd)
{
ssize_t n;
char buf[MAXLINE];
//下面的read会阻塞读取套接字内容,注意如果客户端关闭了套接字,read会读取到0,然后根据条件就自动退出来了,read不保证会读取到一个完整一行(事实上应该采用readline,就是多次读取然后找换行符)
again:
while ((n=read(fd,buf,sizeof(buf)))>0)
writen(fd,buf,n);// 回射,写回去
if (n<0 && errno==EINTR)
goto again; //被中断就重启
else if (n<0)
err_sys(