【计算机网络】Socket编程 -- TCP篇

????个人主页:Jupiter.
???? 所属专栏:Linux从入门到进阶
欢迎大家点赞收藏评论????

在这里插入图片描述

在这里插入图片描述

目录

    • `网络中常用的指令`
        • `ping命令`
        • `netstat`
        • `pidof`
    • `TCP socket相关接口`
        • `socket`
        • `bind()`
        • `listen`
        • `accept`
        • `connect`
        • `send`
        • `recv`
        • `TCPServer.hpp`
      • `TCP代码练习`


网络中常用的指令

ping命令
  • ping命令是一种网络工具,用于测试主机之间网络连接的可达性。

基本用法

在命令行(终端)中,你可以通过简单地输入ping后跟目标主机的地址来使用该命令。例如,要检查与google.com的连接,你可以输入:

bash
ping google.com   #默认是一直检测

或者,如果你想检查与特定IP地址的连接,比如8.8.8.8(Google的公共DNS服务器),你可以输入:

bash
ping 8.8.8.8

-c(或-n,取决于操作系统):指定发送ICMP回显请求消息的次数。例如,ping -c4 google.com会发送4个请求并等待响应。

bash
ping -c4 google.com  #检测四次

netstat

netstat 是一个用来查看网络状态的重要工具. 语法:netstat [选项]

  • 功能:查看网络状态
  • 常用选项:
    • n 拒绝显示别名,能显示数字的全部转化成数字
    • l 仅列出有在 Listen (监听) 的服務状态
    • p 显示建立相关链接的程序名
    • t (tcp)仅显示 tcp 相关选项
    • u (udp)仅显示 udp 相关选项
    • a (all)显示所有选项,默认不显示 LISTEN 相关
// 每个 1s 执行一次 netstat -nltp
$ watch -n 1 netstat -nltp

watch指令

  • watch指令是Linux中周期性执行一个任务的指令。

  • 上面的watch -n n就是刷新的次数,n后面跟的数字,意思就是每隔多少秒刷新的次数,后面再接任务即可。


pidof
  • 在查看服务器的进程 id 时非常方便.
  • 语法:pidof [进程名]
  • 功能:通过进程名, 查看进程 id
C++
$ ps axj | head -1 && ps ajx | grep tcp_server
PPID     PID     PGID     SID   TTY   TPGID  STAT UID  TIME    COMMAND
2958169 2958285 2958285 2958169 pts/2 2958285 S+ 1002  0:00 ./tcp_server 8888
nu/ubcj/aigd:~$ pidof tcp_server
2958285

TCP socket相关接口

Socket API是在网络通信中广泛使用的一套接口,下面介绍程序中用到的 socket API,这些函数都在 sys/socket.h 中。

socket

在这里插入图片描述

  • socket()打开一个网络通讯端口,如果成功的话,就像 open()一样返回一个文件描述符;
  • 应用程序可以像读写文件一样用 read/write 在网络上收发数据;
  • 如果 socket()调用出错则返回-1;
  • 对于 IPv4, family 参数指定为 AF_INET;
  • 对于 TCP 协议,type 参数指定为 SOCK_STREAM, 表示面向流的传输协议
  • protocol 参数的介绍从略,指定为 0 即可。
    // 创建流式套接字
   _Listensockfd = socket(AF_INET, SOCK_STREAM, 0);
   if (_Listensockfd < 0)
   {
   		LOG(FATAL, "socket create failed...");
        exit(SOCK_ERROE);
   }
bind()

在这里插入图片描述

  • 服务器程序所监听的网络地址和端口号通常是固定不变的,客户端程序得知服务器程序的地址和端口号后就可以向服务器发起连接; 服务器需要调用 bind 绑定一个固定的网络地址和端口号;
  • bind()成功返回 0,失败返回-1。
  • bind()的作用是将参数 sockfd 和 myaddr 绑定在一起, 使 sockfd 这个用于网络通讯的文件描述符监听 myaddr 所描述的地址和端口号;
  • 前面讲过,struct sockaddr *是一个通用指针类型,myaddr 参数实际上可以接受多种协议的 sockaddr 结构体,而它们的长度各不相同,所以需要第三个参数 addrlen指定结构体的长度;

我们的程序中对 myaddr 参数是这样初始化的:
在这里插入图片描述

  1. 将整个结构体清零;
  2. 设置地址类型为 AF_INET;
  3. 网络地址为 INADDR_ANY, 这个宏表示本地的任意 IP 地址,因为服务器可能有
    多个网卡,每个网卡也可能绑定多个 IP 地址, 这样设置可以在所有的 IP 地址上监听, 直到与某个客户端建立了连接时才确定下来到底用哪个 IP 地址;
  4. 端口号为 SERV_PORT, 我们定义为 9999;
        // 构建addr对象并且初始化
        struct sockaddr_in Seraddr;
        memset(&Seraddr, 0, sizeof(Seraddr));
        Seraddr.sin_family = AF_INET;
        Seraddr.sin_port = htons(_port);
        Seraddr.sin_addr.s_addr = INADDR_ANY;
        // 绑定
        int n = bind(_Listensockfd, (struct sockaddr *)&Seraddr, sizeof(Seraddr));
        if (n < 0)
        {
            LOG(FATAL, "socket bind failed...");
            exit(BIND_ERROR);
        }
listen

在这里插入图片描述

  • listen()声明 sockfd 处于监听状态, 并且最多允许有 backlog 个客户端处于连接
    等待状态, 如果接收到更多的连接请求就忽略, 这里设置不会太大(一般是 5)
  • listen()成功返回 0,失败返回-1;
       // listen
        n = listen(_Listensockfd, gbacklog);
        if (n < 0)
        {
            LOG(FATAL, "socket listen failed...");
            exit(LISTEN_ERROR);
        }
accept

在这里插入图片描述

  • 在网络编程中,accept() 函数是服务器用于接受来自客户端的连接请求的函数。
  • 三次握手完成后, 服务器调用 accept()接受连接;
  • 如果服务器调用 accept()时还没有客户端的连接请求,就阻塞等待直到有客户端连接上来;
  • addr 是一个传出参数,accept()返回时传出客户端的地址和端口号;
  • 如果给 addr 参数传 NULL,表示不关心客户端的地址;
  • addrlen 参数是一个传入传出参数(value-result argument), 传入的是调用者提供的, 缓冲区 addr 的长度以避免缓冲区溢出问题, 传出的是客户端地址结构体的实际长度(有可能没有占满调用者提供的缓冲区);
  • accept() 函数的返回值是一个新的套接字描述符(socket descriptor)。这个新的套接字描述符代表了服务器和客户端之间建立的连接。如果连接成功建立,accept() 将返回这个新的套接字描述符,之后所有的读写操作都会通过这个新的套接字描述符来进行,以区分不同的客户端连接。
 while (true)
{
       struct sockaddr_in clientaddr;
       socklen_t len = sizeof(clientaddr);

       int sockfd = accept(_Listensockfd, (struct sockaddr *)&clientaddr, &len);
       if (sockfd < 0)
       {
            LOG(WARNING, "accept failed...");
            continue;
       }
       //......
}
connect

在这里插入图片描述

  • 客户端需要调用 connect()连接服务器;
  • connect 和 bind 的参数形式一致, 区别在于 bind 的参数是自己的地址, 而connect 的参数是对方的地址;
  • connect()成功返回 0,出错返回-1;
	// 创建套接字
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd == -1)
    {
        std::cout << "Client socket create failed..." << std::endl;
        exit(1);
    }

    struct sockaddr_in seraddr;
    seraddr.sin_family = AF_INET;
    seraddr.sin_port = htons(Serverport);
    seraddr.sin_addr.s_addr = INADDR_ANY;
    // 不需要显示的bind,系统自动bind

    // 客户端连接服务端
    int n = connect(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));
    if (n < 0)  //连接失败
    {
        std::cout << "connet fail..." << std::endl;
        exit(2);
    }
    while (true)  //连接成功
    {
 		//......
    }


对于TCP,tcp是面向字节流的,无论是服务器还是客户端,读取操作可以直接使用文件读取的那些操作,如:read,write等。

 void Server(int sockfd, InetAddr client)
    {
        char inbuffer[1024];
        while (true)
        {
            ssize_t n = read(sockfd, inbuffer, sizeof(inbuffer) - 1);
            if (n > 0)
            {
                inbuffer[n] = 0;
                write(sockfd, inbuffer, sizeof(inbuffer));
            }
            else if (n == 0)
            {
                break;
            }
        }
        close(sockfd);   //注意:用完需要关闭,因为是有限的,如果不关闭,会造成文件描述符泄漏的问题。
    }

但是在TCP中,OS也提供了其他读写的系统调用;

send

在这里插入图片描述

recv

在这里插入图片描述

TCPServer.hpp
 while (true)
    {
        std::cout << "Please Enter#";
        std::string message;
        std::getline(std::cin, message);
        int n = send(sockfd, message.c_str(), message.size(), 0);
        if (n > 0)
        {
            char outbuffer[1024];
            recv(sockfd, outbuffer, sizeof(outbuffer), 0);
            std::cout << outbuffer << std::endl;
        }
        else
        {
            exit(3);
        }
    }

TCP代码练习

TCP- echo_server

TCPServer.hpp

#include <iostream>
#include <cstdlib>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cstring>
#include <unistd.h>
#include <sys/wait.h>
#include <pthread.h>
#include <functional>

#include "InetAddr.hpp"
#include "Log.hpp"

#include"ThreadPool.hpp"

const static int sockfdefalt = -1;
const static int gbacklog = 16;

using task_t = std::function<void()>;

enum EixtError
{
    SOCK_ERROE = 1,
    BIND_ERROR,
    LISTEN_ERROR,
    USAGE_ERROR
};

class TcpServer;

class ThreadData
{
public:
    ThreadData(int sockfd, InetAddr addr, TcpServer *s) : _sockfd(sockfd), _addr(addr), _this(s)
    {
    }
    ~ThreadData()
    {
    }
    int _sockfd;
    InetAddr _addr;
    TcpServer *_this;
};

class TcpServer
{
public:
    TcpServer(uint16_t port, int _Listensockfd = sockfdefalt) : _port(port), _Listensockfd(_Listensockfd), _IsRuning(false)
    {
    }
    void InitTcpServer()
    {
        // 创建流式套接字
        _Listensockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (_Listensockfd < 0)
        {
            LOG(FATAL, "socket create failed...");
            exit(SOCK_ERROE);
        }
        LOG(DEBUG, "socket create success..._Listensockfd:%d", _Listensockfd);

        // 构建addr对象并且初始化
        struct sockaddr_in Seraddr;
        memset(&Seraddr, 0, sizeof(Seraddr));
        Seraddr.sin_family = AF_INET;
        Seraddr.sin_port = htons(_port);
        Seraddr.sin_addr.s_addr = INADDR_ANY;

        // 绑定
        int n = bind(_Listensockfd, (struct sockaddr *)&Seraddr, sizeof(Seraddr));
        if (n < 0)
        {
            LOG(FATAL, "socket bind failed...");
            exit(BIND_ERROR);
        }
        LOG(DEBUG, "socket bind success...");

        // listen
        n = listen(_Listensockfd, gbacklog);
        if (n < 0)
        {
            LOG(FATAL, "socket listen failed...");
            exit(LISTEN_ERROR);
        }
    }
    static void *Hanldertask(void *args)
    {
        pthread_detach(pthread_self());

        ThreadData *data = static_cast<ThreadData *>(args);
        TcpServer *s = data->_this;
        s->Server(data->_sockfd, data->_addr);

        return nullptr;
    }
    void Server(int sockfd, InetAddr client)
    {
        char inbuffer[1024];
        std::string clientMessage = "[" + client.GetIP() + ":" + std::to_string(client.Getport()) + "]#";

        while (true)
        {
            ssize_t n = read(sockfd, inbuffer, sizeof(inbuffer) - 1);
            if (n > 0)
            {
                inbuffer[n] = 0;
                LOG(DEBUG, "%s %s", clientMessage.c_str(), inbuffer);

                write(sockfd, inbuffer, sizeof(inbuffer));
            }
            else if (n == 0)
            {
                break;
            }
        }

        close(sockfd);
    }
    void Start()
    {
        _IsRuning = true;
        while (_IsRuning)
        {
            struct sockaddr_in clientaddr;
            socklen_t len = sizeof(clientaddr);

            int sockfd = accept(_Listensockfd, (struct sockaddr *)&clientaddr, &len);
            if (sockfd < 0)
            {
                LOG(WARNING, "accept failed...");
                continue;
            }
            LOG(DEBUG, "link success... sockfd:%d", sockfd);

            InetAddr Caddr(clientaddr);

            // 版本0---只能同时处理一个请求 不适用
            // Server(sockfd, Caddr);

            // // 版本1 采用多进程     
            // pid_t pid = fork();
            // if (pid == 0)
            // {
            //     // child 子进程
            //     ::close(_Listensockfd);
            //     if (fork() > 0)  //孙子进程创建成功
            //     {
            //         exit(0);   //子进程退出后,孙子进程就变为孤儿进程,被领养,自动会回收资源
            //     }

            //     Server(sockfd, Caddr);  //孙子进程执行的代码
            //     exit(0);   //直接退出,让父进程直接回收资源
            // }
            // // father 父进程
            // ::close(sockfd);
            // waitpid(pid, nullptr, 0);

            //版本2 采用多线程
            pthread_t pid;
            ThreadData data(sockfd, Caddr, this);
            pthread_create(&pid, nullptr, Hanldertask, &data);

            // 版本3 采用线程池
            // task_t t = std::bind(&TcpServer::Server,this,sockfd,Caddr);
            // ThreadPool<task_t>::GetInstance()->Enqueue(t);
        }
    }
    ~TcpServer()
    {
        close(_Listensockfd);
    }

private:
    uint16_t _port;
    int _Listensockfd;
    bool _IsRuning;
};

TCPClient.hpp

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

void Usage(std::string str)
{
    std::cout << "Usage:\n\t " << str << " Serverip Serverport" << std::endl;
}
//  ./UdpClient Serverip Serverport
int main(int argc, char *args[])
{
    if (argc != 3)
    {
        Usage(args[0]);
        exit(1);
    }
    uint16_t Serverport = std::stoi(args[2]);

    // 创建套接字
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd == -1)
    {
        std::cout << "Client socket create failed..." << std::endl;
        exit(1);
    }

    struct sockaddr_in seraddr;
    seraddr.sin_family = AF_INET;
    seraddr.sin_port = htons(Serverport);
    seraddr.sin_addr.s_addr = INADDR_ANY;
    // 不需要显示的bind,系统自动bind

    // 客户端连接服务端
    int n = connect(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));
    if (n < 0)
    {
        std::cout << "connet fail..." << std::endl;
        exit(2);
    }
    while (true)
    {
        std::cout << "Please Enter#";
        std::string message;
        std::getline(std::cin, message);
        int n = send(sockfd, message.c_str(), message.size(), 0);
        if (n > 0)
        {
            char outbuffer[1024];
            recv(sockfd, outbuffer, sizeof(outbuffer), 0);
            std::cout << outbuffer << std::endl;
        }
        else
        {
            exit(3);
     
上一篇:数据结构-4.1.特殊矩阵的压缩存储


下一篇:Github 2024-10-01 开源项目月报 Top20