通过套接字实现 创建10个子进程向父进程发送10个随机数最后求和

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/qq_41280600/article/details/102720556

一、内容

任务:
10个子进程求和问题。用套接字进行父子进程间数据的通信。
思路:
父进程做服务器,产生完10个子进程后就进行服务器的准备工作。子进程做客户端,连接到服务器后
就发送自己的随机数。父进程接收到随机数后进行累加。由于子进程发起连接请求时,父进程可能还没完成服务器的准备工作,
所以我们用一个信号量进行同步,子进程投入运行首先请求这个信号量,而父进程完成准备工作才释放这个信号量。
要注意的是,由于accept()和recv()都是阻塞函数,
所以服务器要用单独的线程来调用这两个函数,所以服务器将产生10个线程来分别为10个子进程服务。另外,
10个线程都要修改全局变量total,所以要进行并发控制。

二、代码

/*------------------------------------------------------------------------------------------------
任务:
    10个子进程求和问题。用套接字进行父子进程间数据的通信。
思路:
    父进程做服务器,产生完10个子进程后就进行服务器的准备工作。子进程做客户端,连接到服务器后
就发送自己的随机数。父进程接收到随机数后进行累加。由于子进程发起连接请求时,父进程可能还没完成服务器的准备工作,
所以我们用一个信号量进行同步,子进程投入运行首先请求这个信号量,而父进程完成准备工作才释放这个信号量。
要注意的是,由于accept()和recv()都是阻塞函数,
所以服务器要用单独的线程来调用这两个函数,所以服务器将产生10个线程来分别为10个子进程服务。另外,
10个线程都要修改全局变量total,所以要进行并发控制。
/*
1.创建套接字-绑定-监听-接受
2.创建套接字-连接 
3.因为会用到阻塞函数,为了不让主线程阻塞所以使用多线程。让线程去执行阻塞函数
4.accept connect send receive
5.网络地址:主机在一个网络中的地址,对应主机字节序中的一个IP地址和端口。 注意将端口号和ip地址从主机字节序转为网络字节序
6.为了并发控制。当多个子进程想访问那个总和变量的时候,只能让一个子进程进行访问,故而父进程每次只能接收一个随机数。让父进程准备接收子进程时,子进程才能进行连接。
7.因为自带的send函数一次可能无法将数据全部发送完成,所以通过自己写的whille循环将数据全部发送。 
*/ 
//------------------------------------------------------------------------------------------------*/


//Windows的头文件
///*----------------------------------------------------
#include "stdafx.h"
#include <stdio.h>
#include <time.h>
#include <Winsock2.h>
#pragma comment(lib, "ws2_32.lib")
#define WINDOWS_VERSION
//----------------------------------------------------*/

//Linux的头文件
/*------------------------------------------------------
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <pthread.h>
#include <sys/socket.h>
#include <netdb.h> 
#include <netinet/in.h> 
#include <arpa/inet.h>  //inet_ntoa()函数的头文件
------------------------------------------------------*/


//全局变量
//----------------------------------------------------
#ifdef WINDOWS_VERSION
	HANDLE		g_hSem_total;				//控制累加的信号量的句柄
	HANDLE		g_hSem_child_run;			//控制子进程运行的信号量的句柄
	HANDLE		g_hThread[10];				//10个线程的句柄
#else
	union semun
	{
		int val;
		struct semid_ds *buf;
		ushort *array;
	};
	int		sem_total_id;				//控制累加的信号量的ID
	int		sem_child_run_id;			//控制子进程运行的信号量的ID
	pthread_t	thread_id[10];				//10个线程的ID
#endif
int		total;						//和
int		listen_sock;				//用于监听的套接字
short	port;
//----------------------------------------------------


int mysend(int sock, char *buf, int len, int flags)
{
	int sent = 0, remain = len;
	while(remain > 0)
	{
		int n = send(sock, buf+sent, remain, flags);
		if(n == -1)  //出错的最大可能是对方关闭了套接字
			break;
		remain -= n;
		sent += n;
	}
	return sent;
}

int myrecv(int sock, char *buf, int len, int flags)
{
	int received = 0, remain = len;
	while(remain > 0)
	{
		int n = recv(sock, buf+received, remain, flags);
		if(n == 0 || n == -1)  //0是对方调用close(),-1是对方直接退出
			break;
		remain -= n;
		received += n;
	}
	return received;
}

void send_num_to_parent(int num)
{
	int sock = socket(AF_INET, SOCK_STREAM, 0);
	
	//下面4行进行地址结构体的赋值。注意IP地址和端口号都要转换成网络字节序
	struct sockaddr_in server_addr;
	server_addr.sin_family		= AF_INET;
	server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");  //将字符串表示的IP地址转换成网络字节序整数
	server_addr.sin_port		= htons(port);
	
	connect(sock, (struct sockaddr *)&server_addr, sizeof(struct sockaddr));  //connect()是阻塞函数,直到3次握手完成为止,或是超时为止
	
	mysend(sock, (char *)&num, 4, 0);

#ifdef WINDOWS_VERSION
	closesocket(sock);
#else
	close(sock);
#endif
}

#ifndef WINDOWS_VERSION
void p(int semid)
{
	struct sembuf buf = {0, -1, 0};
	semop(semid, &buf, 1);
}

void v(int semid)
{
	struct sembuf buf = {0, 1, 0};
	semop(semid, &buf, 1);
}
#endif

#ifdef WINDOWS_VERSION
DWORD WINAPI thread_function(void *arg) 
#else
void *thread_function(void *arg)
#endif
{ 
    int index = (int)arg;
	
	//准备接受连接请求
	struct sockaddr_in peer_addr;
	int size = sizeof(struct sockaddr_in);
	int comm_sock = accept(listen_sock, (struct sockaddr *)&peer_addr, &size);  //accept()是阻塞函数,直到有客户端向本地址发起连接请求。注意返回值就是对应客户端的套接字,将来的数据收发都在这个套接字上进行
	printf("parent : 线程%d接受一个连接请求\n", index);
	
	//创建下一个线程
	if(index < 9)
#ifdef WINDOWS_VERSION
		g_hThread[index + 1] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)thread_function, (void *)(index + 1), 0, NULL);
#else
		pthread_create(&thread_id[index + 1], NULL, (void *)thread_function, (void *)(index + 1));
#endif
	
	//接收一个整数
	int num;
	myrecv(comm_sock, (char *)&num, 4, 0);
	printf("parent : 线程%d接收到一个整数%d\n", index, num);

	//累加到全局变量。由于10个线程可能形成竞争,所以必须进行并发控制
#ifdef WINDOWS_VERSION
	WaitForSingleObject(g_hSem_total, INFINITE);
	total += num;
	ReleaseSemaphore(g_hSem_total, 1, NULL);
	closesocket(comm_sock);
	ExitThread(0);
	return 0;
#else
	p(sem_total_id);
	total += num;
	v(sem_total_id);
	close(comm_sock);
	pthread_exit(NULL);
#endif
}

void init_socket()
{
	//创建流式套接字,用于监听子进程发出的连接请求
	listen_sock = socket(AF_INET, SOCK_STREAM, 0);
	
	//下面4行进行地址结构体的赋值。注意IP地址和端口号都要转换成网络字节序
	struct sockaddr_in server_addr;
	server_addr.sin_family		= AF_INET;
	server_addr.sin_addr.s_addr = htonl(INADDR_ANY);  //INADDR_ANY表示请操作系统自动选一个可用的本机IP
	server_addr.sin_port		= htons(port);
	
	//将套接字绑定到指定的地址
	bind(listen_sock, (struct sockaddr *)&server_addr, sizeof(struct sockaddr));
	
	//将套接字置于监听状态
	listen(listen_sock, 10);
	
	printf("parent : 服务器开始监听连接请求\n");
}

void do_child(int i)
{
	//p操作,将自己挂起,等服务器准备就绪再运行
#ifdef WINDOWS_VERSION
	g_hSem_child_run = OpenSemaphore(SEMAPHORE_ALL_ACCESS, false, "parent_child_sync");
	WaitForSingleObject(g_hSem_child_run, INFINITE);
	CloseHandle(g_hSem_child_run);
#else
	p(sem_child_run_id);
#endif
	
	srand(time(NULL) + i);
	int num = rand()%10;
	
	printf("child%d: %d\n", i, num);
	
	send_num_to_parent(num);
}

int main(int argc, char* argv[])
{
	//下面4行进行Windows网络环境的初始化。UNIX中不需要
#ifdef WINDOWS_VERSION
	WORD wVersionRequested;
	WSADATA wsaData;
	wVersionRequested = MAKEWORD(2, 2);
	WSAStartup(wVersionRequested, &wsaData);
#endif

	if(argc == 1)  //Windows中的父进程,Linux中的父、子进程
	{
		total = 0;

		//创建并初始化信号量
#ifdef WINDOWS_VERSION
		g_hSem_total = CreateSemaphore(NULL, 1, 1, NULL);
		g_hSem_child_run = CreateSemaphore(NULL, 0, 10, "parent_child_sync");
#else
		sem_total_id = semget(IPC_PRIVATE, 1, 0666);
		union semun x;
		x.val = 1;
		semctl(sem_total_id, 0, SETVAL, x);
		sem_child_run_id = semget(IPC_PRIVATE, 1, 0666);
		x.val = 0;
		semctl(sem_child_run_id, 0, SETVAL, x);
#endif

		//随机产生一个端口号,但要大于1024才行
		srand(time(NULL));
		port = rand()%1024 + 1024;

		//创建10个子进程,但子进程会被p操作挂起
#ifdef WINDOWS_VERSION
		STARTUPINFO si;
		PROCESS_INFORMATION pi;
		for(int i=0; i<10; i++)
		{
			//下面3行形成子进程的启动命令行,格式为:filename -端口号 -[0-9]
			char strCmdLine[256];
			strcpy(strCmdLine, argv[0]);
			sprintf(strCmdLine + strlen(strCmdLine), " -%d -%d", port, i);

			ZeroMemory( &si, sizeof(si) );
			si.cb = sizeof(si);
			ZeroMemory( &pi, sizeof(pi) );
			CreateProcess(NULL, strCmdLine, NULL, NULL, false, 0, NULL, NULL, &si, &pi);
		}
#else
		int i;
		for(i=0; i<10; i++)
		{
			pid_t pid = fork();
			if(pid == 0)
			{
				do_child(i);
				return 0;
			}
		}
#endif

		//初始化网络环境,完成服务器的准备工作
		init_socket();
		
		//创建用于接受连接请求的第一个线程,将来由第一个去创建第二个,第二个创建第三个,以此类推。要保存线程的句柄或ID,因为后面主线程要等待10个子线程结束。
#ifdef WINDOWS_VERSION
		g_hThread[0] = CreateThread(NULL, 0, thread_function, (void *)0, 0, NULL);
#else
		pthread_create(&thread_id[0], NULL, (void *)thread_function, (void *)0);
#endif

		//释放信号量,让子进程运行
#ifdef WINDOWS_VERSION
		ReleaseSemaphore(g_hSem_child_run, 10, NULL);
#else
		struct sembuf buf = {0, 10, 0};
		semop(sem_child_run_id, &buf, 1);
#endif

		//等待10个线程结束
		for(i=0; i<10; i++)
#ifdef WINDOWS_VERSION
			WaitForSingleObject(g_hThread[i], INFINITE);
#else
			pthread_join(thread_id[i], NULL);
#endif

		printf("parent : all threads ended, total=%d\n", total);
			
		//删除信号量
#ifdef WINDOWS_VERSION
		CloseHandle(g_hSem_total);
		CloseHandle(g_hSem_child_run);
#else
		semctl(sem_total_id, IPC_RMID, 0);
		semctl(sem_child_run_id, IPC_RMID, 0);
#endif
	}
	else if(argc == 3)  //Windows中的子进程,命令行格式为:filename -端口号 -[0-9] 
	{
#ifdef WINDOWS_VERSION
		sscanf(argv[1]+1, "%d", &port);
		int index;
		sscanf(argv[2]+1, "%d", &index);

		do_child(index);
#endif
	}
	else
		printf("启动命令不正确\n");
	
	return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_41280600/article/details/102720556