在上一节“回射客户-服务器模型(1)”中存在下面几个问题。
1.就是当服务器断开再立即重新开启时,需要重新绑定地址,而此时的服务器处于TIME_WAIT状态,在这种状态下,它是无法立即重新绑定的。
那么这种情况下,我们可以使用SO_REUSEADDR这个选项来解决这个问题。使用REUSEADDR选项就可以不必等待TIME_WAIT状态消失就可以立即重启服务器。
我们只需在服务器端的代码中使用setsockopt函数来开启一下REUSEADDR选项即可。
//...
int on;
if(setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
perror("setsockopt error");
// 3. 绑定bind
if(bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)
perror("bind error");
//...
这个时候我们再来断开服务器并立即重启一下.
第一次重启:
重启后又断开,服务器处于TIME_WAIT状态:
然后又立即重启服务器,没有问题:
但是请注意,SO_REUSEADDR选项只是保证服务器的开启不受TIME_WAIT状态的限制,但并不是说我们在启动一个服务器后,在没有断开的情况下还可以继续启动一个服务器。
2.不能处理多客户端的连接
原因:当一个客户端发来连接请求后,服务器断的套接口由主动状态变为被动状态,也就是开始接收连接并处理通信的细节问题,没办法再回到监听状态,也就无暇再去处理另一个客户端发送过来的连接请求。
那么要解决这个问题,我们就需要考虑到使用多进程。每当一个客户端发来连接请求,我们就让父进程fork出一个子进程去处理与客户端的通信细节,而父进程自己继续监听其他客户端的连接请求。
//...
int conn;
// 成功返回对等方的套接口
while(1)
{
if((conn = accept(lisenfd, (struct sockaddr*)&peeraddr, &peerlen)) < 0)
perror("accept error");
printf("IP=%s port=%d\n", inet_ntoa(peeraddr.sin_addr), htons(peeraddr.sin_port));
pid_t pid = fork();
if(pid < 0)
perror("fork error");
else if(pid > 0)
{
//父进程处理
//父进程不需要conn
close(conn);
}
else
{
//子进程处理
//子进程不需要监听
close(listenfd);
do_service(int conn);
}
}
void do_service(int conn)
{
char recvbuf[1024];
while(1)
{
memset(recvbuf, 0, sizeof(recvbuf));
int ret = read(conn, recvbuf, sizeof(recvbuf));
fputs(recvbuf, stdout);
write(conn, recvbuf, ret);
}
}
//...
上述代码的改进能够解决连接多客户端的问题,看下面的演示结果:
3.但是上述的改进还存在一个问题就是服务器端不能捕捉到客户端的关闭,因此还需要进行改进。
那么我们的做法就是在do_service函数中,当服务器端从客户端后读取的字节数ret为0时,就表明客户端已经关闭了,并在捕捉到客户端关闭后,退出相应的子进程。
//...
int conn;
// 成功返回对等方的套接口
while(1)
{
if((conn = accept(lisenfd, (struct sockaddr*)&peeraddr, &peerlen)) < 0)
perror("accept error");
printf("IP=%s port=%d\n", inet_ntoa(peeraddr.sin_addr), htons(peeraddr.sin_port));
pid_t pid = fork();
if(pid < 0)
perror("fork error");
else if(pid > 0)
{
//父进程处理
//父进程不需要conn
close(conn);
}
else
{
//子进程处理
//子进程不需要监听
close(listenfd);
do_service(int conn);
//客户端关闭后,就需要退出这个进程
exit(EIXT_SUCCESS);
}
}
void do_service(int conn)
{
char recvbuf[1024];
while(1)
{
memset(recvbuf, 0, sizeof(recvbuf));
int ret = read(conn, recvbuf, sizeof(recvbuf));
if(ret == 0)
{
printf("client close");
break;
}
else if(ret == -1)
ERR_EXIT("read error");
fputs(recvbuf, stdout);
write(conn, recvbuf, ret);
}
}
//...