目录
1、多进程服务器
使用多进程并发服务器时要考虑以下几点:
1.父进程最大文件描述个数(父进程中需要close关闭accept返回的新文件描述符)
2.系统内创建进程个数(与内存大小相关)
3.进程创建过多是否降低整体服务性能(进程调度)
1、建立套接字
int socketFd = socket
2、绑定
3、设置监听
while(1){
4、阻塞等待客户端的连接......
int newClientFd = accept();
//创建一个子进程,子进程负责接收每一个连接上来的客户端数据
pid_t id = fork();
if(id == 0) //子进程{
//关闭 socketFd 文件描述符
while(1) {
//接收 客户端的数据
int ret = read(newClientFd,buf,sizeof(buf));
if(ret == 0)
break; }
//子进程退出的时候 ,会自动发出来一个SIGCHLD信号
break; }
else if(id >0) //父进程
{ //关闭 newClientFd 文件描述符
//捕捉 SIGCHLD信号 执行 信号响应函数,在该函数中 回收子进程
continue;}
}
多进程服务器测试代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h> //close
#include <sys/wait.h>
#include <signal.h>
#include <sys/types.h> //socket /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define SERVER_PORT 60000
#define SERVER_IP "192.168.5.184"
//子进程退出信号处理函数
void handle(int arg)
{
wait(NULL);
printf("子进程退出,回收资源OK\n");
}
int main()
{
//建立套接字
int socket_fd;
//AF_INET-地址族ipv4 SOCK_SREAM-->tcp字节流
socket_fd = socket(AF_INET,SOCK_STREAM,0);
if(socket_fd < 0)
{
perror("soket fail");
return -1;
}
//所以设置端口号可以复用,这两条语句放在 绑定bind 之前
int optval = 1;
setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR,&optval, sizeof(optval));
//填充本机ip地址和端口号(新结构体)
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;//地址族 ipv4
server_addr.sin_port = htons(SERVER_PORT); //本机端口转网络端口--用宏来替代好修改
server_addr.sin_addr.s_addr = inet_addr(SERVER_IP);//本机ip转换为网络ip
//绑定本机ip(旧结构体)
int ret;
ret = bind(socket_fd,(struct sockaddr *)&server_addr,sizeof(struct sockaddr_in));
if(ret < 0)
{
perror("bind fail");
return -1;
}
printf("绑定本机成功 [%s][%d]\n",SERVER_IP,SERVER_PORT);
//监听
ret = listen(socket_fd,20);
if(ret < 0)
{
perror("listen fail");
return -1;
}
//并发接收客户端--重复接收(让代码accept之后下次还停留在accpet)
while(1)
{
//接收客户端(获取客户端的套接字、IP地址、端口)--->阻塞
int socket_cli; //存储客户端的套接字
struct sockaddr_in client_addr; //只定义不需要赋值
socklen_t addrlen = sizeof(struct sockaddr_in);
socket_cli = accept(socket_fd,(struct sockaddr *)&client_addr,&addrlen);
if(socket_cli < 0)
{
perror("accept fail");
}
//拿到客户端的地址和端口号(将网络字节序转换为本机字节序)---难点
char *ip = inet_ntoa(client_addr.sin_addr);//客户端的IP地址 网络ip->本机ip
int port = ntohs(client_addr.sin_port);//客户端的端口号 //网络端口->本机端口
printf("新的客户端上线[%s][%d] socket_cli:%d\n",ip,port,socket_cli);
//创建子进程用来接收客户端数据
pid_t id = fork();
if(id < 0)
{
perror("fork fail");
return -1;
}
else if(id > 0) //父进程---父进程里面不能阻塞否则无法接收下一个客户端
{
//回收子进程资源
signal(SIGCHLD,handle); //信号处理函数
}
else //子进程独立接收客户端信息
{
char buf[1024] = {0};
while(1)
{
memset(buf,0,sizeof(buf));
ret = recv(socket_cli,buf,sizeof(buf),0); //等价read
if(ret == 0)
{
printf("客户端掉线[%s][%d]\n",ip,port);
exit(0); //子进程退出会给父进程发送SIGCHLD
}
printf("[%s][%d] buf:%s ret:%d\n",ip,port,buf,ret);
}
}
}
return 0;
}
2、多线程服务器
void*routine(void*arg)
{
//设置 分离属性 --自动回收
while(1){ //接收 客户端的数据
int ret = read(newClientFd,buf,sizeof(buf));
if(ret == 0)
break;
}
1、建立套接字
int socketFd = socket();
2、绑定
3、设置监听
while(1){
4、阻塞等待客户端的连接
int newClientFd = accept();
//每次一个新的客户端连接上来,开启一条线程,接收 客户端的数据 25 pthread_create(,,routine,&newClientFd);
}
多线程服务器测试代码如下:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define SERVER_IP "192.168.5.2"
#define SERVER_PORT 60000
/*
练习1:修改代码,实现多线程并发服务器
练习2:将客户端的ip和端口在子线程中能够打印
*/
struct recv_info
{
int sock_fd;
struct sockaddr_in cli_addr;
};
//将recv接收数据的这一个阻塞动作放在子线程里面
void *recv_buf(void *arg)
{
//设置子线程的分离属性--注意
pthread_detach(pthread_self());
int ret;
struct recv_info info = *(struct recv_info *)arg; //接收套接字和地址
//接收数据recv-->阻塞
char buf[1024] = {0};
while(1)
{
memset(buf,0,sizeof(buf));
ret = recv(info.sock_fd,buf,sizeof(buf),0);
if(ret == 0)
{
printf("客户端掉线[%s][%d]\n",
inet_ntoa(info.cli_addr.sin_addr),
ntohs(info.cli_addr.sin_port));
close(info.sock_fd); //关闭客户端套接字
pthread_exit(NULL); //客户端掉线线程退出
//printf("客户端掉线[%s][%d]\n",ip,port);
}
//printf("buf:%s ret:%d\n",buf,ret);
printf("[%s][%d] buf:%s ret:%d\n",
inet_ntoa(info.cli_addr.sin_addr), //网络ip转本机ip
ntohs(info.cli_addr.sin_port), //网络端口哦转本机端口
buf,
ret);
}
}
int main()
{
int ret;
//建立套接字
int socket_fd;
socket_fd = socket(AF_INET,SOCK_STREAM,0);
if(socket_fd < 0)
{
perror("socket fail");
return -1;
}
//设置端口复用
int optval = 1;
setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR,&optval, sizeof(optval));
//绑定本机地址和端口
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVER_PORT);//htons是转端口
server_addr.sin_addr.s_addr = inet_addr(SERVER_IP);
//server_addr.sin_addr.s_addr = htonl(INADDR_ANY); //htonl是转地址,常用于广播和组播。
ret = bind(socket_fd,(struct sockaddr *)&server_addr,sizeof(struct sockaddr_in));
if(ret < 0)
{
perror("bind fail");
return -1;
}
printf("绑定本机成功[%s][%d]\n",SERVER_IP,SERVER_PORT);
//监听
ret = listen(socket_fd,20);
if(ret < 0)
{
perror("listen fail");
return -1;
}
/*
多进程并发服务器和多线程并发服务器的核心知识点:
防止accept和recv两个阻塞函数放在同一个while(1)里面。
多进程并发:用一个单独进程放recv (消耗资源多)
多线程并发:用一个单独线程放recv (消耗资源少)
*/
//服务器循环接收客户端那么while(1)要放在accept的前面
while(1)
{
//接收客户端accpet-->阻塞
int socket_client;
struct sockaddr_in client_addr;
socklen_t addrlen = sizeof(struct sockaddr_in);
socket_client = accept(socket_fd,(struct sockaddr *)&client_addr,&addrlen);
char *ip = inet_ntoa(client_addr.sin_addr);
int port = ntohs(client_addr.sin_port);
printf("新的客户端上线[%s][%d] socket_client:%d\n",ip,port,socket_client);
struct recv_info info;
memset(&info,0,sizeof(struct recv_info));//清空一个结构体
info.sock_fd = socket_client;
info.cli_addr = client_addr;
//在accept后面创建子线程
pthread_t pid;
//ret = pthread_create(&pid,NULL,recv_buf,&socket_client);
ret = pthread_create(&pid,NULL,recv_buf,&info);
if(ret < 0)
{
perror("pthread_create fail");
}
}
//关闭套接字
close(socket_fd);
return 0;
}