美文网首页
Linux系统编程10:Socket编程2-接口

Linux系统编程10:Socket编程2-接口

作者: jdzhangxin | 来源:发表于2018-05-05 20:53 被阅读112次

    1. 接口

    1.1 转换操作

    转换操作主要分为三类:字节序转换操作、IP地址转换操作和主机名转换操作。

    1.1.1 字节序转换操作

    • 网络序转主机序
    No. 函数 含义 作用
    1 ntohs() network to host short unsigned short类型从网络序转换到主机序
    2 ntohl() network to host long unsigned long类型从网络序转换到主机序
    • 主机序转网络序
    No. 函数 含义 作用
    1 htons() host to network short unsigned short类型从主机序转换到网络序
    2 htonl() host to network long unsigned long类型从主机序转换到网络序

    1.1.2 IP地址转换操作

    No. 函数 功能 特点
    1 int inet_aton(const char *string, struct in_addr*addr) 点分十进制数串转网络字节序长整型 IPv4专用
    2 in_addr_t inet_addr(const char* string) 点分十进制数串转网络字节序长整型 IPv4专用
    3 char* inet_ntoa(struct in_addr addr) 网络字节序长整型转点分十进制数串 IPv4专用
    4 int inet_pton(int af, const char *src, void *dst) 点分十进制数串转网络字节序长整型 IPv4/IPv6通用(推荐)
    5 const char *inet_ntop(int af, const void *src, char *dst, socklen_t cnt) 网络字节序长整型转点分十进制数串 IPv4/IPv6通用(推荐)

    结构体

    No. 结构体 功能 特性
    1 struct sockaddr 套接字地址结构 IPv4/IPv6通用
    2 struct sockaddr_in IPv4套接字地址结构 IPv4专用
    3 struct in_addr IPv4地址结构 IPv4专用
    4 in_addr_t IPv4地址类型 IPv4专用
    5 struct sockaddr_in6 IPv6套接字地址结构 IPv6专用
    • 套接字地址结构
    struct sockaddr {
        unsigned short sa_family; // 套接字地址簇类型,为AF_INET
        char sa_data[14];         // 套接字地址数据(14位的协议地址)
    };
    
    • IPv4套接字地址结构
    struct sockaddr_in{
        short sin_family;         // 套接字地址簇类型,为AF_INET
        unsigned short sin_port;  // 端口号,网络字节序
        struct in_addr sin_addr;  // IP地址,网络字节序
        unsigned char sin_zero[8];// 填充字节
    };
    

    sin_zero[8]用来保证结构体struct sockaddr_in的大小和结构体struct sockaddr的大小相等。

    • IPv4地址结构
    struct in_addr {
        in_addr_t s_addr;
    };
    
    • IPv4地址类型
    typedef unsigned int in_addr_t;
    
    • IPv6套接字地址结构
    struct sockaddr_in6{
       uint8_t sin6_len;           //IPv6 为固定的24 字节长度
       sa_family_t sin6_family;    //套接字地址簇类型,为AF_INET6
       in_port_t sin6_port;        //16 位端口号,网络字节序
       uint32_t sin6_flowinfo;     //32 位流标签
       struct in6_addr sin6_addr;  //128 位IP地址
    }
    

    1.1.2.1 IPv4专用

    点分十进制数串转网络字节序长整型

    ①推荐方式
    int inet_aton(const char *string, struct in_addr*addr)
    
    • 参数
    No. 参数 含义
    1 string 点分十进制IP地址字符串
    2 addr 网络字节序长整型IP地址
    • 返回值
    No. 返回值 含义
    1 0 成功
    2 非0 失败
    • 示例
    #include <stdio.h>
    #include <stdlib.h>
    #include <arpa/inet.h>
     
    int main(int argc,char* argv[]){
        if(2 != argc){
            printf("usage:%s <ip>\n",argv[0]);
            return 1;
        }
        struct in_addr addr;
        int res = inet_aton(argv[1],&addr);
        if(0 == res){
            perror("inet_aton error");
            return 1;
        }
        printf("res:%d\n%s = 0x%08x",res,argv[1],addr);
     
        return 0;
    }
    
    ①旧方式
    in_addr_t inet_addr(const char* string)
    
    • 参数
    No. 参数 含义
    1 string 点分十进制IP地址字符串
    • 返回值
    No. 返回值 含义
    1 INADDR_NONE 失败
    2 INADDR_NONE 网络字节序长整型IP地址
    • 示例
    #include <stdio.h>
    #include <stdlib.h>
    #include <arpa/inet.h>
     
    int main(int argc,char* argv[]){
        if(2 != argc){
            printf("usage:%s <ip>\n",argv[0]);
            return 1;
        }
        in_addr_t addr = inet_addr(argv[1]);
        if(INADDR_NONE == addr){
            perror("inet_addr error");
            return 1;
        }
        printf("%s = 0x%08x",argv[1],addr);
     
        return 0;
    }
    

    网络字节序长整型转点分十进制数串

    char* inet_ntoa(struct in_addr addr)
    
    • 参数
    No. 参数 含义
    1 addr 网络字节序长整型IP地址
    • 返回值
    No. 返回值 含义
    1 NULL 点分十进制IP地址字符串
    2 NULL 失败
    • 示例
    #include <stdio.h>
    #include <stdlib.h>
    #include <arpa/inet.h>
     
    int main(int argc,char* argv[]){
        if(2 != argc){
            printf("usage:%s <num>\n",argv[0]);
            return 1;
        }
        struct in_addr addr;
        addr.s_addr = strtol(argv[1],NULL,16);
        char* ip = inet_ntoa(addr);
        if(NULL == ip){
            perror("inet_ntoa error");
            return 1;
        }
        printf("0x%x = %s",addr.s_addr,ip);
     
        return 0;
    }
    

    1.1.2.2 IPv4/IPv6通用(推荐)

    点分十进制数串转网络字节序长整型

    int inet_pton(int af, const char *src, void *dst)
    
    • 参数
    No. 参数 含义
    1 af 地址族。AF_INET/AF_INET6
    2 src 点分十进制IP地址字符串
    3 dst 网络字节序长整型IP地址
    • 返回值
    No. 返回值 含义
    1 <0 失败
    2 0 afsrc格式不对
    • 示例
    #include <stdio.h>
    #include <string.h>
    #include <arpa/inet.h>
     
    int main(int argc,char* argv[]){
        char ip[INET_ADDRSTRLEN];
        if(argc == 1){
            scanf("%s",ip);
        }else if(2 != argc){
            printf("usage:%s <ip>\n",argv[0]);
            return 1;
        }
        struct in_addr addr;
        memset(&addr,0,sizeof(addr));
        if(1 != inet_pton(AF_INET,(argc == 1?ip:argv[1]),&addr)){
            perror("inet_pton err");
            return 1;
        }
        printf("%x\n",addr.s_addr);
    }
    

    网络字节序长整型转点分十进制数串

    const char *inet_ntop(int af, const void *src, char *dst, socklen_t cnt)
    
    • 参数
    No. 参数 含义
    1 af 地址族。AF_INET/AF_INET6
    2 src 网络字节序长整型IP地址
    3 dst 点分十进制IP地址字符串
    4 cnt 缓存区dst的大小
    • 返回值
    No. 返回值 含义
    1 NULL 失败
    2 NULL dst指针
    • 示例
    #include <stdio.h>
    #include <string.h>
    #include <arpa/inet.h>
     
    int main(int argc,char* argv[]){
        if(2 != argc){
            printf("usage:%s <dec num>\n",argv[0]);
            return 1;
        }
        struct in_addr addr;
        memset(&addr,0,sizeof(addr));
        addr.s_addr = strtol(argv[1],NULL,16);
        char ip[INET_ADDRSTRLEN];
        if(NULL == inet_ntop(AF_INET,&addr,ip,sizeof(ip))){
            perror("inet_ntop err");
            return 1;
        }
        printf("%s\n",ip);
    }
    

    1.1.3 主机名转换操作

    No. 函数 功能
    1 struct hostent *gethostbyname(const char *hostname) 主机名转地址
    2 struct hostent *gethostbyaddr(const char * addr, int len, int type) 地址转主机名
    3 struct hostent *gethostbyaddr(const char * addr, int len, int type) 地址转主机名

    1.1.3.1 主机名字和地址信息struct hostent

    No. 参数 含义
    1 h_name 主机名字
    2 h_aliases 以空指针结尾的主机别名队列
    3 h_addrtype 地址类型。AF_INET/AF_INET6
    4 h_length 地址长度。在AF_INET类型地址中为4
    5 h_addr 第一个IP地址
    6 h_addr_list 以空指针结尾的IP地址的列表

    1.1.3.2 主机名转地址

    struct hostent *gethostbyname(const char *hostname)
    
    • 参数
    No. 参数 含义
    1 hostname 主机名
    • 返回值
    No. 返回值 含义
    1 NULL 出错
    2 NULL hostent结构指针
    • 示例
    #include <stdio.h>
    #include <netdb.h>
    #include <arpa/inet.h>
     
    int main(int argc,char** argv){
     
        struct hostent* host = gethostbyname(argv[1]);
        if(NULL == host){
            herror("gethostbyname err");
            return 1;
        }
        printf("hostname:%s\n",host->h_name);
        printf("aliases:");
        while(*host->h_aliases != NULL){
            printf("%s ",*host->h_aliases);
            host->h_aliases++;
        }
        printf("\n");
        printf("addrtype:%s\n",host->h_addrtype == AF_INET?"AF_INET":"AF_INET6");
        printf("length:%d\n",host->h_length);
        printf("addrlist:");
        while(*host->h_addr_list != NULL){
            printf("%s ",inet_ntoa(*(struct in_addr*)*host->h_addr_list));
            host->h_addr_list++;
        }
    }
    

    1.1.3.3 地址转主机名

    struct hostent *gethostbyaddr(const char * addr, int len, int type)
    
    No. 参数 含义
    1 addr 网络字节顺序地址
    2 len 地址的长度。在AF_INET类型地址中为4
    3 type 地址类型。AF_INET/AF_INET6
    • 返回值
    No. 返回值 含义
    1 NULL 出错
    2 NULL hostent结构指针
    • 示例
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <netdb.h>
    #include <arpa/inet.h>
     
    int main(int argc,char* argv[]){
        int c,af=AF_INET;
        while((c = getopt(argc,argv,"6")) != -1){
            switch(c){
                case '6':
                    af = AF_INET6;
                break;
            }
        }
     
        if(optind != argc-1){
            printf("usage:%s [-6] <ip>\n",argv[0]);
            return 1;
        }
     
        struct in_addr addr;
        struct in6_addr addr6;
        void* dst = af == AF_INET ? (void*)&addr : (void*)&addr6;
     
        if(0 >= inet_pton(af,argv[1],dst)){
            perror("inet_pton error");
            return 1;
        }
        socklen_t len = AF_INET ? sizeof(struct in_addr) : sizeof(struct in6_addr) ;
        struct hostent * phost = gethostbyaddr(dst,len,af);
        if(NULL == phost){
            perror("gethostbyname error");
            return 1;
        }
         
        char ip[20];
        switch( phost->h_addrtype ){
            case AF_INET:
            case AF_INET6:
                if(NULL == inet_ntop(phost->h_addrtype,(void*)phost->h_addr,ip,20)){
                    perror("inet_ntop error");
                    return 1;
                }
                printf("host:%s\n0x%x = %s",argv[1],phost->h_addr,ip);
                break;
        }
    }
    

    2. socket操作

    2.1 接口

    2.1.1 创建

    int socket(int domain, int type, int protocol)
    
    No. 参数 含义
    1 domain 协议域 AF_INET:IPv4;AF_INET6:IPv6;AF_LOCAL:Unix域
    2 type 类型 SOCK_STREAM:流式套接字;SOCK_DGRAM:数据报套接字;SOCK_RAW:原始套接字
    3 protocol 协议 0:自动根据type匹配协议;IPPROTO_TCP/IPPROTO_UDP
    • 返回值
    No. 返回值 含义
    1 -1 失败
    2 >0 socket描述符

    2.1.2 关闭

    int close(int sockfd)
    int shutdown(int sockfd,int howto)
    
    No. 参数 含义
    1 sockfd socket套接字
    2 howto 关闭方式
    • 关闭方式
    No. 方式 含义
    1 SHUT_RD 0 关闭连接的读
    2 SHUT_WR 1 关闭连接的写
    3 SHUT_RDWR 2 连接的读和写都关闭

    2.1.3 属性

    设置

    int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen)
    
    No. 参数 含义
    1 sockfd 套接字描述符
    2 level 选项层次
    3 optname 选项
    4 optval 选项值指针
    5 optlen optval缓冲区长度
    • 选项层次
    No. 参数 含义
    1 SOL_SOCKET 通用套接字选项
    2 IPPROTO_TCP TCP选项
    3 IPPROTO_IP IP选项
    4 IPPROTO_IPV6 IPv6选项

    选项分为SOL_SOCKET级别和IPPROTO_IP级别两个级别

    • SOL_SOCKET级别
    No. 参数 含义
    1 SO_REUSEADDR 让端口释放后立即就可以被再次使用。一个端口释放后会等待两分钟之后才能再被使用。
    2 SO_RCVBUF 接收确定缓冲区大小
    3 SO_SNDBUF 发送缓冲区大小
    4 SO_SNDTIMEO 发送时限
    5 SO_RCVTIMEO 接收时限
    6 SO_BROADCAST 广播
    7 SO_DONTLINGER 关闭端口不进入TIME_WAIT状态
    8 SO_LINGER 关闭端口进入TIME_WAIT状态的属性
    • IPPROTO_IP级别
    No. 参数 含义
    1 IP_ADD_MEMBERSHIP 加入指定的组播组。
    2 IP_DROP_MEMBERSHIP 离开指定的组播组。
    3 IP_MULTICAST_IF 指定发送组播数据的IP地址。
    4 IP_MULTICAST_LOOP 发送组播数据的主机是否作为接收组播数据的组播成员。
    • 返回值
    No. 返回值 含义
    1 0 成功
    2 -1 失败

    获取

    int getsockopt(int sockfd, int level, int optname,void *optval, socklen_t *optlen)
    
    No. 参数 含义
    1 sockfd 套接字描述符
    2 level 选项层次
    3 optname 选项
    4 optval 选项值指针
    5 optlen optval缓冲区长度
    • 返回值
    No. 返回值 含义
    1 0 成功
    2 -1 失败

    2.1.4 绑定

    int bind(int socket, const struct sockaddr* address, socklen_t address_len)
    
    No. 参数 含义
    1 socket 套接字描述符
    2 address 地址和端口号
    3 address_len address缓冲区的长度
    • 返回值
    No. 返回值 含义
    1 0 成功
    2 SOCKET_ERROR 失败

    2.1.5 监听

    int listen(int sockfd, int backlog)
    
    No. 参数 含义
    1 sockfd 监听的socket描述符
    2 backlog 排队的最大连接个数
    • 返回值
    No. 返回值 含义
    1 0 成功
    2 -1 失败

    2.1.6 连接

    int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    
    No. 参数 含义
    1 sockfd 客户端的socket描述字
    2 addr 服务器的socket地址
    3 addrlen 服务器的socket地址的长度
    • 返回值
    No. 返回值 含义
    1 0 成功
    2 -1 失败

    2.1.7 接受

    int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
    
    No. 参数 含义
    1 sockfd 服务器的socket描述符,监听socket描述符
    2 addr 客户端的socket地址
    3 addrlen 客户端的socket地址的长度
    • 返回值
    No. 返回值 含义
    1 -1 连接描述符
    2 -1 失败

    2.1.8 发送

    ssize_t write(int fd, const void *buf, size_t len);
    
    No. 参数 含义
    1 fd 文件描述符
    2 buf 写入数据
    3 len 写入数据的长度
    • 返回值
    No. 返回值 含义
    1 >0 实际所写的字节数
    2 <0 出错
    ssize_t send(int sockfd, const void *buf, size_t len, int flags);
    
    No. 参数 含义
    1 sockfd sockfd文件描述符
    2 buf 写入数据
    3 len 写入数据的长度
    4 flags 通常为0
    • 返回值
    No. 返回值 含义
    1 >0 实际所写的字节数
    2 <0 出错
    ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);
    
    No. 参数 含义
    1 sockfd sockfd文件描述符
    2 buf 写入数据
    3 len 写入数据的长度
    4 flags 通常为0
    5 dest_addr 目标socket地址
    6 addrlen 目标socket地址长度
    • 返回值
    No. 返回值 含义
    1 >0 实际所写的字节数
    2 <0 出错

    2.1.9 接收

    ssize_t read(int fd, void *buf, size_t len);
    
    No. 参数 含义
    1 fd 文件描述符
    2 buf 读取数据
    3 len 读取数据的长度
    • 返回值
    No. 返回值 含义
    1 0 读到文件的结束
    2 >0 实际所读的字节数
    3 <0 出错
    ssize_t recv(int sockfd, void *buf, size_t len, int flags);
    
    No. 参数 含义
    1 fd 文件描述符
    2 buf 读取数据
    3 len 读取数据的长度
    4 flags 通常为0
    • 返回值
    No. 返回值 含义
    1 0 读到文件的结束
    2 >0 实际所读的字节数
    3 <0 出错
    ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen)
    
    No. 参数 含义
    1 fd 文件描述符
    2 buf 读取数据
    3 len 读取数据的长度
    4 flags 通常为0
    5 dest_addr 目标socket地址
    6 addrlen 目标socket地址长度
    • 返回值
    No. 返回值 含义
    1 0 读到文件的结束
    2 >0 实际所读的字节数
    3 <0 出错

    3. 实践

    3.1 特殊地址设置

    No. 特殊地址 ipv4 ipv6
    1 通配地址 in_addr.sin_addr.s_addr = htonl(INADDR_ANY) in6_addr.sin6_addr=in6addr_any
    2 回环地址 in_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK) in6_addr.sin6_addr=in6addr_loopback
    3 广播地址 in_addr.sin_addr.s_addr = htonl(INADDR_BROADCAST) 无广播

    3.2 原则

    • 客户端/发送端
      必须指定连接/发送的IP(广播地址、回环地址或者某个具体地址)。
      必须指定连接/发送的port。
    • 服务器/接受端
      IP指定为通配地址、回环地址或者某个具体地址。
      必须指定绑定监听/接受的port。

    netstat 查看网络连接状态、socket端口打开状态

    No. 选项 作用
    1 -antp 查看tcp的状态
    2 -anup 查看udp的状态

    3.3 TCP基本流程

    流程图
    No. C/S 函数
    1 Server socket()bind()listen()accept()recv()/read()send()/write()
    2 Client socket()connect()send()/write()recv()/read()

    客户端

    • 流程
    1. 打开套节字
    connfd = socket()
    
    1. 连接服务器
    connect(connfd,...);// 阻塞
    
    1. 写入读取数据
    write(connfd)/read(connfd)
    
    1. 关闭套节字
    close(connfd)
    
    • 示例client.c
    #include <stdio.h>
    #include <string.h>
    #include <arpa/inet.h>
    #include <sys/socket.h>
     
    void show_info(int connfd){
        struct sockaddr_in local_addr;
        bzero(&local_addr,sizeof(local_addr));
        socklen_t local_addr_len = sizeof(local_addr);
        getsockname(connfd,(struct sockaddr*)&local_addr,&local_addr_len);
        printf("client local %s:%d\n",inet_ntoa(local_addr.sin_addr),ntohs(local_addr.sin_port));
         
        struct sockaddr_in peer_addr;
        bzero(&peer_addr,sizeof(peer_addr));
        socklen_t peer_addr_len = sizeof(peer_addr);
        getpeername(connfd,(struct sockaddr*)&peer_addr,&peer_addr_len);
        printf("clinet peer %s:%d\n",inet_ntoa(peer_addr.sin_addr),ntohs(peer_addr.sin_port));
    }
    int main(int argc,char* argv[]){
        if(3 != argc){
            printf("usage:%s <ip> <#port> \n",argv[0]);
            return 1;
        }
     
        int connfd = socket(AF_INET,SOCK_STREAM,0);
        if(-1 == connfd){
            perror("socket err");
            return 1;
        }
        struct sockaddr_in remote_addr;
        bzero(&remote_addr,sizeof(remote_addr));
        remote_addr.sin_family = AF_INET;
        remote_addr.sin_addr.s_addr = inet_addr(argv[1]);
        remote_addr.sin_port = htons(atoi(argv[2]));    
        if(-1 == connect(connfd,(struct sockaddr*)&remote_addr,sizeof(remote_addr))){
            perror("connect err");
            return 1;
        }
        show_info(connfd);
     
        char buf[BUFSIZ];
        bzero(buf,BUFSIZ);
        while(fgets(buf,BUFSIZ,stdin) != NULL){
            write(connfd,buf,strlen(buf)+1);
            printf("client send:%s\n",buf);
            bzero(buf,BUFSIZ);
            // sendfile(fd,connfd,NULL,);
            if(-1 == read(connfd,buf,BUFSIZ)){
                perror("read err");
                return 1;
            }
            printf("client recv:%s\n",buf);
        }
        close(connfd);
    }
    

    服务器

    • 流程
    1. 打开监听套节字
    listenfd = socket()
    
    1. 设置监听套节字地址
    struct sockaddr_in
    
    1. 绑定
    bind(listenfd)
    
    1. 监听
    listen(listenfd,backlog)
    
    1. 打开连接套节字
    connfd = accept(listenfd,...) // 阻塞
    
    1. 读写数据
    read(connfd)/write(connfd)
    recv(connfd)/send(connfd)
    recvfrom(connfd)/sendto(connfd);
    
    1. 关闭套节字
    close(connfd)
    close(listenfd)
    
    • 服务端server.c
    #include <stdio.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <string.h>
    #include <arpa/inet.h>
    #include <sys/socket.h>
     
    void show_info(int connfd){
        struct sockaddr_in local_addr;
        bzero(&local_addr,sizeof(local_addr));
        socklen_t local_addr_len = sizeof(local_addr);
        getsockname(connfd,(struct sockaddr*)&local_addr,&local_addr_len);
        printf("server local %s:%d\n",inet_ntoa(local_addr.sin_addr),ntohs(local_addr.sin_port));
         
        struct sockaddr_in peer_addr;
        bzero(&peer_addr,sizeof(peer_addr));
        socklen_t peer_addr_len = sizeof(peer_addr);
        getpeername(connfd,(struct sockaddr*)&peer_addr,&peer_addr_len);
        printf("server peer %s:%d\n",inet_ntoa(peer_addr.sin_addr),ntohs(peer_addr.sin_port));
    }
    int main(int argc,char* argv[]){
        if(3 != argc){
            printf("usage:%s <ip> <#port>\n",argv[0]);
            return 1;
        }
     
        int listenfd = socket(AF_INET,SOCK_STREAM,0);
        if(-1 == listenfd){
            perror("listenfd open err");
            return 1;
        }
        printf("socket create OK\n");
         
        int flag = 1;
        setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&flag,sizeof(flag));    
     
        struct sockaddr_in local_addr;
        bzero(&local_addr,sizeof(local_addr));
        local_addr.sin_family = AF_INET;
        local_addr.sin_addr.s_addr = inet_addr(argv[1]);
        local_addr.sin_port = htons(atoi(argv[2]));
     
        if(-1 == bind(listenfd,(struct sockaddr*)&local_addr,sizeof(local_addr))){
            perror("bind err");
            return 1;
        }
        printf("bind OK\n");
     
        if(-1 == listen(listenfd,10)){
            perror("listen err");
            return 1;
        }
        printf("listen OK\n");
        struct sockaddr_in remote_addr;
        bzero(&remote_addr,sizeof(remote_addr));
        socklen_t remote_addr_len = sizeof(remote_addr);
        int connfd = accept(listenfd,(struct sockaddr*)&remote_addr,&remote_addr_len);
        if(-1 == connfd){
            perror("accept err");
            return 1;
        }
     
        show_info(connfd);
     
        printf("accept %s:%d\n",inet_ntoa(remote_addr.sin_addr),ntohs(remote_addr.sin_port));
        char buf[BUFSIZ];
        for(;;){
            bzero(buf,BUFSIZ);
            ssize_t len;
            if((len = read(connfd,buf,BUFSIZ-1)) == -1){
                perror("read err");
                return 1;
            }
            if(0 == len){
                break;
            }
            printf("server recv:%s\n",buf);
             
            int fd = open(buf,O_RDONLY);
            if(-1 == fd){
                perror("open file err");
                return 1;
            }
            struct stat file_stat;
            fstat(fd,&file_stat);
            if(-1 == sendfile(connfd,fd,NULL,file_stat.st_size)){
                perror("sendfile err");
                return 1;
            }
            close(fd);
        }   
        close(connfd);
        close(listenfd);
     
    }
    
    Socket与三次握手 Socket与四次挥手

    3.4 UDP

    流程图

    3.4.1 单播

    基本流程
    ① 发送者
    1. 打开socket
    connfd = socket(AF_INET,SOCK_DGRAM,0)  
    
    1. 设置发送地址和端口
    struct sockaddr_in si;  
    si.sin_family = AF_INET;  // 套接字地址簇,一般使用AF_INET
    si.sin_port = htons(端口); // 16位端口,网络序
    si.sin_addr.s_addr = inet_addr(IP地址); // IP地址,网络序
    
    1. 发送数据
    sendto(connfd,buf,buf_size,0,(struct sockaddr *)&si,sizeof(si));  
    
    1. 关闭socket
    close(connfd); 
    
    ②接收者
    1. 打开socket
    int connfd = socket(AF_INET, SOCK_DGRAM, 0);  
    
    1. 设置接收地址和端口
    struct sockaddr_in  si;  
    si.sin_family = AF_INET;  // 套接字地址簇,一般使用AF_INET
    si.sin_port = htons(端口); // 16位端口,网络序
    si.sin_addr.s_addr = INADDR_ANY;   // INADDR_ANY表示接收来自任意IP、任意网卡的发给指定端口的数据
    
    1. 端口绑定
    int ret = bind(connfd, (struct sockaddr *)&si, sizeof(si));
    
    1. 接受数据
    recv(connfd,buf,buf_size,0);  
    
    1. 关闭socket
    close(connfd); 
    
    示例代码
    • unicast_send
    #include<stdio.h>
    #include<sys/socket.h>
    #include<netinet/in.h>
    #include<arpa/inet.h>
    #include<assert.h>
    #include<unistd.h>
    #include<stdlib.h>
    #include<errno.h>
    #include<string.h>
    #include<sys/types.h>
     
    #define BUFFER_SIZE 1024
     
    int main(int argc,char **argv) {
        if(4 != argc){
            printf("usage:%s <IP> <Port> <MSG>\n",argv[0]);
        }
        char *ip=argv[1];        //目的主机IP地址
        int port = atoi(argv[2]);//目的主机端口号
    
        // 1. 打开socket
        int connfd= socket(PF_INET,SOCK_DGRAM,0);
        assert(udpfd >= 0);
    
        // 2. 设置发送地址和端口
        struct sockaddr_in address;     //目的主机地址 
        bzero(&address,sizeof(address));    
        address.sin_family = AF_INET;
        address.sin_port = htons(port);
        inet_pton(AF_INET,ip,&address.sin_addr);
       
        // 3. 发送数据
        sendto(connfd,argv[3],strlen(argv[3])+1,0,(struct sockaddr *)&address,sizeof(address));
      
        // 4. 关闭socket
        close(connfd); 
        return 0;
    }
    
    • unicast_recv
    #include<stdio.h>
    #include<sys/socket.h>
    #include<netinet/in.h>
    #include<arpa/inet.h>
    #include<assert.h>
    #include<unistd.h>
    #include<stdlib.h>
    #include<errno.h>
    #include<string.h>
    #include<sys/types.h>
     
    #define BUFFER_SIZE 1024
     
    int main(int argc,char **argv) {
     
        if(3 != argc) {
            printf("usage:%s <IP> <Port> \n",argv[0]);
        }
     
        char *ip = argv[1];                 //用户自身的IP
        int port = atoi(argv[2]);          //服务器端口
    
        char buf[BUFFER_SIZE];
    
        // 1. 打开socket
        int connfd = socket(PF_INET,SOCK_DGRAM,0);
        assert(client_fd >= 0);
    
        // 2. 设置接收地址和端口
        struct sockaddr_in address,addr;
        socklen_t len = sizeof(addr);
        bzero(&address,sizeof(address));
        address.sin_family = AF_INET;
        address.sin_port = htons(port);
        inet_pton(AF_INET,ip,&address.sin_addr);
     
        // 3. 端口绑定
        int ret = bind(connfd,(struct sockaddr *)&address,sizeof(address));
        assert(ret != -1);
     
        // 4. 接受数据
        printf("recv data\n");
        ret = recvfrom(connfd,buf,BUFFER_SIZE-1,0,(struct sockaddr *)&addr,&len);
        buf[ret] = '\0';
        printf("\n%s\n",buf);
     
        // 5. 关闭socket
        close(connfd); 
        return 0;
    }
    

    3.4.2 组播/多播

    基本流程
    ①发送者

    与单播发送者一致

    ②接收者
    1. 打开套接字
    int socket(int domain, int type, int protocol);   返回套接字
    
    1. 构建服务器地址结构
    struct sockaddr_in serveraddr;
    bzero(&serveraddr, sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;                        
    serveraddr.sin_addr.s_addr = htonl(INADDR_ANY); //IP        
    serveraddr.sin_port = htons(SERVER_PORT);//端口
    
    1. 绑定地址
    int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
    
    1. 构建组播属性结构
    struct ip_mreqn group;
    inet_pton(AF_INET,GROUP,&group.imr_multiaddr);//设置组播地址
    inet_pton(AF_INET,"0.0.0.0",&group.imr_address);//设置本地地址
    group.imr_ifindex=if_nametoindex("ent0");//设置网卡接口
    
    1. 设置组播权限和属性
    setsockopt(sockfd,IPPROTO_IP,IP_MULTICAST_IF,&group,sizeof(group));//设置组播权限及选项
    
    1. 设置客户端组播地址
    struct sockaddr_in cliaddr;
    bzero(&cliaddr,sizeof(cliaddr));
    cliaddr.sin_family=AF_INET;
    inet_pton(AF_INET,GROUP,&cliaddr.sin_addr.s_addr);
    cliaddr.sin_port=htons(CLIENT_PORT);
    
    1. 发送数据
    sendto(sockfd,buf,strlen(buf),0,(structsockaddr*)&cliaddr, sizeof(cliaddr));//往组播地址发送信息,返回数据大小
    
    1. 关闭套接字
    close(fd);
    
    示例代码
    • multicast_recv.c
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <sys/ioctl.h>
    #include <sys/socket.h>
    #include <net/route.h>
    #include <arpa/inet.h>
    void addroute(int connfd,const char* multi_ip,const char* dev){
        struct rtentry rt;
        memset(&rt,0,sizeof(rt));
     
        struct sockaddr_in* addr = (struct scokaddr_in*)&rt.rt_gateway;
        addr->sin_family = AF_INET;
        addr->sin_addr.s_addr = 0;
     
        addr = (struct scokaddr_in*)&rt.rt_dst;
        addr->sin_family = AF_INET;
        addr->sin_addr.s_addr = inet_addr(multi_ip);
     
        addr = (struct scokaddr_in*)&rt.rt_genmask;
        addr->sin_family = AF_INET;
        addr->sin_addr.s_addr = 0xFFFFFFFF;
        rt.rt_flags = RTF_UP|RTF_HOST;
        rt.rt_dev = dev;
     
        if(ioctl(connfd,SIOCADDRT,&rt)){
            perror("ioctl err");
            close(connfd);
        }
    }
    int main(int argc, char *argv[]) {
        if(5 != argc){
            printf("usage:%s <src_ip> <mulit_ip> <port> <dev>\n",argv[0]);
            return 1;
        }
        int connfd = socket(AF_INET, SOCK_DGRAM, 0);
        if(connfd < 0){
            perror("socket error");
            return 1;
        } 
        // 有些系统可能需要在路由中添加组播地址
        // addroute(sock,argv[2],argv[4]);
        int reuse = 1;
        if(setsockopt(connfd, SOL_SOCKET, SO_REUSEADDR, (char *)&reuse, sizeof(reuse)) < 0){
            perror("setsockopt SO_REUSEADDR error");
            close(connfd);
            return 1;
        } 
     
        struct sockaddr_in addr;
        addr.sin_family = AF_INET;
        addr.sin_addr.s_addr = inet_addr(argv[1]);
        addr.sin_port = htons(atoi(argv[3]));
     
        if(bind(connfd, (struct sockaddr*)&addr, sizeof(addr))){
            perror("bind error");
            close(connfd);
            return 1;
        }
       
        struct ip_mreq group;
        group.imr_multiaddr.s_addr = inet_addr(argv[2]);
        group.imr_interface.s_addr = inet_addr(argv[1]);
        if(setsockopt(connfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *)&group, sizeof(group)) < 0){
            perror("Adding multicast group error");
            close(connfd);
            return 1;
        } 
       
        char buf[BUFSIZ];
        if(read(connfd, buf, sizeof(buf)-1) < 0){
            perror("read error");
            close(connfd);
            return 1;
        } 
        printf("recv:%s\n",buf);
         
        close(connfd);
        return 0;
    }
    

    3.4.3 广播

    基本流程
    ①发送者
    1. 打开socket
    cfd = socket(AF_INET,SOCK_DGRAM,0) :
    
    1. 打开广播
    setsockopt(cfd,SOL_SOCKET,SO_BROADCAST,&n,sizeof(n));  
    
    No. 参数 含义
    1 n 0表示关闭属性,非0表示打开属性
    1. 设置发送地址和端口
    struct sockaddr_in si;  
    si.sin_family = AF_INET;  
    si.sin_port = htons(端口);  
    si.sin_addr.s_addr = inet_addr("255.255.255.255");
    
    1. 发送数据
    sendto(cfd,buffer,buffer_size,0,(struct sockaddr *)&si,sizeof(si));  
    
    1. 关闭socket
    close(cfd); 
    
    ②接收者

    与单播发送者一致

    示例代码
    • boardcast_send.c
    #include <stdio.h>  
    #include <stdlib.h> 
    #include <string.h> 
    #include <unistd.h>  
    #include <arpa/inet.h>  
    #include <sys/socket.h>  
     
    int main(int argc,char* argv[]) {  
        if(4 != argc){
            printf("usage:%s <broadcast_ip> <port> <message>\n",argv[0]);
            return 1;
        }
        int connfd= socket(AF_INET,SOCK_DGRAM, 0);
        if (-1 == connfd) {  
            perror("socket error");  
            return 1;  
        }  
     
        int opt  = 1;
        if(setsockopt(connfd,SOL_SOCKET,SO_BROADCAST,(char*)&opt,sizeof(opt))==-1) {
            perror("setsockopt fail.");
            close(connfd);
            return 1;  
        }
     
        struct sockaddr_in addr;
        addr.sin_family = AF_INET;  
        addr.sin_addr.s_addr = inet_addr(argv[1]);  
        addr.sin_port = htons(atoi(argv[2]));
     
        if (-1 == sendto(connfd, argv[3], strlen(argv[3])+1, 0, (struct sockaddr *)&addr, sizeof(addr))) {  
            perror("sendto error");  
            close(connfd); 
            return 1;  
        }  
         
        printf("send ok!\n");        
        close(connfd); 
        return 0;
    }
    

    相关文章

      网友评论

          本文标题:Linux系统编程10:Socket编程2-接口

          本文链接:https://www.haomeiwen.com/subject/begbhftx.html