计算机网络:【socket】【UDP】【地址转换函数】【TCP】

发布于:2025-07-02 ⋅ 阅读:(17) ⋅ 点赞:(0)

一.socket

1.1socket接口

它返回的是一个文件描述符。创建socket文件描述符(TCP/UDP,客户端+服务器)

• socket()打开一个网络通讯端口,如果成功的话,就像 open()一样返回一个文件描 述符;

• 应用程序可以像读写文件一样用 read/write 在网络上收发数据;

• 如果 socket()调用出错则返回-1;

• 对于 IPv4, family 参数指定为 AF_INET;

• 对于 UDP 协议,type 参数指定为 SOCK_DGRAM, 表示面向数据报的传输协议

• protocol 参数的介绍从略,指定为 0 即可。 

Socket的第一个参数:domain(域/协议族)

Socket编程中,socket()函数的第一个参数指定通信的协议族(Protocol Family),决定了 socket 的底层通信协议类型。常见的协议族包括:

  • AF_INET
    IPv4协议族,用于基于IPv4的网络通信(如互联网或本地网络)。这是最常用的选项。

    int socket_fd = socket(AF_INET, SOCK_STREAM, 0);
    

  • AF_INET6
    IPv6协议族,支持IPv6地址格式的通信。

    int socket_fd = socket(AF_INET6, SOCK_STREAM, 0);
    

  • AF_UNIX/AF_LOCAL
    本地通信协议族,用于同一台主机上的进程间通信(通过文件系统路径)。

    int socket_fd = socket(AF_UNIX, SOCK_STREAM, 0);
    

  • AF_PACKET
    底层数据包接口(如直接访问网络层数据包,常用于网络工具开发)。


参数意义

该参数定义了 socket 的地址类型,直接影响后续绑定(bind())或连接(connect())时使用的地址结构体。例如:

  • 使用 AF_INET 时,地址结构体为 struct sockaddr_in(包含IPv4地址和端口)。
  • 使用 AF_UNIX 时,地址结构体为 struct sockaddr_un(包含文件路径)。

注意事项

  • AF_ vs PF_
    历史上有 AF_(地址族)和 PF_(协议族)两种前缀,但在现代系统中两者通常等价(如 AF_INETPF_INET 可互换)。
  • 错误处理
    若参数无效(如不支持的协议族),socket() 会返回 -1,并设置 errnoEAFNOSUPPORT

socket 的第二个参数详解

socket 的第二个参数指定套接字的类型,决定了数据传输的方式和协议特性。以下是常见的套接字类型及其用途:

SOCK_STREAM
  • 面向连接的字节流套接字,使用 TCP 协议。
  • 提供可靠、有序、双向的数据传输。
  • 适用于需要数据完整性的场景,如 HTTP、FTP。
SOCK_DGRAM
  • 无连接的数据报套接字,使用 UDP 协议。
  • 传输速度快但不可靠,可能丢包或乱序。
  • 适用于实时性要求高的场景,如视频流、DNS 查询。
SOCK_RAW
  • 原始套接字,允许直接访问底层协议(如 IP、ICMP)。
  • 需要管理员权限,常用于网络探测或自定义协议开发。
SOCK_SEQPACKET
  • 提供有序、可靠、基于消息的传输(如 SCTP 协议)。
  • 结合了流式和数据报的特性,适用于电信领域。
SOCK_RDM
  • 可靠的数据报套接字,保证数据不丢失但可能乱序。
  • 较少使用,特定于某些协议(如 RDS)

注意事项

  • 第二个参数需与第一个参数(地址族,如 AF_INET)兼容。
  • 某些类型(如 SOCK_RAW)可能需要特殊权限。
  • 具体支持的类型取决于操作系统和协议栈。

 第三个参数

指定具体的传输协议。通常设置为0,表示根据domaintype自动选择默认协议。

1.2bind接口

 绑定端口号(TCP/UDP,服务器)

• 服务器程序所监听的网络地址和端口号通常是固定不变的,客户端程序得知服 务器程序的地址和端口号后就可以向服务器发起连接; 服务器需要调用 bind 绑定一 个固定的网络地址和端口号;

• bind()成功返回 0,失败返回-1。

• bind()的作用是将参数 sockfd 和 myaddr 绑定在一起, 使 sockfd 这个用于网络 通讯的文件描述符监听 myaddr 所描述的地址和端口号;

• struct sockaddr *是一个通用指针类型,myaddr 参数实际上可以接受 多种协议的 sockaddr 结构体,而它们的长度各不相同,所以需要第三个参数 addrlen 指定结构体的长度; 

1.3listen接口

开始监听 socket (TCP, 服务器)

• listen()声明 sockfd 处于监听状态, 并且最多允许有 backlog 个客户端处于连接 等待状态, 如果接收到更多的连接请求就忽略, 这里设置不会太大(一般是 5)

• listen()成功返回 0,失败返回-1;

1.4accept接口

接收请求 (TCP, 服务器)

 

它返回的也是一个文件描述符,跟listensocket配合着使用

• 三次握手完成后, 服务器调用 accept()接受连接;

• 如果服务器调用 accept()时还没有客户端的连接请求,就阻塞等待直到有客户端 连接上来;

• addr 是一个传出参数,accept()返回时传出客户端的地址和端口号;

• 如果给 addr 参数传 NULL,表示不关心客户端的地址;

• addrlen 参数是一个传入传出参数(value-result argument), 传入的是调用者提 供的, 缓冲区 addr 的长度以避免缓冲区溢出问题, 传出的是客户端地址结构体的实际长度(有可能没有占满调用者提供的缓冲区); 

1.5connect接口

建立连接 (TCP, 客户端)

• 客户端需要调用 connect()连接服务器;

• connect 和 bind 的参数形式一致, 区别在于 bind 的参数是自己的地址, 而 connect 的参数是对方的地址;

• connect()成功返回 0,出错返回-1;

 1.6sockeaddr结构

socketAPI 是一层抽象的网络编程接口 , 适用于各种底层网络协议 , IPv4 IPv6, 以及后面的UNIXDomainSocket. 然而 , 各种网络协议的地址格式并不相同 .

• IPv4 和 IPv6 的地址格式定义在 netinet/in.h 中,IPv4 地址用 sockaddr_in 结构 体表示,包括16 地址类型, 16 位端口号和 32 位 IP 地址.

• IPv4、IPv6 地址类型分别定义为常数 AF_INET AF_INET6. 这样,只要取得某 种 sockaddr 结构体的首地址,不需要知道具体是哪种类型的 sockaddr 结构体,就可 以根据地址类型字段确定结构体中的内容.

• socket API 可以都用 struct sockaddr *类型表示, 在使用的时候需要强制转化成sockaddr_in; 这样的好处是程序的通用性, 可以接收 IPv4, IPv6, 以及 UNIX Domain Socket 各种类型的 sockaddr 结构体指针做为参数; 

 

虽然 socket api 的接口是 sockaddr, 但是我们真正在基于 IPv4 编程时, 使用的数据结 构是sockaddr_in; 这个结构里主要有三部分信息: 地址类型, 端口号, IP 地址. 

 

in_addr 用来表示一个 IPv4 的 IP 地址. 其实就是一个 32 位的整数; 

通常这样初始化

网络地址为 INADDR_ANY, 这个宏表示本地的任意 IP 地址,因为服务器可能有 多个网卡,每个网卡也可能绑定多个 IP 地址, 这样设置可以在所有的 IP 地址上监听, 直到与某个客户端建立了连接时才确定下来到底用哪个 IP 地址 

 二.UDP

2.1简单的接口代码(demo)

InetAddr.hpp

#pragma once
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>

class InetAddr
{
public:
    InetAddr(struct sockaddr_in &addr):_addr(addr)
    {
        _port = ntohs(addr.sin_port);
        _ip = inet_ntoa(addr.sin_addr);
    }
    std::string GetIp()
    {
        return _ip;
    }
    uint16_t GetPort()
    {
        return _port;
    }
private:
    std::string _ip;//点分十进制
    uint16_t _port;
    struct sockaddr_in _addr;
};

NoCopy.hpp

#pragma once

#include<iostream>

class NoCopy 
{
public:
    NoCopy()
    {}
    NoCopy(const NoCopy&) = delete;
    const NoCopy& operator=(const NoCopy&) = delete;
    ~NoCopy()
    {}
};

Common.hpp

#pragma once 

enum ExitCode
{
    OK = 0,
    SOCKET_ERR,
    BIND_ERR
};

UdpServer.hpp

#pragma once
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <strings.h>
#include <string.h>
#include <cstdlib>
#include <memory>
#include "nocopy.hpp"
#include "InetAddr.hpp"
#include "Common.hpp"
#include "Log.hpp"
using namespace LogModule;
const static int defaultport = 8888;
const static int defaultsockfd = -1;
const static int defaultsize = 1024;

// 禁止拷贝和赋值
class UdpServer : public NoCopy
{
public:
    UdpServer(int port = defaultport) : _port(port), _sockfd(defaultsockfd)
    {
    }

    void Init()
    {
        // 1.创建socket
        _sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);
        if (_sockfd < 0)
        {
            LOG(LogLevel::FATAL) << "socket error...";
            exit(SOCKET_ERR);
        }
        LOG(LogLevel::DEBUG) << "sockfd success: " << _sockfd;

        // 2.初始化结构体
        struct sockaddr_in local;
        bzero(&local,sizeof(local));
        //memset(&local,0,sizeof(local));
        local.sin_family = AF_INET;
        local.sin_addr.s_addr = INADDR_ANY;//注意这里不要写特定的ip值
        local.sin_port = htons(_port);
        // 3.bind,结构体填完,还要设置到内核中
        int n = ::bind(_sockfd,(struct sockaddr*)&local,sizeof(local));
        if(n!=0)
        {
            LOG(LogLevel::FATAL) << "bind error...";
            exit(BIND_ERR);
        }
        LOG(LogLevel::DEBUG) << "bind success...";

    }
    void Start()
    {
        
        while(true)
        {
            char buffer[defaultsize];
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            ssize_t n = recvfrom(_sockfd,buffer,sizeof(buffer) - 1,0,(struct sockaddr*)&peer,&len);
            if(n > 0)
            {
                InetAddr addr(peer);
                buffer[n] = 0;
                std::cout << "client: [" << addr.GetIp() << " "<<addr.GetPort()<<" say# ]" << buffer <<std::endl;
                sendto(_sockfd,buffer,sizeof(buffer) - 1,0,(struct sockaddr*)&peer,len);
            }
        }
    }

    ~UdpServer()
    {
    }

private:
    //std::string _ip;
    uint16_t _port;
    int _sockfd;
};

UdpClient.cc

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <strings.h>
#include <cstdlib>
#include <string.h>
#include <memory>
#include "nocopy.hpp"
#include "InetAddr.hpp"
#include "Common.hpp"
#include "Log.hpp"

using namespace LogModule;

int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        std::cerr << "Usage: " << argv[0] << " server_ip server_port" << std::endl;
        return 1;
    }
    std::string server_ip = argv[1];
    uint16_t server_port = std::stoi(argv[2]);

    // 1.还是创建socket
    int sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        LOG(LogLevel::FATAL) << "socket error...";
        return 2;
    }
    LOG(LogLevel::DEBUG) << "sockfd success: " << sockfd;
    // 2.clent 也要进行bind,但是不要跟server一样显示的进行bind,因为服务器的port是众所周知的,而client的port可能会非常多
    // 导致端口号冲突,bind的操作OS会随机给我们分配端口号

    // 3.填充server信息
    struct sockaddr_in server;
    memset(&server, 0, sizeof(server));
    server.sin_addr.s_addr = inet_addr(server_ip.c_str()); // ip地址是点分十进制转换为网络序列的32为整数
    server.sin_family = AF_INET;
    server.sin_port = htons(server_port);

    while (true)
    {
        std::string in;
        std::cout << "please enter# ";
        std::getline(std::cin, in);
        // std::cin>>in;
        //  std::cout<< 1 <<std::endl;
        int n = sendto(sockfd, in.c_str(), in.size(), 0, (struct sockaddr *)&server, sizeof(server));
        if (n < 0)
        {
            LOG(LogLevel::ERROR) << "sendto failed";
            continue;
        }

        char buffer[1024];
        struct sockaddr_in peer;
        socklen_t peer_len = sizeof(peer);
        int m = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &peer_len);

        if (m > 0)
        {
            buffer[m] = 0;
            std::cout << buffer << std::endl;
        }
    }

    return 0;
}

UdpServer.cc

#include"UdpServer.hpp"

int main()
{
    Enable_Console_Log_Strtegy();

    std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>();
    usvr->Init();
    usvr->Start();
    return 0;
} 

运行结果:

服务器:

客户端: 

2.2代码小升级,网络字典查询

其实也就是在UdpServer端加上了一个上层调用的函数,让我们能够实现单词之间的转化

UdpServer.hpp

#pragma once
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <strings.h>
#include <string.h>
#include <cstdlib>
#include <memory>
#include <functional>
#include "nocopy.hpp"
#include "InetAddr.hpp"
#include "Common.hpp"
#include "Log.hpp"
using namespace LogModule;
const static int defaultport = 8888;
const static int defaultsockfd = -1;
const static int defaultsize = 1024;

using func_t = std::function<std::string(const std::string&,InetAddr& cli)>;

// 禁止拷贝和赋值
class UdpServer : public NoCopy
{
public:
    UdpServer(func_t func,int port = defaultport) : _port(port), _sockfd(defaultsockfd),_func(func)
    {
    }

    void Init()
    {
        // 1.创建socket
        _sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);
        if (_sockfd < 0)
        {
            LOG(LogLevel::FATAL) << "socket error...";
            exit(SOCKET_ERR);
        }
        LOG(LogLevel::DEBUG) << "sockfd success: " << _sockfd;

        // 2.初始化结构体
        struct sockaddr_in local;
        bzero(&local,sizeof(local));
        //memset(&local,0,sizeof(local));
        local.sin_family = AF_INET;
        local.sin_addr.s_addr = INADDR_ANY;//注意这里不要写特定的ip值
        local.sin_port = htons(_port);
        // 3.bind,结构体填完,还要设置到内核中
        int n = ::bind(_sockfd,(struct sockaddr*)&local,sizeof(local));
        if(n!=0)
        {
            LOG(LogLevel::FATAL) << "bind error...";
            exit(BIND_ERR);
        }
        LOG(LogLevel::DEBUG) << "bind success...";

    }
    void Start()
    {
        
        while(true)
        {
            char buffer[defaultsize];
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            ssize_t n = recvfrom(_sockfd,buffer,sizeof(buffer) - 1,0,(struct sockaddr*)&peer,&len);
            if(n > 0)
            {
                InetAddr addr(peer);
                buffer[n] = 0;
                std::string result = _func(buffer,addr);
                
                std::cout << "client: [" << addr.GetIp() << " "<<addr.GetPort()<<" say# ]" << buffer <<std::endl;
                sendto(_sockfd,result.c_str(),result.size(),0,(struct sockaddr*)&peer,len);
            }
        }
    }

    ~UdpServer()
    {
    }

private:
    //std::string _ip;
    uint16_t _port;
    int _sockfd;

    func_t _func;
};

 Dict.hpp

#include<unordered_map>
#include<fstream>
#include"UdpServer.hpp"

const static std::string defaultdict = "./dictionary.txt";
const static std::string sep = ":";

class Dict
{
public:
    Dict(const std::string &path = defaultdict):_dict_path(path)
    {}
    bool LoadWord()
    {
        std::ifstream in(_dict_path);
        if(!in.is_open())
        {
            LOG(LogLevel::FATAL) << "open file error...";
            return false;
        }
        std::string line;
        while(std::getline(in,line))//注意是从文件流里去读取,一次读一行
        {
            auto pos = line.find(sep);
            if(pos == std::string::npos)
            {  
                LOG(LogLevel::FATAL) << "解析文件失败";
                continue;
            }
            std::string english = line.substr(0,pos);
            std::string chinese = line.substr(pos + sep.size());
            if(english.empty()||chinese.empty())
            {
                LOG(LogLevel::FATAL) << "没有有效内容";
                continue;//返回继续进行解析文件
            }
            _dict[english] = chinese;
            LOG(LogLevel::DEBUG) << "加载" << line << "成功";
        }
        in.close();
        return true;
    }
    std::string Transact(std::string word,InetAddr &client)
    {
        auto pos = _dict.find(word);
        if(pos == _dict.end())
        {
            LOG(LogLevel::DEBUG) << "进入到了翻译模块, [" << client.GetIp() << " : " << client.GetPort() << "]# " << word << "->None";
            return "None";
        }
        LOG(LogLevel::DEBUG) << "进入到了翻译模块, [" << client.GetIp() << " : " << client.GetPort() << "]# " << word << "->" << pos->second;
        return pos->second;
    }
    ~Dict()
    {}
private:
    std::string _dict_path;
    std::unordered_map<std::string,std::string> _dict;
};

UdpServer.cc

#include"UdpServer.hpp"
#include"Dict.hpp"

int main(int argc,char* argv[])
{
    if (argc != 2)
    {
        std::cerr << "Usage: " << argv[0] << " port" << std::endl;
        return 1;
    }
    uint16_t port = std::stoi(argv[1]);
    Enable_Console_Log_Strtegy();

    Dict dict;
    dict.LoadWord();

    std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>([&dict](const std::string& word,InetAddr& cli)->std::string{
        return dict.Transact(word,cli);
    },port);

    usvr->Init();
    usvr->Start();
    return 0;
}

dictionary.txt: 

实现效果: 

 

2.3多线程下简单聊天室

Route.hpp

#pragma once
#include <iostream>
#include <string>
#include <vector>
#include "InetAddr.hpp"
#include "Log.hpp"

using namespace LogModule;

class Route
{
    bool IsExist(InetAddr& client)
    {
        for (auto &user : _online_user)
        {
            if (user == client)
            {
                return true;
            }
        }
        return false;
    }
    void AddUser(InetAddr &client)
    {
        LOG(LogLevel::INFO) << "新增一个在线用户: " << client.StringAddr();
        _online_user.push_back(client);
    }
    void DeleteUser(InetAddr &peer)
    {
        for (auto iter = _online_user.begin(); iter != _online_user.end(); iter++)
        {
            if (*iter == peer)
            {
                _online_user.erase(iter);
                LOG(LogLevel::INFO) << "删除一个在线用户:" << peer.StringAddr() << "成功";
                break;
            }
        }
    }

public:
    Route()
    {
    }
    void MessageRoute(int sockfd, std::string &message, InetAddr &peer)
    {
        if(!IsExist(peer))
        {
            AddUser(peer);
        }
        std::string send_message = peer.StringAddr() + "# " + message; // 127.0.0.1:8080# 你好
        for(auto &user : _online_user)
        {
            sendto(sockfd,send_message.c_str(),send_message.size(),0,user.GetAddr(),sizeof(*user.GetAddr()));
        }
        // 这个用户一定已经在线了
        if (message == "QUIT")
        {
            LOG(LogLevel::INFO) << "删除一个在线用户: " << peer.StringAddr();
            DeleteUser(peer);
        }
    }
    ~Route()
    {
    }

private:
    std::vector<InetAddr> _online_user;
};

UdpServer.cc

#include"UdpServer.hpp"
#include"Route.hpp"
#include"ThreadPool.hpp"

using namespace ThreadPoolModule;

using task_t = std::function<void()>;

int main(int argc,char* argv[])
{
    if (argc != 2)
    {
        std::cerr << "Usage: " << argv[0] << " port" << std::endl;
        return 1;
    }
    uint16_t port = std::stoi(argv[1]);
    Enable_Console_Log_Strtegy();

    //1.路由服务
    Route r;
    //2.线程池
    auto tp = ThreadPool<task_t>::GetInstance();

    std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>([&r,&tp](int sockfd,const std::string& message,InetAddr& cli){
        task_t t = std::bind(&Route::MessageRoute,&r,sockfd,message,cli);
        tp->Enqueue(t);
    },port);

    usvr->Init();
    usvr->Start();
    return 0;
}

UdpServer.hpp

#pragma once
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <strings.h>
#include <string.h>
#include <cstdlib>
#include <memory>
#include <functional>
#include "nocopy.hpp"
#include "InetAddr.hpp"
#include "Common.hpp"
#include "Log.hpp"
using namespace LogModule;
const static int defaultport = 8888;
const static int defaultsockfd = -1;
const static int defaultsize = 1024;

using func_tt = std::function<void(int sockfd,const std::string&,InetAddr&)>;

// 禁止拷贝和赋值
class UdpServer : public NoCopy
{
public:
    UdpServer(func_tt func,uint16_t port = defaultport) : _port(port), _sockfd(defaultsockfd),_func(func)
    {
    }

    void Init()
    {
        // 1.创建socket
        _sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);
        if (_sockfd < 0)
        {
            LOG(LogLevel::FATAL) << "socket error...";
            exit(SOCKET_ERR);
        }
        LOG(LogLevel::DEBUG) << "sockfd success: " << _sockfd;

        // 2.初始化结构体
        struct sockaddr_in local;
        bzero(&local,sizeof(local));
        //memset(&local,0,sizeof(local));
        local.sin_family = AF_INET;
        local.sin_addr.s_addr = INADDR_ANY;//注意这里不要写特定的ip值
        local.sin_port = htons(_port);
        // 3.bind,结构体填完,还要设置到内核中
        int n = ::bind(_sockfd,(struct sockaddr*)&local,sizeof(local));
        if(n!=0)
        {
            LOG(LogLevel::FATAL) << "bind error...";
            exit(BIND_ERR);
        }
        LOG(LogLevel::DEBUG) << "bind success...";

    }
    void Start()
    {
        while(true)
        {
            char buffer[defaultsize];
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            
            ssize_t n = recvfrom(_sockfd,buffer,sizeof(buffer) - 1,0,(struct sockaddr*)&peer,&len);
            if(n > 0)
            {
                InetAddr addr(peer);
                buffer[n] = 0;
                _func(_sockfd,buffer,addr);//路由功能,外部传函数
            }
        }
    }

    ~UdpServer()
    {
    }

private:
    //std::string _ip;
    uint16_t _port;
    int _sockfd;

    func_tt _func;
};

客户端也进行多线程修改

UdpClient.cc

#include <iostream>
#include <string>
#include <cstring>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include "Thread.hpp"

int sockfd = 0;
std::string server_ip;
uint16_t server_port = 0;
pthread_t id;

using namespace ThreadModule;

void Recv()
{
    while (true)
    {
        char buffer[1024];
        struct sockaddr_in peer;
        socklen_t len = sizeof(peer);
        int m = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
        if (m > 0)
        {
            buffer[m] = 0;
            std::cerr << buffer << std::endl; // 2
        }
    }
}
void Send()
{
    struct sockaddr_in server;
    memset(&server, 0, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(server_port);
    server.sin_addr.s_addr = inet_addr(server_ip.c_str());

    const std::string online = "inline";
    sendto(sockfd, online.c_str(), online.size(), 0, (struct sockaddr *)&server, sizeof(server));

    while (true)
    {
        std::string input;
        //std::cout << "Please Enter# "; // 1
        std::getline(std::cin, input); // 0

        int n = sendto(sockfd, input.c_str(), input.size(), 0, (struct sockaddr *)&server, sizeof(server));
        (void)n;
        if (input == "QUIT")
        {
            pthread_cancel(id);
            break;
        }
    }
}

// client 我们也要做多线程改造
// ./udpclient server_ip server_port
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        std::cerr << "Usage: " << argv[0] << " server_ip server_port" << std::endl;
        return 1;
    }
    server_ip = argv[1];
    server_port = std::stoi(argv[2]);

    // 1. 创建socket
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        std::cerr << "socket error" << std::endl;
        return 2;
    }

    // 2. 创建线程
    Thread recver(Recv);
    Thread sender(Send);

    recver.Start();
    sender.Start();


    recver.Join();
    sender.Join();

    // 2. 本地的ip和端口是什么?要不要和上面的“文件”关联呢?
    // 问题:client要不要bind?需要bind.
    //       client要不要显式的bind?不要!!首次发送消息,OS会自动给client进行bind,OS知道IP,端口号采用随机端口号的方式
    //   为什么?一个端口号,只能被一个进程bind,为了避免client端口冲突
    //   client端的端口号是几,不重要,只要是唯一的就行!
    // 填写服务器信息

    return 0;
}

 三.地址转换函数

sockaddr_in 中的成员 struct in_addr sin_addr 表示 32 位 的 IP 地址

但是我们通常用点分十进制的字符串表示 IP 地址,以下函数可以在字符串表示 和 in_addr 表示之间转换;

字符串转 in_addr 的函数:

in_addr 转字符串的函数:

其中 inet_pton 和 inet_ntop 不仅可以转换 IPv4 的 in_addr,还可以转换 IPv6 的 in6_addr,因此函数接口是 void *addrptr。 

inet_ntoa 

inet_ntoa 这个函数返回了一个 char*, 很显然是这个函数自己在内部为我们申请了一块 内存来保存 ip 的结果. 那么是否需要调用者手动释放呢? 

man 手册上说, inet_ntoa 函数, 是把这个返回结果放到了静态存储区. 这个时候不需要 我们手动进行释放. 那么问题来了, 如果我们调用多次这个函数, 会有什么样的效果呢? 参见如下代码:

 

因为 inet_ntoa 把结果放到自己内部的一个静态存储区, 这样第二次调用时的结果会覆 盖掉上一次的结果.

在多线程环境下, 推荐使用 inet_ntop, 这个函数由调用者提供一个缓冲区保存 结果, 可以规避线程安全问题; 

 四.TCP

多线程版本,多进程版本,线程池版本

TcpServer.hpp

#pragma once
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <strings.h>
#include <string.h>
#include <cstdlib>
#include <memory>
#include <functional>
#include <sys/types.h>
#include <sys/wait.h>
#include "nocopy.hpp"
#include "InetAddr.hpp"
#include "Common.hpp"
#include "Log.hpp"
#include "Command.hpp"
#include "ThreadPool.hpp"
using namespace LogModule;
using namespace ThreadPoolModule;

using func_tt = std::function<void()>;

class TcpServer : public NoCopy
{
public:
    TcpServer(uint16_t port) : _port(port), _isruning(false)
    {
    }
    void Init()
    {
        // 1.创建listensocket
        _listensock = ::socket(AF_INET, SOCK_STREAM, 0);
        if (_listensock < 0)
        {
            LOG(LogLevel::FATAL) << "socket error...";
            exit(SOCKET_ERR);
        }
        LOG(LogLevel::DEBUG) << "socket success: " << _listensock;
        // 2.bind
        // struct sockaddr_in server;
        // bzero(&server, sizeof(server));
        // server.sin_addr.s_addr = INADDR_ANY;
        // server.sin_family = AF_INET;
        // server.sin_port = htons(_port);
        InetAddr server(_port);
        int n = ::bind(_listensock, CONV(&server.NetAddr()), server.GetAddrLen());
        if (n < 0)
        {
            LOG(LogLevel::FATAL) << "bind error...";
            exit(BIND_ERR);
        }
        LOG(LogLevel::DEBUG) << "bind success";
        // 3.设置监听状态,listen
        if (::listen(_listensock, 5) < 0)
        {
            LOG(LogLevel::FATAL) << "listen error...";
            exit(LISTEN_ERR);
        }
        LOG(LogLevel::DEBUG) << "listen success";
    }
    void Start()
    {
        _isruning = true;
        while (_isruning)
        {
            // 4.获取链接,accept
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            int sockfd = ::accept(_listensock, CONV(&peer), &len);
            if (sockfd < 0)
            {
                LOG(LogLevel::FATAL) << "accept error...";
                continue;
            }
            InetAddr client(peer);
            LOG(LogLevel::DEBUG) << "accept success ,name: " << client.StringAddr();

            // 5.提供服务
            // Service(sockfd);
            // close(sockfd);
            // ProcessConnection(sockfd,peer);
            //ThreadConnection(sockfd, peer);
            ThreadPoolConnection(sockfd,client);
        }
    }
    void Service(int sockfd,InetAddr& peer)
    {
        char buffer[1024];
        while (true)
        {
            ssize_t n = read(sockfd, buffer, sizeof(buffer) - 1);
            if (n > 0)
            {
                buffer[n] = 0;
                std::cout << "client say# " << buffer << std::endl;
                std::string echo_string = "server echo# ";
                echo_string += buffer;
                write(sockfd, echo_string.c_str(), echo_string.size());
            }
            else if (n == 0) // read 如果返回值是 0,表示读到了文件结尾(对端关闭了连接!)
            {
                LOG(LogLevel::FATAL) << peer.StringAddr() << "client quit";
                break;
            }
            else
            {
                LOG(LogLevel::FATAL)<< peer.StringAddr() << "read error...";
                break;
            }
        }
    }

    // void Service(int sockfd,InetAddr& peer)
    // {
    //     Command com;
    //     char buffer[1024];
    //     while(true)
    //     {
    //         // 1. 先读取数据
    //         // a. n>0: 读取成功
    //         // b. n<0: 读取失败
    //         // c. n==0: 对端把链接关闭了,读到了文件的结尾 --- pipe
    //         ssize_t n = read(sockfd, buffer, sizeof(buffer) - 1);
    //         if (n > 0)
    //         {
    //             // buffer是一个英文单词 or 是一个命令字符串
    //             buffer[n] = 0; // 设置为C风格字符串, n<= sizeof(buffer)-1

    //             std::string echo_string = com.Execute(buffer, peer);


    //             write(sockfd, echo_string.c_str(), echo_string.size());
    //         }
    //         else if (n == 0)
    //         {
    //             LOG(LogLevel::DEBUG) << peer.StringAddr() << " 退出了...";
    //             close(sockfd);
    //             break;
    //         }
    //         else
    //         {
    //             LOG(LogLevel::DEBUG) << peer.StringAddr() << " 异常...";
    //             close(sockfd);
    //             break;
    //         }
    //     }
    // }

    class ThreadData
    {
    public:
        ThreadData(int sockfd, InetAddr &ie, TcpServer *t) : _sockfd(sockfd), _client(ie), _tsv(t)
        {
        }
        ~ThreadData()
        {
        }

        int _sockfd;
        InetAddr _client;
        TcpServer *_tsv;
    };

    //线程池版本
    void ThreadPoolConnection(int sockfd,InetAddr& peer)
    {
        ThreadPool<func_tt>::GetInstance()->Enqueue([this,sockfd,&peer](){
            this->Service(sockfd,peer);
        });
    }


    // 多线程服务
    static void *Routine(void *args)
    {
        pthread_detach(pthread_self());
        ThreadData *tdv = static_cast<ThreadData *>(args);
        tdv->_tsv->Service(tdv->_sockfd,tdv->_client);
        close(tdv->_sockfd);
        delete tdv;
        return nullptr;
    }
    void ThreadConnection(int sockfd, struct sockaddr_in &peer)
    {
        InetAddr client(peer);
        pthread_t tid;
        // std::shared_ptr<ThreadData> tdsv = std::make_shared<ThreadData>(sockfd,client,this);
        ThreadData *tdv = new ThreadData(sockfd, client, this);
        pthread_create(&tid, nullptr, Routine, (void *)tdv);
    }

    // 多进程服务
    void ProcessConnection(int sockfd, struct sockaddr_in &peer)
    {
        pid_t id = fork();
        if (id < 0)
        {
            close(sockfd);
            return;
        }
        else if (id == 0)
        {
            // 子进程再去创建子进程,孙子进程
            // 子进程,子进程除了看到sockfd,能看到listensockfd吗??
            //  我们不想让子进程访问listensock!
            close(_listensock);
            if (fork() > 0)
            {
                exit(0); // 大于0,是子进程
            }
            // 到这里是孙子进程,是孤儿进程,系统去回收
            InetAddr client(peer);
            LOG(LogLevel::DEBUG) << "process connection: " << client.StringAddr();
            Service(sockfd,client);
            close(sockfd);
            exit(0);
        }
        else
        {
            // 父进程去关闭创建的子进程
            close(sockfd);
            // 这里要等待子进程
            pid_t rid = waitpid(id, nullptr, 0);
            (void)rid;
        }
    }

    ~TcpServer()
    {
    }

private:
    uint16_t _port;
    int _listensock;
    bool _isruning;
};

TcpClient.cc

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <strings.h>
#include <string.h>
#include <cstdlib>
#include <memory>
#include <functional>
#include "nocopy.hpp"
#include "InetAddr.hpp"
#include "Common.hpp"
#include "Log.hpp"

using namespace LogModule;
void Usage(const std::string &process)
{
    std::cout << "Usage: " << process << " server_ip server_port" << std::endl;
}
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        return 1;
    }
    std::string server_ip = argv[1];
    uint16_t server_port = std::stoi(argv[2]);
    // 1.建立套接字
    int sockfd = ::socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        LOG(LogLevel::FATAL) << "socket error...";
        exit(SOCKET_ERR);
    }
    LOG(LogLevel::DEBUG) << "socket success: " << sockfd;

    // 2.不需要用户bind
    // 3.连接
    InetAddr local(server_ip, server_port);
    int n = ::connect(sockfd, local.GetAddr(), local.GetAddrLen());
    if (n < 0)
    {
        LOG(LogLevel::FATAL) << "connect error...";
        exit(CONNECT_ERR);
    }
    LOG(LogLevel::DEBUG) << "connect success: " << sockfd;

    // 4.链接成功,通信
    while (true)
    {
        std::string client_buffer;
        std::cout << "Please Enter# ";
        std::getline(std::cin, client_buffer);
        ssize_t n = write(sockfd, client_buffer.c_str(), client_buffer.size());

        char server_buffer[1024];
        ssize_t size = read(sockfd, server_buffer, sizeof(server_buffer)-1);
        if(size > 0)
        {
            server_buffer[size] = 0;
            std::cout << server_buffer << std::endl;
        }
    }
    close(sockfd);

    return 0;
}

实现一个简单的远程命令

Command.hpp

#pragma once
#include <iostream>
#include <string>
#include <set>
#include <unistd.h>
#include <fstream>
#include "InetAddr.hpp"
#include "Log.hpp"

using namespace LogModule;

class Command
{
public:
    Command()
    {
        // 严格匹配
        _WhiteListCommands.insert("ls");
        _WhiteListCommands.insert("pwd");
        _WhiteListCommands.insert("ls -l");
        _WhiteListCommands.insert("touch haha.txt");
        _WhiteListCommands.insert("who");
        _WhiteListCommands.insert("whoami");
    }
    bool IsSafeCommand(const std::string &cmd)
    {
        auto iter = _WhiteListCommands.find(cmd);
        return iter != _WhiteListCommands.end();
    }
    std::string Execute(const std::string &cmd, InetAddr &addr)
    {
        // 1. 属于白名单命令
        if(!IsSafeCommand(cmd))
        {
            return std::string("坏人");
        }
        std::string who = addr.StringAddr();

        // 2. 执行命令
        // std::ifstream in;
        // in.open(cmd.c_str());
        FILE *fp = popen(cmd.c_str(), "r");
        if(nullptr == fp)
        {
            return std::string("你要执行的命令不存在: ") + cmd;
        }

        std::string res;
        char line[1024];
        while(fgets(line, sizeof(line), fp))
        {
            res += line;
        }
        pclose(fp);
        std::string result = who + "execute done, result is: \n" + res;
        LOG(LogLevel::DEBUG) << result;
        return result;
    }
    ~Command()
    {}
private:
    std::set<std::string> _WhiteListCommands;
};