【Linux】socket网络编程之TCP

个人主页~


socket网络编程之TCP

一、TCP实现回显服务器

1、服务端

(一)TcpServer.hpp

cpp 复制代码
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <cstring>
#include <pthread.h>
#include <sys/wait.h>

const std::string defaultip = "0.0.0.0";
const int defaultfd = -1;

//枚举错误类型
enum
{
    UsageError = 1,
    SocketError,
    BindError,
    ListenError,
};

//封装客户端连接相关信息
class ThreadData
{
public:
    ThreadData(int fd, const std::string &ip, const uint16_t &p) : sockfd(fd), clientip(ip), clientport(p)
    {}

public:
    int sockfd;
    std::string clientip;
    uint16_t clientport;
};

class TcpServer
{
public:
    TcpServer(const uint16_t &port, const std::string &ip = defaultip) : listensock_(defaultfd), port_(port), ip_(ip)
    {}
    void InitServer()
    {	
    	//IPv4协议,TCP套接字
        listensock_ = socket(AF_INET, SOCK_STREAM, 0);
        if (listensock_ < 0)
        {
            exit(SocketError);
        }
        //local存储本地服务器地址信息并初始化为0
        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        //IPv4协议,端口号,IP地址
        local.sin_family = AF_INET;
        local.sin_port = htons(port_);
        inet_aton(ip_.c_str(), &(local.sin_addr));
		//调用bind将套接字listensock_ 绑定到本地地址local
        if (bind(listensock_, (struct sockaddr *)&local, sizeof(local)) < 0)
        {
            exit(BindError);
        }
		//将套接字设置为监听状态,最多允许五个客户端连接请求排队等待处理
        if (listen(listensock_, 5) < 0)
        {
            exit(ListenError);
        }
    }

    void Start()
    {
        while (true)
        {
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            
            //新套接字sockfd用于与发起连接请求的客户端进行数据传输
            //原来的listensock_继续监听
            int sockfd = accept(listensock_, (struct sockaddr *)&client, &len);
            if (sockfd < 0)
            {
                continue;
            }
            
            //将客户端端口号转换为主机字节序
            uint16_t clientport = ntohs(client.sin_port);
            
            //将客户端ip转换为点分十进制字符串
            char clientip[32];
            inet_ntop(AF_INET, &(client.sin_addr), clientip, sizeof(clientip));
			
			//多进程,从这里开始的下面这段代码,可以用多线程以及线程池替代,在后面说说多线程
			//这里很巧妙的设计,我们在后边与多线程一起解释
            pid_t id = fork();
            if (id == 0)
            {
                // 子进程关闭监听
                close(listensock_);
                //子进程创建"孙子"进程
                if (fork() > 0)
                    exit(0);
                Service(sockfd, clientip, clientport); 
                close(sockfd);
                exit(0);
            }
            close(sockfd);
            // 父进程等待
            pid_t rid = waitpid(id, nullptr, 0);
            (void)rid;
        }
    }
    //处理发送来的内容,将数据整合打印到屏幕上
    void Service(int sockfd, const std::string &clientip, const uint16_t &clientport)
    {
        char buffer[4096];
        while (true)
        {
            ssize_t n = read(sockfd, buffer, sizeof(buffer));
            if (n > 0)
            {
                buffer[n] = 0;
                std::cout << "tcpclient say# " << buffer << std::endl;
                std::string echo_string = "tcpserver echo# ";
                echo_string += buffer;

                write(sockfd, echo_string.c_str(), echo_string.size());
            }
            else
            {
                break;
            }
        }
    }
    ~TcpServer() {}

private:
    int listensock_;//监听套接字描述符
    uint16_t port_;//端口号
    std::string ip_;//ip地址
};

(二)main.cpp

cpp 复制代码
#include "TcpServer.hpp"
#include <iostream>
#include <memory>

void Usage(std::string proc)
{
    std::cout << "\n\rUsage: " << proc << " port[1024+]\n" << std::endl;
}

int main(int argc, char *argv[])
{
    if(argc != 2)
    {
        Usage(argv[0]);
        exit(UsageError);
    }
    uint16_t port = std::stoi(argv[1]);
    //智能指针维护服务器
    std::unique_ptr<TcpServer> tcp_svr(new TcpServer(port));
    tcp_svr->InitServer();
    tcp_svr->Start();

    return 0;
}

2、客户端

TcpClient.cpp

cpp 复制代码
#include <iostream>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>

void Usage(const std::string &proc)
{
    std::cout << "\n\rUsage: " << proc << " serverip serverport\n"
              << std::endl;
}

int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        exit(1);
    }

    std::string serverip = argv[1];
    uint16_t serverport = std::stoi(argv[2]);
	//创建TCP套接字描述符
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        std::cerr << "socket error" << std::endl;
        return 1;
    }
	//还是老套路,初始化服务器地址结构体结构体
    struct sockaddr_in server;
    memset(&server, 0, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(serverport);
    inet_pton(AF_INET, serverip.c_str(), &(server.sin_addr));
	//连接服务器
    int n = connect(sockfd, (struct sockaddr*)&server, sizeof(server));
    if(n < 0)
    {
        std::cerr << "connet error" << std::endl;
        return 2;
    }
	//循环输出打印
    std::string message;
    while (true)
    {
        std::cout << "Please Enter# ";
        std::getline(std::cin, message);
        write(sockfd, message.c_str(), message.size());

        char inbuffer[4096];
        n = read(sockfd, inbuffer, sizeof(inbuffer));
        if (n > 0)
        {
            inbuffer[n] = 0;
            std::cout << inbuffer << std::endl;
        }  
    }
    close(sockfd);
    return 0;
}

二、服务器Start函数

1、多进程版

cpp 复制代码
//......
			pid_t id = fork();
            if (id == 0)
            {
                // 子进程关闭监听
                close(listensock_);
                //子进程创建"孙子"进程
                if (fork() > 0)
                    exit(0);
                Service(sockfd, clientip, clientport); 
                close(sockfd);
                exit(0);
            }
            //父进程关闭sockfd描述符
            close(sockfd);
            // 父进程等待
            pid_t rid = waitpid(id, nullptr, 0);
            (void)rid;
//......

创建子进程后子进程关闭监听描述符,再创建一个"孙子"进程,然后子进程退出,此时孙子进程成为孤儿进程,被系统领养,再进行其他的工作,工作完成后关闭描述符,退出时由系统回收

父进程关闭新创建的描述符,然后父进程进入进程等待,这个进程等待的时间很短甚至没有,因为子进程在创建完"孙子"进程后就退出了,父进程就可以回收掉子进程继续下一轮的循环

整个过程不会担心父进程由于阻塞等待而造成的一系列问题,也不用修改为非阻塞轮询来消耗资源,被领养的孙子进程有系统回收资源,也不用担心它资源泄露

2、多线程版

cpp 复制代码
//......
//声明
class TcpServer;

class ThreadData
{
public:
    ThreadData(int fd, const std::string &ip, const uint16_t &p, TcpServer *t) : sockfd(fd), clientip(ip), clientport(p), tsvr_(t)
    {}

public:
    int sockfd;				//套接字描述符
    std::string clientip;	//ip地址
    uint16_t clientport;	//端口号
    TcpServer *tsvr_;		//指向TcpServer的指针
};
//......
//TcpServer结构体内
	static void *Routine(void *args)
    {	
    	//将线程分离,结束后自动释放所占资源
        pthread_detach(pthread_self());
        //调用Service函数
        ThreadData *td = static_cast<ThreadData *>(args);
        td->tsvr->Service(td->sockfd, td->clientip, td->clientport);
        delete td;
        return nullptr;
    }
//......

一样的效果


今日分享就到这了~

相关推荐
zm6 分钟前
网络编程epoll和udp
服务器·网络·数据库
张一不吃豆芽8 分钟前
TCPIP详解 卷1协议 八 ICMPv4和ICMPv6 Internet控制报文协议
网络·网络协议·tcp/ip
野犬寒鸦10 分钟前
Linux常用命令详解(下):打包压缩、文本编辑与查找命令
linux·运维·服务器·数据库·后端·github
lwewan26 分钟前
26考研——中央处理器_异常和中断机制(5)
笔记·考研
是垚不是土27 分钟前
探秘高可用负载均衡集群:企业网络架构的稳固基石
运维·服务器·网络·云原生·容器·架构·负载均衡
Petrichorzncu1 小时前
Lua再学习
开发语言·学习·lua
大锤资源1 小时前
用NVivo革新企业创新:洞悉市场情绪,引领金融未来
人工智能·经验分享·学习·金融
字节高级特工1 小时前
【C++】”如虎添翼“:模板初阶
java·c语言·前端·javascript·c++·学习·算法
进取星辰1 小时前
24、TypeScript:预言家之书——React 19 类型系统
linux·运维·ubuntu
月上柳青1 小时前
linux-驱动开发之设备树详解(RK平台为例)
linux·驱动开发·dsp开发