网络编程 socket

计算机网络基础

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

socket介绍

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

#include<stdio.h>
#include<arpa/inet.h>
/*
    #include <arpa/inet.h>
        // 转换端口
            uint16_t htons(uint16_t hostshort); // 主机字节序 - 网络字节序
            uint16_t ntohs(uint16_t netshort); // 主机字节序 - 网络字节序
        // 转IP
            uint32_t htonl(uint32_t hostlong); // 主机字节序 - 网络字节序
            uint32_t ntohl(uint32_t netlong); // 主机字节序 - 网络字节序
*/
int main(){
    //转换端口
    unsigned short a=0x0102;
    unsigned short b=htons(a);
    printf("a:%x b:%x\n",a,b);
    //转换ip
    char buf[4]={192,168,1,100};
    int num=*(int *)buf;
    int sum=htonl(num);
    unsigned char *p=(char *)&sum;
    printf("%d %d %d %d\n",*p,*(p+1),*(p+2),*(p+3));
    return 0;
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

#include <arpa/inet.h>
#include<stdio.h>
#include<string.h>
/*
    in_addr_t inet_addr(const char *cp);
    int inet_aton(const char *cp, struct in_addr *inp);
    char *inet_ntoa(struct in_addr in);

    include <arpa/inet.h>
      int inet_pton(int af, const char *src, void *dst);// p:点分十进制的IP字符串,n:表示network,网络字节序的整数
        af:地址族: AF_INET AF_INET6
        src:需要转换的点分十进制的IP字符串
        dst:转换后的结果保存在这个里面传出参数
        
    const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);// 将网络字节序的整数,转换成点分十进制的IP地址字符串
        af:地址族: AF_INET AF_INET6
        src: 要转换的ip的整数的地址
        dst: 转换成IP地址字符串保存的地方
        size:第三个参数的大小(数组的大小)
        返回值:返回转换后的数据的地址(字符串),和 dst 是一样的

*/
int main(){
    char ip[]="192.168.10.1";
    printf("%d %d %s\n",sizeof(ip),strlen(ip),ip);
    //点分十进制转换为网络字节序整数
    unsigned int num=0;
    inet_pton(AF_INET,ip,&num);
    unsigned char *p=(unsigned char*)&num;
    printf("%d %d %d %d\n",*p,*(p+1),*(p+2),*(p+3));
    //将网络字节序转换为点分十进制
    char ip1[16]="";
    const char *a=inet_ntop(AF_INET,&num,ip1,16);
    printf("a%s\n",a);
    printf("ip1%s\n",ip1);
    return 0;

}

TCP通信流程

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

socker通信示例 server

/*
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <arpa/inet.h> // 包含了这个头文件,上面两个就可以省略

        int socket(int domain, int type, int protocol);创建一个套接字
            - domain: 协议族  AF_INET : ipv4 \ AF_INET6 : ipv6  \ AF_UNIX, AF_LOCAL : 本地套接字通信(进程间通信)
            - type: 通信过程中使用的协议类型   SOCK_STREAM : 流式协议   \  SOCK_DGRAM : 报式协议
            - protocol : 具体的一个协议。一般写0  SOCK_STREAM : 流式协议默认使用 TCP  \   SOCK_DGRAM : 报式协议默认使用 UDP
            - 返回值: 失败:-1  成功:返回文件描述符,操作的就是内核缓冲区。
            
        int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); // 绑定,将fd 和本地的IP + 端口进行绑定(socket命名)
            - sockfd : 通过socket函数得到的文件描述符
            - addr : 需要绑定的socket地址,这个地址封装了ip和端口号的信息
            - addrlen : 第二个参数结构体占的内存大小

        int listen(int sockfd, int backlog); // /proc/sys/net/core/somaxconn  监听这个socket上的连接
            - sockfd : 通过socket()函数得到的文件描述符
            - backlog : 未连接的和已经连接的和的最大值, 5

        int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);接收客户端连接,默认是一个阻塞的函数,阻塞等待客户端连接
            - sockfd : 用于监听的文件描述符
            - addr : 传出参数,记录了连接成功后客户端的地址信息(ip,port)
            - addrlen : 指定第二个参数的对应的内存大小
            - 返回值:成功 :用于通信的文件描述符    失败-1

        int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); 客户端连接服务器
            - sockfd : 用于通信的文件描述符
            - addr : 客户端要连接的服务器的地址信息
            - addrlen : 第二个参数的内存大小
            - 返回值:成功 0, 失败 -1

        ssize_t write(int fd, const void *buf, size_t count); // 写数据
        ssize_t read(int fd, void *buf, size_t count); // 读数据
    
*/

#include <arpa/inet.h> // 包含了这个头文件,上面两个就可以省略
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
int main(){
    //创建监听的套接字
    int  listenFd=socket(AF_INET,SOCK_STREAM,0);
    if(listenFd==-1){
        perror("socket");
        exit(0);
    }
    //绑定
    struct sockaddr_in saddr;
    saddr.sin_family=AF_INET;
    inet_pton(AF_INET,"172.19.2.247",&saddr.sin_addr.s_addr);
    saddr.sin_port=htons(9999);
    int ret=bind(listenFd,(struct sockaddr *)&saddr,sizeof(saddr));
        if(ret==-1){
        perror("bind");
        exit(0);
    }
    //监听
    ret=listen(listenFd,8);
            if(ret==-1){
        perror("listen");
        exit(0);
    }
    //接受客户端连接
    struct sockaddr_in clientaddr;
    socklen_t len=sizeof(clientaddr);
    int cfd=accept(listenFd,(struct sockaddr *)&clientaddr,&len);
            if(cfd==-1){
        perror("bind");
        exit(0);
    }
    //输出客户端的信息
    char clientIP[16];
    inet_ntop(AF_INET,&clientaddr.sin_addr.s_addr,clientIP,sizeof(clientIP));
    unsigned short clientPOrt=ntohs(clientaddr.sin_port);
    printf("ip %s port %d \n",clientIP,clientPOrt);

    //获取客服端的信息
    char recivebuf[1024]={0};
    while (1)
    {
        ssize_t len1=read(cfd,recivebuf,sizeof(recivebuf));
        if(len1==-1){
            perror("read");
            exit(-1);
        }else if(len1>0){
            printf("收到%s\n",recivebuf);
        }else if(len1==0){
            printf("客户端断开连接");
            break;
        }
        //发送信息
        char * s="我说话了我是服务器";
        write(cfd,s,strlen(s));
    }
    close(listenFd);
    close(cfd);
    return 0;
}

socker通信示例 client

#include <arpa/inet.h> 
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
    /*
    // 客户端
1. 创建一个用于通信的套接字(fd)
2. 连接服务器,需要指定连接的服务器的 IP 和 端口
3. 连接成功了,客户端可以直接和服务器通信
- 接收数据
- 发送数据
4. 通信结束,断开连接
*/
int main(){
    //创建套接字
    int cfd=socket(AF_INET,SOCK_STREAM,0);
    if (cfd==-1){
        perror("socket");
        exit(-1);
    }
    //连接服务器
    struct sockaddr_in ser;
    ser.sin_family=AF_INET;
    const char src="172.19.2.247";
    inet_pton(AF_INET,"172.19.2.247",&ser.sin_addr.s_addr);
    ser.sin_port=htons(9999);
    socklen_t len=sizeof(ser);
    int ret=connect(cfd,(struct sockaddr *)&ser,len);
        if (ret==-1){
        perror("connet");
        exit(-1);
    }
    char buf[1024]={0};
    while (1)
    {
        //写数据
        char *s="我告诉你a我是客户";
        ret=write(cfd,s,strlen(s));
        if (ret==-1){
            perror("write");
            exit(-1);
        }
        //读数据
        ssize_t len1=read(cfd,buf,sizeof(buf));
        if(len1==-1){
            perror("read");
            exit(-1);
        }else if(len1>0){
            printf("收到%s\n",buf);
        }else if(len1==0){
                printf("服务器丢失");

        }
        sleep(1);
    }
    close(cfd);
    return 0;
}

TCP三次握手

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

TCP滑动窗口

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
mss: MaximumSegment Size 一次发送的最大数据量
win: 滑动窗口
1.客户端向服务器发起连接,客户单的滑动窗口是4096,一次发送的最大数据量是1460
2.服务器接收连接情况,告诉客户端服务器的窗口大小是6144,一次发送的最大数据量是1024
3.第三次握手
4-9. 客户端连续给服务器发送了6k的数据,每次发送1k
10.,服务器告诉客户端:发送的6k数据以及接收到,存储在缓冲区中,缓冲区数据已经处理了2k,窗口大小是2k
11.服务器告诉客户端:发送的6k数据以及接收到,存储在缓冲区中,缓冲区数据已经处理了4k,窗口大小是4k
12.客户端给服务器发送了1k的数据
13.客户端主动请求和服务器断开连接并且给服务器发送1k数据
14.服务器回复ACK 8194 同意断开连接并且接收到了发的2k数据 滑动窗口大小2k
15-16. 通知客户端滑动窗口的大小
17. 第三次挥手 服务器端给客服端发送FIN 请求断开连接
18.第四次回收 客户端同意断开连接

TCP四次挥手

四次挥手发生在断开连接的时候,在程序中当调用了close()会使用TCP协议进行四次挥手客户端和服务器端都可以主动发起断开连接,
谁先调用close)谁就是发起。因为在TCP连接的时候,采用三次握手建立的的连接是双向的,在断开的时候需要双向断开
在这里插入图片描述

TCP通信并发

要实现TCP通信服务器处理并发的任务,使用多进程或线程来解决

多进程

思路:
1.一个父进程、多个子进程
2.父进程负责等待并接受客户端的连接
3.子进程完成通信 接受一个客户端连接就创建一个子进程通讯
服务器端

#define _POSIX_SOURCE
#include <arpa/inet.h> // 包含了这个头文件,上面两个就可以省略
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
#include<signal.h>
#include<wait.h>
#include<errno.h>>
void *recycleChild(void *arg){
    while (1)
    {
        int ret=waitpid(-1,NULL,WNOHANG);
        if(ret==-1){
            //所有的子进程都被回收了
            break;
        }else if(ret==0){
            //还有子进程没有结束
            break;
        }else if (ret>0)
        {
            //回收到了子进程
            printf("子进程%d 被回收了",ret);
        }
        
    }
    
}
int main(){
    //注册信号捕捉
    struct sigaction act;
    act.sa_flags=0;
    sigemptyset(&act.sa_mask);
    act.sa_handler=recycleChild;
    sigaction(SIGCHLD,&act,NULL);

    //创建监听的套接字
    int  listenFd=socket(AF_INET,SOCK_STREAM,0);
    if(listenFd==-1){
        perror("socket");
        exit(0);
    }
    //绑定
    struct sockaddr_in saddr;
    saddr.sin_family=AF_INET;
    inet_pton(AF_INET,"172.19.2.247",&saddr.sin_addr.s_addr);
    saddr.sin_port=htons(9999);
    int ret=bind(listenFd,(struct sockaddr *)&saddr,sizeof(saddr));
        if(ret==-1){
        perror("bind");
        exit(0);
    }
    //监听
    ret=listen(listenFd,8);
            if(ret==-1){
        perror("listen");
        exit(0);
    }
    int cfd;
    //循环等待客户端连接
    while (1)
    {
        //接受客户端连接
        struct sockaddr_in clientaddr;
        socklen_t len=sizeof(clientaddr);
        cfd=accept(listenFd,(struct sockaddr *)&clientaddr,&len);
        if(cfd==-1){
        	//信号量回收子进程时会导致软中断cfd等于-1对于这个情况我们要单独处理不让父进程退出
            if(errno==EINTR){
                continue;
            }
            perror("accept");
            exit(-1);
        }
        //每一个连接进来创建一个子进程通信
        pid_t pid=fork();
        if(pid==0){
            //获取客户端的信息
            char clientIP[16];
            inet_ntop(AF_INET,&clientaddr.sin_addr.s_addr,clientIP,sizeof(clientIP));
            unsigned short clientPOrt=ntohs(clientaddr.sin_port);
            printf("ip %s port %d \n",clientIP,clientPOrt);
            //获取客服端的信息
            char recivebuf[1024]={0};
            while (1)
            {
                ssize_t len1=read(cfd,recivebuf,sizeof(recivebuf));
                if(len1==-1){
                    perror("read");
                    exit(-1);
                }else if(len1>0){
                    printf("收到%s\n",recivebuf);
                }else if(len1==0){
                    printf("客户端断开连接");
                    break;
                }
                write(cfd,recivebuf,strlen(recivebuf));
            }
            close(cfd);
            exit(0);
        }
    //输出客户端的信息
    }
    close(listenFd);
    
    return 0;
}

客户端

#include <arpa/inet.h> 
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
    /*
    // 客户端
1. 创建一个用于通信的套接字(fd)
2. 连接服务器,需要指定连接的服务器的 IP 和 端口
3. 连接成功了,客户端可以直接和服务器通信
- 接收数据
- 发送数据
4. 通信结束,断开连接
*/
int main(){
    //创建套接字
    int cfd=socket(AF_INET,SOCK_STREAM,0);
    if (cfd==-1){
        perror("socket");
        exit(-1);
    }
    //连接服务器
    struct sockaddr_in ser;
    ser.sin_family=AF_INET;
    const char src="172.19.2.247";
    inet_pton(AF_INET,"172.19.2.247",&ser.sin_addr.s_addr);
    ser.sin_port=htons(9999);
    socklen_t len=sizeof(ser);
    int ret=connect(cfd,(struct sockaddr *)&ser,len);
        if (ret==-1){
        perror("connet");
        exit(-1);
    }
    char buf[1024]={0};
    int i=0;
    while (1)
    {
        //写数据
        sprintf(buf,"这是第%d个\n",i);
        int ret=write(cfd,buf,strlen(buf));
        if (ret==-1){
            perror("write");
            exit(-1);
        }
        //读数据
        ssize_t len1=read(cfd,buf,sizeof(buf));
        if(len1==-1){
            perror("read");
            exit(-1);
        }else if(len1>0){
            printf("收到%s\n",buf);
        }else if(len1==0){
                printf("服务器丢失");
                 break;
        }
        sleep(1);
        i++;
    }
    close(cfd);
    return 0;
}

多线程

服务端

#define _POSIX_SOURCE
#include <arpa/inet.h> // 包含了这个头文件,上面两个就可以省略
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
#include<signal.h>
#include<wait.h>
#include<errno.h>
#include <pthread.h>
struct Message{
    pthread_t tid;
    int cfd;
    struct sockaddr_in caddr;
};
struct Message Messages[10];
void *work(void * arg){
    //子线程和客户端通信
    struct Message *info=(struct Message*)arg;
    char  buf[1024]={0};

    while (1)
    {
        int ret=read(info->cfd,buf,sizeof(buf));
        if (ret==-1){
            perror("read");
            exit(-1);
        }else if(ret>0){
            printf("收到了 %s\n",buf);
        }else{
            printf("客户端断开连接 ");
            break;
        }
        write(info->cfd,buf,strlen(buf));
    }
    close(info->cfd);
}

int main(){
    //创建套接字
    int lfd=socket(AF_INET,SOCK_STREAM,0);
    if (lfd==-1){
        perror("socket");
        exit(-1);
    }
    //为套接字绑定ip和端口
    struct sockaddr_in addr;
    addr.sin_family=AF_INET;
    int ret=inet_pton(AF_INET,"127.0.0.1",&addr.sin_addr);
    if (ret==-1){
        perror("inet_pton");
        exit(-1);
    }
    addr.sin_port=htons(9090);
    ret=bind(lfd,(struct sockaddr *)&addr,sizeof(addr));
    if (ret==-1){
        perror("bind");
        exit(-1);

    } 
    //设置监听
    ret=listen(lfd,10);
    if (ret==-1){
        perror("bind");
        exit(-1);

    }
    //初始化数据
    int max=sizeof(Messages)/sizeof(Messages[0]);
    for(int i=0;i<max;i++){
        bzero(&Messages[i],sizeof(Messages[i]));
        Messages[i].cfd=-1;
        Messages[i].tid=-1;
    }
    int cfd;
    while (1)
    {   
        //建立连接
        struct sockaddr_in caddr;
        socklen_t len=sizeof(caddr);
        cfd=accept(lfd,(struct sockaddr*)&caddr,&len);
        if (ret==-1){
            perror("bind");
            exit(-1);

        }

        struct Message *info;
        for (int i=0;i<max;i++){
            if (Messages[i].cfd==-1){
                info=&Messages[i];
                break;
            }
            if (i==max-1){
                sleep(1);
                i=0;
            }
        }
        info->cfd=cfd;
        //创建线程
        memcpy(&info->caddr,&caddr,len);
        pthread_create(&info->tid,NULL,work,info);

        pthread_detach(info->tid);
    }

    close(lfd);
     
}

客户端

#include <arpa/inet.h> 
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
    /*
    // 客户端
1. 创建一个用于通信的套接字(fd)
2. 连接服务器,需要指定连接的服务器的 IP 和 端口
3. 连接成功了,客户端可以直接和服务器通信
- 接收数据
- 发送数据
4. 通信结束,断开连接
*/
int main(){
    //创建套接字
    int cfd=socket(AF_INET,SOCK_STREAM,0);
    if (cfd==-1){
        perror("socket");
        exit(-1);
    }
    //连接服务器
    struct sockaddr_in ser;
    ser.sin_family=AF_INET;
    const char src="127.0.0.1";
    inet_pton(AF_INET,"127.0.0.1",&ser.sin_addr.s_addr);
    ser.sin_port=htons(9090);
    socklen_t len=sizeof(ser);
    int ret=connect(cfd,(struct sockaddr *)&ser,len);
        if (ret==-1){
        perror("connet");
        exit(-1);
    }
    char buf[1024]={0};
    int i=0;
    while (1)
    {
        //写数据
        sprintf(buf,"这是第%d个\n",i);
        int ret=write(cfd,buf,strlen(buf));
        if (ret==-1){
            perror("write");
            exit(-1);
        }
        //读数据
        ssize_t len1=read(cfd,buf,sizeof(buf));
        if(len1==-1){
            perror("read");
            exit(-1);
        }else if(len1>0){
            printf("收到%s\n",buf);
        }else if(len1==0){
                printf("服务器丢失");
                break;

        }
        sleep(1);
        i++;
    }
    close(cfd);
    return 0;
}

TCP状态转换

在这里插入图片描述
在这里插入图片描述
2MSL(Maximum Segment Lifetime)
主动断开连接的一方, 最后进出入一个 TIME_WAIT状态, 这个状态会持续: 2msl
msl: 官方建议: 2分钟, 实际是30s

当 TCP 连接主动关闭方接收到被动关闭方发送的 FIN 和最终的 ACK 后,连接的主动关闭方
必须处于TIME_WAIT 状态并持续 2MSL 时间。

这样就能够让 TCP 连接的主动关闭方在它发送的 ACK 丢失的情况下重新发送最终的 ACK。

主动关闭方重新发送的最终 ACK 并不是因为被动关闭方重传了 ACK(它们并不消耗序列号,
被动关闭方也不会重传),而是因为被动关闭方重传了它的 FIN。事实上,被动关闭方总是
重传 FIN 直到它收到一个最终的 ACK。

半关闭
当 TCP 链接中 A 向 B 发送 FIN 请求关闭,另一端 B 回应 ACK 之后(A 端进入 FIN_WAIT_2
状态),并没有立即发送 FIN 给 A,A 方处于半连接状态(半开关),此时 A 可以接收 B 发
送的数据,但是 A 已经不能再向 B 发送数据。

半关闭

include <sys/socket.h>
int shutdown(int sockfd, int how);
sockfd: 需要关闭的socket的描述符
how: 允许为shutdown操作选择以下几种方式:
SHUT_RD(0): 关闭sockfd上的读功能,此选项将不允许sockfd进行读操作。
该套接字不再接收数据,任何当前在套接字接受缓冲区的数据将被无声的丢弃掉。
SHUT_WR(1): 关闭sockfd的写功能,此选项将不允许sockfd进行写操作。进程不能在对此套接字发
出写操作。
SHUT_RDWR(2):关闭sockfd的读写功能。相当于调用shutdown两次:首先是以SHUT_RD,然后以
SHUT_WR。

使用 close 中止一个连接,但它只是减少描述符的引用计数,并不直接关闭连接,只有当描述符的引用。计数为 0 时才关闭连接。shutdown 不考虑描述符的引用计数,直接关闭描述符。也可选择中止一个方向的连接,只中止读或只中止写。
注意:

  1. 如果有多个进程共享一个套接字,close 每被调用一次,计数减 1 ,直到计数为 0 时,也就是所用进程都调用了 close,套接字将被释放。
  2. 在多进程中如果一个进程调用了 shutdown(sfd, SHUT_RDWR) 后,其它的进程将无法进行通信。但如果一个进程 close(sfd) 将不会影响到其它进程。

在这里插入图片描述

端口复用

端口复用最常用的用途是:
防止服务器重启时之前绑定的端口还未释放
程序突然退出而系统没有释放端口

#include <sys/types.h>
#include <sys/socket.h>
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t
optlen)
	sockfd 需要复用的套接字的文件描述符
	level 级别 SOL_SOCKET 端口复用的级别
	optname 选项的名称 SO_REUSEADDR SO_REUSEPORT
	optval  端口复用的值 1可以复用 0不可以复用
	optlen optval的大小
端口复用,设置的时机是在服务器绑定端口之前
 setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR,&optval,sizeof(optval));

I/O多路复用

I/O 多路复用使得程序能同时监听多个文件描述符,能够提高程序的性能,Linux 下实现 I/O 多路复用的
系统调用主要有 select、poll 和 epoll。

Select

主旨思想:

  1. 首先要构造一个关于文件描述符的列表,将要监听的文件描述符添加到该列表中。
  2. 调用一个系统函数,监听该列表中的文件描述符,直到这些描述符中的一个或者多个进行I/O
    操作时,该函数才返回。
    a.这个函数是阻塞
    b.函数对文件描述符的检测的操作是由内核完成的
  3. 在返回时,它会告诉进程有多少(哪些)描述符要进行I/O操作
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
/*
        / sizeof(fd_set) = 128 1024
    #include <sys/time.h>
    #include <sys/types.h>
    #include <unistd.h>
    #include <sys/select.h>
    int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
        nfds : 委托内核检测的最大文件描述符的值 + 1
        readfds : 要检测的文件描述符的读的集合,委托内核检测哪些文件描述符的读的属性  
                一般检测读操作 对应的是对方发送过来的数据,因为读是被动的接收数据,检测的就是读缓冲区

        writefds : 要检测的文件描述符的写的集合,委托内核检测哪些文件描述符的写的属性
                   委托内核检测写缓冲区是不是还可以写数据(不满的就可以写)
        exceptfds : 检测发生异常的文件描述符的集合
        timeout : 设置的超时时间
        struct timeval {
            long tv_sec;//seconds 
            long tv_usec; //microseconds 
        };
        - NULL : 永久阻塞,直到检测到了文件描述符有变化
        - tv_sec = 0 tv_usec = 0, 不阻塞
        - tv_sec > 0 tv_usec > 0, 阻塞对应的时间
         返回值 : -1 : 失败 >0(n) : 检测的集合中有n个文件描述符发生了变化

    // 将参数文件描述符fd对应的标志位设置为0
    void FD_CLR(int fd, fd_set *set);

    // 判断fd对应的标志位是0还是1, 返回值 : fd对应的标志位的值,0,返回0, 1,返回1
    int FD_ISSET(int fd, fd_set *set);

    // 将参数文件描述符fd 对应的标志位,设置为1
    void FD_SET(int fd, fd_set *set);

    // fd_set一共有1024 bit, 全部初始化为0
    void FD_ZERO(fd_set *set);

*/
    #include <sys/time.h>
    #include <sys/types.h>
    #include <unistd.h>
    #include <sys/select.h>
    #include<arpa/inet.h>
    #include<string.h>
    #include<stdio.h>
    #include <stdlib.h>
int main(){
    //创建套接字
    int lfd=socket(AF_INET,SOCK_STREAM,0);
    if (lfd==-1){
        perror("socket");
        exit(-1);
    }
    int optval=1;

    setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR,&optval,sizeof(optval));
    
    //为套接字绑定ip和端口
    struct sockaddr_in addr;
    addr.sin_family=AF_INET;
    int ret=inet_pton(AF_INET,"127.0.0.1",&addr.sin_addr);
    if (ret==-1){
        perror("inet_pton");
        exit(-1);
    }
    addr.sin_port=htons(9090);
    ret=bind(lfd,(struct sockaddr *)&addr,sizeof(addr));
    if (ret==-1){
        perror("bind");
        exit(-1);

    } 
    //设置监听
    ret=listen(lfd,10);
    if (ret==-1){
        perror("bind");
        exit(-1);

    }
    //创建一个fd_set 保存需要检测的文件描述符集合
    fd_set rdset,tmp;
    FD_ZERO(&rdset);
    FD_SET(lfd,&rdset);
    int maxfd=lfd;
    while (1)
    {   
        tmp=rdset;
        //select 让内核检测文件描述符的数据
        int ret=select(maxfd+1,&tmp,NULL,NULL,NULL);
        if (ret==-1){
            perror("select");
            exit(-1);
        }else  if(ret==0){
            continue;
        }else if(ret>0){
            //检测到了新的连接
            if (FD_ISSET(lfd,&tmp)){
                struct sockaddr_in caddr;
                socklen_t len=sizeof(caddr);
                int cfd=accept(lfd,(struct sockaddr*)&caddr,&len);
                if (ret==-1){
                    perror("bind");
                    exit(-1);

                }
                //将新的文件描述符加入到集合中
                FD_SET(cfd,&rdset);
                maxfd=maxfd>cfd?maxfd:cfd;
            }
            //已经连接的有写数据的
            for(int i=lfd+1;i<=maxfd;i++){
                if(FD_ISSET(i,&tmp)){
                    //读写操作
                    char  buf[1024]={0};
                    int ret=read(i,buf,sizeof(buf));
                    printf("%d\n",ret);
                    if (ret==-1){
                        perror("read");
                        exit(-1);
                    }else if(ret>0){
                        printf("收到了 %s\n",buf);
                        write(i,buf,strlen(buf));
                    }else {
                        printf("客户端断开连接");
                        close(i);
                        FD_CLR(i,&rdset);
                    }
                }
            }
        }
    }    
    close(lfd);
}

Poll

在这里插入图片描述

/*
    #include <poll.h>
struct pollfd {
    int fd; //委托内核检测的文件描述符 
    short events;  委托内核检测文件描述符的什么事件 
    short revents;  文件描述符实际发生的事件 
};
struct pollfd myfd;
myfd.fd = 5;
myfd.events = POLLIN | POLLOUT;

int poll(struct pollfd *fds, nfds_t nfds, int timeout);
    fds : 是一个struct pollfd 结构体数组,这是一个需要检测的文件描述符的集合
    nfds : 这个是第一个参数数组中最后一个有效元素的下标 + 1
    timeout : 阻塞时长  0 : 不阻塞           -1 : 阻塞,当检测到需要检测的文件描述符有变化,解除阻塞            >0 : 阻塞的时长(单位是毫秒)
    返回值:-1 : 失败           >0(n) : 成功,n表示检测到集合中有n个文件描述符发生变化*/
    #include <sys/time.h>
    #include <sys/types.h>
    #include <unistd.h>
    #include <sys/select.h>
    #include<arpa/inet.h>
    #include<string.h>
    #include<stdio.h>
    #include <stdlib.h>
     #include <poll.h>
int main(){
    //创建套接字
    int lfd=socket(AF_INET,SOCK_STREAM,0);
    if (lfd==-1){
        perror("socket");
        exit(-1);
    }
    int optval=1;

    setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR,&optval,sizeof(optval));
    
    //为套接字绑定ip和端口
    struct sockaddr_in addr;
    addr.sin_family=AF_INET;
    int ret=inet_pton(AF_INET,"127.0.0.1",&addr.sin_addr);
    if (ret==-1){
        perror("inet_pton");
        exit(-1);
    }
    addr.sin_port=htons(9090);
    ret=bind(lfd,(struct sockaddr *)&addr,sizeof(addr));
    if (ret==-1){
        perror("bind");
        exit(-1);

    } 
    //设置监听
    ret=listen(lfd,10);
    if (ret==-1){
        perror("bind");
        exit(-1);

    }
    //初始化检测的文件描述符数组
    struct pollfd fds[1024];
    for(int i=0;i<1024;i++){
        fds[i].fd=-1;
        fds[i].events=POLLIN;
    }
    fds[0].fd=lfd;
    int nfds=0;
    while (1)
    {   

        int ret=poll(fds,nfds+1,-1);
        if (ret==-1){
            perror("poll");
            exit(-1);
        }else  if(ret==0){
            continue;
        }else if(ret>0){
            //检测到了新的连接
            if (fds[0].revents&POLLIN){
                struct sockaddr_in caddr;
                socklen_t len=sizeof(caddr);
                int cfd=accept(lfd,(struct sockaddr*)&caddr,&len);
                if (ret==-1){
                    perror("bind");
                    exit(-1);

                }
                //将新的文件描述符加入到集合中
                for(int i=1;i<1024;i++){
                    if(fds[i].fd==-1){
                        fds[i].fd=cfd;
                        fds[i].events=POLLIN;
                        break;
                    }
                    if(i==1023){
                        sleep(1);
                        i=0;
                    }
                }
                //更新最大的文件索引
                nfds=nfds>cfd?nfds:cfd;
            }
            //已经连接的有写数据的
            for(int i=1;i<=nfds;i++){
                if(fds[i].revents&POLLIN){
                    //读写操作
                    char  buf[1024]={0};
                    int ret=read(fds[i].fd,buf,sizeof(buf));
                    if (ret==-1){
                        perror("read");
                        exit(-1);
                    }else if(ret>0){
                        printf("收到了 %s\n",buf);
                        write(fds[i].fd,buf,strlen(buf));
                    }else {
                        printf("客户端断开连接");
                        close(fds[i].fd);
                        fds[i].fd=-1;
                    }
                }
            }
        }
    }    
    close(lfd);
}

Epoll

在这里插入图片描述

/*
    #include <sys/epoll.h>
        // 创建一个新的epoll实例。在内核中创建了一个数据,这个数据中有两个比较重要的数据,
        一个是需要检测的文件描述符的信息(红黑树),还有一个是就绪列表,存放检测到数据发送改变的文件描述符信息(双向链表)。
    int epoll_create(int size);
        size : 目前没有意义了。随便写一个数,必须大于0
        返回值:-1 : 失败           > 0 : 文件描述符,操作epoll实例的
            Epoll 的工作模式:
            LT 模式 (水平触发)
                假设委托内核检测读事件 -> 检测fd的读缓冲区
                读缓冲区有数据 - > epoll检测到了会给用户通知
                    a.用户不读数据,数据一直在缓冲区,epoll 会一直通知
                    b.用户只读了一部分数据,epoll会通知
                    c.缓冲区的数据读完了,不通知
            LT(level - triggered)
                是缺省的工作方式,并且同时支持 block 和 no-block socket。在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的 fd 进行 IO 操
                作。如果你不作任何操作,内核还是会继续通知你的。
            ET 模式(边沿触发)
                假设委托内核检测读事件 -> 检测fd的读缓冲区
                读缓冲区有数据 - > epoll检测到了会给用户通知
                    a.用户不读数据,数据一致在缓冲区中,epoll下次检测的时候就不通知了
                    b.用户只读了一部分数据,epoll不通知
                    c.缓冲区的数据读完了,不通知
            ET(edge - triggered)是高速工作方式,只支持 no-block socket。
                在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,
                并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了。
                但是请注意,如果一直不对这个 fd 作 IO 操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once)。
                ET 模式在很大程度上减少了 epoll 事件被重复触发的次数,因此效率要比 LT 模式高。epoll工作在 ET 模式的时候,
                必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。

        
    int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);// 对epoll实例进行管理:添加文件描述符信息,删除信息,修改信息
        epfd : epoll实例对应的文件描述符
        op : 要进行什么操作 :EPOLL_CTL_ADD: 添加    EPOLL_CTL_MOD: 修改 EPOLL_CTL_DEL: 删除
        fd : 要检测的文件描述符
        event : 检测文件描述符什么事情
        struct epoll_event {
            uint32_t events;  Epoll events 
            epoll_data_t data;  User data variable 
            };
            常见的Epoll检测事件:
            - EPOLLIN
            - EPOLLOUT
            - EPOLLERR
            typedef union epoll_data {
                void *ptr;
                int fd;
                uint32_t u32;
                uint64_t u64;
            } epoll_data_t;


    int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);// 检测函数
        epfd : epoll实例对应的文件描述符
        events : 传出参数,保存了发送了变化的文件描述符的信息
        maxevents : 第二个参数结构体数组的大小
        timeout : 阻塞时间 0 : 不阻塞 -1 : 阻塞,直到检测到fd数据发生变化,解除阻塞 > 0 : 阻塞的时长(毫秒)
        返回值:成功,返回发送变化的文件描述符的个数 > 0    失败 -1

    struct epoll_event {
        uint32_t events; Epoll events
        epoll_data_t data;  User data variable
    };
        常见的Epoll检测事件:
        - EPOLLIN
        - EPOLLOUT
        - EPOLLERR
        - EPOLLET    
*/

#include <sys/epoll.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include<fcntl.h>
#include <sys/select.h>
#include<arpa/inet.h>
#include<string.h>
#include<stdio.h>
#include <stdlib.h>
#include <poll.h>
#include<errno.h>
#include<error.h>
int main(){
    //创建套接字
    int lfd=socket(AF_INET,SOCK_STREAM,0);
    if (lfd==-1){
        perror("socket");
        exit(-1);
    }
    int optval=1;

    setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR,&optval,sizeof(optval));
    
    //为套接字绑定ip和端口
    struct sockaddr_in addr;
    addr.sin_family=AF_INET;
    int ret=inet_pton(AF_INET,"127.0.0.1",&addr.sin_addr);
    if (ret==-1){
        perror("inet_pton");
        exit(-1);
    }
    addr.sin_port=htons(9090);
    ret=bind(lfd,(struct sockaddr *)&addr,sizeof(addr));
    if (ret==-1){
        perror("bind");
        exit(-1);

    } 
    //设置监听
    ret=listen(lfd,10);
    if (ret==-1){
        perror("bind");
        exit(-1);

    }
    //创建一个epoll实例
    int epfd=epoll_create(100);
    //将监听的文件描述符加入到epoll实例当中
    struct epoll_event epev;
    epev.events=EPOLLIN;
    epev.data.fd=lfd;
    epoll_ctl(epfd,EPOLL_CTL_ADD,lfd,&epev);
    struct epoll_event epevs[5];
    while (1)
    {   
        int ret=epoll_wait(epfd,epevs,5,-1);
        if (ret==-1){
            perror("epoll_wait");
            exit(-1);
        }
        for(int i=0;i<ret;i++){
            if (epevs[i].data.fd==lfd){
                //有客户端连接
                struct sockaddr_in caddr;
                socklen_t len=sizeof(caddr);
                int cfd=accept(lfd,(struct sockaddr*)&caddr,&len);
                //设置cfdf非阻塞
                int flag=fcntl(cfd,F_GETFL);
                flag|O_NONBLOCK;
                fcntl(cfd,F_SETFL,flag);
                if (ret==-1){
                
                    perror("bind");
                    exit(-1);

                }
                epev.events=EPOLLIN|EPOLLET;//设置边缘触发
                epev.data.fd=cfd;
                epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&epev);
            }else{
                //循环读取所有数据
                int len=0;
                char buf[5];
                while ((len=read(epevs[i].data.fd,buf,sizeof(buf)))>0)
                {
                    printf("收到了 %s\n",buf);
                    write(epevs[i].data.fd,buf,strlen(buf));
                }
                
                if(len==0){
                    printf("客户端断开连接");
                    epoll_ctl(epfd,EPOLL_CTL_DEL,epevs[i].data.fd,NULL);
                    close(epevs[i].data.fd);
                }
                else if(len==-1){
                    if (errno==EAGAIN){
                        printf("数据结束");
                    }else{
                        perror("read");
                        exit(-1);
                    }

                }

            }
        }    
    }
    close(lfd);
    close(epfd);
}

UDP

在这里插入图片描述

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 : 数组的大小
            flags : 0
            src_addr : 用来保存另外一端的地址信息,不需要可以指定为NULL
            addrlen : 地址的内存大小

*/
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <sys/epoll.h>
    #include <sys/time.h>
    #include <sys/types.h>
    #include <unistd.h>
    #include <sys/select.h>
    #include<arpa/inet.h>
    #include<string.h>
    #include<stdio.h>
    #include <stdlib.h>
    #include <poll.h>
int main(){
    int lfd=socket(AF_INET,SOCK_DGRAM,0);
    if (lfd==-1){
        perror("socket");
        exit(-1);
    }
    //为套接字绑定ip和端口
    struct sockaddr_in addr;
    addr.sin_family=AF_INET;
    int ret=inet_pton(AF_INET,"127.0.0.1",&addr.sin_addr);
    if (ret==-1){
        perror("inet_pton");
        exit(-1);
    }
    addr.sin_port=htons(9090);
    ret=bind(lfd,(struct sockaddr *)&addr,sizeof(addr));
    char buf[1024];
    char ipbuf[16];
    //通信
    while (1)
    {   
        struct sockaddr_in caddr;
        int len=sizeof(caddr);
        int num=recvfrom(lfd,buf,sizeof(buf),0,(struct sockaddr *)&caddr,&len);
        printf("client iP %s port %d\n",inet_ntop(AF_INET,&caddr.sin_addr.s_addr,ipbuf,sizeof(ipbuf)),ntohs(caddr.sin_port));
        printf("client say %s\n",buf);

        sendto(lfd,buf,strlen(buf)+1,0,(struct sockaddr *)&caddr,sizeof(caddr));
    }
    close(lfd);
    
}

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 : 数组的大小
            flags : 0
            src_addr : 用来保存另外一端的地址信息,不需要可以指定为NULL
            addrlen : 地址的内存大小

*/
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <sys/epoll.h>
    #include <sys/time.h>
    #include <sys/types.h>
    #include <unistd.h>
    #include <sys/select.h>
    #include<arpa/inet.h>
    #include<string.h>
    #include<stdio.h>
    #include <stdlib.h>
    #include <poll.h>
int main(){
    int lfd=socket(AF_INET,SOCK_DGRAM,0);
    if (lfd==-1){
        perror("socket");
        exit(-1);
    }
    //为套接字绑定ip和端口
    struct sockaddr_in addr;
    addr.sin_family=AF_INET;
    int ret=inet_pton(AF_INET,"127.0.0.1",&addr.sin_addr);
    if (ret==-1){
        perror("inet_pton");
        exit(-1);
    }
    addr.sin_port=htons(9090);
    ret=bind(lfd,(struct sockaddr *)&addr,sizeof(addr));
    char buf[1024];
    char ipbuf[16];
    //通信
    while (1)
    {   
        struct sockaddr_in caddr;
        int len=sizeof(caddr);
        int num=recvfrom(lfd,buf,sizeof(buf),0,(struct sockaddr *)&caddr,&len);
        printf("client iP %s port %d\n",inet_ntop(AF_INET,&caddr.sin_addr.s_addr,ipbuf,sizeof(ipbuf)),ntohs(caddr.sin_port));
        printf("client say %s\n",buf);

        sendto(lfd,buf,strlen(buf)+1,0,(struct sockaddr *)&caddr,sizeof(caddr));
    }
    close(lfd);
    
}

广播

在这里插入图片描述

客户端

    #include <sys/types.h>
    #include <sys/socket.h>
    #include <sys/epoll.h>
    #include <sys/time.h>
    #include <sys/types.h>
    #include <unistd.h>
    #include <sys/select.h>
    #include<arpa/inet.h>
    #include<string.h>
    #include<stdio.h>
    #include <stdlib.h>
    #include <poll.h>
int main(){
    int lfd=socket(AF_INET,SOCK_DGRAM,0);
    if (lfd==-1){
        perror("socket");
        exit(-1);
    }

    //为套接字绑定ip和端口
    struct sockaddr_in addr;
    addr.sin_family=AF_INET;
    int ret=inet_pton(AF_INET,"172.19.15.255",&addr.sin_addr);
    if (ret==-1){
        perror("inet_pton");
        exit(-1);
    }
    addr.sin_port=htons(9090);
    ret=bind(lfd,(struct sockaddr *)&addr,sizeof(addr));
    if (ret==-1){
        perror("bind");
        exit(-1);
    }
    char getbuf[1024];
    //通信
    while (1)
    {   
        printf("1\n");
        int num=recvfrom(lfd,getbuf,sizeof(getbuf),0,NULL,NULL);
        printf("收到了 %s\n",getbuf);
    
    }
    close(lfd);
    return 0;
}

服务端

/*
/ 设置广播属性的函数
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 <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 : 数组的大小
            flags : 0
            src_addr : 用来保存另外一端的地址信息,不需要可以指定为NULL
            addrlen : 地址的内存大小

*/
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <sys/epoll.h>
    #include <sys/time.h>
    #include <sys/types.h>
    #include <unistd.h>
    #include <sys/select.h>
    #include<arpa/inet.h>
    #include<string.h>
    #include<stdio.h>
    #include <stdlib.h>
    #include <poll.h>
int main(){
    int lfd=socket(AF_INET,SOCK_DGRAM,0);
    if (lfd==-1){
        perror("socket");
        exit(-1);
    }


    //为套接字绑定ip和端口
    struct sockaddr_in addr;//广播的IP
    addr.sin_family=AF_INET;
    int ret=inet_pton(AF_INET,"172.19.15.255",&addr.sin_addr);
    if (ret==-1){
        perror("inet_pton");
        exit(-1);
    }
    addr.sin_port=htons(9090);

    //设置广播属性
    int op=1;
    ret=setsockopt(lfd,SOL_SOCKET,SO_BROADCAST,&op,sizeof(op));
    if (ret==-1){
        perror("inet_pton");
        exit(-1);
    }
    //通信
    char buf[1024];
    int num=0;
    while (1)
    {   
        sprintf(buf,"hello %d \n",num++);
        sendto(lfd,buf,strlen(buf)+1,0,(struct sockaddr *)&addr,sizeof(addr));
        printf("广播 %s",buf);
        sleep(1);
    }
    close(lfd);

    return 0;
}

组播(多播)

在这里插入图片描述
单播地址标识单个 IP 接口,广播地址标识某个子网的所有 IP 接口,多播地址标识一组 IP 接口。
单播和广播是寻址方案的两个极端(要么单个要么全部)

多播则意在两者之间提供一种折中方案。多播数据报只应该由对它感兴趣的接口接收,也就是说由运行相应多播会话应用系统的主机上的接口接收。另外,广播一般局限于局域网内使用,而多播则既可以用于局域网,也可以跨广域网使用。
a.组播既可以用于局域网,也可以用于广域网
b.客户端需要加入多播组,才能接收到多播的数据
在这里插入图片描述

/*
    int setsockopt(int sockfd, int level, int optname,const void *optval,socklen_t optlen);// 
        服务器设置多播的信息,外出接口
            - level : IPPROTO_IP
            - optname : IP_MULTICAST_IF
            - optval : struct in_addr

         客户端加入到多播组:
            - level : IPPROTO_IP
            - optname : IP_ADD_MEMBERSHIP
            - optval : struct ip_mreq
            
        struct ip_mreq
        {
            IP multicast address of group. 
            struct in_addr imr_multiaddr; // 组播的IP地址
            Local IP address of interface. 
            struct in_addr imr_interface; // 本地的IP地址
        };
        typedef uint32_t in_addr_t;
        struct in_addr
        {
        in_addr_t s_addr;
};*/

服务端

/*
    int setsockopt(int sockfd, int level, int optname,const void *optval,socklen_t optlen);// 
        服务器设置多播的信息,外出接口
            - level : IPPROTO_IP
            - optname : IP_MULTICAST_IF
            - optval : struct in_addr

         客户端加入到多播组:
            - level : IPPROTO_IP
            - optname : IP_ADD_MEMBERSHIP
            - optval : struct ip_mreq
            
        struct ip_mreq
        {
            IP multicast address of group. 
            struct in_addr imr_multiaddr; // 组播的IP地址
            Local IP address of interface. 
            struct in_addr imr_interface; // 本地的IP地址
        };
        typedef uint32_t in_addr_t;
        struct in_addr
        {
        in_addr_t s_addr;
};*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
int main(){
    int lfd=socket(AF_INET,SOCK_DGRAM,0);
    if (lfd==-1){
        perror("socket");
        exit(-1);
    }
    //设置多播得地址

    struct  in_addr multiaddr;
    inet_pton(AF_INET,"239.0.0.10",&multiaddr.s_addr);   
    //设置多播属性
    setsockopt(lfd,IPPROTO_IP,IP_MULTICAST_IF,&multiaddr,sizeof(multiaddr));

    //初始化客户端得多播地址
    struct sockaddr_in addr;//广播的IP
    addr.sin_family=AF_INET;
    int ret=inet_pton(AF_INET,"239.0.0.10",&addr.sin_addr);
    if (ret==-1){
        perror("inet_pton");
        exit(-1);
    }
    addr.sin_port=htons(9090);

    //通信
    char buf[1024];
    int num=0;
    while (1)
    {   
        sprintf(buf,"hello %d \n",num++);
        sendto(lfd,buf,strlen(buf)+1,0,(struct sockaddr *)&addr,sizeof(addr));
        printf("组播 %s",buf);
        sleep(1);
    }
    close(lfd);

    return 0;
}

客户端

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/types.h>       
       #include <sys/socket.h>
#include <netinet/in.h>

int main(){
    int lfd=socket(AF_INET,SOCK_DGRAM,0);
    if (lfd==-1){
        perror("socket");
        exit(-1);
    }
    //为套接字绑定ip和端口
    struct sockaddr_in addr;
    addr.sin_family=AF_INET;
    addr.sin_addr.s_addr=INADDR_ANY;
    addr.sin_port=htons(9090);
    int ret=bind(lfd,(struct sockaddr *)&addr,sizeof(addr));
    //加入多播组
    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(lfd,IPPROTO_IP,IP_ADD_MEMBERSHIP,&op,sizeof(op));
    char getbuf[1024];
    //通信
    while (1)
    {   
        printf("1\n");
        int num=recvfrom(lfd,getbuf,sizeof(getbuf),0,NULL,NULL);
        printf("收到了 %s\n",getbuf);
    
    }
    close(lfd);
    return 0;
}

本地套接字

在这里插入图片描述

服务端

/*
// 本地套接字通信的流程 - tcp
// 服务器端
    1. 创建监听的套接字
    int lfd = socket(AF_UNIX/AF_LOCAL, SOCK_STREAM, 0);
    2. 监听的套接字绑定本地的套接字文件 -> server端
    struct sockaddr_un addr;
    // 绑定成功之后,指定的sockaddr_un.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();
*/
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <sys/epoll.h>
    #include <sys/time.h>
    #include <sys/types.h>
    #include <unistd.h>
    #include <sys/select.h>
    #include<arpa/inet.h>
    #include<string.h>
    #include<stdio.h>
    #include <stdlib.h>
    #include <poll.h>
    #include<sys/un.h>
int main(){
    unlink("server.sock");//每次运行前删掉对应的文件

    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");
    bind(lfd,(struct sockaddr *)&addr,sizeof(addr));
    //监听
    listen(lfd,5);
    //建立连接
    struct sockaddr_un cliaddr;
    int len=sizeof(cliaddr);
    int cfd=accept(lfd,(struct sockaddr *)&cliaddr,&len);
    if (cfd<0){
        perror("accept");
        exit(1);
    }
    printf("客户端的文件 %s\n",cliaddr.sun_path);
    //通信
    char buf[1024];
    int num=0;
    while (1)
    {   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("收到了 %s",buf);
            send(cfd,buf,len,0);
        }
        sleep(1);
    }
    close(cfd);

    close(lfd);

    return 0;
}

客户端

    #include <sys/types.h>
    #include <sys/socket.h>
    #include <sys/epoll.h>
    #include <sys/time.h>
    #include <sys/types.h>
    #include <unistd.h>
    #include <sys/select.h>
    #include<arpa/inet.h>
    #include<string.h>
    #include<stdio.h>
    #include <stdlib.h>
    #include<sys/un.h>
    
    #include <poll.h>
int main(){
    unlink("client.sock");//每次运行前删掉对应的文件

    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");
    int ret=bind(cfd,(struct sockaddr *)&addr,sizeof(addr));
    if (ret==-1){
        perror("bind");
        exit(-1);
    }
    //建立连接
    struct sockaddr_un Serveaddr;
    Serveaddr.sun_family=AF_LOCAL;
    strcpy(Serveaddr.sun_path,"server.sock");
    socklen_t len=sizeof(Serveaddr);
     ret=connect(cfd,(struct sockaddr *)&Serveaddr,len);
        if (ret==-1){
        perror("connect");
        exit(-1);
    }
    //通信
    char buf[1024];
    int num=0;
    while (1)
    {   
        sprintf(buf,"数字是%d\n",num);
        send(cfd,buf,strlen(buf)+1,0);
        printf("客户端说%s\n",buf);
        int len=recv(cfd,buf,sizeof(buf),0);
        if (len==-1){
                perror("recv");
                exit(-1);
        }else if(len==0){
                printf("客户端断开连接\n");
                close(cfd);
                break;
        }else if(len>0){
                printf("收到了 %s",buf);
        }
        sleep(1);
        num++;
    }
    close(cfd);

    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值