前面实现了简单的网络通信, 但是随之而来还是很多的问题. 比如为什么只能建立一个连接; 为什么服务端先断开会有客户端还没有响应断开等等. 问题我们一个个来解决, 本节就先来解决连接问题.
多进程
我们可以为每一个连接重新创建一个进程, 这样就可以实现每次只能连接一个的问题了. 同样多进程可以解决, 那么多线程也可以解决, 而现在先以进程解决, 后面我们再来以线程解决.
一般我们都是将客服端和服务端的代码写在一起, 通过不同的选项来实现不同的功能. 而一次性的代码量又可能有点多, 所以就将修改比较重要的部分罗列出来分析.
该代码将通信中的两个代码合并了一下. 完整代码 service_fork.c.
这里我们主要看服务端的修改 :
// 服务端
int service(int port, const char *ser_addr)
{
int sockfd, clientfd;
sockfd = Socket(0);
Bind(sockfd, port, ser_addr);
listen(sockfd, 1);
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
char buf[1024];
int n;
pid_t pid;
while(1)
{
clientfd = accept(sockfd, (struct sockaddr *)&client_addr, &client_len);
if((pid = fork()) < 0)
EXIT("fork");
else if(0 == pid)
{
close(sockfd);
while(1)
{
n = recv(clientfd, buf, sizeof(buf), 0);
if(0 == n)
break;
send(clientfd, buf, n, 0);
}
close(clientfd);
exit(0);
}
close(clientfd);
}
close(clientfd);
close(sockfd);
return 0;
}
服务端每次有新的连接就创建一个子进程. 因为fork后的子进程会复制所有父进程的文件描述符, 而对于子进程来说他只管连接一端的套接字, 监听套接字在子进程没有意义, 所以子进程第一个步骤就是close(sockfd)
. 同理, 父进程对连接的套接字是交给子进程来完成的, 所以父进程第一个步骤就是close(client)
.
编译运行. 服务端运行 :
./a.out 1 8080 192.168.1.16
客户端运行 :
./a.out 2 8080 192.168.1.16
新问题
上面我们用fork
解决了只能连接一个的问题, 但是现在又有一个新的问题出现了. 恩? 刚解决一个问题又来一个新的问题? 啥问题?
上面我将两个客户端关闭, 结果呢, 服务端的两个子进程的资源没有被回收从而成为了僵尸进程了. 这个问题的解决我们放到下节来解决
总结
后面也验证了一下本机能够支持多进程连接的实验. 最大保持TCP连接的个数.
- 并发编程就是让程序能“同时”处理多个任务.