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

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 失败
  • 说明

如果不需要获取客户端的套接字地址,后两个参数可设置为NULL

 int connfd = accept(listenfd,NULL,NULL);

如果需要获取,则按照如下方式设置。

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);

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);
 
}
  • 说明
    使用下面的代码,是为了避免出现Bind error: Address already in use
int flag = 1;
setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&flag,sizeof(flag));  

启用SO_REUSEADDR选项后,bind()函数将允许地址的立即重用。

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;
}
  • 测试

按照下表设置服务器。

单播接受端

No. Recv IP port
1 unicast_recv 0.0.0.0 8001
2 unicast_recv 127.0.0.1 8001
3 unicast_recv XXX.XXX.XXX.XXX 8001

单播播发送端

unicast_send 0.0.0.0 8001 HelloWorld
unicast_send 127.0.0.1 HelloWorld
unicast_send XXX.XXX.XXX.XXX 8001 HelloWorld

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;
}
  • 测试

按照下表设置服务器。

多播接受端

No. Recv IP Multi port
1 multicast_recv 0.0.0.0 224.0.1.1 8002
2 multicast_recv 127.0.0.1 224.0.1.1 8002
3 multicast_recv XXX.XXX.XXX.XXX 224.0.1.1 8002

单播播发送端

unicast_send 224.0.1.1 8002 HelloWorld

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;
}
  • 测试

按照下表设置服务器。

单播接受端

No. Recv IP port
1 unicast_recv 0.0.0.0 8003
2 unicast_recv 127.0.0.1 8003
3 unicast_recv XXX.XXX.XXX.XXX 8003

多播接受端

No. Recv IP Multi port
1 multicast_recv 0.0.0.0 224.0.1.1 8003
2 multicast_recv 127.0.0.1 224.0.1.1 8003
3 multicast_recv XXX.XXX.XXX.XXX 224.0.1.1 8003

广播发送端

boardcast_send XXX.XXX.XXX.255 8003 HelloWorld
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容