在之前写的TCP网络程序中,我们只是简单实现了的客户端和服务器进行通信
只能处理一个客户的请求
简单TCP网络程序
今天来实现一个服务器可以为多个用户提供服务,这就要求我们服务器中要有多个执行流,那么就可以采用多进程和多线程两种方式,今天先来看多进程版本。
因为之前的一些接口都介绍过了,这里直接扔上代码
server(1) |
#include <stdio.h>
#include <signal.h>
#include <error.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
//简单TCP网络程序,可以实现简单聊天室
//******************服务器端********************
//TCP协议是建立连接的可靠传输协议
//1.绑定Ip 和 端口号
//2.被动打开,使可以被建立连接
//3.接受客户端的连接请求
//开始进行事件循环
// 4.接收来自客户端的请求
// 5.开始计算处理客户端发来的请求
// a)因为这里是回显服务,没有过多的计算处理,只做以下处理
// b)将客户端的请求显示在标准输出上
// c)再将请求发回给客户端
// 6.对客户端发来的请求进行响应
//采多进程的方法完成一个服务器可以同时接受多个用户的请求与响应
//注意三个小点
//1.子进程的资源回收的问题
//2.子进程和父进程有各自的执行逻辑,当子进程结束后,不应该和父进程执行相同的逻辑
//3.socket文件的关闭,子进程退出后,自动关闭文件描述符,父进程应该在创建出子进程后就应该关闭文件描述符
//处理一次连接
void ProcessConnect(int listen_socket,sockaddr_in peer)
{
pid_t pid=fork();
if(pid<0)
{//创建子进程失败
return;
}
if(pid==0)
{//**************子进程***************************
//处理客户端发来的请求
char buf[1024]={0};
//用来处理一次连接。需要循环的处理
while(1)
{
//7.接收来自客户端的请求
ssize_t read_size=read(listen_socket,buf,sizeof(buf)-1);
if(read_size<0)
{
continue;
}
if(read_size==0)
{
printf("client discount!\n");
//用来标识用户已经关闭连接
close(listen_socket);
//子进程处理完该用户的所有请求后,不应该直接return
//这样的话,就会回到main()函数中继续循环,而开始接受其他用户的连接请求
//这样就会和父进程执行相同的逻辑
close(listen_socket);//不用手动关闭也可以,因为子进程退出后,文件描述符会自动关闭
exit(0);
}
buf[read_size]='\0';
//8.将客户端的请求显示再标准输出上
printf("peer %s:%d say %s",inet_ntoa(peer.sin_addr),ntohs(peer.sin_port),buf);
//9.将请求发送给客户端
write(listen_socket,buf,read_size);
}
}
else
{//*********************父进程****************************
close(listen_socket);
//直接退出,回到main()函数中继续循环,接受其他用户的请求
//因为父进程需要回收子进程的资源
//但是无论是wait()还是waitpid(),都会进行阻塞式等待
//并不能达到一个服务器可以同时执行多个用户的请求
//这里比较优雅的方式为,父进程直接忽略掉子进程的退出信号SIGCHILD
//则父进程可以先回到main()函数执行自己的逻辑
//在main()函数中进行信号忽略处理
return;
}
}
// ./service [IP] [Port]
int main(int argc,char * argv[])
{
//1.检查命令行参数
if(argc!=3)
{
printf("Usage: ./service [IP] [Port]\n");
return 1;
}
//2.创建socket
int fd=socket(AF_INET,SOCK_STREAM,0);
if(fd<0)
{
perror("socket");
return 1;
}
//3.将要绑定的IP 和 Prot 存储在 sockaddr_in 结构体中
sockaddr_in addr;
addr.sin_family=AF_INET;
socklen_t addr_len=sizeof(addr);
//将点分十进制的IP地址 转化 为32位数字
addr.sin_addr.s_addr=inet_addr(argv[1]);
//将端口号转位网络字节序
addr.sin_port=htons(atoi(argv[2]));
//4.服务器建立连接
int bind_ret=bind(fd,(sockaddr *)&addr,addr_len);
if(bind_ret<0)
{
perror("bind");
return 1;
}
//5.服务器开始被动打开,处于可以被建立连接状态
int listen_ret=listen(fd,5);
if(listen_ret<0)
{
perror("listen");
return 1;
}
//开始事件循环
//父进程忽略掉子进程退出的信号
//内核自动回收子进程的资源
signal(SIGCHLD,SIG_IGN);
while(1)
{
//6.接受客户端的连接请求
//fd 这个socket用来监听
int listen_socket=accept(fd,(sockaddr *)&addr,&addr_len);
//这里的返回值是一个新的 socket 将内核态建立的连接放到用户态执行
//后续信息传输建立在这个socket
if(listen_socket<0)
{
perror("accept");
continue;
}
//7.处理一次连接
ProcessConnect(listen_socket,addr);
}
close(fd);
return 0;
}
基于对子进程的资源回收,我还实现了下面版本的服务器
基于对子进程的资源回收,我还实现了下面版本的服务器 server(2) |
#include <stdio.h>
#include <sys/wait.h>
#include <signal.h>
#include <error.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
//简单TCP网络程序,可以实现简单聊天室
//******************服务器端********************
//TCP协议是建立连接的可靠传输协议
//1.绑定Ip 和 端口号
//2.被动打开,使可以被建立连接
//3.接受客户端的连接请求
//开始进行事件循环
// 4.接收来自客户端的请求
// 5.开始计算处理客户端发来的请求
// a)因为这里是回显服务,没有过多的计算处理,只做以下处理
// b)将客户端的请求显示在标准输出上
// c)再将请求发回给客户端
// 6.对客户端发来的请求进行响应
//采多进程的方法完成一个服务器可以同时接受多个用户的请求与响应
//注意三个小点
//1.子进程的资源回收的问题
//2.子进程和父进程有各自的执行逻辑,当子进程结束后,不应该和父进程执行相同的逻辑
//3.socket文件的关闭,子进程退出后,自动关闭文件描述符,父进程应该在创建出子进程后就应该关闭文件描述符
//处理一次连接
void ProcessConnect(int listen_socket,sockaddr_in peer)
{
char buf[1024]={0};
//用来处理一次连接。需要循环的处理
while(1)
{
//7.接收来自客户端的请求
ssize_t read_size=read(listen_socket,buf,sizeof(buf)-1);
if(read_size<0)
{
continue;
}
if(read_size==0)
{
printf("client discount!\n");
//用来标识用户已经关闭连接
close(listen_socket);
return;
}
buf[read_size]='\0';
//8.将客户端的请求显示再标准输出上
printf("peer %s:%d say %s",inet_ntoa(peer.sin_addr),ntohs(peer.sin_port),buf);
//9.将请求发送给客户端
write(listen_socket,buf,read_size);
}
}
// ./service [IP] [Port]
int main(int argc,char * argv[])
{
//1.检查命令行参数
if(argc!=3)
{
printf("Usage: ./service [IP] [Port]\n");
return 1;
}
//2.创建socket
int fd=socket(AF_INET,SOCK_STREAM,0);
if(fd<0)
{
perror("socket");
return 1;
}
//3.将要绑定的IP 和 Prot 存储在 sockaddr_in 结构体中
sockaddr_in addr;
addr.sin_family=AF_INET;
socklen_t addr_len=sizeof(addr);
//将点分十进制的IP地址 转化 为32位数字
addr.sin_addr.s_addr=inet_addr(argv[1]);
//将端口号转位网络字节序
addr.sin_port=htons(atoi(argv[2]));
//4.服务器建立连接
int bind_ret=bind(fd,(sockaddr *)&addr,addr_len);
if(bind_ret<0)
{
perror("bind");
return 1;
}
//5.服务器开始被动打开,处于可以被建立连接状态
int listen_ret=listen(fd,5);
if(listen_ret<0)
{
perror("listen");
return 1;
}
//开始事件循环
while(1)
{
//6.接受客户端的连接请求
//fd 这个socket用来监听
int new_socket=accept(fd,(sockaddr *)&addr,&addr_len);
//这里的返回值是一个新的 socket 将内核态建立的连接放到用户态执行
//后续信息传输建立在这个socket
if(new_socket<0)
{
perror("accept");
continue;
}
//7.处理一次连接
//
//这里采用创建两个进程,牺牲一代进程 ,用二代子进程来执行相应的任务
//很巧妙的避免的子进程资源的回收并且不会阻塞式的等待
pid_t pid=fork();
if(pid==0)
{
//这里不需要 用来监听的 socket 只需要 new_socket
//可以直接关闭 fd
close(fd);
pid_t id=fork();
if(id<0)
{
continue;
}
if(id==0)
{
//二代子进程
ProcessConnect(new_socket,addr);
//执行完毕后应该,关闭文件描述符
close(new_socket);
exit(0);
}
else
{
//一代子进程,会直接退出,使二代子进程成为孤儿进程,被1号进程收养,
//1 号祖先进程会回收二代子进程的资源,避免造成内存泄漏
//这里也不用手动的关闭文件,进程退出,内核会自动回收
//close(new_socket);
exit(0);
}
}
else
{//父进程
//这里需要进行阻塞的等待一代子进程,
//因为一代子进程会直接退出,不会造成阻塞
//父进程不需要 new_socket
//父进程只需要接受连接请求,需要监听socket fd
close(new_socket);
//父进程一直都在,所以一定要进行关闭文件,不然会文件描述符泄露,导致后面的请求不会创建出socket
waitpid(pid,NULL,0);
}
}
close(fd);
return 0;
}
client |
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
//********************简单TCP网络程序
//********************客户端*************************
//
//TCP协议是建立连接的可靠传输
//1.与服务器建立连接
//2.向服务器发送请求
//3.接收来自服务器端响应
int main(int argc ,char *argv[])
{
//检查命令行参数
if(argc!=3)
{
printf("Usage : ./client [IP] [Port]\n");
return 1;
}
//1.创建socket
int fd=socket(AF_INET,SOCK_STREAM,0);
if(fd<0)
{
perror("socket");
return 1;
}
//2.建立连接
//将要建立连接的服务器的IP 和 Port 放在结构体中
sockaddr_in service;
service.sin_family=AF_INET;
socklen_t service_len=sizeof(service);
service.sin_addr.s_addr=inet_addr(argv[1]);
service.sin_port=htons(atoi(argv[2]));
int connect_ret=connect(fd,(sockaddr *)&service,service_len);
if(connect_ret<0)
{
perror("connect");
return 1;
}
char buf[1024]={0};
while(1)
{
//3.向服务器发送请求
//从标准输入开始读取数据
ssize_t read_size=read(0,buf,sizeof(buf)-1);
if(read_size<0)
{
perror("read");
return 1;
}
if(read_size==0)
{
printf("done\n");
return 0;
}
buf[read_size]='\0';
//将数据发送给服务器
// int send_ret=sendto(fd,buf,read_size,0,(sockaddr *)&service,service_len);
// if(send_ret<0)
// {
// perror("sendto");
// return 1;
// }
write(fd,buf,read_size);
//4.从服务器接收请求
//构造结构体存放接收方的信息
//这里的service会作为输出型参数,因为服务器端是任意Ip主机都保持连接状态,这里接收服务的参数,然后打印出来
char buf_service[1024]={0};
read_size=read(fd,buf_service,sizeof(buf_service)-1);
if(read_size<0)
{
perror("recvfrom");
return 1;
}
if(read_size==0)
{
printf("read done\n");
return 0;
}
buf_service[read_size]='\0';
//5.将从服务器接收的信息显示到标准输出
printf("service[%s:%d] response :%s",inet_ntoa(service.sin_addr),ntohs(service.sin_port),buf_service);
}
}
采多进程的方法完成一个服务器可以同时接受多个用户的请求与响应
注意三个小点
1. 子进程的资源回收的问题
2. 子进程和父进程有各自的执行逻辑,当子进程结束后,不应该和父进程执行相同的逻辑
3. socket文件的关闭,子进程退出后,自动关闭文件描述符,父进程应该在创建出子进程后就应该关闭文件描述符