Linux_网络编程套接字_2


一、预备知识

认识端口

上一章节,已经认识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(文件描述符)
在这里插入图片描述

  • 20
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值