一、UDP通信

 

 

 服务端代码:

/*
#include <sys/types.h>
#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);
    - 参数:
        - sockfd : 通信的fd
        - buf : 要发送的数据
        - len : 发送数据的长度
        - flags : 0
        - dest_addr : 通信的另外一端的地址信息
        - addrlen : 地址的内存大小
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                struct sockaddr *src_addr, socklen_t *addrlen);
    - 参数:
        - sockfd : 通信的fd
        - buf : 接收数据的数组
        - len : 数组的大小
        - src_addr:用来保存另一端的地址信息,不需要可指定为NULL
        - addrlen:地址内存大小
*/
/*服务器端*/
#include<arpa/inet.h> 
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
int main(){
    //1.创建用于通信的socket
    int fd = socket(AF_INET, SOCK_DGRAM, 0);
    if(fd==-1){
        perror("socket");
        exit(-1);
    }
    
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(9999);
    addr.sin_addr.s_addr = INADDR_ANY;
    //2.绑定
    int ret = bind(fd, (struct sockaddr *)&addr,sizeof(addr));
    //3.通信
    while(1){
        
        char recbuf[128]={0};
        char ipbuf[16];
        struct sockaddr_in cliaddr;
        int len =sizeof(cliaddr);
        //接收数据
        int num = recvfrom(fd, recbuf,sizeof(recbuf), 0, (struct sockaddr *)&cliaddr, &len);
        if (num==-1){
            perror("recvfrom");
            exit(-1);
        }
        printf("client IP: %s, Port : %d\n",
                inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, ipbuf, sizeof(ipbuf)),
                ntohs(cliaddr.sin_port));
        //输出接收到的数据
        printf("client says:%s\n", recbuf);
        sendto(fd, recbuf, strlen(recbuf)+1, 0, (struct sockaddr *)&cliaddr, sizeof(cliaddr));


    }
    close(fd);





    return 0;
}

客户端代码:

#include<arpa/inet.h> 
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
int main(){
    //1.创建用于通信的socket
    int fd = socket(AF_INET, SOCK_DGRAM, 0);
    if(fd==-1){
        perror("socket");
        exit(-1);
    }


    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(9999);
    //服务器端地址
    inet_pton(AF_INET, "127.0.0.1", &saddr.sin_addr.s_addr);
    int num=0;
    while(1){
        
        char sendbuf[128]={0};
        sprintf(sendbuf,"hello,i am client %d\n", num++);
        sendto(fd, sendbuf, strlen(sendbuf)+1, 0, (struct sockaddr *)&saddr, sizeof(saddr));
        //接收数据
        int num = recvfrom(fd, sendbuf,sizeof(sendbuf), 0, NULL, NULL);
        if (num==-1){
            perror("bind");
            exit(-1);
        }
        //输出接收到的数据
        printf("server says:%s\n", sendbuf);
        sleep(1);

    }
    close(fd);
    return 0;
}

UDP协议不需要多线程多进程或者I/O复用就能实现多客户端和服务器通信,因为是不可靠的。

二、广播

向子网中多台计算机发送消息,并且子网中所有的计算机都可以接收到发送方发送的消息,每个广播消息都包含一个特殊的IP地址,这个IP中子网内主机标志部分的二进制全部为1。

  a.只能在局域网中使用。

  b.客户端需要绑定服务器广播使用的端口,才可以接收到广播消息。

 

 

 

// 设置广播属性的函数
int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t
optlen);
    - sockfd : 文件描述符
    - level : SOL_SOCKET
    - optname : SO_BROADCAST
    - optval : int类型的值,为1表示允许广播
    - optlen : optval的大小

和设置I/O端口复用的函数相同

服务端发送广播代码:

/*
// 设置广播属性的函数
int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t
optlen);
    - sockfd : 文件描述符
    - level : SOL_SOCKET
    - optname : SO_BROADCAST
    - optval : int类型的值,为1表示允许广播
    - optlen : optval的大小
*/
/*服务器端*/
#include<arpa/inet.h> 
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
int main(){
    //1.创建用于通信的socket
    int fd = socket(AF_INET, SOCK_DGRAM, 0);
    if(fd==-1){
        perror("socket");
        exit(-1);
    }
    //2.设置广播属性
    int op=1;
    
    setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &op, sizeof(op));
    
    //3.创建一个广播地址
    struct sockaddr_in cliaddr;
    cliaddr.sin_family = AF_INET;
    cliaddr.sin_port = htons(9999);
    inet_pton(AF_INET, "192.168.37.255",&cliaddr.sin_addr.s_addr);
    //4.通信
    int num=0;
    while(1){
        char sendbuf[128]={0};
        sprintf(sendbuf,"hello client %d\n" ,num++);
       
        sendto(fd, sendbuf, strlen(sendbuf)+1, 0, (struct sockaddr *)&cliaddr, sizeof(cliaddr));
        printf("广播的数据: %s\n", sendbuf);
        sleep(1);

    }
    close(fd);





    return 0;
}

客户端接收广播代码:

#include<arpa/inet.h> 
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
int main(){
    //1.创建用于通信的socket
    int fd = socket(AF_INET, SOCK_DGRAM, 0);
    if(fd==-1){
        perror("socket");
        exit(-1);
    }

    //2.客户端绑定本地IP和端口
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(9999);
    addr.sin_addr.s_addr = INADDR_ANY;
    //绑定
    int ret = bind(fd, (struct sockaddr *)&addr, sizeof(addr));
    if (ret==-1){
            perror("bind");
            exit(-1);
    }
    
    while(1){
        char recdbuf[128]={0};
        //3.接收数据
        int num = recvfrom(fd, recdbuf,sizeof(recdbuf), 0, NULL, NULL);
        if (num==-1){
            perror("bind");
            exit(-1);
        }
        //输出接收到的数据
        printf("server says:%s\n", recdbuf);
    }
    close(fd);
    return 0;
}

三、组播(多播)

  

单播地址标识单个 IP 接口,广播地址标识某个子网的所有 IP 接口,多播地址标识一组 IP 接口。单播和广播是寻址方案的两个极端(要么单个要么全部),多播则意在两者之间提供一种折中方案。多播数据报只应该由对它感兴趣的接口接收,也就是说由运行相应多播会话应用系统的主机上的接口接收。另外,广播一般局限于局域网内使用,而多播则既可以用于局域网,也可以跨广域网使用。

  a.组播既可以用于局域网,也可以用于广域网
  b.客户端需要加入多播组,才能接收到多播的数据

IP 多播通信必须依赖于 IP 多播地址,在 IPv4 中它的范围从 224.0.0.0 到 239.255.255.255 ,并被划分为局部链接多播地址、预留多播地址和管理权限多播地址三类:

 

 

 组播服务器端:

/*
// 设置广播属性的函数
int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t
optlen);
    - sockfd : 文件描述符
    - level : SOL_SOCKET
    - optname : SO_BROADCAST
    - optval : int类型的值,为1表示允许广播
    - optlen : optval的大小
*/
/*服务器端*/
#include<arpa/inet.h> 
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
int main(){
    //1.创建用于通信的socket
    int fd = socket(AF_INET, SOCK_DGRAM, 0);
    if(fd==-1){
        perror("socket");
        exit(-1);
    }
    //2.设置多播属性
    struct in_addr imr_multiaddr;
    //初始化多播地址
    inet_pton(AF_INET, "239.0.0.10", &imr_multiaddr.s_addr);
    setsockopt(fd, IPPROTO_IP, IP_MULTICAST_IF, &imr_multiaddr, sizeof(imr_multiaddr));
    
    //3.初始化客户端信息
    struct sockaddr_in cliaddr;
    cliaddr.sin_family = AF_INET;
    cliaddr.sin_port = htons(9999);
    inet_pton(AF_INET, "239.0.0.10",&cliaddr.sin_addr.s_addr);
    //4.通信
    int num=0;
    while(1){
        char sendbuf[128]={0};
        sprintf(sendbuf,"hello client %d\n" ,num++);
       
        sendto(fd, sendbuf, strlen(sendbuf)+1, 0, (struct sockaddr *)&cliaddr, sizeof(cliaddr));
        printf("组播的数据: %s\n", sendbuf);
        sleep(1);

    }
    close(fd);

    return 0;
}

组播客户端:

#include<arpa/inet.h> 
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
int main(){
    //1.创建用于通信的socket
    int fd = socket(AF_INET, SOCK_DGRAM, 0);
    if(fd==-1){
        perror("socket");
        exit(-1);
    }

    //2.客户端绑定本地IP和端口
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(9999);
    addr.sin_addr.s_addr = INADDR_ANY;
    //绑定
    int ret = bind(fd, (struct sockaddr *)&addr, sizeof(addr));
    if (ret==-1){
            perror("bind");
            exit(-1);
    }
    //加入到多播组
    struct ip_mreq op;
    inet_pton(AF_INET,"239.0.0.10", &op.imr_multiaddr.s_addr);
    op.imr_interface.s_addr = INADDR_ANY;
    setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP,&op,sizeof(op));
    
    while(1){
        char recdbuf[128]={0};
        //3.接收数据
        int num = recvfrom(fd, recdbuf,sizeof(recdbuf), 0, NULL, NULL);
        if (num==-1){
            perror("bind");
            exit(-1);
        }
        //输出接收到的数据
        printf("server says:%s\n", recdbuf);
    }
    close(fd);
    return 0;
}

四、本地套接字

  作用:本地进程间通信

    有关系没关系通信都能实现,实现流程和网络套接字类似,一般采用TCP的通信流程。

// 本地套接字通信的流程 - tcp
// 服务器端
1. 创建监听的套接字
    int lfd = socket(AF_UNIX/AF_LOCAL, SOCK_STREAM, 0);
2. 监听的套接字绑定本地的套接字文件 -> server端
    struct sockaddr_un addr;
// 绑定成功之后,指定的sun_path中的套接字文件会自动生成。
    bind(lfd, addr, len);
3. 监听
    listen(lfd, 100);
4. 等待并接受连接请求
   struct sockaddr_un cliaddr;
  int cfd = accept(lfd, &cliaddr, len);
5. 通信
    接收数据:read/recv
    发送数据:write/send
6. 关闭连接
    close();

// 客户端的流程
1. 创建通信的套接字
    int fd = socket(AF_UNIX/AF_LOCAL, SOCK_STREAM, 0);
2. 监听的套接字绑定本地的IP 端口
    struct sockaddr_un addr;
// 绑定成功之后,指定的sun_path中的套接字文件会自动生成。
    bind(lfd, addr, len);
3. 连接服务器
    struct sockaddr_un serveraddr;
    connect(fd, &serveraddr, sizeof(serveraddr));
4. 通信
    接收数据:read/recv
    发送数据:write/send
5. 关闭连接
    close();

以下是完整代码实现:

  注意在实现本地套接字通信时,要删除之前存在的同名套接字文件,保证正常进行连接,使用unlink()函数即可

服务器端:

#include<arpa/inet.h> 
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<sys/un.h>//本地套接字头文件

int main(){
    unlink("server.sock");
    //1.创建套接字
    int lfd = socket(AF_LOCAL, SOCK_STREAM,0);
    if(lfd==-1){
        perror("socket");
        exit(-1);
    }
    struct sockaddr_un addr;
    addr.sun_family = AF_LOCAL;
    strcpy(addr.sun_path, "server.sock");
    
    //2.绑定本地套接字
    int ret =bind(lfd,(struct sockaddr * )&addr,sizeof(addr));
    if(ret==-1){
        perror("bind");
        exit(-1);
    }
    //3.监听
    ret = listen(lfd, 100);
    if(ret==-1){
        perror("bind");
        exit(-1);
    }
    //4.等待客户端连接
    struct sockaddr_un cliaddr;
    int len = sizeof(cliaddr);
    int cfd = accept(lfd, (struct sockaddr*)&cliaddr, &len);
    if(cfd==-1){
        perror("accept");
        exit(-1);
    }
    printf("client socket filename : %s\n", cliaddr.sun_path);

    //5.通信
    while(1){
        char buf[128]={0};
        int len = recv(cfd, buf, sizeof(buf),0);
        if(len==-1){
            perror("accept");
            exit(-1);
        }else if(len==0){
            printf("客户端断开连接\n");
            break;
        }else if(len>0){
            printf("client say:%s\n",buf);
            send(cfd,buf,sizeof(buf),0);
        }
    }
    close(cfd);
    close(lfd);

    return 0;
}

客户端:

#include<arpa/inet.h> 
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<sys/un.h>
int main(){
    unlink("client.sock");
    //1.创建套接字
    int cfd = socket(AF_LOCAL, SOCK_STREAM,0);
    if(cfd==-1){
        perror("socket");
        exit(-1);
    }
    struct sockaddr_un addr;
    addr.sun_family = AF_LOCAL;
    strcpy(addr.sun_path, "client.sock");
    
    //2.绑定本地套接字
    int ret =bind(cfd,(struct sockaddr * )&addr,sizeof(addr));
    if(ret==-1){
        perror("bind");
        exit(-1);
    }
    //3.主动连接服务器
    struct sockaddr_un seraddr;
    seraddr.sun_family = AF_LOCAL;
    
    strcpy(seraddr.sun_path,"server.sock");
    ret =connect(cfd, (struct sockaddr*)&seraddr,sizeof(seraddr));
    if(ret==-1){
        perror("connect");
        exit(-1);
    }
    //4.通信
    int num=0;
    while(1){
        char buf[128];
        sprintf(buf, "hello i am client %d\n", num++);
        send(cfd, buf, sizeof(buf),0);
        printf( "client say: %s\n", buf);
        int len = recv(cfd, buf, sizeof(buf),0);
        if(len==-1){
            perror("recv");
            exit(-1);
        }else if(len==0){
            printf("服务端断开连接\n");
            break;
        }else if(len>0){
            printf("server say:%s\n",buf);
        }
        sleep(1);
    }
    close(cfd);
    return 0;
}