多进程实现并发服务器Socket通信 C/S模式

 介绍

前置基础知识链接在这Socket初体验 C/S模式开发模型_没伞的男孩的博客-CSDN博客

以上链接的CSDN实现的是非并发通信

同一时刻只能有一对主机(客户端与服务端)进行数据交换

本篇CSDN通过调用fork()函数创建父子进程

实现并发的服务器网络通信

在同一时刻 可以有多个客户端主机与一个服务器主机进行数据交换

实现分析

规定

fd0仅仅用于处理连接请求的socket文件描述符

cfd是用于正式连接后 实现通信以及数据交换的socket文件描述符

在父进程中

使用一个Socket(文件描述符称为fd0)

仅仅用于处理连接的请求

对于子进程的退出 采用信号函数处理的方式回收资源

在子进程中

子进程和父进程共享文件描述符

子进程和父进程不共享堆栈资源(发生写入则会深拷贝

在子进程中关闭父进程的fd0

在子进程中使用分别的cfd进行通信

代码(详细注释)

Linux客户端

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#define SERVER_IP "127.0.0.1"
#define SERVER_PORT 666
 
//客户端开发
int main()
{
    //服务器地址标签
    //sockaddr_in 是用于IPV4通信的结构体
    //这个结构体里存了协议设定 IP地址 端口号等信息
	struct sockaddr_in server_addr;

	//客户端连接服务端 TCP协议的固定写法
    //AF_INET 和 SOCK_STREAM 是TCP协议的特有参数
	int sockfd = socket(AF_INET, SOCK_STREAM, 0);
 
	//设置服务器地址标签
	memset(&server_addr, 0, sizeof(struct sockaddr_in));
	server_addr.sin_family = AF_INET;//设置协议
 
	//用到一些调整字节序的函数
    //inet_pton 将"x.x.x.x"字符串形式的IP地址转化为4字节整形
    //inet_pton 自动转化为大端字节序的4字节整形
	inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr);
 
    //htons h to n 指的是本地变网络(host to net) 转化为大端字节序
    //htons 最后的 s 指的是短整型 s对应两个字节 这里是对端口号的转换
	server_addr.sin_port = htons(SERVER_PORT);
 
	//建立C/S连接
    //第一个参数是socket的文件描述符
    //第二个参数是结构体地址 因历史兼容原因要强制类型转换
    //connect函数封装了填写客户端IP地址以及端口号信息的功能
	connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr));
 
	//往服务端写入数据
	char buf[256] = "";
	while(1)
	{	
		read(STDIN_FILENO, buf, sizeof(buf));
		write(sockfd, buf, strlen(buf));
		int len = read(sockfd, buf, sizeof(buf));
		if(len>0)
		{
			//成功从服务器接收到数据
			printf("receive:%s\n", buf);	
		}	
 	}

	return 0; 	
}

Linux服务端

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<sys/wait.h>
#include<string.h>
#include<ctype.h>
#include<arpa/inet.h>
#include<signal.h>
#define SERVER_PORT 666

//信号回调函数 父进程回收子进程
void free_process(int sig)
{
	pid_t pid;
	//第一参数 等待任意的子进程
	//第二参数 子进程退出的状态信息
	//第三参数 表示不阻塞 没有子进程退出就立即返回
	//写成循环 处理多个子进程要退出的情况  
	while(1)
	{
		pid = waitpid(-1, NULL, WNOHANG);
		if(pid <= 0)//没有要回收的子进程
		{
			break;
		}
		else
		{
			printf("子进程 pid = %d 终止\n", pid);
		}
	}
}

//服务端开发
int main(void)
{
    //sockaddr_in 是IPV4协议下存储通信地址的结构体
	struct sockaddr_in server_addr;
 
	//创建socket 获取文件描述符
	int fd0 = socket(AF_INET, SOCK_STREAM, 0);
 
	//初始化结构体标签 写上地址和端口号
	bzero(&server_addr,sizeof(server_addr));
 
    //指定协议家族IPV4
	server_addr.sin_family = AF_INET;
 
    //htons 是 host to net short 
    //转换为大端字节序 并且支持短整型 端口号就是两个字节
    //一个服务器可能有多个网卡多个IP
    //INADDR_ANY 这个宏定义量表示监听所有IP
	server_addr.sin_addr.s_addr = htons(INADDR_ANY);
	server_addr.sin_port = htons(SERVER_PORT);
 
	//用结构体标签初始化信箱
    //考虑历史兼容性 强制类型转换(struct sockaddr *)
	bind(fd0, (struct sockaddr*)&server_addr, sizeof(server_addr));
 
	//启动两条连接队列
    //队列中有已完成TCP连接的和未完成TCP连接的
    //第二个参数限制总共最多128个请求
	listen(fd0, 128);
 
	//等待客户端连接
	printf("等待客户端连接\n");

	//数据交换
	while(1)
	{
		//客户端的标签 寄信者的信息
		struct sockaddr_in client;
		socklen_t client_addr_len = sizeof(client);
		char client_ip[16] = "";

		//创建一个用于数据交换的的socket
        //accept函数从已完成TCP连接的队列中获取连接并开始通信
        //accept函数返回一个新的socket 有新的文件描述符
	//如果没有连接可以提取 accept函数会使代码阻塞
        //注意 这个新的socket 和 之前用于连接的socket(fd0) 不是一个文件描述符
		int cfd = accept(fd0, (struct sockaddr*)&client, &client_addr_len);
		//打印客户端信息
        //inet_ntop 讲4字节整形数转化为"x.x.x.x"类型的IP地址
        //ntohs net to host short 从网络字节序转为主机字节序
        //short 对应占据2字节大小的整形数据 用于表示端口
		//获取客户端IP地址
		inet_ntop(AF_INET,&client.sin_addr.s_addr,client_ip,sizeof(client_ip));
		//获取客户端端口号
		int client_port = ntohs(client.sin_port);
		printf("client ip:%s port:%d 已连接\n", client_ip, client_port);
 
		//实现并发功能
		//调用fork()创建子进程
    		pid_t pid;
		pid = fork();
		if(pid < 0)
		{
			perror("");
			exit(0);
		}
		if(pid ==0)
		{
			//这是子进程
			//关闭用于处理连接的fd0
			close(fd0);
			char buf[1024] = "";
			while(1)
			{
				int n = read(cfd, buf, sizeof(buf));
				if(n < 0)//读数据出错
				{
					perror("");
					close(cfd);
					exit(0);
				}
				if(n == 0)//客户端关闭
				{
					printf("%s %d 已离线\n",client_ip, client_port);
					close(cfd);
					exit(0);
				}
				printf("%s %d : %s\n",client_ip, client_port, buf);
				write(cfd, buf, n);
			}
		}
		else
		{
			//这是父进程
			close(cfd);
	
			//回收子进程
			//不能用wait() 否则代码阻塞影响提取新的连接
			//应当注册信号回调 异步处理 修改信号的处理方式
			//SIGCHILD是子进程终止的信号
			signal(SIGCHLD, free_process);
		}
	}
 
	return 0;
}

运行效果

编译

两个客户端连接一个服务器

两个客户端发送消息

两个客户端依次退出

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

没伞的男孩

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值