网络编程 套接字编程(二)TCPSocket的封装、TCP客户端/服务器多进程、多线程实现(C++)


TCP的通信流程

计算机网络 (三) 传输层 :一文搞懂UDP与TCP协议
在这篇博客中,我描述了UDP与TCP的特性以及通信流程,下面就根据特性来规划该如何通过Socket来实现UDP通信。
在这里插入图片描述

TCPSocket的封装

为了使用更方便,先封装一个TCPSocket

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

const int MAX_LISTEN = 5;

inline void CheckSafe(bool ret)
{
    if(ret == false)
    {
        exit(0);
    }
}

class TcpSocket
{
    public:
        TcpSocket() : _socket_fd(-1)
        {}

        //创建套接字
        bool Socket()
        {
            _socket_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

            if(_socket_fd < 0)
            {
                std::cerr << "socket create error" << std::endl;
                return false;
            }
            return true;
        }
        
        //绑定地址信息
        bool Bind(const std::string& ip, uint16_t& port)
        {
            struct sockaddr_in addr;
            addr.sin_family = AF_INET;
            addr.sin_port = htons(port);
            addr.sin_addr.s_addr = inet_addr(ip.c_str());

            socklen_t len = sizeof(sockaddr_in);

            int ret = bind(_socket_fd, (sockaddr*)&addr, len);
            
            if(ret < 0)
            {
                std::cerr << "bind error" << std::endl;
                return false;
            }
            return true;
        }
                
        //监听
        bool Listen(int backlog = MAX_LISTEN)
        {
            //用初始的套接字开始监听
            int ret = listen(_socket_fd, backlog);

            if(ret < 0)
            {
                std::cerr << "connect error" << std::endl;
            }

            return true;
        }

        //新建连接
        bool Accept(TcpSocket *new_sock, std::string* ip = NULL, uint16_t* port = NULL)
        {
            struct sockaddr_in addr;
            socklen_t len = sizeof(sockaddr_in);

            //创建一个新的套接字与客户端建立连接
            int new_fd = accept(_socket_fd, (sockaddr*)&addr, &len);
      
            if(new_fd < 0)
            {
                std::cerr << "accept error" << std::endl;
            }

            new_sock->_socket_fd = new_fd;

            if(ip != NULL)
            {
                *ip = inet_ntoa(addr.sin_addr);
            }

            if(port != NULL)
            {
                *port = ntohs(addr.sin_port);
            }

            return true;
        }
    
        //发起连接请求
        bool Connect(const std::string& ip, uint16_t port)
        {
            struct sockaddr_in addr;
            addr.sin_family = AF_INET;
            addr.sin_port = htons(port);
            addr.sin_addr.s_addr = inet_addr(ip.c_str());

            socklen_t len = sizeof(sockaddr_in);

            int ret = connect(_socket_fd, (sockaddr*)&addr, len);

            
            if(ret < 0)
            {
                std::cerr << "connect error" << std::endl;
            }
            return true;
        }

        //发送数据
        bool Send(const std::string& data)
        {
            int ret = send(_socket_fd, data.c_str(), data.size(), 0); 
            
            if(ret < 0)
            {
                std::cerr << "send error" << std::endl;
            }

            return true;
        }

        //接收数据
        bool Recv(std::string& data)
        {
            char buff[4096] = { 0 };
            
            int ret = recv(_socket_fd, buff, 4096, 0);

            if(ret == 0)
            {
                std::cerr << "connect error" << std::endl;
                return false;
            }
            else if(ret < 0)
            {
                std::cerr << "recv error" << std::endl;
                return false;
            }
            
            data.assign(buff, ret);

            return true;
        }

        void Close()
        {
            if(_socket_fd > 0)
            {
                close(_socket_fd);
                _socket_fd = -1;
            }
        }
               
    private:
        int _socket_fd;
};

TCP客户端

在这里插入图片描述
按照这个流程,来实现TCP的客户端

#include<iostream>
#include"TcpSocket.hpp"


using namespace std;

int main(int argc, char* argv[])
{
   	if(argc != 3)
    {   
        cerr << "正确输入方式: ./tcp_cli.cc ip port\n" << endl;
        return -1; 
    } 
  
	string srv_ip = argv[1];
	uint16_t srv_port = stoi(argv[2]);
 
	TcpSocket socket;
	//创建套接字
    CheckSafe(socket.Socket());
	//申请连接服务器
	CheckSafe(socket.Connect(srv_ip, srv_port));

	while(1)
	{
		string data;
		cout << "cli send message: ";
		getline(cin, data);
		
		if(data == "quit")
		{
			break;
		}

		//发送数据
		CheckSafe(socket.Send(data));
		data.clear();

        //接收数据
        CheckSafe(socket.Recv(data));
        cout << "srv recv message :" << data << endl;
	}

    //关闭套接字
    socket.Close();

    return 0;
}


TCP服务器

在这里插入图片描述
因为在一段时间内可能会建立多个连接,所以多个执行流分别去控制这些连接。每当创建一个新连接,就分配一个新的执行流去执行它。

下面就分别实现多进程版本和多线程版本的服务器

多进程版本

注意:因为父子进程数据独有,子进程会拷贝一份父进程,所以对于父进程来说新建的套接字用不到,需要关闭。父进程也不需要去阻塞等待子进程,处理好信号即可

#include<iostream>
#include<signal.h>
#include<sys/wait.h>
#include"TcpSocket.hpp"

using namespace std;

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

int main(int argc, char* argv[])
{
    if(argc != 3)
    {   
        cerr << "正确输入方式: ./tcp_srv_process.cc. ip port\n" << endl;
        return -1; 
    } 

    signal(SIGCHLD, sigcb);
    string srv_ip = argv[1];
    uint16_t srv_port = stoi(argv[2]);

    TcpSocket socket;
    //创建套接字
    CheckSafe(socket.Socket());
    //绑定地址信息
    CheckSafe(socket.Bind(srv_ip, srv_port));
    //开始监听
    CheckSafe(socket.Listen());

    while(1)
    {
        TcpSocket new_sock;
        //通过监听套接字建立连接
        CheckSafe(socket.Accept(&new_sock));

        //创建子进程
        int pid = fork();

        if(pid == 0)
        {
            while(1)
            {
                string data;
                //接收数据
                CheckSafe(new_sock.Recv(data));
                cout << "cli send message: " << data << endl;
                data.clear();

                cout << "srv reply message: ";
                //发送数据
                
                getline(cin, data);
                CheckSafe(new_sock.Send(data));
            }

            //关闭子进程连接的套接字
            new_sock.Close();
            exit(0);
        }
        
        //关闭父进程套接字
        new_sock.Close();
    }
    //关闭监听套接字
    socket.Close();
    return 0;
}

多线程版本

注意:因为线程之间共享描述符表,所以主线程创建线程之后千万不能关闭新建的套接字。

#include<iostream>
#include"TcpSocket.hpp"
#include<pthread.h>

using namespace std;

void* thr_work(void* arg)
{
    //因为参数只能是void*,所以要强转获取操作句柄,所以读取前四个字节即可
    
    long fd = (long)arg;
    TcpSocket new_sock;
    new_sock.SetFd(fd);

    while(1)
    {
        string data;
        //接收数据
        new_sock.Recv(data);
        cout << "cli send message: " << data << endl;
        data.clear();

        cout << "srv reply message: ";
        //发送数据
        getline(cin, data);
        new_sock.Send(data);

    }
    new_sock.Close();
    return nullptr;
}

int main(int argc, char* argv[])
{
    if(argc != 3)
    {   
        cerr << "正确输入方式: ./tcp_srv_thread.cc. ip port\n" << endl;
        return -1; 
    } 

    string srv_ip = argv[1];
    uint16_t srv_port = stoi(argv[2]);

    TcpSocket lst_socket;
    //创建监听套接字
    CheckSafe(lst_socket.Socket());
    //绑定地址信息
    CheckSafe(lst_socket.Bind(srv_ip, srv_port));
    //开始监听
    CheckSafe(lst_socket.Listen());

    while(1)
    {
        TcpSocket* new_sock = new TcpSocket();
        //通过监听套接字获取连接
        bool ret = lst_socket.Accept(new_sock);
      
        //连接失败则重新连接
        if(!ret)
        {
            cerr << "连接失败" << endl;
            continue;
        }

        //创建线程
        pthread_t tid;
        
        int res = pthread_create(&tid, NULL, thr_work, (void*)new_sock->GetFd());

        if(res != 0)
        {
            cerr << "线程创建失败" << endl;
            continue;
        }
        
        //不需要知道返回值,所以直接分离线程
        pthread_detach(tid);
    }

    //关闭监听套接字
    lst_socket.Close();
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_35423154/article/details/107390091