Linux网络编程 | Socket编程(二)TCPSocket的封装、TCP服务器多进程、多线程版本的实现


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;
}

  • 3
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: MFC Socket库是一个运用在Windows平台上的网络编程库,它是Microsoft Foundation Classes(MFC)的一部分。通过MFC Socket库,开发人员可以开发客户端和服务器端应用程序,实现网络通信功能。MFC Socket库提供了套接字(Socket)编程的封装,使得开发人员易于使用。同时,它还提供了各种回调函数和事件处理机制,用于处理网络通信时产生的各种事件,实现了高效的网络通信。 MFC Socket库支持TCP和UDP协议的网络编程,使得网络应用程序的开发变得更加灵活和高效。它提供了Socket类,包括CSocket,CAsyncSocket和CClientSocket等,这些类可以让开发人员轻松地实现网络编程功能。开发人员也可以通过MFC Socket实现基于多线程多进程网络应用程序,提高系统的并发处理能力。 总之,MFC Socket库是一个非常实用的网络编程库,开发人员可以快速地构建高效的网络应用程序。它不仅提供了一些常用的网络编程功能,如TCP和UDP通信、文件传输和邮件发送等,还可以支持自定义协议的实现,例如HTTP和FTP等。MFC Socket库是Windows平台下网络编程必不可少的组件之一。 ### 回答2: MFC是Microsoft Foundation Classes(微软基础类)的缩写,它是一套面向对象的C++类库,专门为Windows操作系统的应用程序开发而设计。而MFC socket库则是其中的一部分,用于支持网络编程,在实现客户端和服务端之间的通信时发挥重要作用。 MFC socket库的主要功能是通过所提供的基于Windows Socket API的函数接口,让开发人员轻松地实现网络socket通信。MFC socket库提供了多个类来帮助开发人员构建客户端和服务端应用程序,比如CAsyncSocket可以用来创建异步socket对象,向服务器发送请求,等待服务器响应请求,以及接收服务器返回的数据。此外,MFC socket库还支持Socket多线程应用,可以在多个线程之间共享Socket连接,提高了程序的并发性和效率。 MFC socket库的编程实现相对简单,只需要在MFC应用程序中包含相关的头文件,通过创建Socket对象、指定Socket地址和端口等方法,就可以轻松实现网络连接。不仅如此,MFC socket库还支持各种类型的TCP和UDP协议,开发人员可以根据具体的需求选择不同的协议进行通信。 总的来说,MFC socket库是一个十分重要的网络编程工具之一,它提供了简单、易用的API和各种常用协议的支持,使得开发人员可以轻松地实现Windows平台下的网络通信应用程序。 ### 回答3: MFC是Microsoft Foundation Class的缩写,它是一种开发Windows平台应用程序的框架,用于简化Windows编程的复杂度。其中,socket库是MFC中的一种网络编程库,用于实现网络通信功能。 Socket库的主要作用是实现应用程序之间的数据传输,包括TCP、UDP等不同传输协议。它提供了一系列的函数和方法,用于创建、连接、发送、接收等操作,灵活而强大。通过Socket库,MFC应用程序可以与其他计算机或设备进行数据交互,实现数据通信、远程控制、数据同步等功能。 在使用Socket库时,需要注意一些问题。首先,Socket库只是一种网络编程库,需要结合其他编程技术来进行应用程序开发。其次,网络通信涉及到网络环境、协议、安全等方面的问题,需要进行全面的设计和测试,确保应用程序的正常运行和安全性。 总之,MFC Socket库是一种强大的网络编程库,适用于Windows平台应用程序的开发。它提供了丰富的功能和灵活的操作方式,能够支持各种传输协议和数据通信需求。同时,使用Socket库需要注意一些网络编程的基本知识和技巧,才能保证应用程序的质量和可靠性。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

凌桓丶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值