进程通信————网络套接字

 呵呵,原想本篇是这个系列的最后一篇,因为Linux的基础API只提供了这几种进程通信的方法。然而不巧的是,由于我写帖子时的习惯和为人的风格,这几个帖子都没把进程通信的阻塞问题拿出来讨论,而阻塞问题是让大多数人(包括我)干上两三天都调不通进程程序的根本原因所在。另外,我还想根据我自己的经验,写一些关于进程通信数据格式和交互流程的设计方法,按行话说就是通信协议的设计方法。所以,这个帖子是做不成老小儿,吃不成香油了,让它自己哭去吧,不管它,哈哈!……闲话少说,进入今天的正题吧!

相信大家已经阅读过上篇关于本地套接字的有关内容,网络套接字与其基本类似,所以这里我不想再重复的写些东西了,需要这些东西的朋友们,请去看我的上一篇帖子。这里主要给大家介绍下网络套接字与本地套接字在意义和使用上的区别,再给出几个网络套接字用于进程通信的C++模板类,供大家学习使用。

网络套接字可以通过网络,实现在不同主机上的进程通信。当然,如果你愿意,也完全可以实现同一台主机上的进程通信(将目的IP地址设置为127.x.x.x即可)。学习网络套接字首先要有一些网络知识,但现在没有也没关系,你只需要知道你想要与哪台机器上的哪个进程通信,并以一种方式找到它们,就OK了!其它的事情,还是让Linux来做吧!下面我们就来看看如何找到想要通信的主机和进程。
我们知道,目前的网络,无论大小,几乎都是使用TCP/IP协议族来作为网络内不同主机间的通信协议。TCP/IP协议是一个层次化的协议体系,它包括物理层、链路层、网络层、传输层和应用层五层协议。而它自身定义的协议标准却只有网络层、传输层和应用层。应用层表现在主机上就是我们的应用程序,我们想要实现通信的进程就是工作在应用层上的。因此,我们需要要通过传输层和网络层来实现传输应用层数据的目的,应用层相应进程的标识也就是由网络层和传输层给出的。其中,网络层给出了主机在网络上的标识,那就是我们熟悉的IP地址,传输层给出了应用程序在主机上的标识,那就是我们经常听说的端口号。那么又一个问题来了,应用程序如何与想要的端口号建立联系呢?呵呵,这个问题恐怕不用我讲大家也明白了,对于服务端应用程序,需要设置在相应的端口号上监听;对于客户端应用程序,需要开启一个端口连接到服务器,或将数据直接发送到服务器端的监听端口,并在客户端自己开启的这个端口上等待服务器的回应。

好了,知道了这些,就可以明明白白地使用网络套接字了。网络套接字的创建也需要使用socket系统调用,它的原形在上一篇里已经说过了,三个参数的含义也都相同。但对于网络套接字,第一个参数要被设置为AF_INET而不是AF_UNIX了。第二个参数可以有三个取值,分别是SOCK_STREAM流式套接字、SOCK_DGRAM数据报式套接字和SOCK_RAW原始套接字。SOCK_STREAM实现了传输层及其以下层次的所有功能,传输层使用TCP协议,用户只需要在TCP的端口上实现应用程序;SOCK_DGRAM同样实现了传输层及其以下层次的所有功能,传输层使用UDP协议,用户只需要在UDP的端口上实现应用程序。当该参数设置为SOCK_STREAM或SOCK_DGRAM时,protocol参数要设置为0。SOCK_RAW为原始套接字,它只提供相应协议报文的封装,其它功能不予提供,具体何种协议的封装取决于protocol字段,此时该字段的值不能设置为0,而是要设置为相应的协议代码。Linux为每个协议代码都定义了一个宏,形式为<IPPROTO_协议名>,例如,想使用TCP协议的原始套接字,则该参数为IPPROTO_TCP值,但该TCP协议只有报文封装,具体的连接建立等协议交互细节需要用户自行实现,用户不能只是简单的在某一端口上实现应用程序。

对于SOCK_STREAM类型,上篇我已经进行了很多的说明,这里只介绍一些不同之处。网络套接字的地址类型是struct sockaddr_in结构,且它是要绑定在IP地址和端口上,而不是一个特定的名字上。其它的原理都一样,服务器端创建套接字,绑定在内含IP地址和端口的struct sockaddr_in结构上,然后在那个端口上做监听,等待连接、接受连接并处理连接;客户端创建套接字,根据服务器的IP地址和端口设置一个struct sockaddr_in变量,连接到服务器,发出请求并等待回应。绑定、监听、接受、连接这些系统调用均与本地套接字相同,不再多说,略有不同的是发送和接收的send和recv系统调用,原形如下:
int send(int socket, const void *msg, int len, int flags);
int recv(int socket, void *buf, int len, unsigned int flags);
其中socket为套接字描述符;msg为想要发送的数据的内存地址,实际上就是发送缓冲区的首地址;len是想要发送数据的长度;buf是接收缓冲区的首地址,接收到的数据会存于其中;flags一般设置为0;返回值是真正发送或接收的数据长度。
具体的代码段就不给出了,和上篇没有什么太大区别,这里给出点有意义的东西,可以供大家使用的,呵呵!我这人还算不错吧!……不多说废话,下面就给出来,两个可用于TCP通信的C++模板类。

// TCP_Communication.h
#ifnef TCP_COMMUNICATION_H_
#define TCP_COMMUNICATION_H_

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>
#include <string>
#include <iostream>
#include <iomanip>

// 错误代码定义
enum TCPCommunicationErrorCode
{
    TCP_RESULT_SUCCESS,
    TCP_RESULT_SERVER_SOCKET_CREATION_ERROR,
    TCP_RESULT_SERVER_ADDRESS_BIND_ERROR,
    TCP_RESULT_SERVER_LISTENING_ERROR,
    TCP_RESULT_SERVER_CONNECTION_ACCEPT_ERROR,
    TCP_RESULT_SERVER_THREAD_CREATION_ERROR,
    TCP_RESULT_SERVER_MESSAGE_SEND_ERROR,
    TCP_RESULT_SERVER_MESSAGE_RECEIVE_ERROR,
    TCP_RESULT_CLIENT_SOCKET_CREATION_ERROR,
    TCP_RESULT_CLIENT_CONNECTION_ERROR,
    TCP_RESULT_CLIENT_MESSAGE_SEND_ERROR,
    TCP_RESULT_CLIENT_MESSAGE_RECEIVE_ERROR
};

// 服务器端标准线程样本函数声明
template<class MESSAGE_TYPE>
int StandardSampleServerThread(void*);

// 服务器端模板类声明
template<class MESSAGE_TYPE>
class TCP_Server
{
    enum
    {
        MAX_STACK_SIZE = 6144,
        MAX_CONNECTION_NUMBER = 10,
        MESSAGE_STORAGE_BUFFER_SIZE = 4096
    };

public:

    TCP_Server(std::string, int);
    virtual ~TCP_Server();
    virtual TCPCommunicationErrorCode Run(int (*)(void*), TCP_Server*);

protected:

    int streamSockFd;
    struct sockaddr_in streamSockAddress;
    char messageStorage[MESSAGE_STORAGE_BUFFER_SIZE];

    TCPCommunicationErrorCode Create();
    TCPCommunicationErrorCode Send(int, char*, int, int*);
    TCPCommunicationErrorCode Receive(int, char*, int, int*);
    TCPCommunicationErrorCode GetInnerMessageStorage(char**);

    virtual TCPCommunicationErrorCode MessageParseReceived(char*, MESSAGE_TYPE*) = 0;
    virtual TCPCommunicationErrorCode MessageProcess(MESSAGE_TYPE, MESSAGE_TYPE*) = 0;
    virtual TCPCommunicationErrorCode MessageParseToSend(MESSAGE_TYPE, char*, int*) = 0;

    friend int StandardSampleServerThread<MESSAGE_TYPE>(void*);
};

// 客户端模板类声明
template<class MESSAGE_TYPE>
class TCP_Client
{
    enum
    {
        MESSAGE_STORAGE_BUFFER_SIZE = 4096
    };

public:

    TCP_Client(std::string, int);

    virtual TCPCommunicationErrorCode MessageParseReceived(char*, MESSAGE_TYPE*) = 0;
    virtual TCPCommunicationErrorCode MessageParseToSend(MESSAGE_TYPE, char*, int*) = 0;

    TCPCommunicationErrorCode Create();
    TCPCommunicationErrorCode Connect();
    TCPCommunicationErrorCode Send(char*, int, int*);
    TCPCommunicationErrorCode Receive(char*, int, int*);
    TCPCommunicationErrorCode GetInnerMessageStorage(char**);

    virtual ~TCP_Client();

protected:

    int streamSockFd;
    struct sockaddr_in streamSockAddress;
    char messageStorage[MESSAGE_STORAGE_BUFFER_SIZE];
};

#endif

// TCP_Communication.cpp
#include "TCP_Communication.h"

// 服务器端的标准样本线程模板函数实现
template<class MESSAGE_TYPE>
int StandardSampleServerThread(void *args)
{
    char *msgBuffer[4096];

    MESSAGE_TYPE recvMsg, sendMsg;

    TCP_Server<MESSAGE_TYPE> *pServerObject;

    int clientSockFd, recvBytes, sendBytes, msgSendLength, actionResult;

    recvBytes = ((int*)args)[0];
    pServerObject = (TCP_Server<MESSAGE_TYPE>*)((int*)args)[1];

    while(1)
    {
        actionResult = pServerObject -> Receive(clientSockFd, msgBuffer, 4096, &recvBytes);
        if(actionResult != TCP_RESULT_SUCCESS)
        {
            close(clientSockFd);
            return -1;
        }

        actionResult = pServerObject -> MessageParseReceived(msgBuffer, recvMsg);
        if(actionResult != TCP_RESULT_SUCCESS)
        {
            close(clientSockFd);
            return -1;
        }
       
        actionResult = pServerObject -> MessageProcess(recvMsg, &sendMsg);
        if(actionResult != TCP_RESULT_SUCCESS)
        {
            close(clientSockFd);
            return -1;
        }
       
        actionResult = pServerObject -> MessageParseToSend(sendMsg, msgBuffer, &msgSendLength);
        if(actionResult != TCP_RESULT_SUCCESS)
        {
            close(clientSockFd);
            return -1;
        }
       
        actionResult = pServerObject -> Send(clientSockFd, msgBuffer, msgSendLength, &sendBytes);
        if(actionResult != TCP_RESULT_SUCCESS)
        {
            close(clientSockFd);
            return -1;
        }       
    }
    close(clientSockFd);
    return 0;
}

// 服务器端构造函数实现
template<class MESSAGE_TYPE>
TCP_Server<MESSAGE_TYPE>::TCP_Server(std::string IP_Address, int Listening_Port)
{
    streamSockAddress.sin_family      = AF_INET;
    streamSockAddress.sin_port        = htons(Listening_Port);
    streamSockAddress.sin_addr.s_addr = inet_addr(IP_Address.data());
    bzero(&streamSockAddress.sin_zero, 8);
}

// 服务器端析构函数实现
template<class MESSAGE_TYPE>
TCP_Server<MESSAGE_TYPE>::~TCP_Server()
{
    close(streamSockFd);
}

// 服务器端套接字创建函数实现
template<class MESSAGE_TYPE>
TCPCommunicationErrorCode TCP_Server<MESSAGE_TYPE>::Create()
{
    streamSockFd = socket(AF_INET, SOCK_STREAM, 0);
    if(streamSockFd == -1)
    {
        return TCP_RESULT_SERVER_SOCKET_CREATION_ERROR;
    }
    return TCP_RESULT_SUCCESS;
}

// 服务器端数据发送函数实现
template<class MESSAGE_TYPE>
TCPCommunicationErrorCode TCP_Server<MESSAGE_TYPE>::Send(int clientSockFd, char *msgBuf, int msgLen, int *transBytes)
{
    int sendResult = send(clientSockFd, msgBuf, msgLen, 0);
    if(sendResult == -1)
    {
        return TCP_RESULT_SERVER_MESSAGE_SEND_ERROR;
    }
    return TCP_RESULT_SUCCESS;
}

// 服务器端接收数据函数实现
template<class MESSAGE_TYPE>
TCPCommunicationErrorCode TCP_Server<MESSAGE_TYPE>::Receive(int clientSockFd, char *msgBuf, int bufLen, int *transBytes)
{
    int recvResult = recv(clientSockFd, msgBuf, bufLen, 0);
    if(recvResult == -1)
    {
        return TCP_RESULT_SERVER_MESSAGE_RECEIVE_ERROR;
    }
    return TCP_RESULT_SUCCESS;
}

// 服务器端获取类内临时存储区域地址的函数实现,为纯虚函数的实现提供方便
template<class MESSAGE_TYPE>
TCPCommunicationErrorCode TCP_Server<MESSAGE_TYPE>::GetInnerMessageStorage(char **pMsgBuf)
{
    *pMsgBuf = messageStorage;
    return TCP_RESULT_SUCCESS;
}

// 服务器端启动函数实现
template<class MESSAGE_TYPE>
TCPCommunicationErrorCode TCP_Server<MESSAGE_TYPE>::Run(int (*threadFunc)(void*), TCP_Server* pServerObj)
{
    int bindResult, listenResult, acceptResult, cloneResult, clientLength, threadFlag, argsThreadFunc[2];
    char *stackAreaPointer;
    struct sockaddr_in clientAddress;
    threadFlag = CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND;

    bindResult = bind(streamSockFd, (struct sockaddr*)&streamSockAddress, sizeof(streamSockAddress));
    if(bindResult == -1)
    {
        return TCP_RESULT_SERVER_ADDRESS_BIND_ERROR;
    }

    listenResult = listen(streamSockFd, MAX_CONNECTION_NUMBER);
    if(listenResult == -1)
    {
        return TCP_RESULT_SERVER_LISTENING_ERROR;
    }
   
    while(1)
    {
        clientLength = sizeof(clientAddress);
        acceptResult = accept(streamSockFd, (struct sockaddr*)&clientAddress, &clientLength);
        if(acceptResult == -1)
        {
            return TCP_RESULT_SERVER_CONNECTION_ACCEPT_ERROR;
        }

        stackAreaPointer = new char[MAX_STACK_SIZE];
        if(stackAreaPointer == NULL)
        {
            return TCP_RESULT_SERVER_THREAD_CREATION_ERROR;
        }

        argsThreadFunc[0] = acceptResult;
        argsThreadFunc[1] = (int)(void*)this;
        cloneResult = clone(threadFunc, &(stackAreaPointer[MAX_STACK_SIZE-1]), threadFlag, (void*)argsThreadFunc);
        if(cloneResult == -1)
        {
            return TCP_RESULT_SERVER_THREAD_CREATION_ERROR;
        }
    }
    return TCP_RESULT_SUCCESS;
}

// 客户端构造函数实现
template<class MESSAGE_TYPE>
TCP_Client<MESSAGE_TYPE>::TCP_Client(std::string IP_Address, int Listening_Port)
{
    streamSockAddress.sin_family      = AF_INET;
    streamSockAddress.sin_port        = htons(Listening_Port);
    streamSockAddress.sin_addr.s_addr = inet_addr(IP_Address.data());
    bzero(&streamSockAddress.sin_zero, 8);
}

// 客户端析构函数实现
template<class MESSAGE_TYPE>
TCP_Client<MESSAGE_TYPE>::~TCP_Client()
{
    close(streamSockFd);
}

// 客户端套接字创建函数实现
template<class MESSAGE_TYPE>
TCPCommunicationErrorCode TCP_Client<MESSAGE_TYPE>::Create()
{
    streamSockFd = socket(AF_INET, SOCK_STREAM, 0);
    if(streamSockFd == -1)
    {
        return TCP_RESULT_CLIENT_SOCKET_CREATION_ERROR;
    }
    return TCP_RESULT_SUCCESS;
}

// 客户端套接字连接函数实现
template<class MESSAGE_TYPE>
TCPCommunicationErrorCode TCP_Client<MESSAGE_TYPE>::Connect()
{
    int connectResult = connect(streamSockFd, (struct sockaddr*)&streamSockAddress, sizeof(streamSockAddress));
    if(connectResult == -1)
    {
        return TCP_RESULT_CLIENT_CONNECTION_ERROR;
    }
    return TCP_RESULT_SUCCESS;
}

// 客户端数据发送函数实现
template<class MESSAGE_TYPE>
TCPCommunicationErrorCode TCP_Client<MESSAGE_TYPE>::Send(char *msgBuf, int msgLen, int *transBytes)
{
    int sendResult = send(streamSockFd, msgBuf, msgLen, 0);
    if(sendResult == -1)
    {
        return TCP_RESULT_CLIENT_MESSAGE_SEND_ERROR;
    }
    return TCP_RESULT_SUCCESS;
}

// 客户端数据接收函数实现
template<class MESSAGE_TYPE>
TCPCommunicationErrorCode TCP_Client<MESSAGE_TYPE>::Receive(char *msgBuf, int bufLen, int *transBytes)
{
    int recvResult = recv(streamSockFd, msgBuf, bufLen, 0);
    if(recvResult == -1)
    {
        return TCP_RESULT_CLIENT_MESSAGE_RECEIVE_ERROR;
    }
    return TCP_RESULT_SUCCESS;
}

// 客户端获取类内临时存储区域地址的函数实现,为纯虚函数的实现提供方便
template<class MESSAGE_TYPE>
TCPCommunicationErrorCode TCP_Client<MESSAGE_TYPE>::GetInnerMessageStorage(char **pMsgBuf)
{
    *pMsgBuf = messageStorage;
    return TCP_RESULT_SUCCESS;
}

呵呵,好长的一大段代码,我也写了好长时间诶!有点累嘿嘿!不过还是坚持写完好了!现在给大家解释下吧!开始声明的样本线程函数是可以直接使用的,它从框架上实现了服务器的功能,包括接收请求,处理请求和发送结果,但由于它是死循环,所以不适用于大多数的场合,大家可以根据需要重写一个函数来完成服务器的处理功能。其它的非纯虚函数均与套接字的基本操作相关,非常易懂,大家一看就能明白,一定没有任何问题的,呵呵!两个抽象模板类在使用时都需要派生,并且要实现对应于类型和字节流转化的纯虚函数。纯虚函数中,MessageParseReceived函数是将接收到的网络字节流转化为用户选定的MESSAGE_TYPE类型的消息;MessageParseToSend函数是将用户选定的MESSAGE_TYPE类型转化成字节流,它的最后一个int地址参数是输出参数,带回的值是字节流的长度;服务器端MessageProcess函数是将要处理的MESSAGE_TYPE消息(第一个参数,输入参数)进行处理后,生成MESSAGE_TYPE消息类型的结果(第二个参数,输出参数)并返回。服务器端的虚函数Run也可以重写。这两个抽象模板类具有很强的通用性,可供大家学习和使用。关于SOCK_STREAM类型,本人也就不再多说了,一是上篇已经说了很多,二是这两个模板类已经给出来,大家看看就一定能明白。

SOCK_DGRAM类型的数据报套接字……

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值