c++ 网络编程(三)TCP/IP LINUX/windows 进程间的通信原理与实现代码 基于多进程的服务端实现

原文作者:aircraft

原文链接:https://www.cnblogs.com/DOMLX/p/9613027.html

一.进程间通信的基本概念

进程间通信意味着两个不同进程间可以交换数据,操作系统中应提供两个进程可以同时访问的内存空间。

通过管道实现进程间通信

基于管道(PIPE)的进程间通信结构模型:

通过管道完成进程间通信。管道不是进程的资源,属于操作系统的。两个进程通过操作系统提供的内存空间进行通信。

创建管道的函数:

父进程调用该函数时创建管道,同时获取对应于出入口的文件描述符。父进程的目的是与子进程进行数据交换,因此需要将入口或出口中的1个文件描述符传递给子进程。调用fork函数传递。

二.进程间通信的单向传递

简单的看一个基础单向通信实例代码来理解进程间的通信是怎么实现的:

#include<stdio.h>
#include<unistd.h>
#define BUF_SIZE 30
 
int main(int argc, char *argv[])
{
    int fds[2];
    char str[] = "Who are you?";
    char buf[BUF_SIZE];
    pid_t pid;
 
    pipe(fds);                //创建管道,fds数组中保存用于I/O的文件描述符
    pid = fork();            //子进程将同时拥有管道的I/O文件描述符。
    if (pid == 0)
    {
        write(fds[1],str,sizeof(str));        //fds[1]为管道入口
    }
    else 
    {
        read(fds[0],buf,BUF_SIZE);            //fds[0]为管道出口    
        puts(buf);
    }
    return 0;
}

运行结果:who are you ?

上例中,父子进程都可以访问管道的I/O路径,但子进程仅用输入路径,父进程仅用输出路径。

三.进程间通信的双向传递

管道进行双向数据交换的通信方式:

接下来看一个简单的实例代码:

/* 双向通信的管道 */
#include<stdio.h>
#include<unistd.h>
#define BUF_SIZE 30
 
int main(int argc,char *argv[])
{
    int fds[2];
    char str1[] = "Who are you?";
    char str2[] = "Thank you for your message!";
    char buf[BUF_SIZE];
    pid_t pid;
 
    pipe(fds);
    pid = fork();
    if (pid == 0)
    {
        write(fds[1],str1,sizeof(str1));        //传输数据
        sleep(2);                               //睡眠两秒,避免被下一行的read函数读取了数据。
        read(fds[0],buf,BUF_SIZE);
        printf("Child proc output: %s \n",buf);    //接收数据
    }
    else 
    {
        read(fds[0],buf,BUF_SIZE);                //接收数据
        printf("Parent proc output: %s \n",buf);
        write(fds[1],str2,sizeof(str2));        //传输数据
        sleep(3);        //睡眠,防止父进程在子进程输出之前结束,可删除
            //不理解的话你注释掉这个sleep体会一下就知道了
    }
    return 0;
}

这里为什么有这么多个sleep 呢,摇一摇你们的小脑袋----有没有听见水声???hhh

书上有句话“向管道传递数据的时候,先读的程序会把数据先取走”

看到这里明白了吗???  简而言之就是数据进入管道就变成了无主数据,这时候如果我子进程先写入数据,在父进程没有取出数据前又read把自己的数据给读出来了!!!!大问题!大问题!对吧,这是要搞事情的节奏啊,被谁打死都不知道!!!

那么如何避免这个问题呢?---一个管道不够,我建两个呗---唉,真是的。。。。。

只用1个管道进行双向通信并非易事,需要预测并控制运行流程。因此创建2个管道完成双向通信,各自负责不同的数据流动即可:

由上图可知,是用2个管道可以避免程序流程的预测或控制。

接下来看双通道实现通信代码:

/* 双管道实现进程间通信 */
#include<stdio.h>
#include<unistd.h>
#define BUF_SIZE 30
 
int main(int argc,char *argv[])
{
    int fds1[2],fds2[2];
    char str1[] = "Who are you?";
    char str2[] = "Thank you for your message!";
    char buf[BUF_SIZE];
    pid_t pid;
 
    pipe(fds1), pipe(fds2);                        //创建两个管道
    pid = fork();
    if (pid == 0)
    {
        write(fds1[1],str1,sizeof(str1));    //子进程通过数组fds1传输数据
        read(fds2[0],buf,BUF_SIZE);
        printf("Child proc output: %s \n",buf);
    }
    else 
    {
        read(fds1[0],buf,BUF_SIZE);
        printf("Parent proc output: %s \n",buf);    
        write(fds2[1],str2,sizeof(str2));    //父进程通过数组fds2传输数据
        sleep(3);
    }
    return 0;
}

输入结果:Parent proc output: ”Who are you?"; 

Child proc output: "Thank you for your message!";

好的基本概念都介绍完了,那我们用一下玩玩呗???

四.基于多进程的回声服务端实现

注意啦这里是对我上一章博客代码的扩充,没有看我的上一张网络编程(二)......可以去看看了

这里对网络编程(二)加了一个功能,“可以将回声客户端传输的字符串按序保存到文件中去”

 LINUX 下:

/* 实现并发服务器端 */
/* echo_storeserv.c */
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<signal.h>
#include<sys/wait.h>
#include<arpa/inet.h>
#include<sys/socket.h>
 
#define BUF_SIZE 100
void error_handling(char *message)
{
    fputs(message,stderr);
    fputc('\n',stderr);
    exit(1);
}
 
/* Handler */
void read_childproc(int sig)
{
    pid_t pid;
    int status;
    pid = waitpid(-1,&status,WNOHANG);
    printf("removed proc id: %d \n",pid);
}
 
int main(int argc, char *argv[])
{
    int serv_sock, clnt_sock;
    struct sockaddr_in serv_adr, clnt_adr;
    int fds[2];
 
    pid_t pid;
    struct sigaction act;
    socklen_t adr_sz;
    int str_len, state;
    char buf[BUF_SIZE];
    if (argc != 2) {
        printf("Usage: %s <port> \n",argv[0]);
        exit(1);
    }
 
    act.sa_handler = read_childproc;            //设置信号处理函数
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    state = sigaction(SIGCHLD,&act,0);            //子进程终止时调用Handler
    
    serv_sock = socket(PF_INET,SOCK_STREAM,0);
    memset(&serv_adr,0,sizeof(serv_adr));
    serv_adr.sin_family = AF_INET;
    serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_adr.sin_port = htons(atoi(argv[1]));
 
    if (bind(serv_sock,(struct sockaddr*)&serv_adr,sizeof(serv_adr)) == -1)
        error_handling("bind() error");
    if (listen(serv_sock,5) == -1)
        error_handling("listen() error");
 
    pipe(fds);
  //这里创建一个子进程来服务写入文件数据
    pid = fork();
    if (pid == 0)
    {
        FILE* fp = fopen("echomsg.txt","wt");
        char msgbuf[BUF_SIZE];
        int i, len;
 
        for (i = 0; i < 10; i++ )
        {
            len = read(fds[0],msgbuf,BUF_SIZE);    //从管道出口fds[0]读取数据并保存到文件中
            fwrite((void*)msgbuf,1,len,fp);        
        }
        fclose(fp);
        return 0;
    }
 
 
    while (1) 
    {
        adr_sz = sizeof(clnt_adr);
        clnt_sock = accept(serv_sock,(struct sockaddr*)&clnt_adr,&adr_sz);
        if (clnt_sock == -1)
            continue;
        else 
            puts("new client connected...");
        //这里创建一个子进程来将数据写入管道
        pid = fork();
        if (pid == 0)        //子进程运行区域
        {
            close(serv_sock);
            while((str_len = read(clnt_sock,buf,BUF_SIZE)) != 0)
            {    
                write(clnt_sock,buf,str_len);
                write(fds[1],buf,str_len);        //将从客户端接收到的数据写入到管道入口fds[1]中
            }
 
            close(clnt_sock);
            puts("client disconnected...");
            return 0;        //调用Handler
        }
        else                //父进程运行区域
            close(clnt_sock);
    }
    close(serv_sock);
    return 0;
} 

上面处理文件的进程代码里,可能有的人会对那个for循环怎么实现恰好读十次数据结束,,,有点疑惑------关键在于read函数,这个函数如果没有从管道里面读取到数据就会继续等待!!!  这也是大工程需要注意出现BUG的地方

这里需要大家多开几个客户端来验证服务端的效果,当10次fwrite函数调用完后,大家就可以打开文件查看结果了,如果没有客户端代码可以参考我上一篇博客。

windows下基于多进程的回声服务端实现代码:

/*
 *  @file  : TestEchoServerMultiProcess.cpp
 *  @author: Shilyx
 *  @date  : 2014-04-23 08:43:27.206
 *  @note  : Generated by SlxTemplates, 多进程echo服务器演示
 */
 
#include <WinSock2.h>
#include <Windows.h>
#include <Shlwapi.h>
#pragma warning(disable: 4786)
#include <iostream>
 
#pragma comment(lib, "Ws2_32.lib")
#pragma comment(lib, "Shlwapi.lib")
 
using namespace std;
 
// 初始化WinSock,未检查返回值
void InitWinSock()
{
    WSADATA wd;
 
    WSAStartup(MAKEWORD(2, 2), &wd);
}
 
void Serve(USHORT port)
{
    InitWinSock();
 
    SOCKET sock_base = INVALID_SOCKET;
 
    do
    {
        sock_base = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
 
        if (sock_base == INVALID_SOCKET)
        {
            cerr<<"socket error "<<WSAGetLastError()<<endl;
            break;
        }
 
        sockaddr_in sin;
 
        sin.sin_family = AF_INET;
        sin.sin_addr.s_addr = INADDR_ANY;
        sin.sin_port = htons(port);
 
        if (SOCKET_ERROR == bind(sock_base, (sockaddr *)&sin, sizeof(sin)))
        {
            cerr<<"bind error "<<WSAGetLastError()<<endl;
            break;
        }
 
        if (SOCKET_ERROR == listen(sock_base, 100))
        {
            cerr<<"listen error "<<WSAGetLastError()<<endl;
            break;
        }
 
        HANDLE hProcess = NULL;
        DuplicateHandle(GetCurrentProcess(), GetCurrentProcess(), GetCurrentProcess(), &hProcess, 0, TRUE, DUPLICATE_SAME_ACCESS);
 
        if (NULL == hProcess)
        {
            cerr<<"DuplicateHandle error "<<GetLastError()<<endl;
            break;
        }
 
        TCHAR szSelfPath[MAX_PATH];
 
        GetModuleFileName(GetModuleHandle(NULL), szSelfPath, RTL_NUMBER_OF(szSelfPath));
        PathQuoteSpaces(szSelfPath);
 
        while (true)
        {
            int len = sizeof(sin);
            SOCKET sock = accept(sock_base, (sockaddr *)&sin, &len);
 
            if (sock == INVALID_SOCKET)
            {
                cerr<<"accept error "<<WSAGetLastError()<<endl;
                break;
            }
            else
            {
                TCHAR szCommand[MAX_PATH * 2];
                STARTUPINFO si = {sizeof(si)};
                PROCESS_INFORMATION pi;
 
                si.dwFlags = STARTF_USESHOWWINDOW;
                si.wShowWindow = SW_SHOW;
 
                wnsprintf(szCommand, RTL_NUMBER_OF(szCommand), TEXT("%s %u %u"), szSelfPath, sock, hProcess);
 
                if (CreateProcess(NULL, szCommand, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi))
                {
                    CloseHandle(pi.hProcess);
                    CloseHandle(pi.hThread);
                }
                else
                {
                    cerr<<"CreateProcess error "<<GetLastError()<<endl;
                }
 
                closesocket(sock);
            }
        }
 
    } while (false);
 
    if (sock_base != INVALID_SOCKET)
    {
        closesocket(sock_base);
    }
}
 
DWORD CALLBACK WorkProc(LPVOID lpParam)
{
    SOCKET sock = (SOCKET)lpParam;
 
    while (TRUE)
    {
        char szBuffer[4096];
        int len = recv(sock, szBuffer, sizeof(szBuffer), 0);
 
        if (len <= 0)
        {
            break;
        }
 
        if (send(sock, szBuffer, len, 0) <= 0)
        {
            break;
        }
    }
 
    closesocket(sock);
 
    return 0;
}
 
void Work(SOCKET sock, HANDLE hParentProcess)
{
    InitWinSock();
 
    HANDLE hObjects[] = {hParentProcess, CreateThread(NULL, 0, WorkProc, (LPVOID)sock, 0, NULL)};
 
    WaitForMultipleObjects(RTL_NUMBER_OF(hObjects), hObjects, FALSE, INFINITE);
 
    CloseHandle(hObjects[0]);
    CloseHandle(hObjects[1]);
}
 
int main(int argc, char *argv[])
{
    // 加端口参数启动为父进程
    // 加套接字句柄参数和进程句柄参数为子进程
    // 不加参数显示用法
 
    if (argc == 2)
    {
        int port = StrToIntA(argv[1]);
 
        if (port < 0 || port > 65535)
        {
            cerr<<"端口错误:"<<port<<endl;
            return 0;
        }
 
        // 在端口port处启动echo服务器
        Serve((USHORT)port);
    }
    else if (argc == 3)
    {
        SOCKET sock = StrToIntA(argv[1]);
        HANDLE hParentProcess = (HANDLE)StrToIntA(argv[2]);
 
        // 针对具体tcp连接套接字和父进程句柄开始echo工作
        Work(sock, hParentProcess);
    }
    else
    {
        cout<<"加端口参数启动为父进程"<<endl
            <<"加套接字句柄参数和进程句柄参数为子进程"<<endl
            <<"不加参数显示用法"<<endl;
    }
 
    return 0;
}

同时多进程服务端也是有缺点的,每创建一个进程就代表大量的运算与内存空间占用,相互进程数据交换也很麻烦。。。那么怎么解决呢,在我后面的博客也许会给出答案-----hhhhhhh

最后说一句啦。本网络编程入门系列博客是连载学习的,有兴趣的可以看我博客其他篇。。。。

好了今天对网络编程的学习就到这里结束了,小飞机我要撤了去吃饭了。,,,很多人大学都很迷茫不知道学点什么好,,,,,管他的,想那么多干嘛,先学了再说,对技术如有偏见,那么你的领域就局限于此了---《一专多精》

参考博客:https://blog.csdn.net/my3439955/article/details/9749869

参考书籍:《TCP/IP 网络编程 --尹圣雨》

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏BestSDK

Python开发必备的6个库,有了它事半功倍!

01 Python 必备之 PyPy PyPy 主要用于何处? 如果你需要更快的 Python 应用程序,最简单的实现的方法就是通过 PyPy ,Python ...

45980
来自专栏Spark学习技巧

spark源码系列之内部通讯的三种机制

本文是以spark1.6.0的源码为例讲解。 Spark为协调各个组件完成任务及内部任务处理采用了多种方式进行了各个组件之间的通讯。总共三个部分牵涉的功能是: ...

36180
来自专栏小樱的经验随笔

BugkuCTF web3

1.6K40
来自专栏编程

6款好用的C语言编译器推荐

一些刚开始接触C语言编译的网友想下载一款C语言编译器来使用,不过,网络上有不少C语言编译器相关的软件,让人很难抉择。那么,C语言编译器哪个好?今天的文章里,我给...

1.3K80
来自专栏互联网高可用架构

Java服务化系统线上应急和技术攻关,你必须拥有的那些应用层脚本和Java虚拟机命令

40130
来自专栏吴裕超

js和native交互方法浅析

一、背景 最近接触公司项目,需要和原生app做交互,由此业务需求,开始了学习探索之路。 二、解决方案之WebViewJavascriptBridge  想要和a...

37480
来自专栏向治洪

iOS 组件化之路由设计思路分析

前言 随着用户的需求越来越多,对App的用户体验也变的要求越来越高。为了更好的应对各种需求,开发人员从软件工程的角度,将App架构由原来简单的MVC变成MVVM...

96760
来自专栏葡萄城控件技术团队

Winform文件下载之WebClient

最近升级了公司内部使用的一个下载小工具,主要提升了下面几点: 1. 在一些分公司的局域网中,连接不上外网 2. 服务器上的文件更新后,下载到的还是更新前的文件 ...

22050
来自专栏张戈的专栏

bat/cmd批处理连接SqlServer数据库查询脚本

难得今天晚上 9 点前赶回家,而且最近草稿箱也压了不少“湿货”,就挑一篇发出来好了!不过在发文章之前先吐槽一下那个从昨天攻击张戈博客到现在还在继续的无聊蛋疼之人...

49780
来自专栏逸鹏说道

bootstrap + requireJS+ director+ knockout + web API = 一个时髦的单页程序

bootstrap + requireJS+ director+ knockout + web API = 一个时髦的单页程序 也许单页程序(Single Pa...

41750

扫码关注云+社区

领取腾讯云代金券