计算机网络(三)——TCP套接字编程

文章目录

  • 一、TCP常用接口函数
    • 2.1 设置监听套接字
    • 2.2 accept获取连接
    • 2.3 connect发起连接
    • 2.4 流读取
  • 二、TCP通信
    • 2.1 服务端
    • 2.2 客户端
    • 2.3 不断优化的服务端
      • 2.3.1 多进程版本
      • 2.3.2 多线程版本
      • 2.3.3 线程池版本


一、TCP常用接口函数

TCP是面向连接的,需要两端建立链接才能通信。
客户端主动连接,申请服务。
服务端被动连接,提供服务。

需要给用户一个建立连接的功能。所以就要创建两个套接字

  • 监听套接字,用于连接需求
  • 提供服务的套接字,建立连接后提供服务

2.1 设置监听套接字

设置套接字是listen状态,本质就是允许用户连接。

#include     
#include 

int listen(int sockfd, int backlog);

socket:创建套接字返回的文件描述符
backlog:底层队列的链接长度,长度是有限制的,不宜设置太大一般分为全链接队列和半链接队列

返回值:成功返回0,失败返回-1

2.2 accept获取连接

接受请求,并返回一个新的套接字用于提供服务。

#include 
#include 

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

sockfd是由socket函数返回的套接字文件描述符。
addr和addrlen用来返回已连接的客户端的协议地址。
如果我们对客户端的协议地址不感兴趣,可以把arrd和addrlen均置为空指针

返回值:失败返回-1,成功返回一个新的套接字文件描述符

2.3 connect发起连接

用于向服务端发起连接

#include  
#include 

int connect(int sockfd, const struct sockaddr *addr,
                   socklen_t addrlen);

sockfd是由socket函数返回的套接字描述符。
addr和addrlen用是要连接服务端的协议地址。

返回值:失败返回-1,成功返回0
在通信时,仍然使用我们创建的套接字,也就是sockfd

2.4 流读取

TCP通信是通过文件流传递的,所以可以使用read/write等系统文件流调用接口读取通信的数据。但也有两个专门用于读取网络通信流的接口函数。 可以根据对流的读取结果俩判断客户端是否退出。

#include 
#include 

ssize_t recv(int sockfd, void *buf, size_t len, int flags);
sockfd:accept返回的文件描述符
buf:读数据缓冲区
len:期望读取的数据长度
flags:读数据是IO、不一定有数据让你读,如果读取条件不成立,就挂起等待(默认为0,阻塞等待)

返回值:>0读取到了,<0未读取到,=0表示对端关闭了

#include 
#include 

ssize_t send(int sockfd, const void *buf, size_t len, int flags);
sockfd:accept返回的文件描述符
buf:发送的数据
len:发送的长度
flags:如果发送条件不成立,就挂起等待(默认为0,阻塞等待)

udp中的recvfrom和sendto是用于无连接通信,在有连接的tcp通信中,一般采用recv和send。

inet_ntoa()函数将以网络字节顺序给出的 Internet 主机地址转换为 IPv4 点分十进制表示法的字符串。

char *inet_ntoa(struct in_addr in);

二、TCP通信

2.1 服务端

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

void Usage(std::string proc)
{
    std::cout << "Usgae:" << proc << "port" << std::endl;
}

void ServiceIO(int new_sock)
{
    while (true)
    {
        char buffer[1024];
        memset(buffer, 0, sizeof(buffer));
        //int s = recv(new_sock, buffer, sizeof(buffer) - 1, 0);
        int s = read(new_sock, buffer, sizeof(buffer) - 1);
        if (s > 0)
        {
            buffer[s] = 0;
            std::cout << "client#" << buffer << std::endl;

            // 写回去
            const char* echo_client = "Hello Client";

            int s = write(new_sock, echo_client, strlen(echo_client));
        }
        else if (s == 0)
        {
            std::cout << "client quit..." << std::endl;
            break;
        }
        else 
        {
            std::cerr << "read error" << std::endl;
            break;
        }
    }
}

int main(int argc, char* argv[])
{
    if (argc != 2)
    {
        Usage(argv[0]);
        return 1;
    }
    // 1. 创建监听套接字
    int listen_sock = socket(AF_INET, SOCK_STREAM, 0);
    if (listen_sock < 0)
    {
        std::cerr <<"socket error: " << errno << std::endl;
        return 2;
    }

    // 2.绑定IP和port
    struct sockaddr_in local;
    memset(&local, 0, sizeof local);
    local.sin_family = AF_INET;
    local.sin_addr.s_addr = INADDR_ANY;
    local.sin_port = htons(atoi(argv[1]));
    
    if (bind(listen_sock, (struct sockaddr*)&local, sizeof(local)) < 0)
    {
        std::cout << "bind error:" << errno << std::endl;
        return 3;
    }

    // 3.设置监听状态
    const int back_log = 5;
    if (listen(listen_sock, back_log) < 0)
    {
        std::cout << "listen error: " << errno << std::endl;
        return 4;
    }

    // 4.获取连接,提供服务
    while (true)
    {
        struct sockaddr_in client;
        memset(&client, 0, sizeof(client));
        socklen_t len = sizeof(client);
        int new_sock = accept(listen_sock, (struct sockaddr*)&client, &len);
        if (new_sock < 0)
        {
            // 连接失败就连接下一个
            continue;
        }

        // 接受信息
        std::string client_ip = inet_ntoa(client.sin_addr);
        uint16_t client_port = ntohs(client.sin_port);

        std::cout << "get a new link -> : [" << client_ip << ":" << client_port <<"]# " << new_sock << std::endl;


        // 单进程处理通信
        ServiceIO(new_sock);
        // 处理完关闭文件,防止文件描述符泄漏
        close(new_sock);
    }
}

2.2 客户端

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

void Usage(std::string proc)
{
    std::cout << "Usage: " << proc << "server_ip server_port" << std::endl; 
}

int main(int argc, char* argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        return 1;
    }

    uint16_t ser_port = atoi(argv[2]);
    std::string ser_ip = argv[1];

    // 1.创建套接字文件
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0)
    {
        std::cerr << "socket error" << errno << std::endl;
        return 2;
    }

    struct sockaddr_in server;
    // 置零函数,除memset外,还有个bzero: void bzero(void *s, size_t n);
    server.sin_family = AF_INET;
    // 点分十进制转成四字节整型,并且将主机序列转为网络序列
    server.sin_addr.s_addr = inet_addr(ser_ip.c_str());
    server.sin_port = htons(ser_port);

    // 2. 发起连接
    if (connect(sock, (struct sockaddr*)&server, sizeof(server)) < 0)
    {
        std::cout << "connect server failer !" << std::endl;
        return 3;
    }
    std::cout << "connect success!" << std::endl;

    while(true)
    {
        std::cout << "Please Enter# ";
        char buffer[1024];
        fgets(buffer, sizeof(buffer)-1, stdin);

        write(sock, buffer, strlen(buffer));
        buffer[0] = 0;
        ssize_t s = read(sock, buffer, sizeof(buffer) - 1);
        if(s > 0)
        {
            buffer[s] = 0;
            std::cout << "server echo# " << buffer << std::endl;
        }
    }
    return 0;
}

当没有关闭文件描述符的时候,不断增加
计算机网络(三)——TCP套接字编程_第1张图片
关闭之后每次都从4开始
计算机网络(三)——TCP套接字编程_第2张图片

2.3 不断优化的服务端

2.3.1 多进程版本

只需要修改调用服务函数的地方即可。

多进程也有两种实现方式

  • 父进程忽略子进程退出的信号,自动释放子进程资源,这样就不用影响父进程处理下一个客户
  • 子进程再创建子进程(孙子进程)来处理,然后子进程退出,孙子进程执行服务,此时孙子进程为孤儿进程由操作系统管理

版本1

signal(SIGCHLD, SIG_IGN); // 显示设置忽略17号信号,当进程退出后,自动释放僵尸进程
// 多进程处理通信
pid_t pid = fork();
if (pid < 0)
{
    continue;
}
else if (pid == 0)
{
    // 由于子进程会拷贝父进程的文件描述符,但是子进程只用来处理服务
    // 所以要关闭继承下来的监听文件描述符
    close(listen_sock);

    ServiceIO(new_sock);
    close(new_sock);
    exit(0);
}
else
{
    // 父进程,不能等待子进程,否则就成了单执行流
    close(new_sock);
}

版本2

// 多进程处理通信

pid_t pid = fork();
if (pid < 0)
{
    continue;
}
else if (pid == 0)
{
    // 由于子进程会拷贝父进程的文件描述符,但是子进程只用来处理服务
    // 所以要关闭继承下来的监听文件描述符
    close(listen_sock);

    if (fork() > 0)
        exit(0); // 退出,由孙子进程执行
    ServiceIO(new_sock);
    close(new_sock);
    exit(0);
}
else
{
    // 1. 父进程,不能等待子进程,否则就成了单执行流
    // 2. 父进程等待子进程,由孙子进程执行服务
    waitpid(pid, nullptr, 0); //阻塞等待,但很快
    close(new_sock);
}

计算机网络(三)——TCP套接字编程_第3张图片

2.3.2 多线程版本

void* HandlerRequest(void* args)
{
    // 分离线程
    pthread_detach(pthread_self());
    int sock = *(int*)args;
    delete (int*)args;

    ServiceIO(sock);
    close(sock);
}
// 多线程版本
pthread_t tid;
int* pram = new int(new_sock);
pthread_create(&tid, nullptr, HandlerRequest, pram);

2.3.3 线程池版本

服用之前写的线程池,然后将服务函数封装在类里面,传入线程池处理即可

 //1. 构建一个任务
Task t(new_sock);
//2. 将任务push到后端的线程池即可
ThreadPool<Task>::GetInstance()->PushTask(t); // 单例模式下的线程池

task.cc // 服务函数

#pragma once

#include 
#include 
#include 

namespace ns_task
{
    class Task
    {
    private:
        int sock;

    public:
        Task() : sock(-1) {}
        Task(int _sock) : sock(_sock)
        {
        }
        int Run()
        {
            //提供服务,我们是一个死循环
            // while (true)
            // {
            char buffer[1024];
            memset(buffer, 0, sizeof(buffer));
            ssize_t s = read(sock, buffer, sizeof(buffer) - 1);
            if (s > 0)
            {
                buffer[s] = 0; //将获取的内容当成字符串
                std::cout << "client# " << buffer << std::endl;
                //拉取逻辑
                std::string echo_string = ">>>server<<<, ";
                echo_string += buffer;

                write(sock, echo_string.c_str(), echo_string.size());
            }
            else if (s == 0)
            {
                std::cout << "client quit ..." << std::endl;
                // break;
            }
            else
            {
                std::cerr << "read error" << std::endl;
                // break;
            }
            // }

            close(sock);
        }
        ~Task() {}
    };
}

你可能感兴趣的:(计算机网络,tcp/ip,网络,网络协议)