【Linux网络编程部分----多进程高并发poll模型】

目录

前言

背景

分析

编写步骤

服务器:

客户端:

 服务器端代码

附:文件操作部分

附:目录操作部分

客户端代码

全部代码

头文件部分

服务器全部代码

客户端所有代码

总结:


 

前言

        本文采用  Visual Studio 2022 运用远程登陆,来对虚拟机内的  Linux 进行操作

        如果不知道怎么使用的话可参考: linux远程开发——使用vs2019远程连接linux_vs2019远程调试linux_似末的博客-CSDN博客icon-default.png?t=N176https://blog.csdn.net/wmcy123/article/details/123415371?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522167741273216782427438492%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=167741273216782427438492&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-3-123415371-null-null.142^v73^insert_down1,201^v4^add_ask,239^v2^insert_chatgpt&utm_term=vs%E9%93%BE%E6%8E%A5linux&spm=1018.2226.3001.4187


背景

        在TCP协议下,使用套接字(socket)多进程(fork)之间进行通信,并且客户端能够从服务器设定下的地址上获取文件。


分析

        要分别从服务器和客户端两个方面入手,采取网络编程所固定的格式来进行编写

        服务器:要不断的监听是否有客户端来进行通信,并且采用多进程的方式使得客户端之间互不影响

        客户端:要可随时加入服务器,并且能够自主下载服务端下面的指定文件


编写步骤

服务器:

        第一步:创建套接字
        第二步:监听套接字
        第三步:等待客户端连接
        第四步:发送数据给客户端

        第五步:关闭套接字

客户端:

        第一步:创建套接字
        第二步:链接服务器
        第三步:读写服务器
        第四步:关闭套接字


 服务器端代码

        一、我们先做前期准备,先创建套接字(socket),并先确定其中的内容

int tcp_sock = socket(AF_INET, SOCK_STREAM, 0);      //创建套接字
	struct sockaddr_in Ipv4 {};                      //定义一个Ipv4的结构体信息
	Ipv4.sin_family = AF_INET;                       //协议为IPV4
	Ipv4.sin_addr.s_addr = 0;                        //Ip地址
	Ipv4.sin_port = htons(PORT);                     //端口号  此处PORT为宏定义

        二、创建完后,在对其进行绑定,并且开始监听

if (bind(tcp_sock, (struct sockaddr*)&Ipv4, sizeof(Ipv4)) < 0) //绑定
	{
		perror("bind");
		return 1;
	}
	listen(tcp_sock, 10);    //监听将主动套接字变为被动套接字  监听10路

       三、创建  pollfd  类型的结构来确保我们使用 poll 函数

struct pollfd fds[SIZE]{};    // 将服务器加入到轮询表中 
fds[index].fd = tcp_sock;     // 有数据可读事件 */
	

        四、准备工作已经做完了,就开始服务器的操作

while (true)
	{
		if (poll(fds, SIZE, 3000) == 0)
		{
			//printf("time out \n");        //检测
			continue;
		}
		for (int i = 0; i < index + 1; i++)
		{
			if (tcp_sock == fds[i].fd)        //主服务器  
			{
				if (fds[i].revents & POLLIN)
				{
					int cid = accept(tcp_sock, NULL, NULL);
					fds[index].fd = cid;      //将服务器加入到轮询表中
					fds[index].events = POLLIN; //有数据可读事件
					index++;
					printf("someone coming\n");
				}
			}
			else //与客户端交互
			{
				if (fork() == 0)  //多进程  子进程进行操作。避免出现孤儿进程
				{
					if (fds[i].revents & POLLIN)
					{
						memset(buf, 0, SIZE);   //将buf的内容全部置为0
						buf[SIZE - 1] = 1;
						len = read(fds[i].fd, buf, SIZE);  //读取数据
						if (len > 0)
						{
							if (strcmp(buf, "file") == 0)
							{
								printf("sent file to %d\n", i);
								dir(fds[i].fd);  // 目录文件操作
								read(fds[i].fd, buf, SIZE);
								file(fds[i].fd, buf); 
								memset(buf, 0, SIZE);
							}
							if (buf[SIZE - 1] != 0)
							{
								printf("%d --- :%s\n", i, buf);  
							}
							write(fds[i].fd, buf, len);
						}
					}
					exit(-1);
				}	
			}
			waitpid(-1, NULL, WNOHANG);
		}
	}

附:文件操作部分

void file(int tcp_socket,char name[20])
{	
	data_t cmd{};
	/**** 获取文件名 + 文件大小 *****/
	struct stat stat_buf {};
	if (stat(name, &stat_buf) != 0)        //获取文件信息
	{
		perror("stat");
		return;
	}
	cmd.file_len = stat_buf.st_size;
	char* str = strrchr(name, '/');
	if (str != NULL) //找到了 
	{
		str += 1;   //往后移动一位
	}
	else
	{
		str = name;
	}
	strcpy(cmd.file_name, str);  //获取文件名

	printf("file name:%s\n", cmd.file_name);
	printf("file size:%d\n", cmd.file_len);

	int fd = open(name, O_RDONLY);
	if (fd < 0)
	{
		perror("open");
		return;
	}

	write(tcp_socket, &cmd, sizeof(cmd));     //发送包头:文件名 + 文件大小

	/***** 循环读取文件并发送 ****/
	char buf[1024]{};
	int len = 0;
	while (cmd.file_len > 0)
	{
		len = read(fd, buf, 1024);        //读文件
		write(tcp_socket, buf, len);      //写入
		cmd.file_len -= len;
		
	}
	/****4.关闭套接字 *****/
	close(fd);
}

附:目录操作部分

void dir(int cid)
{
	
	DIR* dir = opendir("/home/student/projects/ProjectDemo/bin/x64/Debug");   // 打开一个目录
	char buf[SIZE]{};
	if (dir == NULL)
	{
		perror("opendir");
		return ;
	}
	struct dirent* dnt;
	while ((dnt = readdir(dir)) != NULL)            // 只要返回结果不为NULL,就一直遍历
	{

		printf("%s\n", dnt->d_name);
		strcpy(buf, dnt->d_name);
		write(cid, buf, sizeof(buf));
	}
	strcpy(buf, "quitquit");
	write(cid, buf, sizeof(buf));
	closedir(dir);
}

客户端代码

        客户端代码与服务器端代码大同小异,可直接参考服务器端来编写

int cid = socket(AF_INET, SOCK_STREAM, 0);
	struct sockaddr_in IPV4 {};
	IPV4.sin_family = AF_INET;
	IPV4.sin_addr.s_addr = 0;		
	IPV4.sin_port = htons(PORT);	
	char name[20]{};

	if (connect(cid, (struct sockaddr*)&IPV4, sizeof(IPV4)) < 0)
	{
		perror("conncet");
		return 1;
	}
	char buf[128]{};
	while (true)
	{
		printf("sent message to service:\n");
		gets(buf);
		write(cid, buf, sizeof(buf));
		if (strncmp(buf, "file",4) == 0)
		{
			dir(cid);
			printf("\n");
			printf("receive a file is \n");
			scanf("%s", name);
			write(cid, name, sizeof(buf));
			file(cid,name);   //文件操作
		}	
		if (strncmp(buf, "quit", 4) == 0)
		{
			break;
		}
	}


全部代码

        在此,给出全部的代码以供大家学习

头文件部分

#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/ip.h>
#include <netinet/in.h>
#include <string.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <poll.h>
#include <sys/wait.h>
#include <dirent.h>

服务器全部代码

typedef struct
{
	char file_name[20]; //文件名
	int file_len;       //文件大小
} data_t;

constexpr auto PORT = 10000;
constexpr auto IP = "192.168.248.128";
constexpr auto SIZE = 1024;

void dir(int cid)
{
	
	DIR* dir = opendir("/home/student/projects/ProjectDemo/bin/x64/Debug");   // 打开一个目录
	char buf[SIZE]{};
	if (dir == NULL)
	{
		perror("opendir");
		return ;
	}
	struct dirent* dnt;
	while ((dnt = readdir(dir)) != NULL)            // 只要返回结果不为NULL,就一直遍历
	{

		printf("%s\n", dnt->d_name);
		strcpy(buf, dnt->d_name);
		write(cid, buf, sizeof(buf));
	}
	strcpy(buf, "quitquit");
	write(cid, buf, sizeof(buf));
	closedir(dir);
}

void file(int tcp_socket,char name[20])
{	
	data_t cmd{};
	/**** 获取文件名 + 文件大小 *****/
	struct stat stat_buf {};
	if (stat(name, &stat_buf) != 0)        //获取文件信息
	{
		perror("stat");
		return;
	}
	cmd.file_len = stat_buf.st_size;
	char* str = strrchr(name, '/');
	if (str != NULL) //找到了 
	{
		str += 1;   //往后移动一位
	}
	else
	{
		str = name;
	}
	strcpy(cmd.file_name, str);  //获取文件名

	printf("file name:%s\n", cmd.file_name);
	printf("file size:%d\n", cmd.file_len);

	int fd = open(name, O_RDONLY);
	if (fd < 0)
	{
		perror("open");
		return;
	}

	write(tcp_socket, &cmd, sizeof(cmd));     //发送包头:文件名 + 文件大小

	/***** 循环读取文件并发送 ****/
	char buf[1024]{};
	int len = 0;
	while (cmd.file_len > 0)
	{
		len = read(fd, buf, 1024);        //读文件
		write(tcp_socket, buf, len);      //写入
		cmd.file_len -= len;
		
	}
	/****4.关闭套接字 *****/
	close(fd);
}

int  main() //多路复用高并发
{
	int tcp_sock = socket(AF_INET, SOCK_STREAM, 0);
	struct sockaddr_in Ipv4 {};
	Ipv4.sin_family = AF_INET;
	Ipv4.sin_addr.s_addr = 0;
	Ipv4.sin_port = htons(PORT);

	if (bind(tcp_sock, (struct sockaddr*)&Ipv4, sizeof(Ipv4)) < 0)
	{
		perror("bind");
		return 1;
	}
	listen(tcp_sock, 10);
	int index = 0;
	struct pollfd fds[SIZE]{};
	/* 将服务器加入到轮询表中 */
	fds[index].fd = tcp_sock;
	/* 有数据可读事件 */
	fds[index].events = POLLIN;
	index++;

	char buf[SIZE]{};
	int len = 0;
	while (true)
	{
		if (poll(fds, SIZE, 3000) == 0)
		{
			//printf("time out \n");
			continue;
		}
		for (int i = 0; i < index + 1; i++)
		{
			if (tcp_sock == fds[i].fd)// 主服务器  
			{
				if (fds[i].revents & POLLIN)
				{
					int cid = accept(tcp_sock, NULL, NULL);
					fds[index].fd = cid;  //将服务器加入到轮询表中
					fds[index].events = POLLIN; //有数据可读事件
					index++;
					printf("someone coming\n");
				}
			}
			else //与客户端交互
			{
				if (fork() == 0)
				{
					if (fds[i].revents & POLLIN)
					{
						memset(buf, 0, SIZE);
						buf[SIZE - 1] = 1;
						len = read(fds[i].fd, buf, SIZE);
						if (len > 0)
						{
							if (strcmp(buf, "file") == 0)
							{
								printf("sent file to %d\n", i);
								dir(fds[i].fd);
								read(fds[i].fd, buf, SIZE);
								file(fds[i].fd, buf);
								memset(buf, 0, SIZE);
							}
							if (buf[SIZE - 1] != 0)
							{
								printf("%d --- :%s\n", i, buf);
							}
							write(fds[i].fd, buf, len);
						}
					}
					exit(-1);
				}	
			}
			waitpid(-1, NULL, WNOHANG);
		}
	}
	close(tcp_sock);
	return 1;
}

客户端所有代码

constexpr auto PORT = 10000;
constexpr auto IP = "192.168.248.128";
constexpr auto SIZE = 1024;

typedef struct
{
	char file_name[20]; //文件名
	int file_len;       //文件大小
} data_t;

void dir(int cid)
{
	char buf[SIZE]{};
	printf("you can get:\n");
	read(cid, buf, sizeof(buf));
	int i = 0;
	while (strcmp(buf,"quitquit") != 0)
	{	
		printf("%s   ", buf);
		read(cid, buf, sizeof(buf));
		i++;
		if (i == 5)
		{
			printf("\n");
			i = 0;
		}
	}
	
}

void file(int cid,char name[20])
{
	data_t cmd;
	read(cid, &cmd, sizeof(cmd)); //接收文件名 + 大小
	remove(name);
	int fd = open(name, O_CREAT, 0666);
	if (fd < 0)
	{
		perror("open");
		return;
	}
	char* buf = (char*)malloc(1024);
	int len = 0;
	while (cmd.file_len > 0)
	{
		len = read(cid, buf, 1024);
		if (len <= 0)
		{
			perror("read");
			break;
		}
		cmd.file_len -= len;
		write(fd, buf, len);
	}
	printf("over\n");
}
int main()//客户端
{

	int cid = socket(AF_INET, SOCK_STREAM, 0);
	struct sockaddr_in IPV4 {};
	IPV4.sin_family = AF_INET;
	IPV4.sin_addr.s_addr = 0;		//ip地址
	IPV4.sin_port = htons(PORT);	//端口号
	char name[20]{};

	if (connect(cid, (struct sockaddr*)&IPV4, sizeof(IPV4)) < 0)
	{
		perror("conncet");
		return 1;
	}
	char buf[128]{};
	while (true)
	{
		printf("sent message to service:\n");
		gets(buf);
		write(cid, buf, sizeof(buf));
		if (strncmp(buf, "file",4) == 0)
		{
			dir(cid);
			printf("\n");
			printf("receive a file is \n");
			scanf("%s", name);
			write(cid, name, sizeof(buf));
			file(cid,name);
		}	
		if (strncmp(buf, "quit", 4) == 0)
		{
			break;
		}
	}
	close(cid);
	return 1;
}

总结:

        网络编程以固定的套路走,只要熟练了,按照一定的步骤走,网络编程就有迹可循。搞懂并不是一个特别困难的过程。加油!

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值