首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >【Linux】socket网络编程之TCP

【Linux】socket网络编程之TCP

作者头像
s-little-monster
发布2025-05-13 08:29:11
发布2025-05-13 08:29:11
25300
代码可运行
举报
运行总次数:0
代码可运行

一、TCP实现回显服务器

1、服务端

(一)TcpServer.hpp
代码语言:javascript
代码运行次数:0
运行
复制
#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
代码语言:javascript
代码运行次数:0
运行
复制
#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
代码语言:javascript
代码运行次数:0
运行
复制
#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、多进程版

代码语言:javascript
代码运行次数:0
运行
复制
//......
			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、多线程版

代码语言:javascript
代码运行次数:0
运行
复制
//......
//声明
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;
    }
//......

一样的效果

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-05-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、TCP实现回显服务器
    • 1、服务端
      • (一)TcpServer.hpp
      • (二)main.cpp
    • 2、客户端
      • TcpClient.cpp
  • 二、服务器Start函数
    • 1、多进程版
    • 2、多线程版
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档