引言
这一篇笔记主要记录在TCP服务器中,如果连接端点都是阻塞的,我们应该如何处理。
TCP是面向连接的,在服务端中,需要先进行连接,再通信。因此就存在一个监听套接字,专门监听对端的连接请求。那么,当存在多个对端的连接请求的时候,当达到服务器的连接上限后,如果服务器要读其中一个端的数据的时候,就不能做其他事情,只有当一件事完成后才能做下一件事,也就是存在阻塞。为了解决这个问题,我们可以使用多进程来解决监听,读数据等各种操作。
实现思路
- 服务器首先会创建一个监听套接字(监听端点)
- 客户端1会对服务器发起连接,并且产生连接套接字fd
- 服务器为了能够实现并发阻塞,会产生一个与其一模一样的子进程,并且屏蔽与客户端1的连接套接字fd
- 子进程1只需要负责与客户端1进行通信,所以可以屏蔽监听端点。
- 子进程1负责与客户端1进行通信
- 当有客户端2进行链接时,如此类推。
- 需要注意的是,当客户端1退出连接后,子进程1也应该退出,但是要防止编程僵尸进程,必须要父进程释放资源,但是父进程一直在监听连接而不会终止,这就很容易导致僵尸进程的出现。此时的处理方法可以参考前面的笔记【Linux学习笔记33】进程的状态与创建退出
- 另外一种情况,当出现很多子进程的时候,如果同时有进行一起退出,同时对父进程发出死亡信号,父进程分辨不出是一个进程的死亡还是多个进程的死亡,随着时间推移,僵尸进程也越来越多。
代码实现
以【Linux学习笔记51】多线程TCP服务端之简易QQ代码为模板修改:
server.c:
#include "head4sock.h"
void cleanup(int sig)
{
/*因为父进程不能辨别有多少个子进程同时退出,-1 会遍历所有子进程
waitpid > 0代表回收了一个子进程资源
然后再进行循环检查是否有子进程退出*/
while (waitpid(-1,NULL,WNOHANG)>0 );
}
int main(int argc, char const *argv[])
{
if(argc != 2)
{
printf("Usage: %s <PORT>\n", argv[0]);
exit(0);
}
// 创建一个TCP套接字
int fd = Socket(AF_INET, SOCK_STREAM, 0); //设为IPv4的套接字
// 绑定地址(IP:PORT)
struct sockaddr_in srvaddr, cliaddr;
socklen_t len = sizeof(srvaddr);
bzero(&srvaddr, len);
srvaddr.sin_family = AF_INET;
srvaddr.sin_port = htons(atoi(argv[1]));
// inet_pton(AF_INET, "192.168.1.166", &srvaddr.sin_addr);
srvaddr.sin_addr.s_addr = htonl(INADDR_ANY);
Bind(fd, (struct sockaddr *)&srvaddr, len);
// 设置监听套接字
Listen(fd, 3); //如果同时有人发出请求,能够同时处理若干个,在Linux中至少同时允许4+3个连接请求
signal(SIGCHLD,cleanup); //捕获孩子进程死亡发送的信号
int connfd;
while (1)
{
connfd = Accept(fd, (struct sockaddr *)&cliaddr, &len);
char peeraddr[50];
bzero(peeraddr, 50);
printf("new connection: %s:%hu\n", //打印端口号
inet_ntop(AF_INET, &cliaddr.sin_addr, peeraddr, 50),
ntohs(cliaddr.sin_port));
pid_t a =fork();
if (a >0) //parent
{
close(connfd); //关闭连接套接字
continue;
}
else if (a == 0) // child
{
close(fd);//关闭监听套接字
break;
}
}
//child
char buf[SIZE];
while (1)
{
bzero(buf,SIZE);
if(Read(connfd,buf,SIZE)== 0)
break;
printf("%s",buf );
write(connfd,buf,strlen(buf));
}
return 0;
}
client.c:
#include "head4sock.h"
void *routine(void *arg)
{
int fd = (int) arg;
char buf[SIZE];
while (1)
{
bzero(buf,SIZE);
if(Read(fd,buf,SIZE)==0 )
{
break;
}
printf("form server: %s ",buf);
}
}
int main(int argc, char const *argv[])
{
if(argc != 3)
{
printf("Usage: %s <IP> <PORT>\n", argv[0]);
exit(0);
}
// 创建一个TCP套接字
int fd = Socket(AF_INET, SOCK_STREAM, 0);
// 准备好对端的地址信息
struct sockaddr_in srvaddr;
socklen_t len = sizeof(srvaddr);
bzero(&srvaddr, len);
srvaddr.sin_family = AF_INET;
inet_pton(AF_INET, argv[1], &srvaddr.sin_addr);
srvaddr.sin_port = htons(atoi(argv[2]));
// 连接服务端
Connect(fd, (struct sockaddr *)&srvaddr, len);
pthread_t tid;
pthread_create(&tid,NULL,routine,(void *)fd);
char buf[SIZE];
while(1)
{
bzero(buf, SIZE);
if(fgets(buf, SIZE, stdin) == NULL||
!strcmp(buf, "quit\n"))
break;
write(fd, buf, strlen(buf));
}
close(fd);
return 0;
}