Linux_网络编程套接字_2

文章目录

  • 一、预备知识
    • 认识端口
    • 认识TCP协议
    • 认识UDP协议
    • 网络字节序
  • 二、socket编程接口
    • 1.socket常见API
    • 2.socket结构
      • 总结
    • 3.其他接口
      • 1.IP地址《=》整数 inet_addr等等
      • 2.读取报文 - recvfrom
  • 三、简单的UDP网络程序 - 聊天室
    • 1.源代码
    • 展示
  • 四、TCP
    • 1.编写TCP服务器程序
      • 1.创建套接字 - socket
      • 2.填写服务信息 - struct sockaddr_in
      • 3.本地信息写入内核 - bind
      • 4.监听socket - listen
      • 5.获取连接 - accept
      • 6.开始服务
        • 1.创建子进程执行服务
        • 2.创建线程执行服务
        • 3.完整代码
    • 2.编写TPC客户端程序
      • 1.创建套接字
      • 2.发起链接请求
      • 3.完整代码
  • 五、守护进程
    • 1.如何自己形成
    • 2.系统接口生成守护进程 - daemon
    • 3.命令行运行变成守护进程 (命令)
  • 六、TCP理论(感性认识,铺垫下一章)


一、预备知识

认识端口

上一章节,已经认识IP地址,IP地址可以标识一台主机。
但在我们在网络通信的时候,只要让两台主机能够通信就可以了吗?
实际上,在进行通信的时候,不仅仅要考虑两台主机间互相交互数据。本质上讲,进行数据交互的时候是用户和用户在进行交互,用户的身份通常是用程序体现的。程序一定是在运行中——进程

主机间在通信的目的
本质是:在各自的主机上的两个进程在互相交换数据!
IP地址可以完成主机和主机的通信,而主机上各自的通信进程,才是发送和接受数据的一方

IP —— 确保主机的唯一性
端口号(port)—— 确保该主机上的进程的唯一性
IP+PORT = 标识互联网中唯一的一个进程!!把这个两个组合叫做secket
网络通信的本质:进程间通信

认识TCP协议

此处我们先对TCP(Transmission Control Protocol 传输控制协议)有一个直观的认识; 后面我们再详细讨论TCP的一些细节问题

  • 传输层协议
  • 有连接
  • 可靠传输
  • 面向字节流

认识UDP协议

此处我们也是对UDP(User Datagram Protocol 用户数据报协议)有一个直观的认识; 后面再详细讨论

  • 传输层协议
  • 无连接
  • 不可靠传输
  • 面向数据报

网络字节序

网络字节序:大端
发送顺序:先发低地址再发高地址

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

  • 这些函数名很好记,h表示host,n表示network,l表示32位长整数,s表示16位短整数。
  • 例如htonl表示将32位的长整数从主机字节序转换为网络字节序,例如将IP地址转换后准备发送。
  • 如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回;
  • 如果主机是大端字节序,这些 函数不做转换,将参数原封不动地返回。

二、socket编程接口

1.socket常见API

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

  • int socket(int domain, int type, int protocol);
    参数:
    domain:域,表明通信方式
    a.本地通信填AF_UNIX
    b.网络通信填AF_INET
    type:套接字类型,决定了我们通信的时候对应的报文类型
    a.流式(tcp)
    b.用户数据报(udp)
    protocol:协议类型
    a.网络应用中:0
    返回值
    文件描述符,没错就是open函数返回那个文件描述符。

绑定端口号 (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 connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

2.socket结构

在这里插入图片描述
各种网络协议的地址格式并不相同,使用结构也不相同,可以先用struct sockaddr接受结构,再根据前16位地址类型判断是什么网络协议。

sockaddr 结构
在这里插入图片描述

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

in_addr结构
在这里插入图片描述
in_addr用来表示一个IPv4的IP地址. 其实就是一个32位的整数;

总结

对于struct sockaddr_in 要填参数就是

        struct sockaddr_in local;
        memset(&local,0,sizeof(local));
        local.sin_family = AF_INET;//协议家族
        ip_ = ip_.empty()?INADDR_ANY:ip_;
        local.sin_addr.s_addr = inet_addr(ip_.c_str());//ip 会用到inet_addr
        local.sin_port =htons(port_);//端口 会用到htons

3.其他接口

1.IP地址《=》整数 inet_addr等等

自动转成网络字节序
在这里插入图片描述

2.读取报文 - recvfrom

在这里插入图片描述
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);

参数:
flags:0(阻塞式读取)

三、简单的UDP网络程序 - 聊天室

  • 云服务禁止bind云服务器上的任何确定IP,只能使用INADDR_ANY
  • 虚拟机随意,查看ip指令ifconfig

1.源代码

聊天室服务器代码:

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

#include "Log.hpp"
using namespace std;
static void Usage(const std::string proc)
{
    std::cout << "Usage:\n\t" << proc << " port [ip]" << std::endl;
}
// struct MyStructHash
// {
//     size_t operator()(const sockaddr_in &obj) const
//     {
//         size_t port_hash = hash<uint16_t>{}(obj.sin_port);
//         size_t ip_hash = hash<uint32_t>{}(obj.sin_addr.s_addr);
//         return port_hash ^ (ip_hash << 1);
//     }
// };

struct MyStructHash
{
    std::size_t operator()(const struct sockaddr_in &addr) const
    {
        // Example hash calculation based on IP address and port
        std::size_t hash = 17;
        hash = hash * 31 + reinterpret_cast<const uint32_t *>(&addr.sin_addr)[0];
        hash = hash * 31 + ntohs(addr.sin_port);
        return hash;
    }
};

struct MyStructEqual
{
    bool operator()(const struct sockaddr_in &lhs, const struct sockaddr_in &rhs) const
    {
        return (lhs.sin_family == rhs.sin_family &&
                lhs.sin_port == rhs.sin_port &&
                memcmp(&lhs.sin_addr, &rhs.sin_addr, sizeof(struct in_addr)) == 0);
    }
};

class UdpServer
{
public:
    UdpServer(int port, std::string ip = "")
        : port_((uint16_t)port),
          ip_(ip),
          sockfd_(-1)
    {
    }
    ~UdpServer() {}
    void init()
    {
        // 1.创建socket套接字
        sockfd_ = socket(AF_INET, SOCK_DGRAM, 0); // 打开了一个文件
        if (sockfd_ < 0)
        {
            logMessage(FATAL, "socket create error :%s:%d",
                       strerror(errno), sockfd_);
            exit(1);
        }
        logMessage(DEBUG, "socket create success: %d", sockfd_);
        // 2.绑定网络信息,指明ip+port
        // 2.1先填充基本信息到struct sockaddr_in 在哪个头文件可以man inet_addr
        struct sockaddr_in local;
        // 初始化,全设置为0
        bzero(&local, sizeof(local));
        local.sin_family = AF_INET; // 填充协议家族
        local.sin_port = htons(port_);

        // 服务器都必须具有IP地址,"xx.yy.zz.aa",字符串风格点分十进制->4字节IP->uint32_t ip
        // INADDR_ANY(0):程序员不关心会bind到哪里一个ip,任意地址bind,强烈推荐做法,所有服务器一般的做法
        // inet_addr:指定填充确定的IP,特殊用途,或者测试时使用,除了做转化,还会自动给我们进行h->n
        local.sin_addr.s_addr = ip_.empty() ? INADDR_ANY : inet_addr(ip_.c_str());
        if (bind(sockfd_, (struct sockaddr *)&local, sizeof(local)) == -1)
        {
            logMessage(FATAL, "bind error: %s", strerror(errno));
            exit(2);
        }
        logMessage(DEBUG, "socket bind success: %d", sockfd_);
        // bind(sockfd,,sizeof(addr));
    }
    void broacast()
    {
        int cnt = 0;
        for (const auto &e : clients_)
        {
            std::cout<<++cnt<<":"<<buffer<<endl;

            sendto(sockfd_,buffer,sizeof(buffer)-1,0,(const struct sockaddr *)&e, (socklen_t)sizeof(e));
        }
    }

    void start()
    {

        while (true)
        {
            struct sockaddr_in peer;      // 输出型参数
            socklen_t len = sizeof(peer); // 输入输出型参数
            // logMessage(NOTICE, "Server 提供 servic 中...");
            // sleep(1);
            // UDP 无连接的
            // 对方给你发消息,你想给对方回消息,要对方的ip和port
            ssize_t s = recvfrom(sockfd_, buffer, sizeof(buffer) - 1,
                                 0, (struct sockaddr *)&peer, &len);
            if (s > 0)
            {
                buffer[s] = 0;
                logMessage(NOTICE,"client #%s",buffer);
                clients_.insert(peer);
                broacast();
            }
            else if (s == -1)
            {
                logMessage(WARNING, "recvfrom error:%s", strerror(errno));
                continue;
            }
            // 除了对方的消息还有对方的ip和port
        }
    }


private:
    char buffer[1024]; // 将来读取到的数据,都放在这里
    uint16_t port_;
    std::string ip_;
    int sockfd_;
    unordered_set<struct sockaddr_in, MyStructHash, MyStructEqual> clients_;
};

//.udpServer port [ip]

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

    port = atoi(argv[1]);
    std::string ip;
    if (argc == 3)
    {
        ip = argv[2];
    }

    UdpServer svr(port, ip);
    svr.init();
    svr.start();
    return 0;
}

聊天室输入栏代码:

#include <iostream>
#include <cstring>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <cassert>
#include<stdlib.h>
static void Usage(std::string name)
{
    std::cout << "Usage\n\t" << name << " server_ip server_port" << std::endl;
}

int main()
{

    std::string server_ip = "114.55.229.240";
    // std::string server_ip = "127.0.0.1";
    uint16_t server_port = 8080;
    std::string userName;
    std::cout<<"请输入你昵称:";
    std::cin>>userName;
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    assert(sockfd > 0);
    // client 需不需要bind不需要
    // 所谓“不需要”,指的是:不需要用户之间bind端口号信息!因为OS会自动给你绑定,
    // 非要做也可,但不推荐
    // 为什么 ?clinet很多,不能给客户端port,port可能被别人使用
    std::string buffer;
    struct sockaddr_in peer;
    bzero(&peer, sizeof(peer));
    peer.sin_family = AF_INET;
    peer.sin_addr.s_addr = inet_addr(server_ip.c_str());
    peer.sin_port = htons(server_port);
    while (true)
    {

        std::cout << userName<<": ";
        std::cin >> buffer;
        system("clear");
        buffer=userName+": "+buffer;
        ssize_t count = sendto(sockfd, buffer.c_str(), buffer.size(), 0, (struct sockaddr *)&peer, (socklen_t)sizeof(peer));
        if(count<0)
        {
            std::cout<<"sendto error"<<std::endl;
        }
    }

    return 0;
}


聊天室显示栏代码:

#include <iostream>
#include <cstring>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <cassert>
static void Usage(std::string name)
{
    std::cout << "Usage\n\t" << name << " server_ip server_port" << std::endl;
}

char buffer[1024];
int main()
{

    std::string server_ip = "114.55.229.240";
    // std::string server_ip = "127.0.0.1";
    uint16_t server_port = 8080;
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    assert(sockfd > 0);
    struct sockaddr_in peer;
    bzero(&peer, sizeof(peer));
    peer.sin_family = AF_INET;
    peer.sin_addr.s_addr = inet_addr(server_ip.c_str());
    peer.sin_port = htons(server_port);
    const char* str ="有人建立链接o~o";
    ssize_t count = sendto(sockfd,str , strlen(str), 0, (struct sockaddr *)&peer, (socklen_t)sizeof(peer));
    while (true)
    {
        ssize_t s = recvfrom(sockfd,buffer,sizeof(buffer)-1,0,nullptr,nullptr);
        if(s>0)
        {
            buffer[s] =0;
            std::cout<<buffer<<std::endl;
        }
        else
        {
            std::cerr<<"recvfrom error"<<std::endl;
        }

    }

    return 0;
}


展示

在这里插入图片描述

四、TCP

1.编写TCP服务器程序

1.创建套接字 - socket

        sock_ = socket(AF_INET, SOCK_STREAM, 0);
        if (sock_ < 0)
        {
            logMessage(FATAL, "socket error: %s", strerror(errno));
            exit(1);
        }

2.填写服务信息 - struct sockaddr_in

        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET; // 协议家族
        local.sin_addr.s_addr = ip_.empty()?INADDR_ANY:inet_addr(ip_.c_str()); // ip 会用到inet_addr
        local.sin_port = htons(port_);                  // 端口 会用到htons

3.本地信息写入内核 - bind

        if (bind(sock_, (struct sockaddr *)&local, sizeof(local)) == -1)
        {
            logMessage(FATAL, "bind error: %s", strerror(errno));
            exit(2);
        }

4.监听socket - listen

为什么要监听?
tcp是面向连接的!

作用将套接字变成监听套接字,获取新的连接。

        if(listen(listensock_,5)<0)
        {
            logMessage(FATAL,"listen : %s",strerror(errno));
            exit(3);
        }
        logMessage(DEBUG, "listen success");

5.获取连接 - accept

    void loop()
    {
        while(true)
        {
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            int servicSock = accept(listensock_,(struct sockaddr*)&peer,&len);
            if(servicSock<0)
            {
                logMessage(WARNING,"accept error : %s",strerror(errno));
            }
            //获取客户端基本信息
            int peerPort = ntohs(peer.sin_port);
            std::string  peerIp = inet_ntoa(peer.sin_addr);
            logMessage(DEBUG,"accept success %s:%d, socket fd: %d",peerIp.c_str(),peerPort,servicSock);
            
            //开始服务
        }
    }

6.开始服务

但开始服务,主线程不能执行,主线程还有获取链接的事情要做,所以就需要别的来做:

1.创建子进程执行服务

进程退出,如何回收资源?
方案一:
父进程将信号SIGCHLD设置为SIG_IGN,就不会产生僵尸进程

signal(SIGCHLD,SIG_IGN);
    void loop()
    {
        signal(SIGCHLD,SIG_IGN);

        while (true)
        {
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            int servicSock = accept(listensock_, (struct sockaddr *)&peer, &len);

            if (servicSock < 0)
            {
                logMessage(WARNING, "accept error : %s", strerror(errno));
            }
            // 获取客户端基本信息
            int peerPort = ntohs(peer.sin_port);
            std::string peerIp = inet_ntoa(peer.sin_addr);
            logMessage(DEBUG, "accept success %s:%d, socket fd: %d", peerIp.c_str(), peerPort, servicSock);
            // 提供服务
            // 单进程版本,只能提供一个服务,
            //  transServic(servicSock,peerIp,peerPort);

            // 多进程版本
            pid_t id = fork();
            assert(id != -1);
            if (id == 0)
            {
                close(listensock_);
                // 子进程
                transServic(servicSock, peerIp, peerPort);
                exit(0);
            }
            close(servicSock);//这一步是一定要。

        }
    }

方案二:
让孙子进行程服务,子进程立刻退出,孙子进程变成孤儿,由系统回收资源,父进程回收子进程资源就行,不会因为等待导致阻塞。

    void loop()
    {
        // signal(SIGCHLD,SIG_IGN);

        while (true)
        {
            std::cout<<"等待"<<std::endl;
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            int servicSock = accept(listensock_, (struct sockaddr *)&peer, &len);

            if (servicSock < 0)
            {
                logMessage(WARNING, "accept error : %s", strerror(errno));
            }
            // 获取客户端基本信息
            int peerPort = ntohs(peer.sin_port);
            std::string peerIp = inet_ntoa(peer.sin_addr);
            logMessage(DEBUG, "accept success %s:%d, socket fd: %d", peerIp.c_str(), peerPort, servicSock);
            // 提供服务
            // 单进程版本,只能提供一个服务,
            //  transServic(servicSock,peerIp,peerPort);

            // 多进程版本
            pid_t id = fork();
            assert(id != -1);
            //对于子进程退出,如何回收资源
            //方案一:设置信号SIGCLHD为默认
            // if (id == 0)
            // {
            //     close(listensock_);
            //     // 子进程
            //     transServic(servicSock, peerIp, peerPort);
            //     exit(0);
            // }
            //方案二:让孙子进程服务,子进程立刻退出,孙子进程变成孤儿,由系统回收资源
            if(id==0)
            {
                close(listensock_);
                if(fork()>0)exit(0);
                transServic(servicSock, peerIp, peerPort);
                exit(0);
            }
            close(servicSock);//这一步是一定要。

            pid_t ret = waitpid(id,nullptr,0);
            assert(ret>0);
            (void)ret;
        }
    }
2.创建线程执行服务
   void loop()
    {
        signal(SIGCHLD,SIG_IGN);

        while (true)
        {
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            int servicSock = accept(listensock_, (struct sockaddr *)&peer, &len);

            if (servicSock < 0)
            {
                logMessage(WARNING, "accept error : %s", strerror(errno));
            }
            // 获取客户端基本信息
            int peerPort = ntohs(peer.sin_port);
            std::string peerIp = inet_ntoa(peer.sin_addr);

            logMessage(DEBUG, "accept success %s:%d, socket fd: %d", peerIp.c_str(), peerPort, servicSock);
            // 提供服务
            // 单进程版本,只能提供一个服务,
            //  transServic(servicSock,peerIp,peerPort);

            // // 多进程版本
            // pid_t id = fork();
            // assert(id != -1);
            // //对于子进程退出,如何回收资源
            // //方案一:设置信号SIGCLHD为默认
            // if (id == 0)
            // {
            //     close(listensock_);
            //     // 子进程
            //     transServic(servicSock, peerIp, peerPort);
            //     exit(0);
            // }
            // close(servicSock);//这一步是一定要。

            // //方案二:让孙子进程服务,子进程立刻退出,孙子进程变成孤儿,由系统回收资源
            // if(id==0)
            // {
            //     close(listensock_);
            //     if(fork()>0)exit(0);
            //     transServic(servicSock, peerIp, peerPort);
            //     exit(0);
            // }
            // close(servicSock);//这一步是一定要。

            // pid_t ret = waitpid(id,nullptr,0);
            // assert(ret>0);
            // (void)ret;

            // 多线程版本
            ThreadData* td = new ThreadData(peerPort,peerIp,servicSock,this);
            pthread_t tid;
            pthread_create(&tid, nullptr, threadRoutine, (void *)td);
        }
    }
    static void* threadRoutine(void* argc)
    {
       pthread_detach(pthread_self());
       ThreadData* td = (ThreadData*)argc; 
       ServerTcp* ts = td->this_;
       td->this_->transServic(td->sock_,td->clinetIp_,td->clientPort_);
       close(td->sock_);
       delete td;
    }
class ThreadData
{
public:
    uint16_t clientPort_;
    std::string clinetIp_;
    int sock_;
    ServerTcp *this_;
    ThreadData(uint16_t port, std::string ip, int sock, ServerTcp *ts) : clientPort_(port), clinetIp_(ip), sock_(sock), this_(ts)
    {}
};
3.完整代码
#include "util.hpp"
#include "Log.hpp"

class ServerTcp;

class ThreadData
{
public:
    uint16_t clientPort_;
    std::string clinetIp_;
    int sock_;
    ServerTcp *this_;
    ThreadData(uint16_t port, std::string ip, int sock, ServerTcp *ts) : clientPort_(port), clinetIp_(ip), sock_(sock), this_(ts)
    {}
};

class ServerTcp
{
public:
    ServerTcp(uint16_t port, const std::string ip = "") : port_(port), ip_(ip), listensock_(-1)
    {
    }
    ~ServerTcp()
    {
    }

    void init()
    {
        listensock_ = socket(AF_INET, SOCK_STREAM, 0);
        if (listensock_ < 0)
        {
            logMessage(FATAL, "socket error: %s", strerror(errno));
            exit(1);
        }
        logMessage(DEBUG, "socket success");
        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET;                                                // 协议家族
        local.sin_addr.s_addr = ip_.empty() ? INADDR_ANY : inet_addr(ip_.c_str()); // ip 会用到inet_addr
        local.sin_port = htons(port_);                                             // 端口 会用到htons
        if (bind(listensock_, (struct sockaddr *)&local, sizeof(local)) < 0)
        {
            logMessage(FATAL, "bind error: %s", strerror(errno));
            exit(2);
        }
        logMessage(DEBUG, "bind success");
        if (listen(listensock_, 5) < 0)
        {
            logMessage(FATAL, "listen : %s", strerror(errno));
            exit(3);
        }
        logMessage(DEBUG, "listen success");
    }

    void loop()
    {
        signal(SIGCHLD,SIG_IGN);

        while (true)
        {
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            int servicSock = accept(listensock_, (struct sockaddr *)&peer, &len);

            if (servicSock < 0)
            {
                logMessage(WARNING, "accept error : %s", strerror(errno));
            }
            // 获取客户端基本信息
            int peerPort = ntohs(peer.sin_port);
            std::string peerIp = inet_ntoa(peer.sin_addr);

            logMessage(DEBUG, "accept success %s:%d, socket fd: %d", peerIp.c_str(), peerPort, servicSock);
            // 提供服务
            // 单进程版本,只能提供一个服务,
            //  transServic(servicSock,peerIp,peerPort);

            // // 多进程版本
            // pid_t id = fork();
            // assert(id != -1);
            // //对于子进程退出,如何回收资源
            // //方案一:设置信号SIGCLHD为默认
            // if (id == 0)
            // {
            //     close(listensock_);
            //     // 子进程
            //     transServic(servicSock, peerIp, peerPort);
            //     exit(0);
            // }
            // close(servicSock);//这一步是一定要。

            // //方案二:让孙子进程服务,子进程立刻退出,孙子进程变成孤儿,由系统回收资源
            // if(id==0)
            // {
            //     close(listensock_);
            //     if(fork()>0)exit(0);
            //     transServic(servicSock, peerIp, peerPort);
            //     exit(0);
            // }
            // close(servicSock);//这一步是一定要。

            // pid_t ret = waitpid(id,nullptr,0);
            // assert(ret>0);
            // (void)ret;

            // 多线程版本
            ThreadData* td = new ThreadData(peerPort,peerIp,servicSock,this);
            pthread_t tid;
            pthread_create(&tid, nullptr, threadRoutine, (void *)td);
        }
    }
    // 大小写转化服务
    void transServic(int sock, const std::string clientIp, uint16_t clientPort)
    {
        
        assert(sock > 0);
        assert(!clientIp.empty());
        assert(clientPort >= 1024);
        char inbuffer[BUFFER_SIZE];
        while (true)
        {
            ssize_t s = read(sock, inbuffer, sizeof(inbuffer) - 1);

            if (s > 0)
            {
                inbuffer[s] = '\0';
                if (strcasecmp(inbuffer, "quit") == 0)
                {
                    logMessage(DEBUG, "client quit--%s[%d]", clientIp.c_str(), clientPort);
                    break;
                }
                logMessage(DEBUG, "trans before:%s[%d]>>>%s", clientIp.c_str(), clientPort, inbuffer);
                for (int i = 0; i < s; i++)
                {
                    if (isalpha(inbuffer[i]) && islower(inbuffer[i]))
                    {
                        inbuffer[i] = toupper(inbuffer[i]);
                    }
                }
                logMessage(DEBUG, "trans after:%s[%d]>>>%s", clientIp.c_str(), clientPort, inbuffer);
                write(sock, inbuffer, strlen(inbuffer));

            }
            else if (s == 0)
            {
                // pipe:读端一直在读,写端不写了,并且关闭写端,读端会如何?s == 0,代表对端关闭
                // s == 0 :代表对端关闭,client退出
                logMessage(DEBUG, "client quit--%s[%d]", clientIp.c_str(), clientPort);
                break;
            }
            else
            {
                logMessage(DEBUG, "%s[%d] - read:%s", clientIp.c_str(), clientPort, strerror(errno));
                exit(6);
            }
        }
        // 只要走的这里client退出,服务到此结束
        close(sock); // 如果一个进程对应的文件fd,打开了没有被归还,文件描述符泄漏!
        logMessage(DEBUG, "server close %d done ", sock);
    }
    static void* threadRoutine(void* argc)
    {
       pthread_detach(pthread_self());
       ThreadData* td = (ThreadData*)argc; 
       ServerTcp* ts = td->this_;
       td->this_->transServic(td->sock_,td->clinetIp_,td->clientPort_);
       close(td->sock_);
       delete td;
    }

private:
    int listensock_;
    uint16_t port_;
    std::string ip_;
};

static void Usage(std::string proc)
{
    std::cerr << "Usage:\n\t" << proc << " port [ip]" << std::endl;
    std::cerr << "example:\n\t" << proc << " 8080 127.0.0.1" << std::endl;
}

int main(int argc, char *argv[])
{
    if (argc != 3 && argc != 2)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    uint16_t port = atoi(argv[1]);
    std::string ip;
    if (argc == 3)
    {
        ip = argv[2];
    }
    ServerTcp svr(port, ip);
    svr.init();
    svr.loop();
    return 0;
}


2.编写TPC客户端程序

1.创建套接字

    if (argc != 3)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    uint16_t port = atoi(argv[2]);
    std::string ip = argv[1];

    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0)
    {
        std::cerr << "sock error: " << strerror(errno) << std::endl;
        exit(SOCKET_ERR);
    }

2.发起链接请求

    struct sockaddr_in peer;
    memset(&peer, 0, sizeof(peer));
    peer.sin_family = AF_INET;
    peer.sin_port = htons(port);
    peer.sin_addr.s_addr = inet_addr(ip.c_str());
    // 2.2发起请求
    if (connect(sock, (const struct sockaddr *)&peer, sizeof(peer)) < 0)
    {
        std::cerr << "connect error : " << strerror(errno) << std::endl;
        exit(CONNECT_ERR);
    }

3.完整代码

#include "util.hpp"
#include "Log.hpp"

static void Usage(std::string proc)
{
    std::cerr << "Usage:\n\t" << proc << " serverIp serverPort" << std::endl;
    std::cerr << "Example:\n\t" << proc << " 127.0.0.1 8080" << std::endl;
}
volatile bool quit = false;

int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    uint16_t port = atoi(argv[2]);
    std::string ip = argv[1];

    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0)
    {
        std::cerr << "sock error: " << strerror(errno) << std::endl;
        exit(SOCKET_ERR);
    }
    // 2.需要bind吗?需要,但不需要自己显示bind
    // 3.需要listen吗?不需要
    // 4.需要accpet吗?不需要

    // 2需要发起链接请求-connect 向服务器发起请求
    // 2.1先填充需要连接的远端主机的基本信息
    struct sockaddr_in peer;
    memset(&peer, 0, sizeof(peer));
    peer.sin_family = AF_INET;
    peer.sin_port = htons(port);
    peer.sin_addr.s_addr = inet_addr(ip.c_str());
    // 2.2发起请求
    if (connect(sock, (const struct sockaddr *)&peer, sizeof(peer)) < 0)
    {
        std::cerr << "connect error : " << strerror(errno) << std::endl;
        exit(CONNECT_ERR);
    }

    std::cout << "info :connect success:" << sock << std::endl;
    std::string message;
    while (!quit)
    {
      
        message.clear();
        std::cout << "请输入你的消息>>>";
        std::getline(std::cin, message);
        if (strcasecmp(message.c_str(), "quit") == 0)
            quit = true;
        ssize_t s = write(sock, message.c_str(), message.size());

        if (s > 0 )
        {
            message.resize(1024);
            ssize_t s = read(sock, (char *)message.c_str(), 1024);
            if (s > 0)
            {
                message[s] = 0;
            }
            else
            {
                break;
            }
            std::cout << " sever echo>>" << message << std::endl;
        }
        else if (s <= 0)
        {
            break;
        }
    }

    close(sock);
    return 0;
}

五、守护进程

一般以服务器的方式工作,对外提供服务的服务器,都是以守护进程(精灵进程)的方式在服务器中工作的,一旦启动之后,除非用户主动关闭,否则,一直会在运行。

当我们连接服务器,服务器就会给我构建一个会话,这个会话包含一个前台进程组,和0或多个后台进程组,而我们用的bash就是一个前台进程组。任何时候只能有一个前台进程组。

在命令行中启动一个进程就是在会话中启动一个进程组(可以一个),来完成某种任务

所有会话内的进程fork创建子进程,一般而言依旧属于当前会话!

一个进程自成进程组,自成新的会话就叫做守护进程or精灵进程

1.如何自己形成

注意:守护进程要编写,必须调用一个函数setsid():将调用进程设置成为独立的会话
在这里插入图片描述
进程组的组长,不能调用setsid()
我如何不成为组长?
你可以成为进程组内的第二个进程!常规做法,fork()子进程,子进程就不在是组长进程啦,它就可以成功调用setsid()

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

管道:写端一直在写,读端关闭,写端会被终止,被信号终止SIGPIPE——为什么要做a原因

选做:
a.忽略SIGPIPE信号
b.更改进程的工作目录
c. 重定向/关闭0,1,2
1.关闭进程描述符(0,1,2)
2./dev/null文件 类似与所有Linux下的一个“文件黑洞&&垃圾桶”,凡是从/dev/null里面读写一概被丢弃
打开 /dev/null,并且进行对0,1,2,重定向!

注:守护进程名字格式: 名字+d

#pragma once
#include<signal.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
void deamonnize()
{
    //1.忽略SIGPIPE
    signal(SIGPIPE,SIG_IGN);
    //2.更改进程工作目录
    //chdir("路径")
    //3.让自己不要成为进程组组长
    if(fork()>0)exit(0);
    //4.设置自己的一个独立的会话
    setsid();
    //5.重定向/关闭0,1,2
    // //a.关闭
    // close(0);
    // close(1);
    // close(2);
    //b.打开/dev/null
    int fd = open("dev/null",O_RDWR);
    dup2(fd,0);
    dup2(fd,1);
    dup2(fd,2);
    //STDIN_FILENO --0
    //STDOUT_FILENO--1
    //STDERR_FILENO--2
    if(fd>2)close(fd);


}

2.系统接口生成守护进程 - daemon

在这里插入图片描述

3.命令行运行变成守护进程 (命令)

nohup ./程序 &

注:默认形成日志 nohup.out

六、TCP理论(感性认识,铺垫下一章)

1.tcp是面向连接的,client conect&&sercer accept
2.tcp在建立连接的时候,采用的是三次握手,在断开连接的时候,采用的是四次挥手
3.connect发起三次握手(client),close() client&& close() server->closet()执行4次挥手的2次
4.什么是3次握手?什么是4次挥手?
3次握手:
客户端->服务器:可以建立连接吗?
服务器->客户端:可以,什么时候开始?
客户端->服务器:就现在!
4次挥手:
客户端->服务器:断开连接可以吗?
服务器->客户端:可以
上面是客户端询问服务器
服务器->客户端:那我就和你断开连接可以吗?
客户端->服务器:好的!
上面是服务器询问客户端
在双方都表示同意断开连接时,才可以关闭相应fd(文件描述符)
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/534942.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

三行命令解决Ubuntu Linux联网问题

本博客中Ubuntu版本为23.10.1最新版本&#xff0c;后续发现了很多问题我无法解决&#xff0c;已经下载了另外一个版本22.04&#xff0c;此版本自带网络 一开始我找到官方文档描述可以通过命令行连接到 WiFi 网络&#xff1a;https://cn.linux-console.net/?p10334#google_vig…

网络协议——RSTP(快速生成树)与MSTP(多实例生成树)

一. RSTP 1. STP的不足 1、依靠计时器超时的方式进行收敛导致它的收敛时间需要30到50秒 2、端口状态和端口角色没有细致区分&#xff0c;指导数据转发依靠的不是端口状态而是端口所扮演角色。 3、如果拓扑频繁变化导致用户通信质量差&#xff0c;甚至通信中断&#xf…

【THM】What the Shell?(什么是壳?)-初级渗透测试

什么是shell? 在我们深入了解发送和接收 shell 的复杂性之前,了解 shell 的实际含义非常重要。用最简单的术语来说,shell 是我们与命令行环境 (CLI) 交互时使用的工具。换句话说,Linux中常见的 bash 或 sh 程序都是 shell 的例子,Windows 上的 cmd.exe 和 Powershell 也是…

简单了解JVM

一.JVM简介 jvm及Java virtual machineJava虚拟机&#xff0c;它是一个虚构出来的计算机&#xff0c;一种规范。其实抛开这么专业的句子不说&#xff0c;就知道 JVM 其实就类似于一台小电脑运行在 windows 或者 linux 这些操作系统环境下即可。它直接和操作系统进行交互&#…

BUUCTF---misc--snake

1.下载附件&#xff0c;解压后是一张蛇的图片 2.查看属性&#xff0c;没有有用信息&#xff0c;用winhex打开&#xff0c;在文末找到了PK字样&#xff0c;图片被压缩了。 3.用binwalk命令&#xff0c;查看&#xff0c;有压缩包 4.用foremost命令分离&#xff0c;将文件保存到te…

repl_backlog原理

2.2.3.repl_backlog原理 master怎么知道slave与自己的数据差异在哪里呢? 这就要说到全量同步时的repl_baklog文件了。 这个文件是一个固定大小的数组&#xff0c;只不过数组是环形&#xff0c;也就是说角标到达数组末尾后&#xff0c;会再次从0开始读写&#xff0c;这样数组…

基因查询常用汇总网(自备)

目录 NCBI genecards HPA数据库 gepia2 cbioporta kmplot生存分析 ualcan ​​​​​​​ 进行一些常用的基因功能蛋白及表达的网站查询汇总&#xff0c;方便个人使用 NCBI National Center for Biotechnology Information (nih.gov) 查询基因的曾用名和其他ID&…

kubernetes有ingress-controler以及没有外部loadbalancer 的情况下使用istio-gateway.

那就配置一个ingress-使用已有ingress-controler代理istio-gateway class创建的gateway svc来公开。

day8 nest商业项目初探·四(java转ts全栈/3R教室)

背景&#xff1a;从头一点点学起太慢了&#xff0c;直接看几个商业项目吧&#xff0c;看看根据Java的经验&#xff0c;自己能看懂多少&#xff0c;然后再系统学的话也会更有针对性。今天看下一个项目 【加拿大 | 7.8w】TS全栈&#xff1a;Youni校园社交网络 (2022.10) - 3R酷 |…

嵌入式网线连接——笔记本电脑设置

一、需求 我们调试很多设备经常需要用到网线去调试&#xff0c;当然主流是USB&#xff0c;和网线。 二、笔记本电脑端设备 有网口的&#xff0c;非常方便&#xff0c;如果没有网口&#xff0c;则需要用到USB转网口 连接指示灯&#xff1a; 绿色&#xff1a;灯亮表示连接正常…

Python+Selenium+Unittest 之Unittest5(常用装饰器-跳过用例执行)

目录 1、unittest.skip()&#xff08;跳过该装饰器下的用例&#xff09; 2、unittest.skipIf()&#xff08;跳过该装饰器下判断条件为True的用例&#xff09; 3、unittest.skipUnless()&#xff08;跳过该装饰器下判断条件为False的用例&#xff09; 4、unittest.expectedF…

【java探索之旅】走进循环结构 深入解析while、for和do while的用法

&#x1f3a5; 屿小夏 &#xff1a; 个人主页 &#x1f525;个人专栏 &#xff1a; Java编程秘籍 &#x1f304; 莫道桑榆晚&#xff0c;为霞尚满天&#xff01; 文章目录 &#x1f4d1;前言一、循环结构1.1 while循环1.2 while代码示例1.3 break1.4 continue 二、for循环2.1 基…

Cohere推出全新升级版RAG大型AI模型:支持中文,搭载1040亿参数,现开源其权重!

4月5日&#xff0c;知名类ChatGPT平台Cohere在其官方网站上发布了一款全新的模型——Command R。 据官方消息&#xff0c;Command R拥有1040亿个参数&#xff0c;并且支持包括英语、中文、法语、德语在内的10种语言。这一模型的显著特点之一在于其对内置的RAG&#xff08;检索增…

网工内推 | 网安、AGV测试网络工程师,厂商认证优先,应届可投

01 神州数码 招聘岗位&#xff1a;网络工程师 职责描述&#xff1a; 1、负责国内外主流安全产品&#xff08;如防火墙、入侵防御、WAF、安全审计等&#xff09;的上线安装、调试、测试、割接、运维等工作。 2、能够独立进行安全类项目实施、问题排查及处理。 3、在出现网络攻…

量子城域网系列(二):量子密钥与通信系统中各层协议融合应用

写在前面。国家标准中对量子保密通信的定义&#xff1a;量子保密通信是利用QKD与其他密码技术结合形成的保密通信技术。 经过这段时间的讨论&#xff0c;我们基本上明白了量子保密通信的内涵、基础协议、技术原理等。我们知道了当前语境下的量子密钥分发网络核心是实现两点之间…

Ubuntu系统使用Docker本地部署Android模拟器并实现公网访问

文章目录 1. 虚拟化环境检查2. Android 模拟器部署3. Ubuntu安装Cpolar4. 配置公网地址5. 远程访问小结 6. 固定Cpolar公网地址7. 固定地址访问 本文主要介绍如何在Ubuntu系统使用Docker部署docker-android安卓模拟器&#xff0c;并结合cpolar内网穿透工具实现公网远程访问本地…

数据分析:小红书爆火赛道的内容洞察

导语 近期&#xff0c;一个名为女生独立计划 的账号于今年2月29日在小红书上发布了第一条作品&#xff0c;而后的40天内&#xff0c;该账号又陆续发布16条作品&#xff0c;目前粉丝量已经达到30W。 那么短期内快速崛起的女生独立计划 内容有何突出之处&#xff1f;且听小编和…

注意力机制篇 | YOLOv8改进之清华开源ACmix:自注意力和CNN的融合 | 性能速度全面提升

前言:Hello大家好,我是小哥谈。混合模型ACmix将自注意力机制和卷积神经网络进行整合,使其同时具有自注意力机制和卷积神经网络的优点。这是清华大学、华为和北京人工智能研究院共同发布在2022年CVPR中的论文。ACmix结合了自注意力机制和卷积神经网络的优势,以提高模型的性能…

Linux权限问题补全—4

1.第二种修改权限的方法&#xff1a; 使用八进制方式修改文件权限&#xff0c;可以一次修改拥有者&#xff0c;所属组&#xff0c;other&#xff0c;三者的权限。举例&#xff1a; ①将三者的权限都修改为可读&#xff0c;可写&#xff0c;可执行 ②将所有用户的权限都取消rwx权…

python 如何获得重定向输入

通过内置的fileinput模块即可实现&#xff0c;创建文件filein.py&#xff1a; #!/usr/bin/env python import fileinput f_input fileinput.input() for line in f_input:print(line, end)增加可执行权限&#xff1a; chmod ax filein.py 使用&#xff1a; $ ls | ./filein.py…