8. 王道_网络编程

1 Berkeley Socket

在这里插入图片描述

2 地址信息设置

struct sockaddr 和 struct sockaddr_in

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

大小端转换

在这里插入图片描述

在这里插入图片描述

  • 大小端的判断
  • 使用gdb
    在这里插入图片描述
    在这里插入图片描述
  • 程序判断
#include <stdio.h>

int main()
{
    int i = 0x12345678; 
    char *p = (char*)&i; // 78  低地址存高位置数--小端 
    printf("p = %x\n",*p);
    return 0;
}

在这里插入图片描述

  • 注意端口号是2字节,16位置,所以一般使用htons
    在这里插入图片描述

f
在这里插入图片描述

在这里插入图片描述


#include <54func.h>


int main()
{
    struct sockaddr_in  addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(1234);
    char ip[] = "192.168.72.128";
    //inet_aton(ip,&addr.sin_addr);
    addr.sin_addr.s_addr = inet_addr(ip);
    printf("ip = %x\n",addr.sin_addr.s_addr);
    return 0;
}

域名和IP地址的对应关系

在这里插入图片描述

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

  • gethostbyname只是用ipv4,断网不能使用
  • 报错,不会设置errno
    在这里插入图片描述
    在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

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

3 TCP通信

鸟瞰TCP通信

在这里插入图片描述

socket

在这里插入图片描述

connect

在这里插入图片描述

bind

在这里插入图片描述

  • bind只能绑定本机IP。

如何排查网络故障

  • 使用netstat -an
    • 在这里插入图片描述
  • 抓包
    • 使用tcpdump命令可以查看包的状态。
      在这里插入图片描述
      在这里插入图片描述
      将tcpdump后的结果打包为文件然后使用法国wireshirk解读
      在这里插入图片描述

listen

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

DDOS攻击

在这里插入图片描述

accept

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

  • read返回值
    • 大于0,实际读到的字节数,并且buf=1024
      • 如果read读到的数据的长度等于buf,返回的就是1024
      • 如果read读到的数据长度小于buf,那就是小于1024的数值。
    • 返回值为0,数据读完(读到文件、管道、socket末尾 —对端关闭)
    • 返回值为-1,表明出现异常
      • errno == EINTR 说明被信号中断 所以需要重启或者退出
      • errno == EAGAIN(EWOULDBLOCK)非阻塞方式读,并且没有数据
    • 其他值 真的出现错误 perror打印 exit

send和recv

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

在这里插入图片描述

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

使用select实现TCP即时聊天

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

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

在这里插入图片描述

TIME_WAIT和setsockopt

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

  • TCP断开连接,主动方会进入TIME_WASIT状态,bind重复绑定时会报错。

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

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

select监听socket支持断开重连

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

双人聊天

在这里插入图片描述

  • 阿珍

#include <54func.h>

int main(int argc,char *argv[])
{
    // ./4_server 10.102.1.35 1234
    ARGS_CHECK(argc,3);
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    struct sockaddr_in serverAddr;
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(atoi(argv[2]));
    serverAddr.sin_addr.s_addr = inet_addr(argv[1]);
    int ret = bind(sockfd,(struct sockaddr*)&serverAddr,sizeof(serverAddr));
    ERROR_CHECK(ret,-1,"bind");
    listen(sockfd,50);
    // sockefd为监听socket
    struct sockaddr_in clientAddr;
    socklen_t socklen = sizeof(clientAddr);
    // socklen 必须赋初值
    // netfd为通信socket
    int netfd  = accept(sockfd,NULL,NULL);

    char buf[4096] = {0};
    fd_set rdset;
    while(1)
    {
        FD_ZERO(&rdset);
        FD_SET(STDIN_FILENO,&rdset);
        FD_SET(netfd,&rdset);
        select(netfd+1,&rdset,NULL,NULL,NULL);
        if(FD_ISSET(STDIN_FILENO,&rdset))
        {
            memset(buf,0,sizeof(buf));
            printf("Me:");
            int ret = read(STDIN_FILENO,buf,sizeof(buf));
            if(ret == 0)
            {
                break;
            }
            printf("\n");
            send(netfd,buf,strlen(buf)-1,0);
        }
        if(FD_ISSET(netfd,&rdset))
        {
            memset(buf,0,sizeof(buf));
            ret  = recv(netfd,buf,sizeof(buf),0);
            // 当对端关闭的之后,netfd一直可读,此时recv读取数据的返回值为0
            if(ret == 0)
            {
                printf("阿强关闭了与你的对话\n");
                break;
            }
            printf("阿强: %s\n",buf);
        }
    }
    close(netfd);
    close(sockfd);

    return 0;
}
  • 阿强
#include <54func.h>

int main(int argc,char *argv[])
{
    // ./4_client 10.102.1.35 1234
    ARGS_CHECK(argc,3);
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    struct sockaddr_in serverAddr;
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(atoi(argv[2]));
    serverAddr.sin_addr.s_addr = inet_addr(argv[1]);
    int ret = connect(sockfd,(struct sockaddr*)&serverAddr,sizeof(serverAddr));

    char buf[4096] = {0};
    fd_set rdset;
    while(1)
    {
        FD_ZERO(&rdset);
        FD_SET(STDIN_FILENO,&rdset);
        FD_SET(sockfd,&rdset);  // 是将sockfd不是将ret
        select(sockfd+1,&rdset,NULL,NULL,NULL);
        if(FD_ISSET(STDIN_FILENO,&rdset))
        {
            printf("Me:");
            memset(buf,0,sizeof(buf));
            ret = read(STDIN_FILENO,buf,sizeof(buf));
            if(ret == 0)
            {
                break;
            }
            printf("\n");
            send(sockfd,buf,strlen(buf)-1,0);  // 这里为什么-1

        }
        else if(FD_ISSET(sockfd,&rdset))
        {
            memset(buf,0,sizeof(buf));
            ret = recv(sockfd,buf,sizeof(buf),0);
            if(ret == 0)
            {
                printf("阿珍关闭了对话\n");
                break;
            }
            printf("阿珍:%s\n",buf);
        }

    }


    close(sockfd);
    return 0;
}
断开重连:客户端终止服务端仍然可以接受新连接
  • accept本质上是一个读操作,读的对象就是分配监听队列中的连接,所以可以使用select监听。这样就可以有新连接到来再做accept操作,这样服务器既可以处理旧连接的收发和新连接的建立

在这里插入图片描述

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

  • 只修改服务器
#include <54func.h>

int main(int argc,char *argv[])
{
    // ./4_server 10.102.1.35 1234
    ARGS_CHECK(argc,3);
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    struct sockaddr_in serverAddr;
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(atoi(argv[2]));
    serverAddr.sin_addr.s_addr = inet_addr(argv[1]);
    int reuse = 1;// 允许重用
    int ret = setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse));
    ret = bind(sockfd,(struct sockaddr*)&serverAddr,sizeof(serverAddr));
    ERROR_CHECK(ret,-1,"bind");
    listen(sockfd,50);
    // sockefd为监听socket
    struct sockaddr_in clientAddr;
    socklen_t socklen = sizeof(clientAddr);
    // socklen 必须赋初值
    // netfd为通信socket


    char buf[4096] = {0};
    fd_set monitorSet; //  监听集合
    fd_set readySet;  // 就绪集合
    FD_ZERO(&monitorSet);
    FD_SET(sockfd,&monitorSet);
    int netfd = -1; //netfd为-1s时阿强不存在
    while(1)
    {

        // 这样实际的目的是监听集合里面的内容不会随着selct变换
        memcpy(&readySet,&monitorSet,sizeof(monitorSet));
        select(20,&readySet,NULL,NULL,NULL);

        if(FD_ISSET(sockfd,&readySet))
        {
            netfd = accept(sockfd,NULL,NULL);
            printf("阿强来了\n");
            //阿强已经来了,我只监听阿强给我发的消息以及我发的消息
            FD_CLR(sockfd,&monitorSet);
            FD_SET(STDIN_FILENO,&monitorSet);
            FD_SET(netfd,&monitorSet);
        }


        if(FD_ISSET(netfd,&readySet))
        {
            memset(buf,0,sizeof(buf));
            ret  = recv(netfd,buf,sizeof(buf),0);
            // 当对端关闭的之后,netfd一直可读,此时recv读取数据的返回值为0
            if(ret == 0)
            {

                FD_SET(sockfd,&monitorSet);
                FD_CLR(STDIN_FILENO,&monitorSet);
                FD_CLR(netfd,&monitorSet);
                close(netfd);
                netfd = -1;
                printf("阿强关闭了与你的对话\n");
                continue;
            }
            printf("阿强: %s\n",buf);
        }

        if(FD_ISSET(STDIN_FILENO,&readySet))
        {
            memset(buf,0,sizeof(buf));
            int ret = read(STDIN_FILENO,buf,sizeof(buf));
            if(ret == 0) // Ctrl+D
            {

                FD_SET(sockfd,&monitorSet);
                FD_CLR(STDIN_FILENO,&monitorSet);
                FD_CLR(netfd,&monitorSet);
                printf("我要断开了\n");
                break;
            }
            send(netfd,buf,strlen(buf)-1,0);
        }
    }
    close(netfd);
    close(sockfd);
    return 0;
}

在这里插入图片描述

聊天室
  • 服务端:建立连接和转发数据

在这里插入图片描述

#include <54func.h>

// 聊天室代码
int main(int argc,char *argv[])
{
    ARGS_CHECK(argc,3);
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    struct sockaddr_in serverAddr;
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(atoi(argv[2]));
    serverAddr.sin_addr.s_addr = inet_addr(argv[1]);

    int reuse = 1;
    int ret = setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse));
    ret = bind(sockfd,(struct sockaddr*)&serverAddr,sizeof(serverAddr));
    ERROR_CHECK(ret,-1,"bind");
    listen(sockfd,50);

    fd_set monitorSet;
    fd_set readySet;
    FD_ZERO(&monitorSet);
    FD_SET(sockfd,&monitorSet);

    // 设计数据结构,存储所有客户端的netfd
    int netfd[1024];
    for(int i = 0;i < 1024;++i)
    {
        netfd[i] = -1;
    }
    int curidx = 0; // 下一次加入的netfd的下标,不用的直接置为-1
    //用于查找的哈希表  netfd--->idx
    int fdToIdx[1024];
    for(int i =0;i<1024;i++)
    {
        fdToIdx[i] = -1;
    }

    char buf[1024];
    while(1)
    {
        memcpy(&readySet,&monitorSet,sizeof(fd_set));
        select(1024,&readySet,NULL,NULL,NULL);

        if(FD_ISSET(sockfd,&readySet))
        {
            // 将得到的通信新描述符放到数组里
            netfd[curidx] = accept(sockfd,NULL,NULL);
            // 根据通信描述符找到找到对应的下标 ,从而在netfd中找到次通信描述符
            fdToIdx[netfd[curidx]] = curidx;
            printf("i = %d, netfd = %d \n",curidx,netfd[curidx]);
            FD_SET(netfd[curidx],&monitorSet);
            ++curidx;
        }
        for(int i =0;i<curidx;i++)
        {
            if(netfd[i] != -1 && FD_ISSET(netfd[i],&readySet)) //netfd[i] 没有断掉,而且以及就绪
            {
                bzero(buf,1024);
                ssize_t sret = recv(netfd[i],buf,sizeof(buf),0);
                if(sret == 0)
                {
                    // 某个客户端断开连接
                    FD_CLR(netfd[i],&monitorSet);
                    close(netfd[i]);
                    fdToIdx[netfd[i]] = -1;
                    netfd[i] = -1;
                    continue;
                }
                for(int j = 0;j<curidx;j++) // 给所有已连接的sockfd发送消息
                {
                    if(netfd[j] != -1 && j!=i) // 该链接没有断开而且不是自己
                    {
                        send(netfd[j],buf,strlen(buf),0);
                        printf("i = %d %d %s \n",j,netfd[j],buf);
                    }
                }
            }
        }


    }
    return 0;
}

在这里插入图片描述

将sockfd放入monitiorfd中
while()
{
	复制monitiorfd到readyfd
	select轮询readyfd
	soclfd---》有新连接到来
	{
		将连接套接字放入monitiorfd中
	}
	一次轮询所有的已连接套接字
	{
		某一一个发送消息
		发送给其他人
		有一个断开了,从monitior中删除
	}
}

在这里插入图片描述


#include <54func.h>

typedef struct Conn_s{
        int isConnected; // 0:未连接  1:已经连接
        int netfd;
        time_t lastActive;
}Conn_t;

// 聊天室代码
int main(int argc,char *argv[])
{
    ARGS_CHECK(argc,3);
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    struct sockaddr_in serverAddr;
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(atoi(argv[2]));
    serverAddr.sin_addr.s_addr = inet_addr(argv[1]);

    int reuse = 1;
    int ret = setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse));
    ret = bind(sockfd,(struct sockaddr*)&serverAddr,sizeof(serverAddr));
    ERROR_CHECK(ret,-1,"bind");
    listen(sockfd,50);

    fd_set monitorSet;
    fd_set readySet;
    FD_ZERO(&monitorSet);
    FD_SET(sockfd,&monitorSet);

    // 设计数据结构,存储所有客户端的netfd
    Conn_t client[1024];
    for(int i = 0;i < 1024;++i)
    {
        client[i].isConnected = 0;
    }
    int curidx = 0; // 下一次加入的netfd的下标,不用的直接置为-1
    //用于查找的哈希表  netfd--->idx
    int fdToIdx[1024];
    for(int i =0;i<1024;i++)
    {
        fdToIdx[i] = -1;
    }

    time_t now;
    char buf[1024];
    while(1)
    {
        memcpy(&readySet,&monitorSet,sizeof(fd_set));
        struct timeval timeout;
        timeout.tv_sec = 1;
        timeout.tv_usec = 0;
    select(1024,&readySet,NULL,NULL,&timeout);
        now = time(NULL);
        printf("now = %s\n",ctime(&now));
        if(FD_ISSET(sockfd,&readySet))
        {
            // 将得到的通信新描述符放到数组里
            client[curidx].netfd = accept(sockfd,NULL,NULL);
            client[curidx].lastActive = time(NULL);
            client[curidx].isConnected = 1;
            // 根据通信描述符找到找到对应的下标 ,从而在netfd中找到次通信描述符
            fdToIdx[client[curidx].netfd] = curidx;
            printf("i = %d, netfd = %d time=%s \n",curidx,client[curidx].netfd,ctime(&client[curidx].lastActive));
            FD_SET(client[curidx].netfd,&monitorSet);
            ++curidx;
        }
        for(int i =0;i<curidx;i++)
        {
            if(client[i].isConnected == 1 && FD_ISSET(client[i].netfd,&readySet)) //netfd[i] 没有断掉,而且以及就绪
            {
                bzero(buf,1024);
                ssize_t sret = recv(client[i].netfd,buf,sizeof(buf),0);
                if(sret == 0)
                {
                    // 某个客户端断开连接
                    FD_CLR(client[i].netfd,&monitorSet);
                    close(client[i].netfd);
                    fdToIdx[client[i].netfd] = -1;
                    client[i].isConnected = 0;
                    continue;
                }
                client[i].lastActive = time(NULL);
                printf("i = %d, netfd = %d time=%s \n",i,client[i].netfd,ctime(&client[i].lastActive));
                for(int j = 0;j<curidx;j++)
                {
                    if(client[j].isConnected != 0 && j!=i) // 该链接没有断开而且不是自 己
                    {
                        send(client[j].netfd,buf,strlen(buf),0);
                        printf("i = %d %d %s \n",j,client[j].netfd,buf);
                    }
                }
            }
        }
        for(int i=0;i<curidx;i++)
        {
            if(client[i].isConnected ==1 && (now-client[i].lastActive)>5)
            {
                printf("netfd = %d  timeout~~~~~~~~~~~\n",client[i].netfd);
                FD_CLR(client[i].netfd,&monitorSet);
                close(client[i].netfd);
                fdToIdx[client[i].netfd] = -1;
                client[i].isConnected = 0;
            }
        }


    }
    return 0;
}

在这里插入图片描述

服务端程序设计思路

在这里插入图片描述

4 UDP通信

鸟瞰UDP通信

在这里插入图片描述

sendto和recvfrom

在这里插入图片描述

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

#include <54func.h>

int main(int argc,char **argv)
{
    ARGS_CHECK(argc,3);
    int sockfd = socket(AF_INET,SOCK_DGRAM,0);
    ERROR_CHECK(sockfd,-1,"sockfd");
    struct sockaddr_in serAddr;
    memset(&serAddr,0,sizeof(serAddr));

    serAddr.sin_family = AF_INET;
    serAddr.sin_addr.s_addr = inet_addr(argv[1]);
    serAddr.sin_port = htons(atoi(argv[2]));

    int ret = bind(sockfd,(struct sockaddr*)&serAddr,sizeof(serAddr));
    ERROR_CHECK(ret,-1,"bind");

    struct sockaddr_in cliAddr;
    memset(&cliAddr,0,sizeof(cliAddr));


    char buf[1024] = {0};
    socklen_t len = sizeof(cliAddr);
    recvfrom(sockfd,buf,sizeof(buf),0,(struct sockaddr*)&cliAddr,&len);
    printf("buf = %s\n",buf);
    recvfrom(sockfd,buf,sizeof(buf),0,(struct sockaddr*)&cliAddr,&len);
    printf("buf = %s\n",buf);
    sendto(sockfd,"hello client",12,0,(struct sockaddr*)&cliAddr,len);

    close(sockfd);

    return 0;
}
#include <54func.h>

int main(int argc,char **argv)
{
    ARGS_CHECK(argc,3);
    int sockfd = socket(AF_INET,SOCK_DGRAM,0);
    ERROR_CHECK(sockfd,-1,"sockfd");
    struct sockaddr_in serAddr;
    memset(&serAddr,0,sizeof(serAddr));
    serAddr.sin_family = AF_INET;
    serAddr.sin_addr.s_addr = inet_addr(argv[1]);
    serAddr.sin_port = htons(atoi(argv[2]));
    char buf[1024] = {0};
    socklen_t len = sizeof(serAddr);
    sendto(sockfd,"hello udp",9,0,(struct sockaddr*)&serAddr,len);
    sendto(sockfd,"hello server",12,0,(struct sockaddr*)&serAddr,len);

    recvfrom(sockfd,buf,sizeof(buf),0,(struct sockaddr*)&serAddr,&len);
    printf("buf = %s\n",buf);
    close(sockfd);

    return 0;
}

在这里插入图片描述

使用UDP的即时聊天

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

  • 当有多个客户端发送消息时,分不清是谁是谁,因为没有像TCP的netfd

  • 使用UDP的即时聊天


#include<54func.h>

int main(int argc,char* argv[]){
    ARGS_CHECK(argc,3);
    int sfd = socket(AF_INET,SOCK_DGRAM,0);
    ERROR_CHECK(sfd,-1,"socket");
    struct sockaddr_in serAddr;
    memset(&serAddr,0,sizeof(serAddr));
    serAddr.sin_family = AF_INET;
    serAddr.sin_addr.s_addr = inet_addr(argv[1]);
    serAddr.sin_port = htons(atoi(argv[2]));
    int ret = bind(sfd,(struct sockaddr*)&serAddr,sizeof(serAddr));
    ERROR_CHECK(ret,-1,"bind");
    char buf[64]={0};
    struct sockaddr_in cliAddr;
    memset(&cliAddr,0,sizeof(cliAddr));
    socklen_t len = sizeof(cliAddr);
    recvfrom(sfd,buf,sizeof(buf),0,(struct sockaddr*)&cliAddr,&len);
    printf("buf = %s\n",buf);
    fd_set rdset;
    while(1)
    {
        FD_ZERO(&rdset);
        FD_SET(STDIN_FILENO,&rdset);
        FD_SET(sfd,&rdset);
        select(10,&rdset,NULL,NULL,NULL);
        if(FD_ISSET(STDIN_FILENO,&rdset))
        {
            memset(buf,0,sizeof(buf));
            ret = read(STDIN_FILENO,buf,sizeof(buf));
            if(ret == 0)
                break;
            sendto(sfd,buf,strlen(buf)-1,0,(struct sockaddr*)&cliAddr,len);
        }
        else if(FD_ISSET(sfd,&rdset))
        {
            memset(buf,0,sizeof(buf));
            ret = recvfrom(sfd,buf,sizeof(buf),0,(struct sockaddr*)&cliAddr,&len);
            //对端断开的时候,newFd一直可读
            //recv读数据的返回值是0
            if(0 == ret)
            {
                printf("byebye\n");
                close(sfd);
                return 0;
            }
            printf("buf=%s\n",buf);
        }
    }
    close(sfd);
    return 0;
}

#include <54func.h>

int main(int argc,char* argv[])
{
    ARGS_CHECK(argc,3);
    int sfd = socket(AF_INET,SOCK_DGRAM,0);
    ERROR_CHECK(sfd,-1,"socket");
    struct sockaddr_in serAddr;
    memset(&serAddr,0,sizeof(serAddr));
    serAddr.sin_family = AF_INET;
    serAddr.sin_addr.s_addr = inet_addr(argv[1]);
    serAddr.sin_port = htons(atoi(argv[2]));
    char buf[64]={0};
    socklen_t len = sizeof(serAddr);
    int ret = sendto(sfd,"hello server",12,0,(struct sockaddr*)&serAddr,len);
    printf("ret=%d\n",ret);
    fd_set rdset;
    while(1){
        FD_ZERO(&rdset);
        FD_SET(STDIN_FILENO,&rdset);
        FD_SET(sfd,&rdset);
        select(sfd+1,&rdset,NULL,NULL,NULL);
        if(FD_ISSET(STDIN_FILENO,&rdset)){
            memset(buf,0,sizeof(buf));
            ssize_t ret = read(STDIN_FILENO,buf,sizeof(buf));
            if(ret == 0)
            {
            	sendto(sfd,buf,0,0,(struct sockaddr*)&serAddr,len);
            	break;
            }
        }
        else if(FD_ISSET(sfd,&rdset)){
            memset(buf,0,sizeof(buf));
            recvfrom(sfd,buf,sizeof(buf),0,(struct sockaddr*)&serAddr,&len);
            printf("buf=%s\n",buf);
        }
    }
    close(sfd);
    return 0;
}
  • 断开连接对方不知道
  • sendto可以发送长度为0的报在这里插入图片描述
  • 当主动方要退出时,按照Ctl+D,被动放收到长度为0的报,断开在这里插入图片描述
  • 但是不能对付意外退出,例如客户端输入Ctrl+C
    • 可以注册该信号,当捕获到之后,发送长度为0的包即可。

在这里插入图片描述
在这里插入图片描述
UNIX 域套接字(stream sockets)和原始套接字(raw sockets)

5 epoll系统调用

在这里插入图片描述

  • select的缺陷
    在这里插入图片描述

epoll的基本原理

在这里插入图片描述

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

使用epoll取代select

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

  • epoll的四个步骤
    • epoll_create
    • epoll_ctl
    • epoll_wait
    • 遍历就绪集合
#include<54func.h>

int main(int argc,char* argv[]){
    ARGS_CHECK(argc,3);
   // 创建监听套接字
    int sfd = socket(AF_INET,SOCK_STREAM,0);
    ERROR_CHECK(sfd,-1,"socket");
    struct sockaddr_in serAddr;
    memset(&serAddr,0,sizeof(serAddr));
    serAddr.sin_family = AF_INET;
    serAddr.sin_addr.s_addr = inet_addr(argv[1]);
    serAddr.sin_port = htons(atoi(argv[2]));
    int ret =0;
    //reuse=1表示允许地址重用
    int reuse = 1;
    ret = setsockopt(sfd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse));

    ERROR_CHECK(ret,-1,"setsockopt");
    ret = bind(sfd,(struct sockaddr*)&serAddr,sizeof(serAddr));
    ERROR_CHECK(ret,-1,"bind");
    ret = listen(sfd,10);
    ERROR_CHECK(ret,-1,"listen");

    //newFd代表的是跟客户端的TCP连接
    int newFd = accept(sfd,NULL,NULL);
    ERROR_CHECK(newFd,-1,"accept");
    char buf[64]={0};

    int epfd = epoll_create(1);
    ERROR_CHECK(epfd,-1,"epoll_create");
    struct epoll_event event,evs[2];
    memset(&event,0,sizeof(event));
    //把关心的描述符和对应的时间填到结构体里
    event.data.fd = STDIN_FILENO;
    event.events = EPOLLIN;
    ret = epoll_ctl(epfd,EPOLL_CTL_ADD,STDIN_FILENO,&event);
    ERROR_CHECK(ret,-1,"epoll_ctl");
    event.data.fd = newFd;
    ret = epoll_ctl(epfd,EPOLL_CTL_ADD,newFd,&event);
    ERROR_CHECK(ret,-1,"epoll_ctl");

    int readyNum=0;
    while(1){
        readyNum = epoll_wait(epfd,evs,2,-1);//不用每次都重置监听集合了
        for(int i=0;i<readyNum;i++){
            if(evs[i].data.fd == STDIN_FILENO){
                memset(buf,0,sizeof(buf));
                read(STDIN_FILENO,buf,sizeof(buf));
                send(newFd,buf,strlen(buf)-1,0);
            }
            else if(evs[i].data.fd == newFd){
                memset(buf,0,sizeof(buf));
                ret = recv(newFd,buf,sizeof(buf),0);
                //对端断开的时候,newFd一直可读
                //recv读数据的返回值是0
                if(0 == ret){
                    printf("byebye\n");
                    close(sfd);
                    close(newFd);
                    return 0;
                }
                printf("buf=%s\n",buf);
            }
        }
    }
    close(newFd);
    close(sfd);
    return 0;
}

重新连接


#include<54func.h>

int main(int argc,char* argv[]){
    ARGS_CHECK(argc,3);
   // 创建监听套接字
    int sfd = socket(AF_INET,SOCK_STREAM,0);
    ERROR_CHECK(sfd,-1,"socket");
    struct sockaddr_in serAddr;
    memset(&serAddr,0,sizeof(serAddr));
    serAddr.sin_family = AF_INET;
    serAddr.sin_addr.s_addr = inet_addr(argv[1]);
    serAddr.sin_port = htons(atoi(argv[2]));
    int ret =0;
    //reuse=1表示允许地址重用
    int reuse = 1;
    ret = setsockopt(sfd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse));

    ERROR_CHECK(ret,-1,"setsockopt");
    ret = bind(sfd,(struct sockaddr*)&serAddr,sizeof(serAddr));
    ERROR_CHECK(ret,-1,"bind");
    ret = listen(sfd,10);
    ERROR_CHECK(ret,-1,"listen");

    //newFd代表的是跟客户端的TCP连接
    int netFd = -1; // 最开始没有阿强连入
    char buf[64]={0};

    int epfd = epoll_create(1);
    ERROR_CHECK(epfd,-1,"epoll_create");
    struct epoll_event event;
    memset(&event,0,sizeof(event));
    //把关心的描述符和对应的时间填到结构体里
    event.data.fd = sfd;
    event.events = EPOLLIN; // 读就绪
    ret = epoll_ctl(epfd,EPOLL_CTL_ADD,sfd,&event); // 最开始j只监控sockfd
    ERROR_CHECK(ret,-1,"epoll_ctl");


    int readyNum=0;
    struct epoll_event  readySet[1024];
    while(1)
    {
        readyNum = epoll_wait(epfd,readySet,2,-1);//不用每次都重置监听集合了
        for(int i=0;i<readyNum;i++)
        {
            if(readySet[i].data.fd == sfd)
            {
                netFd = accept(sfd,NULL,NULL);
                printf("阿强连接上了 netfd = %d\n ",netFd);
                // 不接受t其他阿强了,
                epoll_ctl(epfd,EPOLL_CTL_DEL,sfd,NULL);

                event.events =  EPOLLIN;
                event.data.fd = STDIN_FILENO;
                epoll_ctl(epfd,EPOLL_CTL_ADD,STDIN_FILENO,&event);
                event.events =  EPOLLIN;
                event.data.fd = netFd;
                epoll_ctl(epfd,EPOLL_CTL_ADD,netFd,&event);
            }
            else if(netFd != -1 && readySet[i].data.fd == netFd)
            {
                bzero(buf,sizeof(buf));
                ssize_t sret = recv(netFd,buf,sizeof(buf),0);
                if(sret ==0)
                {
                    printf("阿强关闭了\n");
                    event.events =  EPOLLIN;
                    event.data.fd = sfd;
                    epoll_ctl(epfd,EPOLL_CTL_ADD,sfd,&event);
                    epoll_ctl(epfd,EPOLL_CTL_DEL,netFd,NULL);
                    epoll_ctl(epfd,EPOLL_CTL_DEL,STDIN_FILENO,NULL);
                    close(netFd);
                    netFd = -1;
                    break;
                }
                printf("buf= %s\n",buf);
            }
            else if(netFd != -1 && readySet[i].data.fd == STDIN_FILENO)
            {
                bzero(buf,sizeof(buf));
                ssize_t sret = read(STDIN_FILENO,buf,sizeof(buf));
                if(sret ==0)  // Ctrl+D
                {
                    printf("我要踢走q阿强了\n");
                    event.events =  EPOLLIN;
                    event.data.fd = sfd;
                    epoll_ctl(epfd,EPOLL_CTL_ADD,sfd,&event); //加入监听描述,需要最后的参数。删除描述符不需要最后的参数
                    epoll_ctl(epfd,EPOLL_CTL_DEL,netFd,NULL);
                    epoll_ctl(epfd,EPOLL_CTL_DEL,STDIN_FILENO,NULL);
                    close(netFd);
                    netFd = -1;
                    break;
                }
                send(netFd,buf,strlen(buf),0);

            }
        }
    }
    close(netFd);
    close(sfd);
    return 0;
}

使用epoll关闭长期不发消息的连接


#include<54func.h>


int main(int argc,char* argv[]){
        ARGS_CHECK(argc,3);
        //创建监听套接字
        int sfd = socket(AF_INET,SOCK_STREAM,0);
        ERROR_CHECK(sfd,-1,"socket");
        struct sockaddr_in serAddr;
        memset(&serAddr,0,sizeof(serAddr));
        serAddr.sin_family = AF_INET;
        serAddr.sin_addr.s_addr = inet_addr(argv[1]);
        serAddr.sin_port = htons(atoi(argv[2]));
        int ret =0;
        //reuse=1表示允许地址重用
        int reuse = 1;
        ret =
                setsockopt(sfd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse));
        ERROR_CHECK(ret,-1,"setsockopt");
        ret = bind(sfd,(struct sockaddr*)&serAddr,sizeof(serAddr));
        ERROR_CHECK(ret,-1,"bind");
        ret = listen(sfd,10);
        ERROR_CHECK(ret,-1,"listen");
        //newFd代表的是跟客户端的TCP连接
        int newFd = 0;
        char buf[4]={0};
        int epfd = epoll_create(1);
        ERROR_CHECK(epfd,-1,"epoll_create");
        struct epoll_event event,evs[2];
        memset(&event,0,sizeof(event));
        //把关心的描述符和对应的时间填到结构体里
        event.data.fd = STDIN_FILENO;
        event.events = EPOLLIN;
        ret = epoll_ctl(epfd,EPOLL_CTL_ADD,STDIN_FILENO,&event);
        ERROR_CHECK(ret,-1,"epoll_ctl");
        event.data.fd = sfd;
        ret = epoll_ctl(epfd,EPOLL_CTL_ADD,sfd,&event);
        ERROR_CHECK(ret,-1,"epoll_ctl");
        int readyNum=0;
        time_t lastTime,nowTime;
        lastTime = nowTime = time(NULL);
        int isCliLogin = 0;
        while(1){
                readyNum = epoll_wait(epfd,evs,2,1000);
                printf("readyNum=%d \n",readyNum);
                if(0 == readyNum && 1 == isCliLogin ){
                        nowTime = time(NULL);
                        if(nowTime - lastTime > 5){
                                printf("close\n");
                                close(newFd);
                                isCliLogin = 0;
                        }
                }
                for(int i=0;i<readyNum;i++){
                        lastTime = time(NULL);
                        printf("readyNum=%d fd=%d\n",readyNum,evs[i].data.fd);
                        if(evs[i].data.fd == STDIN_FILENO){
                                memset(buf,0,sizeof(buf));
                                read(STDIN_FILENO,buf,sizeof(buf));
                                send(newFd,buf,strlen(buf)-1,0);
                        }
                        else if(evs[i].data.fd == newFd){
                                memset(buf,0,sizeof(buf));
                                ret = recv(newFd,buf,sizeof(buf),0);
                                //对端断开的时候,newFd一直可读
                                //recv读数据的返回值是0
                                if(0 == ret){
                                        printf("byebye\n");
                                        close(newFd);
                                        isCliLogin = 0;
                                        continue;
                                }
                                printf("buf=%s\n",buf);
                        }
                        else if(evs[i].data.fd == sfd){
                                newFd = accept(sfd,NULL,NULL);
                                ERROR_CHECK(newFd,-1,"accept");
                                printf("newFd=%d\n",newFd);
                                isCliLogin = 1;//表示有客户端登录
                                event.data.fd = newFd;
                                event.events = EPOLLIN;
                                ret = epoll_ctl(epfd,EPOLL_CTL_ADD,newFd,&event);
                                ERROR_CHECK(ret,-1,"epoll_ctl");
                        }
                }
        }
        close(newFd);
        close(sfd);
        return 0;
}

在这里插入图片描述


#include<54func.h>

typedef struct Conn_s{
    int isAlive;
    int netfd;
    time_t lastActive;
}Conn_t;


int main(int argc,char* argv[]){
    ARGS_CHECK(argc,3);
   // 创建监听套接字
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    ERROR_CHECK(sockfd,-1,"socket");
    struct sockaddr_in serAddr;
    memset(&serAddr,0,sizeof(serAddr));
    serAddr.sin_family = AF_INET;
    serAddr.sin_addr.s_addr = inet_addr(argv[1]);
    serAddr.sin_port = htons(atoi(argv[2]));
    int ret =0;
    //reuse=1表示允许地址重用
    int reuse = 1;
    ret = setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse));

    ERROR_CHECK(ret,-1,"setsockopt");
    ret = bind(sockfd,(struct sockaddr*)&serAddr,sizeof(serAddr));
    ERROR_CHECK(ret,-1,"bind");
    ret = listen(sockfd,10);
    ERROR_CHECK(ret,-1,"listen");

    int epfd = epoll_create(1);
    struct epoll_event events;
    events.events = EPOLLIN;
    events.data.fd = sockfd;
    epoll_ctl(epfd,EPOLL_CTL_ADD,sockfd,&events);

    Conn_t conn[1024];
    for(int i = 0;i<1024;i++)
    {
        conn[i].isAlive = 0;
    }

    int fdtoidx[1024]; // fdtoidx[fd] ---> idx conn[idx]
    for(int i = 0;i<1024;i++)
    {
        fdtoidx[i] = -1;
    }

    int curidx = 0;
    char buf[1024];
    time_t now;
    while(1)
    {
        struct epoll_event readySet[1024];
        int readyNum = epoll_wait(epfd,readySet,1024,1000); // 每1秒就绪一次
        now = time(NULL);
        printf("now = %s\n",ctime(&now));
        for(int i =0;i<readyNum;i++)
        {
            if(readySet[i].data.fd == sockfd)
            {
                int netfd = accept(sockfd,NULL,NULL);
                conn[curidx].isAlive = 1;
                conn[curidx].netfd = netfd;
                conn[curidx].lastActive = time(NULL);
                fdtoidx[netfd] = curidx;
                printf("id = %d, netfd = %d\n",curidx,netfd);

                events.events = EPOLLIN;
                events.data.fd = netfd;
                epoll_ctl(epfd,EPOLL_CTL_ADD,netfd,&events);

                curidx++;

            }else
            {
                int netfd = readySet[i].data.fd;
                bzero(buf,sizeof(buf));
                ssize_t sret = recv(netfd,buf,sizeof(buf),0);
                if(sret == 0 )
                {
                    printf("%d client  closed\n",netfd);
                    epoll_ctl(epfd,EPOLL_CTL_DEL,netfd,NULL);
                    int idx = fdtoidx[netfd];  // 找到netfd对应的下标
                    conn[idx].isAlive = 0;
                    fdtoidx[netfd] = -1;
                    close(netfd);
                    continue;
                }

                // 更新活跃时间
                int idx = fdtoidx[netfd];
                conn[idx].lastActive = time(NULL);


                for(int j = 0;j<curidx;j++)
                {
                    //if(conn[j].isAlive == 1 && fdtoidx[netfd] != j)
                    if(conn[j].isAlive == 1 && conn[j].netfd != netfd)
                    {
                        printf("     %d发送给%d : %s\n",netfd,conn[j].netfd,buf);
                        send(conn[j].netfd,buf,strlen(buf),0);
                    }

                }
            }

        }
        for(int i =0;i<curidx;i++)
        {
            if(conn[i].isAlive==1 && (now-conn[i].lastActive)>10)
            {
                printf("%d  超时\n",conn[i].netfd);
                epoll_ctl(epfd,EPOLL_CTL_DEL,conn[i].netfd,NULL);
                close(conn[i].netfd);
                conn[i].isAlive = 0;
                fdtoidx[conn[i].netfd] = -1;
            }
        }


    }
    return 0;
}

在这里插入图片描述

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

在这里插入图片描述

非阻塞读操作

在这里插入图片描述

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

在这里插入图片描述

在这里插入图片描述

阻塞式的,无数据可读时,阻塞。写端关闭,读端直接返回0
while+recv/read 必须是非阻塞的

  • 读管道变成非阻塞的

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

  • 5种IO模型

同步,read和write有明确的先后顺序
异步:read和write无顺序

  • 同步阻塞
    在这里插入图片描述

  • 同步非阻塞
    在这里插入图片描述

  • 同步的IO多路复用

本质上是对同步非阻塞模型的优化,将数据是否准备好(轮询)交给内核去完成,准备好之后让内核通知线程
在这里插入图片描述

  • 异步
    在这里插入图片描述

  • 信号驱动IO
    在这里插入图片描述

epoll的边缘触发

在这里插入图片描述

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

在这里插入图片描述

  • epoll水平触犯->边缘触发
    在这里插入图片描述

在这里插入图片描述

  • 边缘触发存在数据残留:如何解决 while多次读取

在这里插入图片描述

在这里插入图片描述


#include<54func.h>

int main(int argc,char* argv[]){
    ARGS_CHECK(argc,3);
   // 创建监听套接字
    int sfd = socket(AF_INET,SOCK_STREAM,0);
    ERROR_CHECK(sfd,-1,"socket");
    struct sockaddr_in serAddr;
    memset(&serAddr,0,sizeof(serAddr));
    serAddr.sin_family = AF_INET;
    serAddr.sin_addr.s_addr = inet_addr(argv[1]);
    serAddr.sin_port = htons(atoi(argv[2]));
    int ret =0;
    //reuse=1表示允许地址重用
    int reuse = 1;
    ret = setsockopt(sfd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse));

    ERROR_CHECK(ret,-1,"setsockopt");
    ret = bind(sfd,(struct sockaddr*)&serAddr,sizeof(serAddr));
    ERROR_CHECK(ret,-1,"bind");
    ret = listen(sfd,10);
    ERROR_CHECK(ret,-1,"listen");

    //newFd代表的是跟客户端的TCP连接
    int newFd = accept(sfd,NULL,NULL);
    ERROR_CHECK(newFd,-1,"accept");
    char buf[8]={0};

    int epfd = epoll_create(1);
    ERROR_CHECK(epfd,-1,"epoll_create");
    struct epoll_event event;
    memset(&event,0,sizeof(event));
    //把关心的描述符和对应的时间填到结构体里
    event.data.fd = STDIN_FILENO; // 就绪之后放STDIN_FINENO

    event.events = EPOLLIN|EPOLLET; // 读就绪  水平s触发->边缘触发
    ret = epoll_ctl(epfd,EPOLL_CTL_ADD,STDIN_FILENO,&event);
    ERROR_CHECK(ret,-1,"epoll_ctl");

    event.data.fd = newFd;  // 就绪之后放入netfd
    event.events = EPOLLIN; // 读就绪
    ret = epoll_ctl(epfd,EPOLL_CTL_ADD,newFd,&event);
    ERROR_CHECK(ret,-1,"epoll_ctl");

    int readyNum=0;
    struct epoll_event  readySet[2];
    while(1){
        readyNum = epoll_wait(epfd,readySet,2,-1);//不用每次都重置监听集合了
        printf("ready epoll");
        for(int i=0;i<readyNum;i++){
            if(readySet[i].data.fd == STDIN_FILENO){
                memset(buf,0,sizeof(buf));
                read(STDIN_FILENO,buf,sizeof(buf));
                send(newFd,buf,strlen(buf)-1,0);
            }
            else if(readySet[i].data.fd == newFd){
                bzero(buf,sizeof(buf));
                memset(buf,0,sizeof(buf));
                ret = recv(newFd,buf,2,MSG_DONTWAIT); // 临时非阻塞
                if(ret == -1) // 为-1表示消息读完了
                {
                    break;
                }
                //对端断开的时候,newFd一直可读
                //recv读数据的返回值是0
                if(0 == ret){
                    printf("byebye\n");
                    close(sfd);
                    close(newFd);
                    return 0;
                }
                printf("buf=%s\n",buf);
            }
        }
    }
    close(newFd);
    close(sfd);
    return 0;
}

是的,对于使用 epoll 的边缘触发(Edge-Triggered, ET)模式,确实需要将文件描述符设置为非阻塞模式。这是因为边缘触发模式只在数据状态发生变化时通知你一次,而不是在数据可读时持续通知。因此,如果不将文件描述符设置为非阻塞模式,可能会导致阻塞在 recv 或 read 调用中,从而无法正确处理其他事件。


#include <fcntl.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <cstring>
#include <iostream>

void setNonBlocking(int fd) {
    int flags = fcntl(fd, F_GETFL, 0);
    if (flags == -1) {
        perror("fcntl F_GETFL");
        exit(EXIT_FAILURE);
    }
    flags |= O_NONBLOCK;
    if (fcntl(fd, F_SETFL, flags) == -1) {
        perror("fcntl F_SETFL");
        exit(EXIT_FAILURE);
    }
}

int main(int argc, char* argv[]) {
    if (argc != 3) {
        std::cerr << "Usage: " << argv[0] << " <IP> <Port>" << std::endl;
        return 1;
    }

    int sfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sfd < 0) {
        perror("socket");
        return 1;
    }

    struct sockaddr_in serAddr;
    memset(&serAddr, 0, sizeof(serAddr));
    serAddr.sin_family = AF_INET;
    serAddr.sin_addr.s_addr = inet_addr(argv[1]);
    serAddr.sin_port = htons(atoi(argv[2]));

    int reuse = 1;
    if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0) {
        perror("setsockopt");
        close(sfd);
        return 1;
    }

    if (bind(sfd, (struct sockaddr*)&serAddr, sizeof(serAddr)) < 0) {
        perror("bind");
        close(sfd);
        return 1;
    }

    if (listen(sfd, 10) < 0) {
        perror("listen");
        close(sfd);
        return 1;
    }

    int newFd = accept(sfd, NULL, NULL);
    if (newFd < 0) {
        perror("accept");
        close(sfd);
        return 1;
    }

    // 设置新连接的文件描述符为非阻塞模式
    setNonBlocking(newFd);

    int epfd = epoll_create(1);
    if (epfd < 0) {
        perror("epoll_create");
        close(sfd);
        close(newFd);

在这里插入图片描述

水平触发,会导致epoll_wait不断就绪,直到数据读取完成
上面的while+边缘触发:则是epoll_wait就绪一次,不断读取数据

在这里插入图片描述

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

  • 为什么使用边缘触发

旧版本的epoll支持边缘触发
假如epoll_wait和子线程recv在两个不同的线程执行

在这里插入图片描述

6 socket属性调整

在这里插入图片描述

在这里插入图片描述

7 recv和send的标志

MSG_DONTWAIT

在这里插入图片描述

MSG_PEEK

在这里插入图片描述

nc命令

  • 可以不用些客户端,nc IP port

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值