Socket编程实践(7) --Socket-Class封装(改进版v2)

简介:     本篇博客定义一套用于TCP通信比较实用/好用Socket类库(运用C++封装的思想,将socket API尽量封装的好用与实用), 从开发出Socket库的第一个版本以来, 作者不知...

    本篇博客定义一套用于TCP通信比较实用/好用Socket类库(运用C++封装的思想,将socket API尽量封装的好用与实用), 从开发出Socket库的第一个版本以来, 作者不知道做了多少改进, 每次有新的/好的想法尽量实现到该库当中来; 而且我还使用该库开发出作者第一个真正意义上的基于Linux的Server程序[MyHttpd, 在后续的博客当中, 我一定会将MyHttpd的实现原理与实现代码更新到这篇博客所属的专栏中, 希望读者朋友不吝赐教];

    可能在以后的学习和工作中, 作者还可能会加入新的功能和修复发现的BUG, 因此, 如果有读者喜欢这个Socket库, 请持续关注这篇博客, 我会把最新的更新信息都发布到这篇博客当中, 当然, 如果有读者朋友发现了这个Socket库的BUG, 还希望读者朋友不吝赐教, 谢谢您的关注;

 

实现中的几个注意点:

    1)TCPSocket类几个成员函数的访问权限为protected, 使Socket类可以进行继承,但不允许私自使用;

    2)TCPClient类的send/receive方法使用了著名的writen/readn(来源UNP)实现, 解决了TCP的粘包问题.

    3)TCPServer端添加了地址复用, 可以方便TCP服务器重启;

    4)添加了异常类,让我们在编写易出错的代码时,可以解放思想,不用一直考虑该函数调用出错会发生什么情况!

    5)TCPSocket类中添加了getfd接口, 如果有这三个类完成不了的功能, 则可以将socket获取出来, 使用Linux的系统调用完成相应的功能;

    6)TCPClient中有好几个发送/接受的接口, 其中, 使用send发送的数据一定要使用receive来接收, 因为作者使用的是自定义应用层协议来解决的TCP粘包问题, 请读者朋友注意;


由于实现思想较简单, 因此在代码中并未添加大量的注释, 请读者耐心读下去, 在博文结尾处, 会有该库的测试使用示例与Makefile文件, 并将整个文件夹(完整的项目实现源代码)放到了CSDN的下载资源(不需要下载分的O(∩_∩)O~)中, 下载链接见下:

http://download.csdn.net/detail/hanqing280441589/8486489

Socket类

TCPSocket/TCPClient/TCPServer类设计

class TCPSocket
{
protected:
    TCPSocket();
    virtual ~TCPSocket();

    bool create();
    bool bind(unsigned short int port, const char *ip = NULL) const;
    bool listen(int backlog = SOMAXCONN) const;
    bool accept(TCPSocket &clientSocket) const;
    bool connect(unsigned short int port, const char *ip) const;

    /**注意: TCPSocket基类并没有send/receive方法**/

    bool reuseaddr() const;
    bool isValid() const
    {
        return (m_sockfd != -1);
    }
public:
    bool close();
    int getfd() const
    {
        return m_sockfd;
    }
    //flag: true=SetNonBlock, false=SetBlock
    bool setNonBlocking(bool flag) const;

protected:
    int m_sockfd;
};
/** TCP Client **/
class TCPClient : public TCPSocket
{
private:
    struct Packet
    {
        unsigned int    msgLen;     //数据部分的长度(网络字节序)
        char            text[1024]; //报文的数据部分
    };
public:
    TCPClient(unsigned short int port, const char *ip) throw(SocketException);
    TCPClient();
    TCPClient(int clientfd);
    ~TCPClient();

    size_t send(const std::string& message) const throw(SocketException);
    size_t receive(std::string& message) const throw(SocketException);
    size_t read(void *buf, size_t count) throw(SocketException);
    void   write(const void *buf, size_t count) throw(SocketException);
    size_t write(const char *msg) throw(SocketException);
};
/** TCP Server **/
class TCPServer : public TCPSocket
{
public:
    TCPServer(unsigned short int port, const char *ip = NULL, int backlog = SOMAXCONN) throw(SocketException);
    ~TCPServer();
    void accept(TCPClient &client) const throw(SocketException);
    TCPClient accept() const throw(SocketException);
};

TCPSocket/TCPClient/TCPServer类实现

TCPSocket::TCPSocket(): m_sockfd(-1) {}
TCPSocket::~TCPSocket()
{
    if (isValid())
        ::close(m_sockfd);
}

bool TCPSocket::create()
{
    if (isValid())
        return false;

    if ((m_sockfd = ::socket(AF_INET, SOCK_STREAM, 0)) == -1)
        return false;
    return true;
}

bool TCPSocket::bind(unsigned short int port, const char *ip) const
{
    if (!isValid())
        return false;

    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    if (ip == NULL)
        addr.sin_addr.s_addr = htonl(INADDR_ANY);
    else
        addr.sin_addr.s_addr = inet_addr(ip);
    if ( ::bind(m_sockfd, (const struct sockaddr *)&addr, sizeof(addr)) == -1 )
        return false;
    return true;
}
bool TCPSocket::listen(int backlog) const
{
    if (!isValid())
        return false;

    if ( ::listen(m_sockfd, backlog) == -1)
        return false;
    return true;
}
bool TCPSocket::accept(TCPSocket &clientSocket) const
{
    if (!isValid())
        return false;

    clientSocket.m_sockfd =
        ::accept(this->m_sockfd, NULL, NULL);
    if (clientSocket.m_sockfd == -1)
        return false;
    return true;
}

bool TCPSocket::connect(unsigned short int port, const char *ip) const
{
    if (!isValid())
        return false;

    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = inet_addr(ip);
    if ( ::connect(m_sockfd, (const struct sockaddr *)&addr, sizeof(addr)) == -1)
        return false;
    return true;
}

bool TCPSocket::setNonBlocking(bool flag) const
{
    if (!isValid())
        return false;
    int opt = fcntl(m_sockfd, F_GETFL, 0);
    if (opt == -1)
        return false;
    if (flag)
        opt |= O_NONBLOCK;
    else
        opt &= ~O_NONBLOCK;
    if (fcntl(m_sockfd, F_SETFL, opt) == -1)
        return false;
    return true;
}
bool TCPSocket::reuseaddr() const
{
    if (!isValid())
        return false;

    int on = 1;
    if (setsockopt(m_sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1)
        return false;
    return true;
}
bool TCPSocket::close()
{
    if (!isValid())
        return false;
    ::close(m_sockfd);
    m_sockfd = -1;
    return true;
}
/** client TCP Socket **/
TCPClient::TCPClient(unsigned short int port, const char *ip)
throw(SocketException)
{
    if (create() == false)
        throw SocketException("tcp client create error");
    if (connect(port, ip) == false)
        throw SocketException("tcp client connect error");
}
TCPClient::TCPClient() {}
TCPClient::TCPClient(int clientfd)
{
    m_sockfd = clientfd;
}
TCPClient::~TCPClient() {}
/** client端特有的send/receive **/
static ssize_t readn(int fd, void *buf, size_t count);
static ssize_t writen(int fd, const void *buf, size_t count);

//send
size_t TCPClient::send(const std::string& message)
const throw(SocketException)
{
    Packet buf;
    buf.msgLen = htonl(message.length());
    strcpy(buf.text, message.c_str());
    if (writen(m_sockfd, &buf, sizeof(buf.msgLen)+message.length()) == -1)
        throw SocketException("tcp client writen error");
    return message.length();
}
//receive
size_t TCPClient::receive(std::string& message)
const throw(SocketException)
{
    //首先读取头部
    Packet buf = {0, 0};
    size_t readBytes = readn(m_sockfd, &buf.msgLen, sizeof(buf.msgLen));
    if (readBytes == (size_t)-1)
        throw SocketException("tcp client readn error");
    else if (readBytes != sizeof(buf.msgLen))
        throw SocketException("peer connect closed");

    //然后读取数据部分
    unsigned int lenHost = ntohl(buf.msgLen);
    readBytes = readn(m_sockfd, buf.text, lenHost);
    if (readBytes == (size_t)-1)
        throw SocketException("tcp client readn error");
    else if (readBytes != lenHost)
        throw SocketException("peer connect closed");
    message = buf.text;
    return message.length();
}
size_t TCPClient::read(void *buf, size_t count) throw(SocketException)
{
    ssize_t readBytes = ::read(m_sockfd, buf, count);
    if (readBytes == -1)
        throw SocketException("tcp client read error");
    return (size_t)readBytes;
}
void TCPClient::write(const void *buf, size_t count) throw(SocketException)
{
    if ( ::write(m_sockfd, buf, count) == -1 )
        throw SocketException("tcp client write error");
}
size_t TCPClient::write(const char *msg) throw(SocketException)
{
    if ( ::write(m_sockfd, msg, strlen(msg)) == -1 )
        throw SocketException("tcp client write error");
    return strlen(msg);
}
/** Server TCP Socket**/
TCPServer::TCPServer(unsigned short int port, const char *ip, int backlog)
throw(SocketException)
{
    if (create() == false)
        throw SocketException("tcp server create error");
    if (reuseaddr() == false)
        throw SocketException("tcp server reuseaddr error");
    if (bind(port, ip) == false)
        throw SocketException("tcp server bind error");
    if (listen(backlog) == false)
        throw SocketException("tcp server listen error");
}
TCPServer::~TCPServer() {}
void TCPServer::accept(TCPClient &client) const
throw(SocketException)
{
    //显式调用基类TCPSocket的accept
    if (TCPSocket::accept(client) == -1)
        throw SocketException("tcp server accept error");
}
TCPClient TCPServer::accept() const
throw(SocketException)
{
    TCPClient client;
    if (TCPSocket::accept(client) == -1)
        throw SocketException("tcp server accept error");
    return client;
}
/** readn/writen实现部分 **/
static ssize_t readn(int fd, void *buf, size_t count)
{
    size_t nLeft = count;
    ssize_t nRead = 0;
    char *pBuf = (char *)buf;
    while (nLeft > 0)
    {
        if ((nRead = read(fd, pBuf, nLeft)) < 0)
        {
            //如果读取操作是被信号打断了, 则说明还可以继续读
            if (errno == EINTR)
                continue;
            //否则就是其他错误
            else
                return -1;
        }
        //读取到末尾
        else if (nRead == 0)
            return count-nLeft;

        //正常读取
        nLeft -= nRead;
        pBuf += nRead;
    }
    return count;
}
static ssize_t writen(int fd, const void *buf, size_t count)
{
    size_t nLeft = count;
    ssize_t nWritten = 0;
    char *pBuf = (char *)buf;
    while (nLeft > 0)
    {
        if ((nWritten = write(fd, pBuf, nLeft)) < 0)
        {
            //如果写入操作是被信号打断了, 则说明还可以继续写入
            if (errno == EINTR)
                continue;
            //否则就是其他错误
            else
                return -1;
        }
        //如果 ==0则说明是什么也没写入, 可以继续写
        else if (nWritten == 0)
            continue;

        //正常写入
        nLeft -= nWritten;
        pBuf += nWritten;
    }
    return count;
}

SocketException类

//SocketException类的设计与实现
class SocketException
{
public:
    typedef std::string string;
    SocketException(const string &_msg = string())
        : msg(_msg) {}
    string what() const
    {
        if (errno == 0)
            return msg;
        //如果errno!=0, 则会加上错误描述
        return msg + ": " + strerror(errno);
    }

private:
    string msg;
};

Socket类测试(echo)

Server端测试代码

void sigHandler(int signo)
{
    while (waitpid(-1, NULL, WNOHANG) > 0)
        ;
}

int main()
{
    signal(SIGCHLD, sigHandler);
    signal(SIGPIPE, SIG_IGN);
    try
    {
        TCPServer server(8001);
        std::string msg;
        while (true)
        {
            TCPClient client = server.accept();
            pid_t pid = fork();
            if (pid == -1)
                err_exit("fork error");
            else if (pid > 0)
                client.close();
            else if (pid == 0)
            {
                try
                {
                    while (true)
                    {
                        client.receive(msg);
                        cout << msg << endl;
                        client.send(msg);
                    }
                }
                catch (const SocketException &e)
                {
                    cerr << e.what() << endl;
                    exit(EXIT_FAILURE);
                }
                exit(EXIT_SUCCESS);
            }
        }
    }
    catch (const SocketException &e)
    {
        cerr << e.what() << endl;
        exit(EXIT_FAILURE);
    }
}

Client端测试代码

int main()
{
    signal(SIGPIPE, SIG_IGN);
    try
    {
        TCPClient client(8001, "127.0.0.1");
        std::string msg;
        while (getline(cin, msg))
        {
            client.send(msg);
            msg.clear();
            client.receive(msg);
            cout << msg << endl;
            msg.clear();
        }
    }
    catch (const SocketException &e)
    {
        cerr << e.what() << endl;
    }
}

Makefile

.PHONY: clean all 
CC = g++ 
CPPFLAGS = -Wall -g -pthread -std=c++11
BIN = serverclient
SOURCES = $(BIN.=.cpp)

all: $(BIN)
$(BIN): $(SOURCES) Socket.cpp

clean:
    -rm -rf $(BIN) bin/ obj/ core


目录
相关文章
|
4月前
|
网络协议 网络性能优化 开发者
Python网络编程基础(Socket编程)UDP Socket编程
【4月更文挑战第8天】Python网络编程中,UDP与TCP协议各有特点。TCP提供可靠连接,确保数据顺序与完整性,适合文件传输等;UDP则无连接,速度快,常用于实时音视频,牺牲了数据可靠性。Python的socket库支持两者,开发者可根据需求选择。
|
4月前
|
网络协议 Python
Python网络编程基础(Socket编程)创建UDP socket对象
【4月更文挑战第8天】在Python中创建UDP服务器涉及使用`socket`模块创建socket对象,如`udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)`,然后绑定到特定IP地址和端口,如`udp_socket.bind((&#39;localhost&#39;, 12345))`。服务器通过`recvfrom`在无限循环中监听和接收数据报。这只是基础,实际应用还需处理接收、解析、响应及错误处理等。接下来可学习如何利用socket对象进行数据交互以构建完整服务器。
|
4月前
|
API C++
socket编程之常用api介绍与socket、select、poll、epoll高并发服务器模型代码实现(1)
前言   本文旨在学习socket网络编程这一块的内容,epoll是重中之重,后续文章写reactor模型是建立在epoll之上的。
70 0
|
3月前
|
缓存 网络协议 Linux
c++实战篇(三) ——对socket通讯服务端与客户端的封装
c++实战篇(三) ——对socket通讯服务端与客户端的封装
|
20天前
|
网络协议 C# 开发者
WPF与Socket编程的完美邂逅:打造流畅网络通信体验——从客户端到服务器端,手把手教你实现基于Socket的实时数据交换
【8月更文挑战第31天】网络通信在现代应用中至关重要,Socket编程作为其实现基础,即便在主要用于桌面应用的Windows Presentation Foundation(WPF)中也发挥着重要作用。本文通过最佳实践,详细介绍如何在WPF应用中利用Socket实现网络通信,包括创建WPF项目、设计用户界面、实现Socket通信逻辑及搭建简单服务器端的全过程。具体步骤涵盖从UI设计到前后端交互的各个环节,并附有详尽示例代码,助力WPF开发者掌握这一关键技术,拓展应用程序的功能与实用性。
42 0
|
2月前
|
网络协议 开发者 Python
深度探索Python Socket编程:从理论到实践,进阶篇带你领略网络编程的魅力!
【7月更文挑战第25天】在网络编程中, Python Socket编程因灵活性强而广受青睐。本文采用问答形式深入探讨其进阶技巧。**问题一**: Socket编程基于TCP/IP,通过创建Socket对象实现通信,支持客户端和服务器间的数据交换。**问题二**: 提升并发处理能力的方法包括多线程(适用于I/O密集型任务)、多进程(绕过GIL限制)和异步IO(asyncio)。**问题三**: 提供了一个使用asyncio库实现的异步Socket服务器示例,展示如何接收及响应客户端消息。通过这些内容,希望能激发读者对网络编程的兴趣并引导进一步探索。
29 4
|
3月前
|
程序员 API 开发者
Socket与HTTP协议的实践
【6月更文挑战第4天】本文介绍了Python中的网络编程,包括Socket编程和基于HTTP协议的实践。Socket编程是网络通信的基础,Python的`socket`模块简化了其使用。文中展示了服务器和客户端的简单示例,以及如何通过多线程处理多个客户端连接。另外,文章讨论了HTTP协议,推荐了`requests`库,并给出了发送GET和POST请求的例子。最后,总结了Socket编程和HTTP协议在网络编程中的应用及其在Web开发和API交互中的重要性。
36 5
|
3月前
|
移动开发 Java
Java Socket编程 - 基于Socket实现HTTP下载客户端
Java Socket编程 - 基于Socket实现HTTP下载客户端
30 1
|
4月前
|
监控 安全 Linux
socket编程之常用api介绍与socket、select、poll、epoll高并发服务器模型代码实现(3)
高并发服务器模型-poll poll介绍   poll跟select类似, 监控多路IO, 但poll不能跨平台。其实poll就是把select三个文件描述符集合变成一个集合了。
64 0
|
4月前
|
存储 算法 网络协议
【探索Linux】P.26(网络编程套接字基本概念—— socket编程接口 | socket编程接口相关函数详细介绍 )
【探索Linux】P.26(网络编程套接字基本概念—— socket编程接口 | socket编程接口相关函数详细介绍 )
44 0