Linux/Unix 套接字Socket编程(socket基本概念,流程,流式/数据报socket,Unix domain socket示例)

发布于:2025-07-09 ⋅ 阅读:(15) ⋅ 点赞:(0)

套接字

I socket基础

Socket(套接字)是网络通信的基石,它提供了进程间通信的端点。通过Socket,不同主机或同一主机上的不同进程可以进行数据交换。Socket本质上是一个编程接口(API),它封装了底层网络协议的细节,使开发者能够更方便地进行网络编程。

1、通信domain

socket通过通信domain确定通信范围,使用的协议族等,目前常用的domain包括:

在这里插入图片描述

2、通用socket地址结构体
  • socket API使用通用地址结构体sockaddr来表示不同协议族的地址
    #include <sys/socket.h>
    struct sockaddr {
        sa_family_t sa_family;  // address family(AF_xxx)
        char        sa_data[];  // socket address
    };
  • sa_family字段指定地址族(如AF_INET、AF_INET6等)

  • sa_data字段包含实际的地址信息,其格式和长度由地址族决定

  • 通常使用协议特定的地址结构体(如sockaddr_in),在调用函数时强制转换为sockaddr类型
3、socket类型
  • socket主要分为两大类型,流socket(SOCK_STREAM)和数据报socket(SOCK_DGRAM);

  • 流socket提供了一个可靠的双向字节流通信信道,服务端发送的数据会完整无误的到达客户端,数据可以在两个socket之间任意方向上传输,数据不错在消息边界

  • 数据报socket中数据以数据报的形式进行交换,数据的传输是不可靠的,数据的到达可能是无序的,重复的,甚至会丢失

  • 流socket的正常工作需要两个socket相互连接,数据报socket的正常工作不需要连接另一个socket

  • 数据报socket使用UDP协议,流socket使用TCP协议
4、创建和关闭socket
  • 系统调用socket()用于创建一个socket
    #include <sys/socket.h>
    int socket(int domain, int type, int protocol);
    /*return file descriptor on success, or -1 on error*/
  • 参数domain是通信域,指定socket使用的协议族,可选:

   AF_INET(IPv4协议族);AF_INET6(IPv6协议族);AF_UNIX(UNIX域本地通信)

  • 参数type指定socket类型,常见值:

   SOCK_STREAM(TCP流式socket);SOCK_DGRAM(UDP数据报socket)

  • 参数protocol通常设为0,表示使用默认协议:

   type为SOCK_STREAM时,默认TCP;type为SOCK_DGRAM时,默认UDP

  • 可以通过close()关闭一个socket
    #include <unistd.h>
    int close(int sockfd);
    /*return 0 on success or -1 on error*/
  • 参数sockfd是需要关闭的socket描述符(socket()成功时的返回值)
5、将socket绑定到地址
  • 系统调用bind()用于将socket绑定到一个地址上面,绑定后客户端可以通过该地址访问服务端:
    #include <sys/socket.h>
    int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    /*return 0 on success, or -1 on error*/
  • 参数sockfd是系统调用socket()中返回的文件描述符

  • 参数addr指向要绑定的地址结构体,其类型取决于socket的domain,可以是:

   socketaddr_u(AF_UNIX); socketaddr_in(AF_INET); socketaddr_in6(AF_INET6)

  • 参数addrlen指定地址结构体的长度
6、流socket
(1)流socket服务端-客户端模型

  流socket采用TCP协议,分为客户端和服务端:

  • 服务端流程
    通过socket()创建socket;通过bind()绑定到地址;通过listen()将socket设置为被动监听状态;当有客户端请求连接时通过accept()接受客户端连接;通过recv()和send()和客户端完成数据交换;通信完成后通过close()关闭socket

  • 客户端流程
    通过socket()创建socket;准备服务端地址信息(IP和端口);通过connect()连接服务端;连接成功后通过send()发送数据;通过recv()接收服务端响应;通信完成后通过close()关闭socket

在这里插入图片描述

(2)系统调用listen()

  系统调用listen()将socket标记为被动,准备接受来自客户端的连接请求:

    #include <sys/socket.h>
    int listen(int sockfd, int backlog);
    /*return 0 on success, or -1 on error*/

  参数sockfd是已绑定的socket文件描述符;backlog指定等待连接队列的最大长度

(3)系统调用accept()

  系统调用accept()用于接受来自绑定地址的客户端的连接请求:

    #include <sys/socket.h>
    int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
    /*return new socket fd on success, or -1 on error*/

  参数sockfd是监听socket的文件描述符;addr是监听socket的地址信息;addrlen是参数addr的大小

(4)系统调用connect()

  系统调用connect()用于发起与服务端的连接:

    #include <sys/socket.h>
    int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    /*return 0 on success, or -1 on error*/

  参数sockfd是要连接的socket文件描述符;addr指定要连接的服务器地址;addrlen地址长度

(5)系统调用recv()

  系统调用recv()用于从已连接的socket接收数据:

    #include <sys/socket.h>
    ssize_t recv(int sockfd, void *buf, size_t len, int flags);
    /*return number of bytes received, 0 on peer shutdown, or -1 on error*/

  参数sockfd是已连接的socket文件描述符;buf指向接收缓冲区;len指定缓冲区大小;flags控制接收行为(通常设为0)

(6)系统调用send()

  系统调用send()用于向已连接的socket发送数据:

    #include <sys/socket.h>
    ssize_t send(int sockfd, const void *buf, size_t len, int flags);
    /*return number of bytes sent, or -1 on error*/

  参数sockfd是已连接的socket文件描述符;buf指向发送数据缓冲区;len指定发送数据长度;flags控制发送行为(通常设为0)

7、数据报socket
(1)数据报socket模型

  数据报socket使用无连接的UDP协议进行通信:

  • 服务端流程
    通过socket()创建socket;通过bind()绑定到地址;通过recvfrom()接收客户端数据;通过sendto()发送响应数据;通信完成后通过close()关闭socket

  • 客户端流程
    通过socket()创建socket;准备服务端地址信息;通过sendto()发送数据;通过recvfrom()接收服务端响应;通信完成后通过close()关闭socket

在这里插入图片描述

(2)系统调用recvfrom()

  系统调用recvfrom()用于从数据报socket接收数据并获取发送方地址:

    #include <sys/socket.h>
    ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                    struct sockaddr *src_addr, socklen_t *addrlen);
    /*return number of bytes received, or -1 on error*/

  参数sockfd是socket文件描述符;buf指向接收缓冲区;len指定缓冲区大小;flags控制接收行为;src_addr返回发送方地址;addrlen指定地址结构体长度

(3)系统调用sendto()

  系统调用sendto()用于向指定地址的数据报socket发送数据:

    #include <sys/socket.h>
    ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
                  const struct sockaddr *dest_addr, socklen_t addrlen);
    /*return number of bytes sent, or -1 on error*/

  参数sockfd是socket文件描述符;buf指向发送数据;len指定数据长度;flags控制发送行为;dest_addr指定目标地址;addrlen指定地址结构体长度

II Unix domain Socket

1、Unix domain Socket地址结构

  Unix domain socket使用文件系统路径名作为地址,结构体定义如下:

    struct sockaddr_un {
        sa_family_t sun_family;    /* AF_UNIX */
        char sun_path[108];        /* 路径名 */
    };

  sun_family固定为AF_UNIX;sun_path指定socket文件路径(最大107字符+NULL终止符)

2、Unix domain 流Socket
  • 服务端流程
    socket(AF_UNIX, SOCK_STREAM, 0)→bind()→listen()→accept()→recv()/send()→close()
    示例:
    /* Create a stream socket server */
    int main(){
        /* Create a stream socket */
        int sfd = socket(AF_UNIX, SOCK_STREAM, 0);
        if(sfd == -1){
            perror("socket");
            return -1;
        }

        /* Remove any existing socket file */
        unlink(SOCKET_ADDR_STREAM);

        /* Set up server address structure */
        struct sockaddr_un addr;
        memset(&addr, 0, sizeof(addr));
        addr.sun_family = AF_UNIX;
        strncpy(addr.sun_path, SOCKET_ADDR_STREAM, strlen(SOCKET_ADDR_STREAM)+1);
        
        /* Bind socket to address */
        int err = bind(sfd, (struct sockaddr *)&addr, sizeof(addr.sun_path));
        if(err == -1 ){
            perror("bind");
            close(sfd);
            return -1;
        }

        /* Listen for incoming connections */
        err = listen(sfd, SOMAXCONN);
        if(err == -1){
            perror("listen");
            close(sfd);
            return -1;
        }

        /* Client address structure and buffer */
        struct sockaddr_un addr_cli;
        socklen_t len_cli = sizeof(addr_cli);
        char buff[1024];
        ssize_t len;

        /* Main server loop */
        while(1){
            /* Accept incoming connection */
            int cfd = accept(sfd, (struct sockaddr *)&addr_cli, &len_cli);
            if(cfd == -1){
                perror("accept");
                close(sfd);
                return -1;
            }

            /* Receive data from client */
            memset(buff, 0, sizeof(buff));
            len = recv(cfd, buff, sizeof(buff), 0);
            if(len == -1){
                perror("recv");
                close(sfd);
                close(cfd);
                return -1;
            }

            /* Print received message */
            printf("recevied %zd bytes : \n %s \n", len, buff);

            /* Check for exit command */
            if(strcmp(buff,"exit") == 0){
                printf("server closed, see you next time !\n");
                break;
            }
        }

        /* Clean up */
        close(sfd);
        unlink(SOCKET_ADDR_STREAM);
        return 0;
    }
  • 客户端流程
    socket(AF_UNIX, SOCK_STREAM, 0)→connect()→send()/recv()→close()
    示例:
    /* Create a stream socket client */
    int socket_stream_client(int argc, char *argv[]){
        /* Check command line arguments */
        if(argc < 2 || argv[1] == NULL){
            printf("Usage:%s <message send to server>\n", argv[0]);
            return -1;
        }

        /* Create stream socket */
        int sfd = socket(AF_UNIX, SOCK_STREAM, 0);
        if(sfd == -1){
            perror("socket");
            return -1;
        }

        /* Set up server address structure */
        struct sockaddr_un addr;
        memset(&addr, 0, sizeof(addr));
        addr.sun_family = AF_UNIX;
        strncpy(addr.sun_path, SOCKET_ADDR_STREAM, strlen(SOCKET_ADDR_STREAM)+1);

        /* Connect to server */
        int err = connect(sfd, (struct sockaddr *)&addr, sizeof(addr.sun_path));
        if(err == -1){
            perror("connect");
            close(sfd);
            return -1;
        }

        /* Send message to server */
        ssize_t len = send(sfd, argv[1], strlen(argv[1]), 0);
        if(len == -1){
            perror("send");
            return -1;
        }

        /* Clean up */
        close(sfd);
        return 0;
    }
3、Unix domain 数据报socket
  • 服务端流程
    socket(AF_UNIX, SOCK_DGRAM, 0)→bind()→recvfrom()/sendto()→close()
    示例:
    /* Create a datagram socket server */
    int socket_dgram_server(){
        /* Create a datagram socket */
        int sfd = socket(AF_UNIX, SOCK_DGRAM, 0);
        if(sfd == -1){
            perror("socket");
            return -1;
        }

        /* Remove any existing socket file */
        unlink(SOCKET_ADDR_DGRAM);

        /* Set up server address structure */
        struct sockaddr_un addr;
        memset(&addr, 0, sizeof(addr));
        addr.sun_family = AF_UNIX;
        strncpy(addr.sun_path, SOCKET_ADDR_DGRAM, strlen(SOCKET_ADDR_DGRAM)+1);

        /* Bind socket to address */
        int err = bind(sfd, (struct sockaddr *)&addr, sizeof(addr.sun_path));
        if(err == -1){
            perror("bind");
            close(sfd);
            return -1;
        }

        /* Buffer for received data */
        char buff[1024];
        /* Client address structure */
        struct sockaddr_un cli_addr;
        socklen_t len = sizeof(cli_addr);

        /* Main server loop */
        while(1){
            memset(buff, 0, sizeof(buff));
            /* Receive data from client */
            ssize_t num_bytes = recvfrom(sfd, buff, sizeof(buff), 0,
                                        (struct sockaddr *)&cli_addr, &len);
            if(num_bytes == -1){
                perror("recvfrom");
                close(sfd);
                return -1;
            }

            /* Print received message */
            printf("received %zd bytes: %s\n", num_bytes, buff);

            /* Check for exit command */
            if(strcmp(buff, "exit") == 0){
                printf("server closed, see you next time!\n");
                break;
            }
        }

        /* Clean up */
        close(sfd);
        unlink(SOCKET_ADDR_DGRAM);
        return 0;
    }
  • 客户端流程
    socket(AF_UNIX, SOCK_DGRAM, 0)→sendto()/recvfrom()→close()
    示例:
    /* Create a datagram socket client */
    int socket_dgram_client(int argc, char *argv[]){
        /* Check command line arguments */
        if(argc < 2 || argv[1] == NULL){
            printf("Usage:%s <message send to server>\n", argv[0]);
            return -1;
        }

        /* Create datagram socket */
        int sfd = socket(AF_UNIX, SOCK_DGRAM, 0);
        if(sfd == -1){
            perror("socket");
            return -1;
        }

        /* Set up server address structure */
        struct sockaddr_un addr;
        memset(&addr, 0, sizeof(addr));
        addr.sun_family = AF_UNIX;
        strncpy(addr.sun_path, SOCKET_ADDR_DGRAM, strlen(SOCKET_ADDR_DGRAM)+1);

        /* Send message to server */
        ssize_t len = sendto(sfd, argv[1], strlen(argv[1]), 0,
                            (struct sockaddr *)&addr, sizeof(addr.sun_path));
        if(len == -1){
            perror("sendto");
            close(sfd);
            return -1;
        }

        /* Clean up */
        close(sfd);
        return 0;
    }

网站公告

今日签到

点亮在社区的每一天
去签到