【TCP】socket套接字——快速上手

前言

socket套接字的有些知识在上一篇【UDP】socket套接字带你快速上手中已经讲过了,这里就不在过多的叙述了。

初识TCP

 TCP的主要特点

  1. TCP是面向连接的传输层协议
  2. TCP提供可靠交付的服务
  3. TCP提供全双工通信
  4. TCP是面向字节流的

TCP socket API 

 socket()

  • 此函数是用来创建socket文件描述符的。创建成功则返回socket文件描述符,失败则返回-1并且设置对应的错误码
  • 第一个参数是用来指定通信域的,也就是选择用于通信的协议族的,这里用的是IPv4协议,参数指定就是AF_INET
  • 第二个参数是用来指定通信类型的。这里所用到的是TCP,所以参数指定为SOCK_STREAM,表示面向流的传输协议
  • 第三个参数暂时用不到,设置为0即可

bind() 

  • 此函数是用来将用户设置的IP地址和port端口号在内核中和当前进程强关联。成功返回0,失败返回-1并且设置对应的错误码
  • 第一个参数是你创建好的socket文件描述符
  • 第二个参数是一个struct sockaddr的结构体指针
  • 第三个参数是这个sockaddr的大小

listen() 

  • 此函数用来声明sockfd处于监听状态,并且最多允许有backlog个客户端处于连接等待状态,如果接收到更多的连接请求就忽略
  • listen成功返回0,失败返回-1并且设置对应的错误码

accept()

  • 三次握手完成后,服务器调用accept()接受连接,如果服务器调用accept()时还没有客户端的连接请求,就阻塞等待直到客户端连接上来
  • 第一个参数是创建好的socket文件描述符
  • 第二个参数是一个struct sockaddr的结构体指针

  • 第三个参数是此结构体的大小

  • 返回值为socket文件描述符

理解accept的返回值

accept中传入了一个socket文件描述符,但是它返回的依然是一个文件描述符,那么如何理解这两个文件描述符呢?

举个例子,假如你家有个饭店,刚好这时到了饭点了,你就派出了张三到饭店门口去拉客,此时来了一名顾客正好被张三拉进到店里去了,你又派出了李四服务员去服务这位顾客,并且让张三又返回到店门口继续拉客。

在上述例子中,张三就是我们传入accept中的socket文件描述符,是获取到的新连接的,而李四就是accept返回的socket文件描述符,是真正提供服务的。

 connect()

  • 在客户端中调用,用来连接服务器
  • 参数形式和bind的一致,区别在于bind传入的是自己的地址,为connect传入的是对方的地址
  • listen成功返回0,失败返回-1并且设置对应的错误码

tcp_client.cc

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

void Usage(std::string proc)
{
    std::cout << "\nUsage: " << proc << " serverIp serverPort\n"
              << std::endl;
}

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

    bool alive = false;
    int sock = 0;
    std::string line;
    while (true)
    {
        if (!alive)
        {
            sock = socket(AF_INET, SOCK_STREAM, 0);
            if (sock < 0)
            {
                std::cerr << "socket error" << std::endl;
                exit(2);
            }
            struct sockaddr_in server;
            memset(&server, 0, sizeof(server));
            server.sin_family = AF_INET;
            server.sin_port = htons(serverport);
            server.sin_addr.s_addr = inet_addr(serverip.c_str());

            if (connect(sock, (struct sockaddr *)&server, sizeof(server)) < 0)
            {
                std::cerr << "connect error" << std::endl;
                exit(3);
            }
            alive = true;
            std::cout << "connect success" << std::endl;
        }
        std::cout << "请输入你的内容# ";
        std::getline(std::cin, line);
        if (line == "quit")
            break;

        ssize_t s = send(sock, line.c_str(), line.size(), 0);
        if (s > 0)
        {
            char buffer[1024];
            ssize_t s = recv(sock, buffer, sizeof(buffer) - 1, 0);
            if (s > 0)
            {
                buffer[s] = 0;
                std::cout << "server 回应# " << buffer << std::endl;
            }
            else if (s == 0)
            {
                close(sock);
            }
        }
        else
        {
            close(sock);
        }
        alive = false;
    }

    return 0;
}

 tcp_server.cc

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

static void Usage(std::string proc)
{
    std::cout << "\nUsage" << proc << " port\n" << std::endl;
}

int main(int argc, char* argv[])
{
    if(argc != 2)
    {
        Usage(argv[0]);
        exit(1);
    }
    uint16_t port = atoi(argv[1]);
    std::unique_ptr<Tcp_Server> svr(new Tcp_Server(port));
    svr->InitServer();
    svr->Start();
    return 0;
}

 tcp_server.hpp

#pragma once

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

static void service(int sock, const std::string &clientip, const uint16_t &clientport)
{
    char buffer[1024];
    while (true)
    {
        ssize_t s = read(sock, buffer, sizeof(buffer) - 1);
        if (s > 0)
        {
            buffer[s] = 0;
            std::cout << clientip << ":" << clientport << "# " << buffer << std::endl;
        }
        else if (s == 0) // 对端关闭连接
        {
            logMessage(NORMAL, "%s:%d shutdown, me too", clientip.c_str(), clientport);
            break;
        }
        else
        {
            logMessage(ERROR, "read socket error, %d:%s", errno, strerror(errno));
            break;
        }
        
        write(sock, buffer, strlen(buffer));
    }
    close(sock);
}

class ThreadDate
{
public:
    int _sock;
    std::string _ip;
    uint16_t _port;
};

class Tcp_Server
{
private:
    const static int gbacklog = 20;
    static void *threadRoutine(void *args)//必须得是static,因为不是static的话会多出一个this指针
    {
        pthread_detach(pthread_self());//线程分离
        ThreadDate *td = static_cast<ThreadDate*> (args);
        service(td->_sock, td->_ip, td->_port);
        delete td;
        
        return nullptr;
    }

public:
    Tcp_Server(uint16_t port, std::string ip = "")
        : _port(port), _ip(ip), listensock(-1)
    {
    }
    ~Tcp_Server()
    {
    }

    void InitServer()
    {
        // 1.创建socket
        listensock = socket(AF_INET, SOCK_STREAM, 0);
        if (listensock < 0)
        {
            //创建socket失败
            logMessage(FATAL, "create socket error, %d:%S", errno, strerror(errno));
            exit(2);
        }
        logMessage(NORMAL, "create socket success, listensock:%d", listensock);

        // 2.bind
        struct sockaddr_in local;
        memset(&local, 0, sizeof local);
        local.sin_family = AF_INET;
        local.sin_port = htons(_port);
        local.sin_addr.s_addr = _ip.empty() ? INADDR_ANY : inet_addr(_ip.c_str()); //INADDR_ANY:任意IP
        if (bind(listensock, (struct sockaddr *)&local, sizeof(local)) < 0)
        {
            //bind失败
            logMessage(FATAL, "bind error, %d:%s", errno, strerror(errno));
            exit(3);
        }

        // 3.建立连接
        if (listen(listensock, gbacklog) < 0)
        {
            //建立连接失败
            logMessage(FATAL, "listen error, %d:%s", errno, strerror(errno));
            exit(4);
        }

        logMessage(NORMAL, "init server success");
    }

    void Start()
    {
        // signal(SIGCHLD, SIG_IGN);//多进程版
        while (true)
        {
            // 4.获取连接
            struct sockaddr_in src;
            socklen_t len = sizeof(src);
            int servicesock = accept(listensock, (struct sockaddr *)&src, &len);
            // 获取连接失败
            if (servicesock < 0)
            {
                logMessage(ERROR, "accept error, %d:%s", errno, strerror(errno));
                continue;
            }
            // 获取连接成功
            uint16_t client_port = ntohs(src.sin_port);
            std::string client_ip = inet_ntoa(src.sin_addr);
            logMessage(NORMAL, "link success, servicesock: %d | %s : %d |\n", servicesock, client_ip.c_str(), client_port);

            // 5.开始通信
            // 版本1——单进程循环版
            // 缺点:一次只能处理一个客户端,处理完后才能处理下一个
            // service(servicesock, client_ip, client_port);
            // close(servicesock);

            //版本2.0——多进程(创建子进程)
            // pid_t id = fork();
            // assert(id != -1);
            // if(id == 0)
            // {
                //子进程
                // close(listensock);
                // service(servicesock, client_ip, client_port);
                // exit(0);
            // }
            //父进程
            // close(servicesock);

            //版本2.1——多进程版本(孤儿进程)
            // pid_t id = fork();
            // assert(id != -1);
            // if(id == 0)
            // {
                //子进程
                // close(listensock);
                // if(fork() > 0) exit(0);//子进程退出,由操作系统领养
                //孙子进程
                // service(servicesock, client_ip, client_port);
                // exit(0);
            // }
            //父进程
            // waitpid(id, nullptr, 0);
            // close(servicesock);

            //版本3——多线程版
            ThreadDate *td = new ThreadDate();
            td->_sock = servicesock;
            td->_ip = client_ip;
            td->_port = client_port;
            pthread_t tid;
            pthread_create(&tid, nullptr, threadRoutine, td);
        }
    }

private:
    uint16_t _port;
    std::string _ip;
    int listensock;
};

完整代码链接

TCP socket:TCP Socket 

上一篇:(最新)华为 2024 届实习招聘-硬件通⽤/单板开发——第十一套和十二套


下一篇:已解决java.nio.file.FileSystemException文件系统异常的正确解决方法,亲测有效!!!