zoukankan      html  css  js  c++  java
  • Linux 系统编程学习笔记

    预备知识

    socket概念

    socket可以表示很多概念:

    • 在TCP/IP协议中,“IP地址 + TCP/UDP端口号”唯一标识网络通讯中的一个进程,“IP地址+端口号”称为socket。
    • 在TCP协议中,建立连接的2个进程各自有一个socket来标识,那么这两个socket组成的socket pair就唯一标识一个连接。socket本身有“插座”的意思,因此用来描述连接的一对一关系。
    • TCP/IP协议最早在BSD UNIX上实现,为TCP/IP设计的应用层编程接口称为socket API。

    网络编程中的socket, 通常指socket API (socket, bind, listen, accept, connect等)。

    网络字节序

    内存中多字节数据相对于内存地址,有大小端之分,网络数据流也有。

    先看下什么是大端、小端?
    详解大端模式和小端模式 | 博客园

    以存放0x12345678为例,说明大端、小端的区别:

    1. 大端 big endian:高位字节存放在内存的低地址端,低位字节存放在内存的高地址端
      0x12345678本身 => 高位字节:0x12,低位字节:0x78 ,高位、低位字节取决于数据本身
    低地址 --------------------> 高地址
    0x12  |  0x34  |  0x56  |  0x78
    
    1. 小端 little endian:低位字节存放在内存的低地址端,高位字节存放在内存的高地址端
      0x12345678本身 => 高位字节:0x12,低位字节:0x78
    低地址 --------------------> 高地址
    0x78  |  0x56  |  0x34  |  0x12
    

    网络数据流的地址
    发送主机通常将发送缓冲区中的数据,按内存地址从低到高的顺序发出,接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存.
    把地址的高低, 与字节序号的高低一一对应, 即地址低 <=> 字节序号低, 地址高 <=> 字节序号高.

    网络数据流的大端小端是什么?
    TCP/IP协议规定,网络数据流的采用大端字节序,即低地址高(位)字节。
    下图是UDP协议数据包格式示意图:

    下图以发送源端口号=0x3e8为例, 说明网络数据流的大端, 小端直接区别:

    大端, 小端如何转换?
    网络字节序是网络协议规定的, 而存储字节序取决于硬件体系结构. 它们如何转换?
    如果发送和接收主机是大端字节序,就不需要转换;如果任何一端是小端字节序,就需要转换. 可以调用以下库函数做网络字节序和主机字节序转换(为了使网络程序具备可移植性,建议使用).
    注: IP地址, 端口号可以利用下面的库函数转换.

    #include <arpa/inet.h>
    
    // h - host, n - network
    // l - 32bit long, s - 16bit short
    // 如果主机是小端字节序,将参数做相应的大小端转换,然后返回;
    // 如果主机是大端字节序,参数直接返回
    uint32_t htonl(uint32_t hostlong); // 32bit unsigned long主机序 -> 网络字节序
    uint16_t htons(uit16_t hostshort); // 16bit unsigned short主机序 -> 网络字节序
    uint32_t ntohl(uint32_t netlong);  // 32bit unsigned short网络字节序 -> 主机序
    uint16_t ntohs(uint16_t netshort); // 16bit unsigned short网络字节序 -> 主机序
    

    socket地址的数据类型及相关函数

    socket API是一层抽象的网络编程接口,适用于各种底层网络协议(如Ipv4、Ipv6及UNIX Domain Socket)。然而各种网络协议的地址格式并不相同:
    IPv4地址格式 , 地址类型; IPv6地址格式 struct sockaddr_in6; UNIX Domain Socket地址格式 struct sockaddr_un

    地址格式(socket addr类型) 地址类型/协议族(sa_family_t) 备注
    IPv4 struct sockaddr_in AF_INET 用于IPv4通信
    IPv6 struct sockaddr_in6 AF_INET6 用于IPv6通信
    Unix Domain Socket struct sockaddr_un AF_INET6 用于Unix Domain Socket通信

    注: 头文件 netinet/in.h

    sockaddr数据结构示意图:

    问题:从上面的表格知道, 可以从结构体类型上区分是使用IPv4, IPv6 or Unix Domain Socket,为何还要多添加一个字段sa_family_t用于表示地址类型呢?
    一种地址类型(协议族)只有一种地址格式与之对应, 如AF_INET表示IPv4通信, 只有struct sockaddr_in对应的IPv4数据报文格式, 才能与之对应. 为了统一接口, 无论IPv4, IPv6, 还是Unixi Domain Socket, 都使用通用的struct sockaddr格式, 这样socket API可以接受各种类型的sockaddr结构体指针做参数,而不需要知道具体是哪种类型, 例如bind, accept, connect等函数的参数可以用同一种类型struct sockaddr*来表示地址格式. 具体的哪种类型的地址, 就用地址类型来区分.
    传递参数示例(需要强制类型转换):

    struct sockaddr_in sockaddr; // IPv4地址格式, 包含了IP地址 + 端口信息
    ... // 设置sockaddr的IP地址, 端口. 发送数据注意使用网络序, 接收数据使用主机序
    
    // 绑定套接字与IP地址 + 端口信息 
    // servaddr对应实参可能是struct sockaddr_in, struct sockaddr_in6, 或struct sockaddr_un
    // 如果使用具体的地址格式类型, 有多少种不同协议地址类型, 就需多少种接口
    bind(listen_fd, (struct_sockaddr *)&sockaddr, sizeof(sockaddr)); 
    

    IP地址转换 字符串 <-> 32bit数据
    IPv4的sockaddr_in的成员sin_addr (struct in_addr)表示32位IP地址, 常需要转化成点分十进制的字符串IP地址(const char *类型).

    点分十进制字符串 -> in_addr:

    #include <arpa/inet.h>
    
    /* strptr 点分十进制IP地址;
    * addrptr 32bit ip地址
    */
    int inet_aton(const char *strptr, struct in_addr *addrptr);
    int_addr inet_addr(const char *strptr);
    // 支持转换IPv4的in_addr,也支持IPv6的in6_addr
    int inet_pton(int family, const char *strptr, void *addrptr); 
    

    in_addr转点分十进制字符串:

    char *inet_ntoa(struct in_addr inaddr);
    // 支持转换IPv4的in_addr,也支持IPv6的in6_addr
    const char *inet_ntop(int family, const void *addrptr, char *strptr, size_t len);
    

    基于TCP协议的网络程序

    TCP client/server程序一般流程:

    时序图:

    连接建立的程序过程

    1. 服务器调用socket(), bind(), listen() 进行初始化;
    2. 服务器调用accept()阻塞等待,监听端口状态;
    3. 客户端调用socket()初始化;
    4. 客户端调用connect()发送SYN段,并阻塞等待服务器应答;
    5. 服务器收到SYN段后,应答一个SYN-ACK段;
    6. 客户端收到服务器的SYN-ACK段后,从已阻塞的connect()返回,同时应答一个ACK段;
    7. 服务器收到客户端应答的ACK段后,从已阻塞的accept返回。

    数据传输的过程

    1. 服务器从accept()返回后立刻调用read(),读socket,如果没有数据就阻塞等待;
    2. 客户端调用write()发送请求给服务器,服务器收到后从read()返回,并对客户端的请求进行处理;
    3. 客户端write()之后,调用read()阻塞等待服务器的应答;
    4. 服务器wirte()之后,调用read()阻塞等待下一条请求,客户端读取后从read()返回,发送下一条请求(跳到2),这样不断循环。

    连接的断开过程

    1. 如果客户端没有更多请求了,调用close()关闭连接;
    2. 服务器从read()返回0,就得知客户端关闭了连接,也调用close()关闭连接。
      注意:任何一方调用close(),连接的两个传输方向都关闭,不能再发送数据。如果一方调用shutdown则连接处于半关闭状态,仍可接收对方发来的数据。

    最简单的TCP网络程序

    完整代码参见: linuxstudy 文件名:server.c, client.c

    server端

    例,server.c的作用是从client读字符,然后将每个字符转换为大写并回送到客户端。

    // server.c
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <ctype.h>
    #include <unistd.h>
    #include <stdbool.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    
    #define MAXLINE   80
    #define SERV_PORT 8000
    
    int main() {
      struct sockaddr_in servaddr, cliaddr;
      socklen_t cliaddr_len;
      int listenfd, connfd;
      char buf[MAXLINE];
      char str[INET_ADDRSTRLEN];  // 16
      int i, n;
    
      // step1 socket() 初始化socket,获取文件描述符
      // 类似于打开一个通道, listenfd就是这个通道的文件描述符
      listenfd = socket(AF_INET, SOCK_STREAM, 0); // AF_INET - IPv4 IP地址;AF_INET6 - IPv6 IP地址;AF_UNIX - Unix Domain Socket
      
      // 初始化sockaddr, 设置socket IP地址+端口信息
      bzero(&servaddr, sizeof(servaddr)); // 清空sockaddr
      servaddr.sin_family = AF_INET;
      servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 将32位主机序 IP地址INADDR_ANY 转化成网络序IP地址 
      servaddr.sin_port = htons(SERV_PORT); // 将16位主机序 端口号SERV_PORT 转换成网络序端口号
      /*
      思考:这里为什么要用htonl/htons转换IP地址和端口?为什么不转地址类型AF_INET?
      因为地址类型AF_INET是用于程序判断IP地址类型的,而IP地址和端口是要用网卡发送到网络上的数据,为了确保程序可移植性,需要转换成网络字节序
      */
      
      // step2 bind() 绑定socket和IP地址
      bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); // 注意bind第二个参数接收的是struct sockaddr *类型,而变量sockaddr是sockaddr_in类型
      
      // step3 listen() 设置监听队列
      listen(listenfd, 20); // 处于监听状态的的流套接字listenfd, 将维护一个客户请求队列, 最多容纳20个用户请求
    
      printf("Accepting connections...
    ");
      while(true) {
        cliaddr_len = sizeof(cliaddr);
        
        // step4 accept() 阻塞等待客户端连接请求
        connfd = accept(listenfd, &cliaddr, &cliaddr_len); // 阻塞等待客户连接请求(SYN段),如果从accept成功返回表明已经收到客户端SYN段
        // step5 read()/write() 读取客户端数据或向客户端发送数据
        // 为什么是从accept返回的connfd读取,而不是socket返回的listenfd?因为accept绑定的是客户端,listenfd绑定的是服务器端(也就是自身)的IP地址及端口信息
        n = read(connfd, buf, MAXLINE); // 从监听端口读取n byte数据,存储到buf[0..n-1]
        printf("received from %s at PORT %d
    ", inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),  // 将k客户端IP地址信息in_addr转换成字符串str,
          ntohs(cliaddr.sin_port)); // 将客户端端口号由主机字节序转化成网络字节序
    
        for (i = 0; i < n; ++i) {
          buf[i] = toupper(buf[i]); // buf[0..n-1]转换成大写
        }
        write(connfd, buf, n); // 写回给客户端
    
        // step6 close() 发送FIN段,关闭TCP连接
        close(connfd);
      }
      
      return 0;
    }
    

    socket(), bind(), listen(), accept(), read(), write() 这几个socket API都位于头文件sys/socket.h中。

    • socket()
      socket()打开一个网络通讯端口,如果成功,就像open一样返回一个文件描述符,可以通过该文件描述符绑定IP地址、监听端口、与客户端建立连接。
      要真正让APP可以像读写文件一样read()/write()在网络上收发数据,还需要绑定IP地址及端口,还有和客户端建立连接之后。
    #include <sys/socket.h>
    #include <sys/types.h>
    
    // 成功返回一个文件描述符;出错返回-1
    // family 用于指示协议族的名字,AF_INET为IPv4,AF_INET6为IPv6,AF_UNIX为Unix Domain Socket
    // type 指示类型,SOCK_STREAM表示TCP协议,SOCK_DGRAM表示UDP协议
    // protocol 用于指示对于这种socket的具体协议类型。一般情况下,指定了前2个参数就,如果只存在一种协议类型对应该情况,就可以将protocol设置为0;某些情况下,会存在多个协议类型时,就必须指定具体的协议类型。
    int socket(int family, int type, int protocol);
    

    SOCK_STREAM(流) : 提供有序,可靠的双向连接字节流。 可以支持带外数据传输机制,无论多大的数据都不会截断;
    SOCK_DGRAM(数据报):支持数据报(固定最大长度的无连接,不可靠的消息),数据报超过最大长度,会被截断;

    • bind()
      服务器监听的网络地址和端口号通常是固定不变的(通常是知名服务, 如Telnet/STMP等, 对应着知名端口号), 客户端程序得知服务器程序的地址和端口号后,可以向服务器发起连接请求.
      服务器调用bind绑定一个固定的网络地址和端口号; 客户端IP和端口不固定,不需要bind绑定端口, 也就是不需要调用bind.
    // 绑定sockfd和myaddr
    // addrlen 绑定地址从长度
    // 为什么需要第3个参数addrlen? 
    // 事实上,myaddr可以接受多种协议的sockaddr结构体,而它们参数各不相同,所以需要addrlen指定结构体长度
    int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);
    

    程序中为什么要将地址类型设为INADDR_ANY?
    因为服务器可能有多个网卡,每个网卡也可能绑定多个IP地址,这样设置可以监听所有本地IP地址,直到与某个客户端建立了连接才确定到底用哪个IP地址

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // INADDR_ANY转换过来, 0.0.0.0, 泛指本机IP地址, 可以使用一套代码同时监听多个网卡
    servaddr.sin_port = htons(SERV_PORT);  // SERV_PORT = 8000
    
    • listen()
      服务器一般可以服务多个客户端,如果有大量客户端同时发起连接,服务器可能会来不及处理,尚未accept处理的客户端处于等待状态。listen可以让服务器为sockfd维护一个队列,监听客户端连接请求。
    // 监听端口
    // sockfd 用socket()成功创建的TCP套接字(文件描述符)
    // backlog 监听队列的大小,即最多容许backlog个客户端处于等待连接状态
    // 成功返回0,失败-1
    int listen(int sockfd, int backlog);
    
    • accept()
      accept用于从指定套接字的连接队列中取出第一个连接,并返回一个新的套接字用于与客户端通信。
      accept会一直阻塞,直到有客户端成功连接(已完成三次握手)
      客户端是连接的请求方,而不是接受方,不需要调用accept。
    // sockaddr 处于监听状态的套接字
    // cliaddr 用于保存客户端的地址信息
    // addrlen_t [in][out] 传入传出参数,传入值是cliaddr缓存的大小以避免溢出,传出值是客户端地址结构体的实际长度。 NULL表示不关心客户端的地址
    int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen_t);
    

    服务器程序结构:

    while(true) {
      cliaddr_len = sizeof(cliaddr);
      connfd = accept(sockfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
      n = read(connfd, buf, MAXLINE);
      ...
      close();
    }
    

    这里的read, write跟文件IO里面的读写操作,是同一个IO接口,位于头文件unistd.h

    client端

    client.c从命令行获取一个字符串,发给服务器,然后接收服务器返回的字符串并打印。

    // client.c
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <sys/socket.h>
    #include <netinet/in.h> 
    
    #define MAXLINE    80
    #define SERV_PORT  8000
    
    int main(int argc, char *argv[]) {
      struct socketaddr_in sockaddr;
      char buf[MAXLINE];
      int sockfd, n;
      char *str;
    
      if (argc != 2) {
        fputs("usage: ./client message
    ", stderr);
        exit(1);
      }
      
      str = argv[1]; // 要传给服务器的字符串
      
      sockfd = socket(AF_INET, SOCK_STREAM, 0);
      bzero(&sockaddr, sizeof sockaddr);
      sockaddr.sin_family = AF_INET;
      sockaddr.sin_port = htons(SERV_PORT);
      inet_pton(AF_INET, "127.0.0.1", &sockaddr.sin_addr);
      
      connect(sockfd, (struct sockaddr *)&sockaddr, sizeof sockaddr);
    
      write(sockfd, str, strlen(str));
      n = read(sockfd, buf, MAXLINE);
      printf("Response from Server:
    ");
      write(STDOUT_FILENO, buf, n);
      
      close(sockfd);
      return 0;
    }
    

    先编译、运行服务器:

    $ gcc server.c -o server
    $ ./server
    

    查看服务器端口占用情况:

    $ netstat -apn|grep 8000
    

    服务器占用8000端口,但IP地址还未确定

    另开终端,编译运行客户端:

    $ gcc client.c -o client
    $ ./client abcd
    Response from Server:
    ABCD
    

    回到server终端,可以看到server输出:

    $ ./server
    Accepting connections...
    received from 127.0.0.1 at PORT xxx
    

    FAQ
    如何处理多个client请求?
    不同client连接请求,可以用listen设置监听队列大小来缓存,但是对于已经连接上的client,如何并发处理?

    1. 方式一:使用fork并发处理。阻塞IO
      每accept一个客户连接请求,就fork一个子进程来负责read/write以及close。

    2.方式二:使用select()同时监听多个阻塞文件的文件描述符(套接字),哪个有数据达到就处理哪个,不需要fork和多进程。 相当于IO多路复用。
    参考linux select函数解析以及事例 | 知乎select()函数以及FD_ZERO、FD_SET、FD_CLR、FD_ISSET

    如何解决client进程未终止,但服务器程序重启,端口被占用的问题?
    服务器重启或退出时,会主动关闭连接,服务器发送FIN段给客户端,客户端收到FIN后处于CLOSE_WAIT状态,但client没有终止,也没有关闭socket 描述符(TCP套接字),因此不会发送FIN给服务器,因此server的TCP链接处于FIN_WAIT2状态。

    bind error: Address already in use
    

    解决办法:使用setsockopt()设置socket描述符选项SO_REUSEADDR为1,表示允许创建端口号相同但IP地址不同的多个socket描述符。具体来说是在socket()和bind()之间插入如下代码:

    int op = 1;
    setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof opt);
    

    基于UDP协议的网络程序

    典型的UDP通讯流程

    UDP和TCP的主要区别:

    1. UDP是面向消息的,无连接的,连接不可靠,TCP是面向字节流的,要传输数据必须先进行连接(三次握手),连接可靠;
    2. IP数据报文格式不一样,TCP对应IP报文首部包含了SYN、WIN、ACK、FIN等与连接相关的信息,UDP的IP报文首部则不包含这些;
    3. 程序上,UDP在创建socket文件描述符后,服务器端只需进行bind IP地址信息,无需listen、accept等与监听、连接有关的操作,客户端也无需connect进行连接请求;

    一个简单的UDP服务器和客户端程序示例
    server.c

    // server.c
    #include <stdlib.h>
    #include <stdio.h>
    #include <string.h>
    #include <netinet/in.h>
    #include <stdbool.h>
    #include <sys/socket.h>
    #include <arpa/inet.h>
    #include <ctype.h>
    
    #include "wrap.h"
    
    #define MAXLINE   80
    #define SERV_PORT 8000
    
    int main() {
        struct sockaddr_in servaddr, cliaddr;
        socklen_t cliaddr_len;
        int sockfd;
        char buf[MAXLINE];
        char str[INET_ADDRSTRLEN]; // 16
        int i, n;
    
        // socket() choose protocol - IPv4 UDP
        sockfd = Socket(AF_INET, SOCK_DGRAM, 0);
    
        bzero(&servaddr, sizeof servaddr);
        servaddr.sin_family = AF_INET;
        servaddr.sin_port = htons(SERV_PORT);
        servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    
        // bind() bind ip info
        Bind(sockfd, (struct sockaddr *)&servaddr, sizeof servaddr);
    
        printf("Accepting connections ...
    ");
    
        while(true) {
            cliaddr_len = sizeof cliaddr;
            n = recvfrom(sockfd, buf, MAXLINE, 0, (struct sockaddr *)&cliaddr, &cliaddr_len);
            if (n == -1)
                perr_exit("recvfrom error");
    
            printf("received from %s at PORT %d
    ",
                   inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof str), // in_addr to string
                   ntohs(cliaddr.sin_port)
            );
    
            for (i = 0; i < n; ++i) {
                buf[i] = toupper(buf[i]);
            }
    
            n = sendto(sockfd, buf, n, 0, (struct sockaddr *)&cliaddr, cliaddr_len);
    
            if (n == -1) perr_exit("sendto error");
        }
    
        return 0;
    }
    

    client.c

    // client.c
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <netinet/in.h>
    #include <string.h>
    #include <arpa/inet.h>
    
    #include "wrap.h"
    
    #define MAXLINE   80
    #define SERV_PORT 8000
    
    int main() {
        struct sockaddr_in servaddr;
        int sockfd, n;
        char buf[MAXLINE];
        char str[INET_ADDRSTRLEN]; // 16
        socklen_t servaddr_len;
    
        // socket() choose UDP protocol
        sockfd = Socket(AF_INET, SOCK_DGRAM, 0);
    
        bzero(&servaddr, sizeof servaddr);
        servaddr.sin_family = AF_INET;
        servaddr.sin_port = htons(SERV_PORT);
        inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
    
        while (fgets(buf, MAXLINE, stdin) != NULL) {
            n = sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr *)&servaddr, sizeof servaddr);
            if (n == -1) perr_exit("sendto error");
    
            n = recvfrom(sockfd, buf, MAXLINE, 0, NULL, 0);
            if (n == -1) perr_exit("recvfrom error");
            write(STDOUT_FILENO, buf, n);
        }
    
        Close(sockfd);
        return 0;
    }
    

    UNIX Domain Socket IPC

    参考Linux下进程间通讯方式 - UNIX Domain Socket

    服务端: socket -> bind -> listen -> accet -> recv/send -> close
    客户端: socket -> connect -> recv/send -> close
    当然,客户端也可以bind,自己指定socket文件,便于服务器区分不同的客户端。

    服务器端
    使用listen设置监听队列时,socket()的type参数不能使用SOCK_DGRAM,需使用SOCK_STREAM。

    // server.c
    #include <stdlib.h>
    #include <stdio.h>
    #include <stddef.h>
    #include <sys/socket.h>
    #include <sys/un.h>
    #include <unistd.h>
    #include <errno.h>
    #include <sys/stat.h>
    #include <ctype.h>
    #include <stdbool.h>
    
    #define QLEN  2
    #define MAXLINE 80
    
    /**
     * create a server endpoint of a connection
     * @param name
     * @return Returns fd if all OK, <0 on error.
     */
    int serv_listen(const char *name) {
        int fd, len, err, rval;
        struct sockaddr_un un;
    
        if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
            return -1;
        unlink(name); // remove the special file if it exists
        printf("unlink name is %s
    ", name);
    
        // fill in socket address structure
        memset(&un, 0, sizeof un);
        un.sun_family = AF_UNIX;
        strcpy(un.sun_path, name);
    
        printf("server sun_path is %s
    ", un.sun_path);
    
        int opt = 1;
        setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof opt);
    
        len = offsetof(struct sockaddr_un, sun_path) + strlen(name);
    
        // bind the name to descriptor
        if (bind(fd, (struct sockaddr *)&un, len) < 0) {
    //    if (bind(fd, (struct sockaddr *)&un, sizeof un) < 0) {
            rval = -2;
            goto errout;
        }
        if (listen(fd, QLEN) < 0) {
            printf("serv_listen -3
    ");
            rval = -3;
            goto errout;
        }
    
        return fd;
    
    errout:
        err = errno;
        close(fd);
        errno = err;
        return rval;
    }
    
    int serv_accept(int listenfd, uid_t *uidptr) {
        int clifd, len, err, rval;
        struct sockaddr_un un;
        struct stat statbuf;
    
        len = sizeof un;
        if ((clifd = accept(listenfd, (struct  sockaddr *)&un, &len)) < 0)
            return -1;
    
        len -= offsetof(struct sockaddr_un, sun_path); // length of pathname
    
        if (stat(un.sun_path, &statbuf) < 0) {
            rval = -2;
            goto errout;
        }
    
        if (S_ISSOCK(statbuf.st_mode) == 0) {
            rval = -3;
            goto errout;
        }
    
        if (uidptr != NULL)
            *uidptr = statbuf.st_uid;
        unlink(un.sun_path);
        return clifd;
    
    errout:
        err = errno;
        close(clifd);
        return rval;
    }
    
    int main() {
        int servfd, clifd;
        char *name = "foo.socket";
        uid_t uid;
        char buf[MAXLINE];
        int i;
    
        servfd = serv_listen(name);
        if (servfd == -1) {
            perror("socket error
    ");
            exit(1);
        }
        else if (servfd == -2) {
            perror("bind error
    ");
            exit(2);
        }
        else if (servfd == -3) {
            perror("listen error
    ");
            exit(3);
        }
        else printf("listen successfully
    ");
        for (; ; ) {
            clifd = serv_accept(servfd, &uid);
            if (clifd < 0) {
                perror("serv_accept error");
                exit(4);
            }
    
            while (true) {
                int ret = recv(clifd, buf, MAXLINE, 0);
                if (ret < 0) {
                    printf("recv error
    ");
                    break;
                }
    
                printf("received: %s size = %ld
    ", buf, strlen(buf));
    
                for (i = 0; i < MAXLINE; ++i) {
                    buf[i] = toupper(buf[i]);
                }
                send(clifd, buf, ret, 0);
            }
            close(clifd);
        }
    
        close(servfd);
        return 0;
    }
    

    客户端
    使用bind的socket文件名不能与服务器端的同名(同路径+同文件名),而connect的socket文件名需要与服务器端的socket文件同名;

    // client.c
    #include <stdlib.h>
    #include <stdio.h>
    #include <stddef.h>
    #include <sys/stat.h>
    #include <sys/un.h>
    #include <sys/socket.h>
    #include <unistd.h>
    #include <errno.h>
    
    #define MAXLINE  80
    #define CLIA_PATH  "/var/tmp/"  // + 5 for pid = 14 chars
    
    int cli_conn(const char *name) {
        int fd, len, err, rval;
        struct sockaddr_un un;
    
        // create a UNIX domain socket
        if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
            perror("socket error
    ");
            return -1;
        }
    
        memset(&un, 0, sizeof un);
        un.sun_family = AF_UNIX;
        sprintf(un.sun_path, "%s%05d", CLIA_PATH, getpid());
        len = offsetof(struct sockaddr_un, sun_path) + strlen(un.sun_path);
    
        unlink(un.sun_path);
        puts(un.sun_path);
    
    //    if (bind(fd, (struct sockaddr *)&un, sizeof un)) {
        if (bind(fd, (struct sockaddr *)&un, len) < 0) {
            perror("bind error
    ");
            rval = -2;
            goto errout;
        }
    
        // fill socket address structure with server's address
        memset(&un, 0, sizeof un);
        un.sun_family = AF_UNIX;
        strcpy(un.sun_path, name); // server create socket file with the same
        len = offsetof(struct sockaddr_un, sun_path) + strlen(name);
        if (connect(fd, (struct sockaddr*)&un, len) < 0) {
            perror("connect error...
    ");
            rval = -4;
            goto errout;
        }
        return fd;
    
        errout:
        err = errno;
        close(fd);
        errno = err;
        return rval;
    }
    
    int main() {
        int sockfd;
        char* name = "foo.socket";
        char buf[MAXLINE];
    
        sockfd= cli_conn(name);
        if (sockfd < 0) {
            perror("connect error
    ");
            exit(1);
        }
        printf("connect successfully
    ");
    
        while (fgets(buf, MAXLINE, stdin) != NULL) {
            int n = send(sockfd, buf, strlen(buf), 0);
            if (n < 0) {
                perror("send error
    ");
                exit(1);
            }
            else if (n == 0) {
                printf("send 0 byte
    ");
                break;
            }
    
            int ret = recv(sockfd, buf,MAXLINE, 0);
            write(STDOUT_FILENO, buf, ret);
        }
        printf("terminate
    ");
    
        return 0;
    }
    

    参考

    《linuxC编程一站式学习》

  • 相关阅读:
    1063. Set Similarity
    A1047. Student List for Course
    A1039. Course List for Student
    最大公约数、素数、分数运算、超长整数计算总结
    A1024. Palindromic Number
    A1023. Have Fun with Numbers
    A1059. Prime Factors
    A1096. Consecutive Factors
    A1078. Hashing
    A1015. Reversible Primes
  • 原文地址:https://www.cnblogs.com/fortunely/p/14624029.html
Copyright © 2011-2022 走看看