C/S架构学习之多进程实现TCP并发服务器

  • 多进程实现TCP并发服务器的实现流程:
  • 一、自定义信号处理函数(sig_func函数):
c 复制代码
	void sig_func(int signum)
	{
	    wait(NULL);
	}
  • wait函数:
c 复制代码
	#include <sys/types.h>
	
	#include <sys/wait.h>
	
	pid_t wait(int *wstatus);
	/*
	功能:
	    	wait函数是在父进程中使用,用来回收子进程的资源。
	
	    	这个函数会阻塞等待任意一个子进程退出。
	
	    	子进程在退出的时候exit的参数可以被父进程接收到。
	
	参数:
	
	    	wstatus:用来获取子进程退出的信息的。
	
	    		如果不关心子进程退出的状态,可以传NULL
	
	   	 		如果关心子进程退出的状态,可以传一个int类型变量的地址。
	
	    	0-6 :7个bit位表示终止子进程的信号的编号
	
	    	8-15:8个bit位表示子进程退出的状态
	
	       	WIFEXITED(wstatus) 	为true	说明子进程是正常结束
	
	        WEXITSTATUS(wstatus) 	如果子进程是正常的退出的 
	
									使用这个宏可以获取子进程退出的值
	
	        WIFSIGNALED(wstatus) 为true	说明子进程是被信号中断的
	
	        WTERMSIG(wstatus)  	如果子进程是被信号中断的 
	
	                         		使用这个宏可以获取终止子进程的信号的编号
	返回值:
	
	    	成功  	返回退出的子进程的pid
	
	    	失败  	-1  	重置错误码 
	 */
  • 二、创建套接字(socket函数):
  • 通信域选择IPV4网络协议、套接字类型选择流式;
c 复制代码
	int sockfd = socket(AF_INET,SOCK_STREAM,0); //通信域选择IPV4、套接字类型选择流式
  • 三、填充服务器的网络信息结构体:
  • 1.定义网络信息结构体变量;
  • 2.求出结构体变量的内存空间大小;
  • 3.结构体清零;
  • 4.使用IPV4网络协议;
  • 5.在终端输入的IP地址,即inet_addr(argv[1])
  • 6.在终端输入的网络字节序的端口号,即htons(atoi(argv[2]))
c 复制代码
	struct sockaddr_in serveraddr; //定义网络信息结构体变量
    socklen_t serveraddrlen = sizeof(serveraddr);//求出结构体变量的内存空间大小

    memset(&serveraddr,0,serveraddrlen); //结构体清零

    serveraddr.sin_family = AF_INET;  //使用IPV4网络协议
    serveraddr.sin_addr.s_addr = inet_addr(argv[1]);  //IP地址
    serveraddr.sin_port = htons(atoi(argv[2]));//网络字节序的端口号
  • 四、套接字和服务器的网络信息结构体进行绑定(bind函数):
c 复制代码
	int ret = bind(sockfd,(struct sockaddr *)&serveraddr,serveraddrlen);
  • 五、套接字设置成被动监听(listen函数):
c 复制代码
	int ret1 = listen(sockfd, 5);
  • 六、定义网络信息结构体变量保存来自客户端的消息(clientaddr):
c 复制代码
	struct sockaddr_in clientaddr;
    socklen_t clientaddr_len = sizeof(clientaddr);
  • 七、注册信号处理函数回收资源( signal函数):
c 复制代码
	signal(SIGUSR1,sig_func);
  • signal函数:
c 复制代码
	// SIGUSR1、SIGUSR2:
	
	// 该信号保留给用户程序使用
	
	// 默认操作:终止

	#include <signal.h>
	
	typedef void (*sighandler_t)(int);//给函数指针起别名
	
	sighandler_t signal(int signum, sighandler_t handler);
	/*
	功能:
	
		在进程中注册信号处理函数
	
	参数:
		signum:信号的编号(除了 SIGKILL 和 SIGSTOP之外的)
	
		handler:信号处理方式
	
			忽略: SIG_IGN
	
			默认: SIG_DFL
	
			捕捉: 自定义信号处理函数
	
				void sig_func(int signum){
	
					//捕捉到信号后的处理逻辑
	
				}
	返回值:
	
		成功  	返回指向handler的指针
	
		失败 	SIG_ERR  重置错误码
	*/
  • 八、阻塞等待客户端的连接(accept函数):
c 复制代码
	int acceptfd = accept(sockfd, (struct sockaddr *)&clientaddr, &clientaddr_len);
  • 九、创建子进程(fork函数)用接收来自客户端的数据(recv函数)和给客户端发送应答消息(send函数),且双方通信结束后,给父进程发信号回收资源(kill函数):
c 复制代码
 		if(-1 == (pid = fork()))
        {
            perror("fork error");
            
            exit(-1);

        }else if(0 == pid){
        
			int nbytes = recv(acceptfd,buf,sizeof(buf),0);
			printf("客户端发来数据[%s]\n",buf);
			
			strcat(buf,"----k"); //组装应答消息
			int ret2 = send(acceptfd,buf,sizeof(buf),0);
			 //给父进程发信号,回收资源
            kill(getppid(),SIGUSR1);
		}else if(0 < pid){

            //等待新的客户端连接

            close(accept_fd);
        }
  • kill函数:
c 复制代码
	#include <sys/types.h>
	
	#include <signal.h>
	
	int kill(pid_t pid, int sig);
	/*
	功能:
	
		给指定pid的进程发信号
	
	参数:
	    	pid 进程号
	
	        		>0 给指定的pid发信号,常用的用法
	
	        		0  给同组的进程发信号
	
	        		-1 给所有有权限操作的进程发信号,init除外
	
	        		<-1 如-100,给进程组id为100的所有进程发信号
	
	    	sig 信号的编号
	
	返回值:
	
	    	成功 0
	
	    	失败 -1 重置错误码 
	*/
  • 十、关闭套接字(close函数):
c 复制代码
	close(sockfd);
  • 综合应用实例代码如下所示:
c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdbool.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>


//自定义信号处理函数
void sig_func(int signum)
{
    wait(NULL);
}

int main(int argc, char const *argv[])
{
    //入参合理性检查
    if(3 != argc)
    {
        printf("Usage : %s <IP> <PORT>\n",argv[0]);
        exit(-1);
    }
    //创建套接字
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    if(-1 == sockfd)
    {
        perror("socket error");
        exit(-1);
    }
    //填充服务器网络信息结构体
    struct sockaddr_in serveraddr;
    socklen_t serveraddr_len = sizeof(serveraddr);
    memset(&serveraddr,0,serveraddr_len);
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
    serveraddr.sin_port = htons(atoi(argv[2]));

    //将套接字与服务器网络信息结构体绑定
    if(-1 == bind(sockfd,(struct sockaddr *)&serveraddr,serveraddr_len))
    {
        perror("bind error");
        exit(-1);
    }

    //将套接字设置成被监听状态
    if(-1 == listen(sockfd,5))
    {
        perror("listen error");
        exit(-1);
    }

    //定义结构体保存客户端信息
    struct sockaddr_in clientaddr;
    socklen_t clientaddr_len = sizeof(clientaddr);

    //注册信号处理函数回收资源
    signal(SIGUSR1,sig_func);

    //阻塞等待客户端连接
    int accept_fd = 0;
    char buf[128] = {0};
    pid_t pid = 0;
    int nbytes = 0;

    while(true)
    {
        if(-1 == (accept_fd = accept(sockfd,(struct sockaddr*)&clientaddr,&clientaddr_len)))
        {
            perror("accept error");
            exit(-1);
        }

        //创建进程
        if(-1 == (pid = fork()))
        {
            perror("fork error");
            
            exit(-1);

        }else if(0 == pid){ //子进程

            //收发数据
            close(sockfd);
            printf("客户端[%s : %d]连接到服务器\n",inet_ntoa(clientaddr.sin_addr),ntohs(clientaddr.sin_port));

            //接收客户端数据,并作出应答
            while(true)
            {
                memset(buf,0,sizeof(buf));
                //接收消息
                if(-1 == (nbytes = recv(accept_fd,buf,sizeof(buf),0)))
                {
                    perror("recv error");
                    break;

                }else if(0 == nbytes){

                    printf("客户端[%s : %d]断开了连接\n",inet_ntoa(clientaddr.sin_addr),ntohs(clientaddr.sin_port));
                    break;

                }
                if(!strcmp(buf,"quit"))
                {
                    printf("客户端[%s : %d]退出了\n",inet_ntoa(clientaddr.sin_addr),ntohs(clientaddr.sin_port));
                    break;
                }
                printf("客户端[%s : %d]发来消息[%s]\n",inet_ntoa(clientaddr.sin_addr),ntohs(clientaddr.sin_port),buf);

                //组装应答
                strcat(buf,"------k");
                //发送应答
                if(-1 == send(accept_fd,buf,sizeof(buf),0))
                {
                    perror("send error");
                    exit(-1);
                }
            }

            //关闭套接字

            close(accept_fd);
            //给父进程发信号,回收资源
            kill(getppid(),SIGUSR1);

            //退出子进程
            exit(0);


        }else if(0 < pid){

            //等待新的客户端连接

            close(accept_fd);
        }
    }
    //关闭套接字
    close(sockfd);

    return 0;
}
  • 本示例代码,仅供参考;
相关推荐
真果粒wrdms18 分钟前
【SQLite3】常用API
linux·服务器·c语言·jvm·数据库·oracle·sqlite
细心的莽夫29 分钟前
集合复习(java)
java·开发语言·笔记·学习·java-ee
Caramel_biscuit30 分钟前
C++专业面试真题(1)学习
c++·学习·面试
yours_Gabriel35 分钟前
java基础:面向对象(二)
java·开发语言·笔记·学习
古月居GYH42 分钟前
Autoware内容学习与初步探索(一)
学习
木觞清4 小时前
Django学习第三天
python·学习·django
安步当歌8 小时前
【FFmpeg】av_write_trailer函数
c语言·c++·ffmpeg·视频编解码·video-codec
muren9 小时前
昇思MindSpore学习笔记2-01 LLM原理和实践 --基于 MindSpore 实现 BERT 对话情绪识别
笔记·深度学习·学习
这是另一个世界9 小时前
黑客技术大纲
网络·学习·web安全·网络安全
国中之林9 小时前
【qt】如何获取本机的IP地址?
服务器·qt·网络协议·学习·tcp/ip