Linux网络套接字类封装

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

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

// 服务器端模板类声明
class TCPServer
{
    enum
    {
        MAX_STACK_SIZE = 6144,
        MAX_CONNECTION_NUMBER = 10,
        MESSAGE_STORAGE_BUFFER_SIZE = 4096
    };

public:

    TCPServer(std::string, int);
    virtual ~TCPServer();
    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*);
};

// 客户端模板类声明
class TCPClient
{
    enum
    {
        MESSAGE_STORAGE_BUFFER_SIZE = 4096
    };

public:

    TCPClient(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 ~TCPClient();

protected:

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

#endif

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

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

    MESSAGE_TYPE recvMsg, sendMsg;

    TCPServer *pServerObject;

    int clientSockFd, recvBytes, sendBytes, msgSendLength, actionResult;

    recvBytes = ((int*)args)[0];
    pServerObject = (TCP_Server*)((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;
}

// 服务器端构造函数实现
TCPServer::TCPServer(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);
}

// 服务器端析构函数实现
TCPServer::~TCPServer()
{
    close(streamSockFd);
}

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

// 服务器端数据发送函数实现
TCPCommunicationErrorCode TCPServer::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;
}

// 服务器端接收数据函数实现
TCPCommunicationErrorCode TCPServer::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;
}

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

// 服务器端启动函数实现
TCPCommunicationErrorCode TCPServer::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;
}

// 客户端构造函数实现
TCPClient::TCPClient(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);
}

// 客户端析构函数实现
TCPClient::~TCPClient()
{
    close(streamSockFd);
}

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

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

// 客户端数据发送函数实现
TCPCommunicationErrorCode TCPClient::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;
}

// 客户端数据接收函数实现
TCPCommunicationErrorCode TCPClient::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;
}

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

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


SOCK_DGRAM 类型的数据报套接字在传输层上使用 UDP 协议。UDP 协议的效率高于 TCP 协议,但其可靠性远远低于 TCP 协议。TCP 协议使用应答包机制和重传机制保证数据包可以可靠的从一段传输到另一端,而 UDP 协议不采用任何差错控制机制,减少了数据包的传输,因此效率高于 TCP 协议,可靠性低于 TCP 协议,适合对传输速率要求高,但可靠性要求低的场合(例如语音、视频和网络电视等)。SOCK_DGRAM 类型的套接字不需要在特定端口上进行显式的监听操作,而是使用 recvfrom 系统调用直接从特定端口上收取数据,使用 sendto 向特定端口发送数据,操作非常简单。两个系统调用的原型如下:

int sendto(int socket, const void *msg, int len, unsigned int flags, const struct sockaddr *to , int tolen);
ssize_t recvfrom(int socket, void *buf, size_t len, int flags, struct sockaddr *from, socklen_t *fromlen);

这两个系统调用比 send 和 recv 分别多出了 to/from 和 tolen/fromlen 参数。to/from 参数是要发送到的进程地址(其中包括 IP 地址和端口号),tolen 参数表示要发送到的目标地址长度,一般传送 sizeof(struct sockaddr) 实参;fromlen 参数既是输入参数也是输出参数,一般使用一个变量,赋其值为 sizeof(struct sockaddr),再传入该变量的地址即可。其他参数的用法与 send/recv 相同。

SOCK_DGRAM 类型的 UDP 服务器,在使用 socket 系统调用创建套接字后,直接 bind 到相应的端口即可,然后使用 recvfrom 系统调用接收客户端发送过来的数据;SOCK_DGRAM 类型的 UDP 客户端,在使用 socket 系统调用创建套接字后,就可以直接使用 sendto 和 recvfrom 从相应端口收发数据了。下面两段小代码展示了服务器和客户端的初始化过程(以本地通信,服务器端口2345为例):

// UDP 服务器
int serverSockFd = socket(AF_INET, SOCK_DGRAM, 0);
struct sockaddr_in dgramSockAddress;
dgramSockAddress.sin_family      = AF_INET;
dgramSockAddress.sin_port        = htons(2345);
dgramSockAddress.sin_addr.s_addr = inet_addr("127.0.0.1");
bzero(&dgramSockAddress.sin_zero, 8);
bind(dgramSockFd, (struct sockaddr*)&dgramSockAddress, sizeof(dgramSockAddress));
char recvBuffer[1024];
char sendBuffer[1024];
int sendlen, fromlen;
struct sockaddr_in clientSockAddress;
while (true)
{
    fromlen = sizeof(struct sockaddr);
    if (recvfrom(serverSockFd, recvBuffer, 1024, 0, (struct sockaddr *)&clientSockAddress, &fromlen) >= 0)
    {
        // do some process, put sending message to "sendBuffer", message length to "sendlen" ...
        if (sendto(serverSockFd, sendBuffer, sendlen, 0, (struct sockaddr *)&clientSockAddress, sizeof(struct sockaddr)) >= 0)
        {
            // do some other process ...
        }
        else
        {
            // do wrong process ...
        }
    }
    else
    {
        // do wrong process ...
    }
}

// UDP 客户端
int clientSockFd = socket(AF_INET, SOCK_DGRAM, 0);
struct sockaddr_in serverSockAddress;
serverSockAddress.sin_family      = AF_INET;
serverSockAddress.sin_port        = htons(2345);
serverSockAddress.sin_addr.s_addr = inet_addr("127.0.0.1");
bzero(&serverSockAddress.sin_zero, 8);
char recvBuffer[1024];
char sendBuffer[1024];
int sendlen, fromlen;
// do some process, put sending message to "sendBuffer", message length to "sendlen" ...
if (sendto(clientSockFd, sendBuffer, sendlen, (struct sockaddr *)&serverSockAddress, sizeof(struct sockaddr)) >= 0)
{
    // do some process, usually like "recvfrom" above ...
}
else
{
    // do wrong process ...
}

由于 UDP 服务器和客户端均比较简单,这里就不再给出 C++ 类了,请大家参照上面 TCP 类的样式自己去封装。

SOCK_RAW 类型的原始套接字可以直接对承载协议本身进行操作,操作相对比较底层,封装性差,使用起来不那么方便。但是,原始套接字可以做更加基础的事,比如在传输层之下,直接使用 IP 协议,或者使用 ARP/ICMP 等其他网络层协议。但是协议时序需要自己去实现,例如,用原始套接字实现 TCP 协议,则 TCP 连接时的三次握手包,应答包等,均需要自己来构造,非常麻烦,故不常用,这里不多介绍。但是,如果编写 ICMP traceroute 或者 ping 程序的话,那么原始套接字就是首选了。一般进程间的通信不使用原始套接字,而与操作系统协议栈下层通信的时,才考虑使用原始套接字。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值