Linux网络编程套接字

目录

理解源IP和目的IP地址

认识端口号

理解"端口号"和"进程ID"

理解源端口号和目的端口号

认识TCP协议

认识UDP协议

网络字节序

socket编程接口

[socket 常见API](#socket 常见API)

sockaddr结构

sockaddr结构

sockaddr_in结构

in_addr结构

首先介绍一下udpServer启动的过程:

udpServer发送和接收数据:

下面做个简单的udpServer演示:

[下面写一个udpClient 客户端:](#下面写一个udpClient 客户端:)

下面对udpServer中client发来的字符串进行解析,只用来分析是不是命令:

该进成一个简单的聊天室,会显示client的ip和port,将udpclient改成多线程版本

地址转换函数

字符串转in_addr的函数:

in_addr转字符串的函数:

关于inet_ntoa

[TCP socket API详解](#TCP socket API详解)

socket():

bind():

listen():

accept():

connect()

单进程版本的tcpServer:

多进程版本的tcpServer:

多线程版本的tcpServer:

线程池版本的tcpServer:

下面来介绍一个函数setsockopt函数,防止偶发性的服务器无法进行立即重启

守护进程

TCP通信流程简单描述:

服务器初始化:

建立连接的过程:

数据传输的过程:

断开连接的过程:

TCP和UDP对比


理解源IP和目的IP地址

在IP数据包头部中,有两个IP地址,分别叫做源IP地址,和目的IP地址。

认识端口号

端口号(port)是传输层协议的内容。

1.端口号是一个2字节16位的整数。

2.端口号用来标识一个进程,告诉操作系统,当前的这个数据要交给哪一个进程来处理。

3.IP地址+端口号能够标识网络上的某一台主机的某一个进程。

4.一个端口号只能被一个进程占用。

理解"端口号"和"进程ID"

我们之前指导的pid表示唯一一个进程。此处我们的端口号也是唯一表示一个进程。那么二者有什么关系吗?

另外,一个进程可以绑定多个端口号。但是一个端口号不能被多个进程绑定。

理解源端口号和目的端口号

传输层协议(TCP和UDP)的数据段中有两个端口号,分别叫做源端口号和目的端口号,就是在描述"数据是谁发的,要发给谁"。

在公网上:IP地址能表示唯一的一台主机,端口号port,用来标识该主机上的唯一的一个进程。

IP:Port=标识全网唯一一台主机上的一个进程。

为什么有pid了还设计出端口号呢?pid不是已经能标识进程的唯一性了吗?

因为:

1.不是所有的进程都要网络通信,但是所有的进程都要有pid。

2.系统和网络功能解耦合。

认识TCP协议

1.传输层协议。

2.有连接。

3.可靠传输。

4.面向字节流。

认识UDP协议

1.传输层协议。

2.无连接。

3.不可靠传输。

4.面向数据报。

网络字节序

我们已经知道,内存中的多字节数据相对于内存地址有大端和小端之分,磁盘文件中多字节数据相对于文件中的偏移地址也有大端小端之分,网络数据流同样有大端小端之分。那么如何定义网络数据流的地址呢?

1.发送主机通常将发送缓冲区中数据按照内存地址从低到高的顺序发出。

2.接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按照内存地址从低到高的顺序保存。

3.因此,网络数据流的地址应该这样规定:先发出的数据是低地址,后发出的数据是高地址。

4.TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节。

5.不管这台主机是大端机还是小端机,都会按照这个TCP/IP规定的网络字节序来发送/接收数据。

6.如果当前发送主机是小端,就需要先将数据转成大端,否则就忽略,直接发送即可。

为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,可以调用下面库函数做网络字节序和主机字节序的转换。

1.这些函数名很好记,h表示host,n表示network,l表示32位长整数,s表示16位短整数。

2.例如htonl表示将32位的长整数从主机字节序转换为网络字节序,例如将IP地址转换后准备发送。

3.如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回。

4.如果主机是大端字节序,这些函数将不做转换,将参数原封不动的返回。

socket编程接口

socket 常见API

//创建socket文件描述符(TCP/UDP,客户端+服务器)

int socket(int domain,int type,int protocol);

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

int bind(int socket,const struct sockaddr * address,socklen_t address_len);

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

int listen(int socket,int backlog);

//接收请求(TCP,服务器)

int accept(int socket,struct sockaddr * address,socklen_t * address_len);

//建立连接(TCP,客户端)

int connet(int socket,const struct sockaddr * addr,socklen_t addrlen);

sockaddr结构

socket API是一层抽象的网络编程接口,适用于各种底层网络协议,如IPv4,IPv6,以及后面要讲的UNIX Domain Socket,然而,各种网络协议的地址格式并不相同。

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

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

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

套接字编程的种类:

1.域间套接字编程---->同一个机器内。

2.原始套接字编程---->网络工具。

3.网络套接字编程---->用户间的网络通信。

sockaddr结构
sockaddr_in结构

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

in_addr结构

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

首先介绍一下udpServer启动的过程:

1.创建socket,使用:

int socket(int domain,int type,int protocol);接口来完成创建socket。

2.在bind之前,需要对bind内使用的结构体进行创建并初始化!

int bind(int socket,const struct sockaddr * address,socklen_t address_len);

就是创建一个sockaddr_in结构体,并对其做初始化!填充sockaddr_in结构体内的成员变量,如sin_family(协议),sin_port(端口号),sin_addr.s_addr(IP地址)。

对sin_addr.s_addr=INADDR_ANY。可以用INADDR_ANY这个宏来初始化。

3.上述sockaddr_in结构体初始化完成之后,进行bind。

int bind(int socket,const struct sockaddr * address,socklen_t address_len);

然后udpServer启动就准备就绪了!

udpServer发送和接收数据:

因为udp是面向数据报的,所以不能用write和read,可以用recvfrom()函数接收,sendto()函数发送。

下面做个简单的udpServer演示:

cpp 复制代码
udpserver.hpp


#pragma once
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <string>
#include <strings.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Log.hpp"

extern Log lg;

const uint16_t defaultport = 8080;
static std::string defaultip = "0.0.0.0";
const int size = 1024;

enum
{
    SOCK_ERR = 1,
    BIND_ERR,
};

class UdpServer
{
public:
    UdpServer(uint16_t port = defaultport, const std::string &ip = defaultip)
        : _port(port), _ip(ip), _isrunning(false)
    {
    }

    void Init()
    {
        // 初始化UdpServer
        // 1.创建socket
        _socket = socket(AF_INET, SOCK_DGRAM, 0);
        if (_socket < 0)
        {
            lg(Fatal, "create socket error,socket:%d,error:%s", _socket, strerror(errno));
            exit(SOCK_ERR);
        }
        lg(Info, "create socket success!,socket:%d", _socket);

        struct sockaddr_in local;
        bzero(&local, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(_port);                  // 注意转成网络字节序
        local.sin_addr.s_addr = inet_addr(_ip.c_str()); // ip也要转成网络字节序
        // local.sin_addr.s_addr = inet_addr(INADDR_ANY);  // 也可以使用这个宏初始化
        // 2.bind
        if (bind(_socket, (const struct sockaddr *)&local, sizeof(local)) < 0)
        {
            lg(Error, "bind error,error:%s", strerror(errno));
            exit(BIND_ERR);
        }
        lg(Info, "bind success!");
        // bind成功之后,UdpServer算是初始化完成了
    }

    void Run()
    {
        // 跑起来4
        _isrunning = true;
        char buffer[size];
        while (_isrunning)
        {
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            ssize_t n = recvfrom(_socket, buffer, sizeof(buffer), 0, (struct sockaddr *)&client, &len);
           if (n < 0)
            {
                lg(Warning, "recvfrom error,errno:%d,err string:%s", errno, strerror(errno));
                continue;
            }
            
            buffer[n] = 0;
            // 处理数据,再给client发回去
            std::string str = buffer;
            std::string echo_string = "server echo@" + str;
            sendto(_socket, echo_string.c_str(), echo_string.size(), 0, (const struct sockaddr *)&client, len);
        }
    }

    ~UdpServer()
    {
        if (_socket)
            close(_socket);
    }

private:
    int _socket;
    std::string _ip;
    uint16_t _port;
    bool _isrunning;
};
cpp 复制代码
main.cc




#include "udpServer.hpp"
#include <memory>

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

//    ./udpserver port
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        Usage(argv[0]);
        return 0;
    }

    uint16_t port = std::stoi(argv[1]);

    std::unique_ptr<UdpServer> svr(new UdpServer(port));
    svr->Init();
    svr->Run();

    return 0;
}

关于端口号,【0,1023】为系统内定的端口号,一般都要有固定的应用层协议使用,http:80,https:443,mysql:3306...

一般建议端口号设置大一点。

下面写一个udpClient 客户端:

cpp 复制代码
udpClient.cc





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

const int SIZE = 1024;

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

// udpclient serverip serverport
int main(int argc, char *argv[])
{
    // udpclient
    if (argc != 3)
    {
        Usage(argv[0]);
        exit(0);
    }
    uint16_t serverport = std::stoi(argv[2]);
    std::string serverip = argv[1];

    struct sockaddr_in server;
    bzero(&server, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(serverport);                  // 转为网络字节序
    server.sin_addr.s_addr = inet_addr(serverip.c_str()); // 字符串ip也要转为网络字节序
    socklen_t len = sizeof(server);

    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        std::cout << "socket error" << std::endl;
        exit(1);
    }

    std::string line;
    while (true)
    {
        char buffer[SIZE];
        std::cout << "Please Enter@";
        std::getline(std::cin, line);

        // 发给server端
        sendto(sockfd, line.c_str(), line.size(), 0, (const struct sockaddr *)&server, len);

        // 接收server端发来的
        struct sockaddr_in temp;
        socklen_t sz = sizeof(temp);
        ssize_t n = recvfrom(sockfd, buffer, sizeof(buffer), 0, (struct sockaddr *)&temp, &sz);
        if (n > 0)
        {
            buffer[n] = 0;
            std::cout << buffer << std::endl;
        }
    }

    close(sockfd);
    return 0;
}
cpp 复制代码
Makefile文件




.PHONY:all
all:udpserver udpclient

udpclient:udpClient.cc
	g++ -o $@ $^ -std=c++11

udpserver:main.cc
	g++ -o $@ $^ -std=c++11

.PHONY:clean
clean:
	rm -f udpserver udpclient

client也要bind!只不过不需要用户显示的bind!一般操作系统自由选择!

一个端口号只能被一个进程bind,对server是如此,对client也是如此!

其实client的port是多少,并不重要,只要保证主机上的唯一性就行了!

系统在什么时候给client bind呢?------>首次发送数据的时候!

下面对udpServer中client发来的字符串进行解析,只用来分析是不是命令:

cpp 复制代码
udpServer.hpp




#pragma once
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <string>
#include <strings.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <functional>
#include "Log.hpp"

typedef std::function<std::string(const std::string &)> func_t;

extern Log lg;

const uint16_t defaultport = 8080;
static std::string defaultip = "0.0.0.0";
const int size = 1024;

enum
{
    SOCK_ERR = 1,
    BIND_ERR,
};

class UdpServer
{
public:
    UdpServer(uint16_t port = defaultport, const std::string &ip = defaultip)
        : _port(port), _ip(ip), _isrunning(false)
    {
    }

    void Init()
    {
        // 初始化UdpServer
        // 1.创建socket
        _socket = socket(AF_INET, SOCK_DGRAM, 0);
        if (_socket < 0)
        {
            lg(Fatal, "create socket error,socket:%d,error:%s", _socket, strerror(errno));
            exit(SOCK_ERR);
        }
        lg(Info, "create socket success!,socket:%d", _socket);

        struct sockaddr_in local;
        bzero(&local, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(_port);                  // 注意转成网络字节序
        local.sin_addr.s_addr = inet_addr(_ip.c_str()); // ip也要转成网络字节序
        // local.sin_addr.s_addr = inet_addr(INADDR_ANY);  // 也可以使用这个宏初始化
        // 2.bind
        if (bind(_socket, (const struct sockaddr *)&local, sizeof(local)) < 0)
        {
            lg(Error, "bind error,error:%s", strerror(errno));
            exit(BIND_ERR);
        }
        lg(Info, "bind success!");
        // bind成功之后,UdpServer算是初始化完成了
    }

    void Run(func_t func)
    {
        // 跑起来4
        _isrunning = true;
        char buffer[size];
        while (_isrunning)
        {
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            ssize_t n = recvfrom(_socket, buffer, sizeof(buffer), 0, (struct sockaddr *)&client, &len);
            if (n < 0)
            {
                lg(Warning, "recvfrom error,errno:%d,err string:%s", errno, strerror(errno));
                continue;
            }

            buffer[n] = 0;
            // 处理数据,再给client发回去
            std::string str = buffer;
            std::string echo_string = "server echo@" + func(str);
            sendto(_socket, echo_string.c_str(), echo_string.size(), 0, (const struct sockaddr *)&client, len);
        }
    }

    ~UdpServer()
    {
        if (_socket)
            close(_socket);
    }

private:
    int _socket;
    std::string _ip;
    uint16_t _port;
    bool _isrunning;
};
cpp 复制代码
main.cc





#include "udpServer.hpp"
#include <memory>
#include <cstdio>
#include <vector>

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

bool SafeCheck(const std::string &cmd)
{
    std::vector<std::string> words{
        "rm",
        "mv",
        "which",
        "top",
        "cp",
        "kill",
    };

    for (auto wd : words)
    {
        auto pos = cmd.find(wd);
        if (pos != std::string::npos)
            return false;
    }
    return true;
}

std::string ExecuteCommand(const std::string &cmd)
{
    // 安全检查
    if (!SafeCheck(cmd))
        return "Bad Command!";
    FILE *pf = popen(cmd.c_str(), "r");
    if (pf == nullptr)
    {
        perror("popen");
        exit(1);
    }
    std::string result;
    char buffer[4096];
    while (true)
    {
        char *ok = fgets(buffer, sizeof(buffer), pf);
        if (ok == nullptr)
            break;
        result += buffer;
    }
    pclose(pf);
    return result;
}

//    ./udpserver port
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        Usage(argv[0]);
        return 0;
    }

    uint16_t port = std::stoi(argv[1]);

    std::unique_ptr<UdpServer> svr(new UdpServer(port));
    svr->Init();
    svr->Run(ExecuteCommand);

    return 0;
}

注:popen是用来创建管道和子进程,子进程执行的结果会通过管道交给父进程,父进程可以用FILE* 来读取。

该进成一个简单的聊天室,会显示client的ip和port,将udpclient改成多线程版本

cpp 复制代码
udpServer.hpp



#pragma once
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <string>
#include <strings.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <functional>
#include "Log.hpp"

// typedef std::function<std::string(const std::string &)> func_t;
typedef std::function<std::string(const std::string &, const std::string &, uint16_t)> func_t;

extern Log lg;

const uint16_t defaultport = 8080;
static std::string defaultip = "0.0.0.0";
const int size = 1024;

enum
{
    SOCK_ERR = 1,
    BIND_ERR,
};

class UdpServer
{
public:
    UdpServer(uint16_t port = defaultport, const std::string &ip = defaultip)
        : _port(port), _ip(ip), _isrunning(false)
    {
    }

    void Init()
    {
        // 初始化UdpServer
        // 1.创建socket
        _socket = socket(AF_INET, SOCK_DGRAM, 0);
        if (_socket < 0)
        {
            lg(Fatal, "create socket error,socket:%d,error:%s", _socket, strerror(errno));
            exit(SOCK_ERR);
        }
        lg(Info, "create socket success!,socket:%d", _socket);

        struct sockaddr_in local;
        bzero(&local, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(_port);                  // 注意转成网络字节序
        local.sin_addr.s_addr = inet_addr(_ip.c_str()); // ip也要转成网络字节序
        // local.sin_addr.s_addr = inet_addr(INADDR_ANY);  // 也可以使用这个宏初始化
        // 2.bind
        if (bind(_socket, (const struct sockaddr *)&local, sizeof(local)) < 0)
        {
            lg(Error, "bind error,error:%s", strerror(errno));
            exit(BIND_ERR);
        }
        lg(Info, "bind success!");
        // bind成功之后,UdpServer算是初始化完成了
    }

    void Run(func_t func)
    {
        // 跑起来4
        _isrunning = true;
        char buffer[size];
        while (_isrunning)
        {
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            ssize_t n = recvfrom(_socket, buffer, sizeof(buffer), 0, (struct sockaddr *)&client, &len);
            if (n < 0)
            {
                lg(Warning, "recvfrom error,errno:%d,err string:%s", errno, strerror(errno));
                continue;
            }
            std::string clientip = inet_ntoa(client.sin_addr);
            uint16_t clientport = ntohs(client.sin_port);

            buffer[n] = 0;
            // 处理数据,再给client发回去
            std::string str = buffer;
            std::string echo_string = func(str, clientip, clientport);
            sendto(_socket, echo_string.c_str(), echo_string.size(), 0, (const struct sockaddr *)&client, len);
        }
    }

    ~UdpServer()
    {
        if (_socket)
            close(_socket);
    }

private:
    int _socket;
    std::string _ip;
    uint16_t _port;
    bool _isrunning;
};
cpp 复制代码
main.cc


#include "udpServer.hpp"
#include <memory>
#include <cstdio>
#include <vector>

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

std::string Handler(const std::string &info, const std::string &clientip, uint16_t clientport)
{
    std::cout << "[" << clientip << ":" << clientport << "]#";
    std::string echo_str = "server get a message: " + info;
    std::cout << echo_str << std::endl;
    return echo_str;
}

//    ./udpserver port
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        Usage(argv[0]);
        return 0;
    }

    uint16_t port = std::stoi(argv[1]);

    std::unique_ptr<UdpServer> svr(new UdpServer(port));
    svr->Init();
    svr->Run(Handler);

    return 0;
}
cpp 复制代码
udpclient.cc




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

const int SIZE = 1024;

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

struct ThreadData
{
    struct sockaddr_in server;
    int socket;
};

void *Recv(void *args)
{
    // 接收
    ThreadData *td = static_cast<ThreadData *>(args);
    char buffer[SIZE];
    while (true)
    {
        // client接收的时候,要用临时sockaddr_in,因为recvfrom函数会改变sockaddr_in
        struct sockaddr_in temp;
        socklen_t len = sizeof(temp);
        ssize_t n = recvfrom(td->socket, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&temp, &len);
        if (n > 0)
        {
            buffer[n] = 0;
            std::cout << buffer << std::endl;
        }
    }
    return nullptr;
}

void *Send(void *args)
{
    // 发送
    ThreadData *td = static_cast<ThreadData *>(args);
    socklen_t len = sizeof(td->server);
    std::string line;
    while (true)
    {
        usleep(100);
        line.clear();
        std::cout << "Please Enter@";
        std::getline(std::cin, line);
        sendto(td->socket, line.c_str(), line.size(), 0, (const struct sockaddr *)&(td->server), len);
    }
    return nullptr;
}

// 多线程版本
//  udpclient serverip serverport
int main(int argc, char *argv[])
{
    // udpclient
    if (argc != 3)
    {
        Usage(argv[0]);
        exit(0);
    }
    uint16_t serverport = std::stoi(argv[2]);
    std::string serverip = argv[1];

    ThreadData td;
    bzero(&td.server, sizeof(td.server));
    td.server.sin_family = AF_INET;
    td.server.sin_port = htons(serverport);                  // 转为网络字节序
    td.server.sin_addr.s_addr = inet_addr(serverip.c_str()); // 字符串ip也要转为网络字节序
    socklen_t len = sizeof(td.server);

    td.socket = socket(AF_INET, SOCK_DGRAM, 0);
    if (td.socket < 0)
    {
        std::cout << "socket error" << std::endl;
        exit(1);
    }

    pthread_t recv, send;
    pthread_create(&recv, nullptr, Recv, &td);
    pthread_create(&send, nullptr, Send, &td);

    pthread_join(recv, nullptr);
    pthread_join(send, nullptr);

    close(td.socket);
    return 0;
}

/dev/pts下面是终端文件,对应我们的xshell下面新打开开的终端。也就意味着我们可以利用重定向往特定的终端文件上面去打印!

地址转换函数

我们本篇文章只介绍基于IPv4的socket网络编程,sockaddr_in 中的成员struct in_addr sin_addr表示32位的IP地址。但是我们通常用点分十进制的字符串表示IP地址,以下函数可以在字符串表示和in_addr表示之间转换。

字符串转in_addr的函数:

#include <arpa/inet.h>

int inet_aton(const char * strptr,struct in_addr * addrptr);

in_addr_t inet_addr(const char * strptr);

int inet_pton(int family,const char* strptr, void* addrptr);

in_addr转字符串的函数:

char* inet_ntoa(struct in_addr inaddr);

const char* inet_ntop(int family,const void * addrptr, char * strptr, size_t len);

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

关于inet_ntoa

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

man手册上说,inet_ntoa函数,是把这个返回结果放到了静态存储区。这个时候不需要我们手动进行释放。

那么问题来了,如果我们调用多次这个函数,会有什么样的效果呢?参考下面代码:

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

int main()
{
    struct sockaddr_in addr1;
    struct sockaddr_in addr2;
    addr1.sin_addr.s_addr = 0;
    addr2.sin_addr.s_addr = 0xffffffff;
    char *ptr1 = inet_ntoa(addr1.sin_addr);
    char *ptr2 = inet_ntoa(addr2.sin_addr);
    printf("ptr1:%s,ptr2:%s\n", ptr1, ptr2);

    return 0;
}

运行结果为:

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

在APUE中明确提出inet_ntoa不是线程安全的函数。

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

TCP socket API详解

下面将介绍TCP要用到的socket API,这些函数都在sys/socket.h中。

socket():

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

2.应用程序可以像读写文件一样用read()/write()在网络上收发数据。

3.如果socket()调用出错则返回-1.

4.对于IPv4,family参数指定为AF_INET。

5.对于TCP协议,type参数指定为SOCK_STREAM,表示面向流的传输协议。

6.protocol参数再次不做详细介绍,指定为0即可。

bind():

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

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

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

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

我们的程序中对myaddr参数是这样初始化的:

bzero(&serveraddr,sizeof(serveraddr));

serveraddr.sin_family=AF_INET;

serveraddr.sin_port=htons(SERV_PORT);

serveraddr.sin_addr.s_addr=htonl(INADDR_ANY);
1.将整个结构体清零。

2.设置地址类型为AF_INET。

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

4.端口号为SERV_PORT,我们定义为9999.

listen():

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

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

accept():

1.三次握手完成之后,服务器调用accept()接收连接。

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

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

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

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

注意:accept()的返回值是一个sockfd,参数传入被监听成功的sockfd,然后再传入新的sockfd,用于之后的IO。

connect()

1.客户端需要调用connect()连接服务器。

2.connect()和bind()的参数形式一致,区别在于bind()的参数是自己的地址,而connect()的参数是对方的地址。

3.connect()成功返回0,出错返回-1.

由于tcp/udp客户端不需要固定的端口号,因此不必调用bind(),客户端的端口号由内核自动分配!

注意:

1.客户端不是不允许调用bind(),只是没有必要调用bind()固定一个端口号,否则如果在同一台机器上启动多个服务端,就会出现端口号被占用导致不能正确建立连接。

2.服务器也不是必须调用bind(),但如果服务器不调用bind(),内核会自动给服务器分配监听端口,每次启动服务器时,端口号都不一样,客户端要连接服务器就会遇到麻烦。

单进程版本的tcpServer:

cpp 复制代码
tcpServer.hpp



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

static std::string defaultip = "0.0.0.0";
const uint16_t defaultport = 8080;
const int backlog = 10;

enum
{
    SOCK_ERR = 1,
    BIND_ERR,
    LISTEN_ERR,
};

class TcpServer
{
public:
    TcpServer(uint16_t port = defaultport, const std::string &ip = defaultip)
        : _listensock(-1), _serverport(port), _serverip(ip)
    {
    }

    void Init()
    {
        // tcpServer初始化
        // 1.创建socket
        _listensock = socket(AF_INET, SOCK_STREAM, 0);
        if (_listensock < 0)
        {
            lg(Fatal, "create sockfd err,sockfd:%d,err string:%s", _listensock, strerror(errno));
            exit(SOCK_ERR);
        }
        lg(Info, "create sockfd success!,sockfd:%d", _listensock);
        // 在bind之前填充结构体
        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(_serverport);
        inet_pton(AF_INET, _serverip.c_str(), &(local.sin_addr));

        // 2.进行bind
        if (bind(_listensock, (const struct sockaddr *)&local, sizeof(local)) < 0)
        {
            lg(Fatal, "bind error,err string:%s", strerror(errno));
            exit(BIND_ERR);
        }
        lg(Info, "bind success!");

        // 3.listen
        if (listen(_listensock, backlog) < 0)
        {
            lg(Fatal, "listen error,err string:%s", strerror(errno));
            exit(LISTEN_ERR);
        }
        lg(Info, "listen sockfd success !");

        // 至此,创建tcpserver完毕
    }

    void Start()
    {
        std::cout << "tcpServer running..." << std::endl;
        while (true)
        {
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            int sockfd = accept(_listensock, (struct sockaddr *)&client, &len);
            if (sockfd < 0)
            {
                lg(Fatal, "accept error,err string:%s", strerror(errno));
                continue;
            }

            uint16_t clientport = ntohs(client.sin_port);
            char clientip[32];
            inet_ntop(AF_INET, &(client.sin_addr), clientip, sizeof(clientip));

            lg(Info, "get a new link...,sockfd:%d,clientip:%s,clientport:%d", sockfd, clientip, clientport);

            // 进行通信
            Service(sockfd, clientport, clientip);
            close(sockfd);
        }
    }

    void Service(int sockfd, uint16_t clientport, const std::string &clientip)
    {
        char buffer[4096];
        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 info = buffer;
                std::string echo_str = "server get a message: " + info;
                write(sockfd, echo_str.c_str(), echo_str.size());
            }
            else if (n == 0)
            {
                lg(Info, "%s:%d quit,server close sockfd!", clientip.c_str(), clientport);
                break;
            }
            else
            {
                lg(Info, "read error! err string:%s", strerror(errno));
                break;
            }
        }
    }

    ~TcpServer()
    {
    }

private:
    int _listensock;
    std::string _serverip;
    uint16_t _serverport;
};
cpp 复制代码
main.cc



#include <memory>
#include "tcpServer.hpp"

void Usage(const std::string &proc)
{
    std::cout << "\n\tUsage: " << proc << " port!" << std::endl;
}

// ./tcpserver port
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        Usage(argv[0]);
        exit(0);
    }

    uint16_t clientport = std::stoi(argv[1]);

    std::unique_ptr<TcpServer> tcp_svr(new TcpServer(clientport));
    tcp_svr->Init();
    tcp_svr->Start();

    return 0;
}
cpp 复制代码
tcpClient.cc



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

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

// ./tcpclient serverip serverport
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        exit(0);
    }

    std::string serverip = argv[1];
    uint16_t serverport = std::stoi(argv[2]);

    // 创建socket
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        std::cout << "create sockfd errno!" << std::endl;
        exit(1);
    }

    // tcp客户端不需要显示bind,由OS自动选择
    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));
    socklen_t len = sizeof(server);

    // tcp客户端需要connect
    int n = connect(sockfd, (const struct sockaddr *)&server, len);
    if (n < 0)
    {
        std::cout << "connect error!" << std::endl;
        exit(1);
    }

    while (true)
    {
        std::string line;
        while (true)
        {
            // 发送信息
            std::cout << "Please Enter@";
            std::getline(std::cin, line);

            write(sockfd, line.c_str(), line.size());

            // 接收server的信息
            char buffer[4096];
            int n = read(sockfd, buffer, sizeof(buffer));
            if (n > 0)
            {
                buffer[n] = 0;
                std::cout << buffer << std::endl;
            }
        }
    }

    close(sockfd);
    return 0;
}

我们发现,单进程版本的tcpserver和tcpclient一次只能有一个进行通信。

多进程版本的tcpServer:

cpp 复制代码
tcpServer.hpp---多进程版本




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

static std::string defaultip = "0.0.0.0";
const uint16_t defaultport = 8080;
const int backlog = 10;

enum
{
    SOCK_ERR = 1,
    BIND_ERR,
    LISTEN_ERR,
};

class TcpServer
{
public:
    TcpServer(uint16_t port = defaultport, const std::string &ip = defaultip)
        : _listensock(-1), _serverport(port), _serverip(ip)
    {
    }

    void Init()
    {
        // tcpServer初始化
        // 1.创建socket
        _listensock = socket(AF_INET, SOCK_STREAM, 0);
        if (_listensock < 0)
        {
            lg(Fatal, "create sockfd err,sockfd:%d,err string:%s", _listensock, strerror(errno));
            exit(SOCK_ERR);
        }
        lg(Info, "create sockfd success!,sockfd:%d", _listensock);
        // 在bind之前填充结构体
        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(_serverport);
        inet_pton(AF_INET, _serverip.c_str(), &(local.sin_addr));

        // 2.进行bind
        if (bind(_listensock, (const struct sockaddr *)&local, sizeof(local)) < 0)
        {
            lg(Fatal, "bind error,err string:%s", strerror(errno));
            exit(BIND_ERR);
        }
        lg(Info, "bind success!");

        // 3.listen
        if (listen(_listensock, backlog) < 0)
        {
            lg(Fatal, "listen error,err string:%s", strerror(errno));
            exit(LISTEN_ERR);
        }
        lg(Info, "listen sockfd success !");

        // 至此,创建tcpserver完毕
    }

    void Start()
    {
        signal(SIGCHLD, SIG_IGN); // 对SIGCHLD信号的默认动作改为SIG_IGN忽略,Linux优化了
        // 所以在Linux下只要对SIGCHLD信号的默认动作改为SIG_IGN就可以使父进程不需要再等待子进程了!
        std::cout << "tcpServer running..." << std::endl;
        while (true)
        {
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            int sockfd = accept(_listensock, (struct sockaddr *)&client, &len);
            if (sockfd < 0)
            {
                lg(Fatal, "accept error,err string:%s", strerror(errno));
                continue;
            }

            uint16_t clientport = ntohs(client.sin_port);
            char clientip[32];
            inet_ntop(AF_INET, &(client.sin_addr), clientip, sizeof(clientip));

            lg(Info, "get a new link...,sockfd:%d,clientip:%s,clientport:%d", sockfd, clientip, clientport);

            // 通信----多进程版本
            pid_t id = fork();
            if (id == 0)
            {
                // child----注意child会继承father的文件描述符
                close(_listensock);
                if (fork() > 0)
                    exit(0);
                Service(sockfd, clientport, clientip); // 这里是孙子进程,被system领养
                close(sockfd);
                exit(0);
            }
            // father
            close(sockfd);
        }
    }

    void Service(int sockfd, uint16_t clientport, const std::string &clientip)
    {
        char buffer[4096];
        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 info = buffer;
                std::string echo_str = "server get a message: " + info;
                write(sockfd, echo_str.c_str(), echo_str.size());
            }
            else if (n == 0)
            {
                lg(Info, "%s:%d quit,server close sockfd!", clientip.c_str(), clientport);
                break;
            }
            else
            {
                lg(Info, "read error! err string:%s", strerror(errno));
                break;
            }
        }
    }

    ~TcpServer()
    {
    }

private:
    int _listensock;
    std::string _serverip;
    uint16_t _serverport;
};

主要利用了:

多线程版本的tcpServer:

cpp 复制代码
tcpServer.hpp-----多线程版本





#include <iostream>
#include <unistd.h>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <pthread.h>
#include "Log.hpp"

static std::string defaultip = "0.0.0.0";
const uint16_t defaultport = 8080;
const int backlog = 10;

enum
{
    SOCK_ERR = 1,
    BIND_ERR,
    LISTEN_ERR,
};

class TcpServer;

struct ThreadData
{
    ThreadData(int fd, const std::string &ip, uint16_t port, TcpServer *t)
        : _sockfd(fd), _clientip(ip), _clientport(port), _tsv(t)
    {
    }
    int _sockfd;
    std::string _clientip;
    uint16_t _clientport;
    TcpServer *_tsv;
};

class TcpServer
{
public:
    TcpServer(uint16_t port = defaultport, const std::string &ip = defaultip)
        : _listensock(-1), _serverport(port), _serverip(ip)
    {
    }

    void Init()
    {
        // tcpServer初始化
        // 1.创建socket
        _listensock = socket(AF_INET, SOCK_STREAM, 0);
        if (_listensock < 0)
        {
            lg(Fatal, "create sockfd err,sockfd:%d,err string:%s", _listensock, strerror(errno));
            exit(SOCK_ERR);
        }
        lg(Info, "create sockfd success!,sockfd:%d", _listensock);
        // 在bind之前填充结构体
        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(_serverport);
        inet_pton(AF_INET, _serverip.c_str(), &(local.sin_addr));

        // 2.进行bind
        if (bind(_listensock, (const struct sockaddr *)&local, sizeof(local)) < 0)
        {
            lg(Fatal, "bind error,err string:%s", strerror(errno));
            exit(BIND_ERR);
        }
        lg(Info, "bind success!");

        // 3.listen
        if (listen(_listensock, backlog) < 0)
        {
            lg(Fatal, "listen error,err string:%s", strerror(errno));
            exit(LISTEN_ERR);
        }
        lg(Info, "listen sockfd success !");

        // 至此,创建tcpserver完毕
    }

    static void *Routine(void *args)
    {
        pthread_detach(pthread_self());
        ThreadData *td = static_cast<ThreadData *>(args);
        td->_tsv->Service(td->_sockfd, td->_clientport, td->_clientip);
        close(td->_sockfd);
        delete td;
        return nullptr;
    }

    void Start()
    {
        // signal(SIGCHLD, SIG_IGN); // 对SIGCHLD信号的默认动作改为SIG_IGN忽略,Linux优化了
        // 所以在Linux下只要对SIGCHLD信号的默认动作改为SIG_IGN就可以使父进程不需要再等待子进程了!
        std::cout << "tcpServer running..." << std::endl;
        while (true)
        {
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            int sockfd = accept(_listensock, (struct sockaddr *)&client, &len);
            if (sockfd < 0)
            {
                lg(Fatal, "accept error,err string:%s", strerror(errno));
                continue;
            }

            uint16_t clientport = ntohs(client.sin_port);
            char clientip[32];
            inet_ntop(AF_INET, &(client.sin_addr), clientip, sizeof(clientip));

            lg(Info, "get a new link...,sockfd:%d,clientip:%s,clientport:%d", sockfd, clientip, clientport);

            // 通信----多线程版本
            ThreadData *td = new ThreadData(sockfd, clientip, clientport, this);
            pthread_t tid;
            pthread_create(&tid, nullptr, Routine, td);
        }
    }

    void Service(int sockfd, uint16_t clientport, const std::string &clientip)
    {
        char buffer[4096];
        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 info = buffer;
                std::string echo_str = "server get a message: " + info;
                write(sockfd, echo_str.c_str(), echo_str.size());
            }
            else if (n == 0)
            {
                lg(Info, "%s:%d quit,server close sockfd!", clientip.c_str(), clientport);
                break;
            }
            else
            {
                lg(Info, "read error! err string:%s", strerror(errno));
                break;
            }
        }
    }

    ~TcpServer()
    {
    }

private:
    int _listensock;
    std::string _serverip;
    uint16_t _serverport;
};

线程池版本的tcpServer:

cpp 复制代码
tcpServer.hpp




#include <iostream>
#include <unistd.h>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <pthread.h>
#include "Log.hpp"
#include "ThreadPool.hpp"
#include "Task.hpp"

static std::string defaultip = "0.0.0.0";
const uint16_t defaultport = 8080;
const int backlog = 10;

enum
{
    SOCK_ERR = 1,
    BIND_ERR,
    LISTEN_ERR,
};

class TcpServer;

struct ThreadData
{
    ThreadData(int fd, const std::string &ip, uint16_t port, TcpServer *t)
        : _sockfd(fd), _clientip(ip), _clientport(port), _tsv(t)
    {
    }
    int _sockfd;
    std::string _clientip;
    uint16_t _clientport;
    TcpServer *_tsv;
};

class TcpServer
{
public:
    TcpServer(uint16_t port = defaultport, const std::string &ip = defaultip)
        : _listensock(-1), _serverport(port), _serverip(ip)
    {
    }

    void Init()
    {
        // tcpServer初始化
        // 1.创建socket
        _listensock = socket(AF_INET, SOCK_STREAM, 0);
        if (_listensock < 0)
        {
            lg(Fatal, "create sockfd err,sockfd:%d,err string:%s", _listensock, strerror(errno));
            exit(SOCK_ERR);
        }
        lg(Info, "create sockfd success!,sockfd:%d", _listensock);
        // 在bind之前填充结构体
        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(_serverport);
        inet_pton(AF_INET, _serverip.c_str(), &(local.sin_addr));

        // 2.进行bind
        if (bind(_listensock, (const struct sockaddr *)&local, sizeof(local)) < 0)
        {
            lg(Fatal, "bind error,err string:%s", strerror(errno));
            exit(BIND_ERR);
        }
        lg(Info, "bind success!");

        // 3.listen
        if (listen(_listensock, backlog) < 0)
        {
            lg(Fatal, "listen error,err string:%s", strerror(errno));
            exit(LISTEN_ERR);
        }
        lg(Info, "listen sockfd success !");

        // 至此,创建tcpserver完毕
    }

    void Start()
    {
        // signal(SIGCHLD, SIG_IGN); // 对SIGCHLD信号的默认动作改为SIG_IGN忽略,Linux优化了
        // 所以在Linux下只要对SIGCHLD信号的默认动作改为SIG_IGN就可以使父进程不需要再等待子进程了!
        ThreadPool<Task>::GetInstance()->Start();
        std::cout << "tcpServer running..." << std::endl;
        while (true)
        {
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            int sockfd = accept(_listensock, (struct sockaddr *)&client, &len);
            if (sockfd < 0)
            {
                lg(Fatal, "accept error,err string:%s", strerror(errno));
                continue;
            }

            uint16_t clientport = ntohs(client.sin_port);
            char clientip[32];
            inet_ntop(AF_INET, &(client.sin_addr), clientip, sizeof(clientip));

            lg(Info, "get a new link...,sockfd:%d,clientip:%s,clientport:%d", sockfd, clientip, clientport);

            // 通信----多线程版本
            Task t(sockfd, clientip, clientport);
            ThreadPool<Task>::GetInstance()->Push(t);
        }
    }

    ~TcpServer()
    {
    }

private:
    int _listensock;
    std::string _serverip;
    uint16_t _serverport;
};
cpp 复制代码
ThreadPool.hpp




#pragma once
#include <iostream>
#include <unistd.h>
#include <string>
#include <vector>
#include <queue>
#include <pthread.h>

const int defaultnum = 5;

struct ThreadInfo
{
    pthread_t tid;
    std::string threadname;
};

template <class T>
class ThreadPool
{
public:
    bool IsQueueEmpty()
    {
        return _tasks.empty();
    }
    std::string GetThreadName(pthread_t tid)
    {
        for (const auto &td : _threads)
        {
            if (td.tid == tid)
            {
                return td.threadname;
            }
        }
        return "None";
    }

    static void *ThreadRoutine(void *args)
    {
        pthread_detach(pthread_self());
        ThreadPool<T> *tp = static_cast<ThreadPool<T> *>(args);
        // std::string name = tp->GetThreadName(pthread_self());
        while (true)
        {
            tp->Lock();
            while (tp->IsQueueEmpty())
            {
                tp->SleepThread();
            }
            T t = tp->Pop();
            tp->Unlock();
            t();
        }
        return nullptr;
    }

    void Start()
    {
        int num = _threads.size();
        for (size_t i = 0; i < num; i++)
        {
            _threads[i].threadname = "thread-" + std::to_string(i + 1);
            pthread_create(&(_threads[i].tid), nullptr, ThreadRoutine, this);
        }
    }

    void Push(const T &in)
    {
        Lock();
        _tasks.push(in);
        WakeUpThread();
        Unlock();
    }

    T Pop()
    {
        T t = _tasks.front();
        _tasks.pop();
        return t;
    }

    static ThreadPool<T> *GetInstance()
    {
        if (_inst == nullptr)
        {
            pthread_mutex_lock(&_lock);
            if (_inst == nullptr)
            {
                std::cout << "create Instance !" << std::endl;
                _inst = new ThreadPool();
            }
            pthread_mutex_unlock(&_lock);
        }
        return _inst;
    }

public:
    void Lock()
    {
        pthread_mutex_lock(&_mutex);
    }
    void Unlock()
    {
        pthread_mutex_unlock(&_mutex);
    }
    void WakeUpThread()
    {
        pthread_cond_signal(&_cond);
    }
    void SleepThread()
    {
        pthread_cond_wait(&_cond, &_mutex);
    }

private:
    ThreadPool(int num = defaultnum) : _threads(num)
    {
        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&_cond, nullptr);
    }
    ~ThreadPool()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_cond);
    }
    ThreadPool(const ThreadPool<T> &) = delete;
    ThreadPool<T> operator=(const ThreadPool<T> &) = delete;

private:
    std::vector<ThreadInfo> _threads;
    std::queue<T> _tasks;
    pthread_mutex_t _mutex;
    pthread_cond_t _cond;

    static pthread_mutex_t _lock;
    static ThreadPool<T> *_inst;
};

template <class T>
pthread_mutex_t ThreadPool<T>::_lock = PTHREAD_MUTEX_INITIALIZER; // 这样初始化不需要destroy

template <class T>
ThreadPool<T> *ThreadPool<T>::_inst = nullptr;
cpp 复制代码
Task.hpp




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

class Task
{
public:
    Task(int sockfd, const std::string &clientip, uint16_t clientport)
        : _sockfd(sockfd), _clientip(clientip), _clientport(clientport)
    {
    }
    Task() {}

    std::string ShowClientDetail(const std::string &echo, const std::string &ip, uint16_t port)
    {
        std::cout << "[" << ip << ":" << port << "]#" << " " << echo << std::endl;
        std::string ret = "tcpserver get a message:" + echo;
        return ret;
    }

    void run()
    {
        char buffer[4096];
        while (true)
        {
            memset(buffer, 0, sizeof(buffer));
            ssize_t n = read(_sockfd, buffer, sizeof(buffer));
            if (n > 0)
            {
                // std::cout << buffer << std::endl;
                std::string line = buffer;
                std::string echo_str = ShowClientDetail(line, _clientip, _clientport);
                write(_sockfd, echo_str.c_str(), echo_str.size());
            }
            else if (n == 0)
            {
                lg(Info, "client quit,me too!");
                break;
            }
            else
            {
                lg(Warning, "read error!");
                break;
            }
        }
        close(_sockfd);
    }
    void operator()()
    {
        run();
    }

private:
    int _sockfd;
    std::string _clientip;
    uint16_t _clientport;
};
cpp 复制代码
main.cc




#include <memory>
#include "tcpServer.hpp"

void Usage(const std::string &proc)
{
    std::cout << "\n\tUsage: " << proc << " port!" << std::endl;
}

// ./tcpserver port
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        Usage(argv[0]);
        exit(0);
    }

    uint16_t clientport = std::stoi(argv[1]);

    std::unique_ptr<TcpServer> tcp_svr(new TcpServer(clientport));
    tcp_svr->Init();
    tcp_svr->Start();

    return 0;
}
cpp 复制代码
tcpclient.cc




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

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

// ./tcpclient serverip serverport
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        exit(0);
    }

    std::string serverip = argv[1];
    uint16_t serverport = std::stoi(argv[2]);

    // 创建socket
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        std::cout << "create sockfd errno!" << std::endl;
        exit(1);
    }

    // tcp客户端不需要显示bind,由OS自动选择
    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));
    socklen_t len = sizeof(server);

    // tcp客户端需要connect
    int n = connect(sockfd, (const struct sockaddr *)&server, len);
    if (n < 0)
    {
        std::cout << "connect error!" << std::endl;
        exit(1);
    }

    while (true)
    {
        std::string line;
        while (true)
        {
            // 发送信息
            line.clear();
            std::cout << "Please Enter@";
            std::getline(std::cin, line);
            std::string echo_str = "tcpclient say@ " + line;

            write(sockfd, echo_str.c_str(), echo_str.size());

            // 接收server的信息
            char buffer[4096];
            int n = read(sockfd, buffer, sizeof(buffer));
            if (n > 0)
            {
                buffer[n] = 0;
                std::cout << buffer << std::endl;
            }
        }
    }

    close(sockfd);
    return 0;
}

下面来介绍一个函数setsockopt函数,防止偶发性的服务器无法进行立即重启

复制代码

1.sockfd:为套接字描述符。

2.level:指定选项所在的协议层

SOL_SOCKET:通用套接字选项

IPPROTO_IP:IPv4协议选项

IPPROTO_IPV6:IPv6协议选项

IPPROTO_TCP:TCP协议选项

IPPROTO_UDP:UDP协议选项

3.optname:(选项名称)

常见的有:

SOL_SOCKET 级别:

  • SO_REUSEADDR允许重用本地地址(快速重启服务器)

  • SO_REUSEPORT:允许多个套接字绑定到相同端口

  • SO_KEEPALIVE:启用TCP保活机制

  • SO_RCVBUF / SO_SNDBUF:设置接收/发送缓冲区大小

  • SO_RCVTIMEO / SO_SNDTIMEO:设置接收/发送超时

  • SO_LINGER:控制close()的行为

IPPROTO_TCP 级别:

  • TCP_NODELAY:禁用Nagle算法(立即发送小数据包)

  • TCP_KEEPIDLE / TCP_KEEPINTVL / TCP_KEEPCNT:保活参数

**4.optval:**指向包含选项值的缓冲区指针,类型取决于具体选项。

5.optlen:optval缓冲区的长度。

守护进程

一般我们使用云服务器远程连接登录,一次登录是一个session,一个session只能有一个前台进程在运行,键盘信号只能发送给前台进程!

在同一个session下,启动多个后台进程,可以使用jobs命令来查看后台进程的情况。

每个后台进程都有自己的任务号。

如果不想受到任何用户登录和注销的影响,就把进程---->守护进程化。

守护进程的系统接口:

pid_t setsid(void);

注意:这个函数不能由进程组组长调用,来创建一个新的session。

重要步骤:

if(fork()> 0)exit(0);

setsid();//孤儿进程

注意:

下面利用setsid函数来实现一个简单的daemon小组件

cpp 复制代码
Daemon.hpp




#pragma once
#include <iostream>
#include <unistd.h>
#include <string>
#include <cstdlib>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

const char *file = "/dev/null";

void Daemon(const std::string &cwd = "")
{
    // 1.忽略异常信号
    signal(SIGCHLD, SIG_IGN);
    signal(SIGPIPE, SIG_IGN);
    signal(SIGSTOP, SIG_IGN);

    // 2.将自己变成独立会话
    if (fork() > 0)
        exit(0);
    setsid();

    // 3.更改当前调用进程的cwd路径
    if (!cwd.empty())
        chdir(cwd.c_str());

    // 4.把标准输入,标准输出,标准错误重定向到/dev/null
    int fd = open(file, O_RDWR);
    if (fd > 0)
    {
        dup2(fd, 0);
        dup2(fd, 1);
        dup2(fd, 2);
        close(fd);
    }
}

测试一下:

cpp 复制代码
process.cc


#include <iostream>
#include <unistd.h>
#include "Daemon.hpp"

int main()
{
    Daemon();
    while (1)
    {
        std::cout << "process running..." << std::endl;
        sleep(1);
    }

    return 0;
}

ls /proc/PID -l :查看PID进程的一些详细信息。

守护进程一般都用d来结尾,比如说sshd...

TCP通信流程简单描述:

下面是基于TCP协议的客户端/服务器程序的一般流程:

服务器初始化:

1.调用socket,创建文件描述符。

2.调用bind,将当前的文件描述符和IP/port绑定在一起,如果这个端口已经被其他进程占用了,就会bind失败。

3.调用listen,声明当前这个文件描述符作为一个服务器的文件描述符,为后面的accept做好准备。

4.调用accept,并阻塞,等待客户端连接过来。

建立连接的过程:

1.调用socket,创建文件描述符。

2.调用connect,向服务器发起连接请求。

3.connect会发出SYN段并阻塞等待服务器应答。(第一次)

4.服务器收到客户端的SYN,会应答一个SYN-ACK段表示"同意建立连接"。(第二次)

5.客户端收到SYN-ACK后会从connect()返回,同时应答一个ACK段。(第三次)

这个建立连接的过程,通常称为三次握手。

数据传输的过程:

1.建立连接成功后,TCP协议提供全双工的通信服务,所谓全双工的意思是:在同一条连接中,同一时刻,通信双方可以同时写数据。相对的概念是半双工:同一条连接在同一时刻,只能由一方来写数据。

2.服务器从accept()返回后立刻调用read(),读socket就像读管道一样,如果没有数据到达就阻塞等待。

3.这时客户端调用write()发送请求给服务器,服务器收到后从read()返回,对客户端的请求进行处理,在此期间客户端调用read()阻塞等待服务器的应答。

4.服务器调用write()将处理结果发回给客户端,再次调用read()阻塞等待下一条请求。

5.客户端收到后从read()返回,发送下一条请求,如此循环下去。

断开连接的过程:

1.如果客户端没有更多的请求了,就调用close()关闭连接,客户端会向服务器发送FIN段。(第一次)

2.此时服务器收到FIN后,会回应一个ACK,同时read会返回0 。(第二次)

3.read()返回之后,服务器就知道客户端关闭了连接,也调用close()关闭连接,这个时候服务器会向客户端发送一个FIN。(第三次)

4.客户端收到FIN,再返回一个ACK给服务器。(第四次)

这个断开连接的过程,通常称为 四次挥手。

在学习socket API时要注意应用程序和TCP协议层是如何交互的:

1.应用程序调用某个socket函数时,TCP协议层完成什么动作,比如调用connect()会发出SYN段。

2.应用程序如何知道TCP协议层的状态变化,比如从某个阻塞的socket函数返回就表明TCP协议收到了某些段,再比如read()返回0就表明收到了FIN段。

TCP和UDP对比

1.可靠传输vs不可靠传输。

2.有连接vs无连接。

3.字节流vs数据报。

相关推荐
那些年的笔记2 小时前
Linux屏幕旋转方法
linux·运维·服务器
竹之却2 小时前
CentOS 系列,防火墙相关指令
linux·运维·centos
gaize12132 小时前
科普篇“机架、塔式、刀片”三类服务器对比
运维·服务器
AlexDeng3 小时前
Git 实战:我用 git worktree 一次性拉取多个工作目录
git
以太浮标3 小时前
华为eNSP模拟器综合实验之- VLAN聚合(VLAN Aggregation或Super VLAN)解析
运维·网络·华为·信息与通信
咕噜企业分发小米3 小时前
如何利用云服务器搭建游戏服务器并实现跨平台游戏?
运维·服务器·游戏
import_random3 小时前
[git版本控制]git pull origin main
git
一颗青果3 小时前
进程组 | 会话 |终端 | 前台后台 | 守护进程
linux·运维·jvm
古城小栈3 小时前
Rust 交叉编译:Windows ====> Linux (musl 静态编译)
linux·windows·rust