• socket相关命令


    socket相关命令

    学习《Linux高性能服务器编程》第五章Linux网络编程基础API,为了印象深刻一些,多动手多实践,所以记下这个笔记。这一篇主要记录Linux中socket相关的命令,包括创建socket、命名socket、监听socket、接受连接、发起连接和关闭连接。

    创建socket

    socket使用系统调用可以创建一个socket

    #include           /* See NOTES */
    #include 
    
    int socket(int domain, int type, int protocol);
    
    • 1
    • 2
    • 3
    • 4

    domain参数是告诉系统使用的是那个底层协议族,一般都是使用IPv4,所以使用AF_INET即可。关于socket系统调用支持的所有协议族,可以查看man手册(虽然参数名不一样,但是并无大碍)。

    image-20220812095854483

    type参数指定服务类型。服务类型主要有SOCK_STREAM服务(流服务)和SOCK_UGRAM(数据报)服务。对TCP/IP协议族而言,其值取SOCK_STREAM表示传输层使用TCP协议,取SOCK_DGRAM表示传输层使用UDP协议。

    image-20220812101617247
    并且从Linux内核2.6.17起,增加了SOCK_NONBLOCKSOCK_CLOEXEC这两个标志值,表示将新创建的socket设为非阻塞,以及fork调用创建子进程时在子进程中关闭该socket。在Linux内核2.6.17前,需要调用fcntl进行设置。

    protocol参数设置具体的协议。但是在前两个参数确定的情况下,这个参数的值基本上唯一的,所有几乎在所有情况下,我们都把这个值设置为0,表示使用默认协议。

    socket系统调用成功时返回一个socket文件描述符,失败则返回-1并设置errno。

    #include 
    
    int main(int argc, char const *argv[])
    {
        int lfd = 0;
        lfd = socket(AF_INET, SOCK_STREAM, 0); //创建一个 socket
        close(lfd);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    命名socket

    创建socket时,我们指定了地址族,但是并没有给定具体的地址,这样作为服务器别人是访问不到我们的。将一个socket 与socket地址绑定称为给socket命名。命名socket的系统调用是bind。

    #include           /* See NOTES */
    #include 
    
    int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    
    • 1
    • 2
    • 3
    • 4

    bindaddr所指的socket地址分配给未命名的sockfd文件描述符,addrlen参数指出该socket地址的长度。

    image-20220812104757237

    bind成功时返回0,失败则返回-1并设置errno。其中两种常见的errnoEACCESEADDRINUSE,它们的含义分别是:

    • EACCES,被绑定的地址是受保护的地址,仅超级用户能够访问。比如普通用户将socket绑定到知名服务端口(端口号为0~1023)上时,bind将返回EACCES错误。
    • EADDRINUSE,被绑定的地址正在使用中。比如将socket绑定到一个处于TIME_WAIT状态的socket地址。

    image-20220812105430276

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    
    #define SERV_PORT 8080
    
    int main(int argc, char const *argv[])
    {
        int lfd = 0, cfd = 0;
        int ret, i;
    
        struct sockaddr_in serv_addr, clit_addr; // 定义服务器地址结构 和 客户端地址结构
        socklen_t clit_addr_len;                 // 客户端地址结构大小
    
        serv_addr.sin_family = AF_INET;                // IPv4
        serv_addr.sin_port = htons(SERV_PORT);         // 转为网络字节序的 端口号
        serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 获取本机任意有效IP
    
        lfd = socket(AF_INET, SOCK_STREAM, 0); //创建一个 socket
        if (lfd == -1)
        {
            perror("socket error");
        }
    
        bind(lfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)); //给服务器socket绑定地址结构(IP+port)
        close(lfd);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    监听socket

    socket被命名后,还需要调用listen创建一个监听队列来存放处理的客户连接。

    #include           /* See NOTES */
    #include 
    
    int listen(int sockfd, int backlog);
    
    • 1
    • 2
    • 3
    • 4

    image-20220812112118847

    sockfd参数指定被监听的socket。backlog参数提示内核监听队列的最大长度。监听队列的长度如果超过backlog,服务器将不受理新的客户连接,客户端也将收到ECONNREFUSED错误信息。

    在内核版本2.2之前的Linux中,backlog参数是指所有处于半连接状态(SYN_RCVD)和完全连接状态(ESTABLISHED)的socket 的上限。但自内核版本2.2之后,它只表示处于完全连接状态的socket的上限,处于半连接状态的socket的上限则由/proc/sys/net/ipv4/tcp_max_syn_backlog 内核参数定义。backlog 参数的典型值是5。

    image-20220812113608890

    listen成功时返回0,失败则返回-1并设置erron

    本来想测试backlog这个参数的效果,但是怎么也成功不了,不知道原因,以后有机会再进行尝试吧。

    接受连接

    接受连接通过accept进行

    #include           /* See NOTES */
    #include 
    
    int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
    
    • 1
    • 2
    • 3
    • 4

    image-20220812143816774

    sockfd指执行过listen的监听套接字的文件描述符。

    addr是传出参数,用来获取接受连接的远端socket地址,地址的长度由addrlen参数指出。

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    int main(int argc, char const *argv[])
    {
        if (argc <= 2)
        {
            printf("usage: %s ip_address port_number\n", basename(argv[0]));
            return 1;
        }
        const char *ip = argv[1];
        int port = atoi(argv[2]);
    
        sockaddr_in address;
        bzero(&address, sizeof(address));
        address.sin_family = AF_INET;
        inet_pton(AF_INET, ip, &address.sin_addr);
        address.sin_port = htons(port);
    
        int sock = socket(AF_INET, SOCK_STREAM, 0);
        assert(sock >= 0);
    
        int ret = bind(sock, (sockaddr *)&address, sizeof(address));
        assert(ret != -1);
    
        ret = listen(sock, 5);
        assert(ret != -1);
    
        struct sockaddr_in client;
        socklen_t client_addrlength = sizeof(client);
        int connfd = accept(sock, (struct sockaddr *)&client, &client_addrlength);
    
        if (connfd < 0)
        {
            printf("errno is: %d\n", errno);
        }
        else
        {
            char remote[INET_ADDRSTRLEN];
            printf("connected with ip: %s and port: %d\n",
                   inet_ntop(AF_INET, &client.sin_addr, remote, INET_ADDRSTRLEN), ntohs(client.sin_port));
            close(connfd);
        }
    
        close(sock);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    image-20220812151534158

    并且书上面的实验说明了accept直接从监听队列中取出连接,而不论连接处于何种状态,更不关心任何网络状况的变化。比如:客户端在服务器accept之前就断网了,accept还是可以正常进行,它并不会返回错误。

    发起连接

    发动连接一般是客户端进行的,通过系统调用connect与服务器进行连接。

    #include           /* See NOTES */
    #include 
    
    int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    
    • 1
    • 2
    • 3
    • 4

    image-20220812154447716

    sockfd参数由socket系统调用返回一个socketaddr参数是服务器监听的socket地址。addrlen参数指这个地址长度。

    connect成功时返回0。一旦成功建立连接,sockfd就唯一地标识了这个连接,客户端就可以通过读写sockfd来与服务器通信。connect 失败则返回-1并设置errno。其中两种常见的errnoECONNREFUSEDETIMEDOUT,它们的含义如下:

    • ECONNREFUSED表示目标端口不存在,连接被拒绝。
    • ETIMEDOUT表示连接超时。

    关闭连接

    关闭连接一般来说使用

    #include 
    int close(int fd);
    
    • 1
    • 2

    fd参数是待关闭的socket。不过,close并不会立即关闭这个连接,而是将fd的引用数量减1,直到fd引用数量为0,才真正关闭连接。在多进程程序中,一次fork系统调用默认将父进程中socket的引用计算加1,因此必须在子进程和父进程都对该socket进行close调用才能将连接关闭。

    如果想立刻终止连接,直接调用shutdown

    #include 
    int shutdown(int sockfd, int how);
    
    • 1
    • 2

    image-20220812160846669

    sockfd参数是待关闭的socket,howto参数决定了shutdown的行为。

    可选值含义
    SHUT_RD关闭sockfd上读的这一半。应用程序不能再针对socket文件描述符执行读操作,并且该sockct接收缓冲区中的数据都被丢弃。
    SHUT_WR关闭sockfd上写的这一半。sockfd 的发送缓冲区中的数据会在真正关闭连接之前全部发送出去,应用程序不可再对该socket文件描述符执行写操作。这种情况下,连接处于半关闭状态。
    SHUT_RDWR同时关闭sockfd上的读和写。

    可以看出shutdown可以灵活的关闭socket上的读或写。而close在关闭连接时只能将socket上的读和写同时关闭。

    shutdown成功时返回0,失败则返回-1并设置errno

  • 相关阅读:
    ADAS&&自动驾驶
    如何查询具有指定特性或名称的文件 (C#)
    idea常用的一些配置信息
    猿创征文|OpenCV编程——计算机视觉的登堂入室
    [Java反序列化]—CommonsCollections1
    Keras实战(一)
    YOLOv8血细胞检测(5):可变形大核注意力(D-LKA Attention),超越自注意力| 2023.8月最新发表
    王道数据结构笔记03-红黑树/RBT
    基于花朵授粉算法的无线传感器网络部署优化附Matlab代码
    Filter
  • 原文地址:https://blog.csdn.net/qq_41474648/article/details/126335668