【Linux网络编程】4.TCP协议、select多路IO转换

目录

TCP协议

TCP通讯时序

三次握手

四次挥手

滑动窗口

测试代码1

测试结果

Address already in use解决方法

批量杀进程

测试代码2

测试结果

测试代码4

测试结果

TCP状态转换

主动发起连接请求端

主动关闭连接请求端

被动接收连接请求端

被动关闭连接请求端

2MSL时长

端口复用

测试代码5

测试结果

半关闭

参数sockfd

参数how

select多路IO转换

FD_ZERO

参数set

FD_SET

参数fd

参数set

FD_CLR

参数fd

参数set

FD_ISSET

参数fd

参数set

返回值

select

参数nfds

参数readfds

参数writefds

参数exceptfds

参数timeout

返回值

优缺点

测试代码6

测试结果

TCP协议

TCP通讯时序

三次握手

  1. 主动发起连接请求端,发送SYN标志位,请求建立连接。 携带序号、数据字节数(0)、滑动窗口大小。

  2. 被动接受连接请求端,发送ACK标志位,同时携带SYN请求标志位。携带序号、确认序号、数据字节数(0)、滑动窗口大小。

  3. 主动发起连接请求端,发送ACK标志位,应答服务器连接请求。携带确认序号。

巧记

  • 女:我喜欢你。

  • 男:好,我知道了,我也喜欢你。

  • 女:好,我也知道了,那我们在一起吧。

四次挥手

 

  1. 主动关闭连接请求端, 发送FIN标志位。

  2. 被动关闭连接请求端, 应答ACK标志位。(半关闭完成

  3. 被动关闭连接请求端, 发送FIN标志位。

  4. 主动关闭连接请求端, 应答ACK标志位。(连接全部关闭

巧记

  • 女:我不喜欢你了,我们分手吧,还有什么想说的赶紧说吧。

  • 男:好,我知道了,我也不喜欢你了。

  • 男:我没什么想说的了,我们分手吧。

  • 女:好,我也知道了,我们以后不要再联系了。

滑动窗口

 

  • mss:一条数据的最大数据量。

  • win:滑动窗口

  • 1:第1次握手。

  • 2:第2次握手,应答,滑动窗口的空间为6k。

  • 3:第3次握手。

  • 4~9:连续发送6次数据,每次发送1k的数据。

  • 10:应答接收到6k的数据。win 2048:处理完2k的数据,空出2k的数据空间。

  • 11:应答接收到6k的数据。win 4096:已经处理了4k的数据,空出了4k的空间。

  • 12:发送1k的数据。

  • 13:发送数据完成,第1次挥手。

  • 14~16:处理接收到的数据,第2次挥手。

  • 17:第3次挥手。

  • 18:第4次挥手。

测试代码1

多进程服务器接收多个客户端数据,并返回。

/*
	多进程并发服务器
*/

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <semaphore.h>
#include <pthread.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <strings.h>
#include <signal.h>
#include <errno.h>
#include <sys/wait.h>

#define DuanKouHao 8080

void ChuLi_HanShu() //处理函数
{
    while ((waitpid(0, NULL, WNOHANG)) > 0) //非阻塞回收
        ;
    return;
}

int main(int argc, char *argv[])
{
    int fd_FWQ; //服务器文件描述符
    int fd_KHD; //客户端文件描述符
    int flag;
    struct sockaddr_in DiZhi_JieGou_FWQ; //服务器地址结构
    struct sockaddr_in DiZhi_JieGou_KHD; //客户端地址结构
    socklen_t KHD_DaXiao;                //客户端大小
    pid_t JinCheng_ID;                   //进程ID
    int ZiJie_DaXiao;                    //字节大小
    char data[1024];
    char Show_Data[1024]; //显示的数据
    clock_t start, stop;  //clock_t为clock()函数返回的变量类型
    double duration;

    bzero(&DiZhi_JieGou_FWQ, sizeof(DiZhi_JieGou_FWQ)); //地址结构清零

    fd_FWQ = socket(AF_INET, SOCK_STREAM, 0); //创建服务器套接字
    if (fd_FWQ == -1)
    {
        perror("创建服务器套接字错误");
        exit(1);
    }

    DiZhi_JieGou_FWQ.sin_family = AF_INET; //IPv4
    DiZhi_JieGou_FWQ.sin_port = htons(DuanKouHao);
    DiZhi_JieGou_FWQ.sin_addr.s_addr = htonl(INADDR_ANY);
    flag = bind(fd_FWQ, (struct sockaddr *)&DiZhi_JieGou_FWQ, sizeof(DiZhi_JieGou_FWQ)); //绑定服务器地址结构
    if (flag == -1)
    {
        perror("绑定服务器地址结构错误");
        exit(1);
    }

    flag = listen(fd_FWQ, 6); //设置连接上限
    if (flag == -1)
    {
        perror("设置连接上限错误");
        exit(1);
    }

    KHD_DaXiao = sizeof(DiZhi_JieGou_KHD);

    while (1)
    {
        fd_KHD = accept(fd_FWQ, (struct sockaddr *)&DiZhi_JieGou_KHD, &KHD_DaXiao); //建立客户端连接,阻塞等待连接
        if (fd_KHD == -1)
        {
            perror("建立客户端连接错误");
            exit(1);
        }

        JinCheng_ID = fork(); //创建子进程
        if (JinCheng_ID < 0)
        {
            perror("创建子进程错误");
            exit(1);
        }
        else if (JinCheng_ID == 0) //子进程
        {
            close(fd_FWQ);
            break;
        }
        else if (JinCheng_ID > 0) //父进程,注册信号捕捉函数
        {
            struct sigaction ChuLi_FangShi;
            ChuLi_FangShi.sa_handler = ChuLi_HanShu; //捕捉信号后的处理函数
            sigemptyset(&ChuLi_FangShi.sa_mask);     //清空信号集
            ChuLi_FangShi.sa_flags = 0;              //默认处理方式
            flag = sigaction(SIGINT, &ChuLi_FangShi, NULL);
            if (flag == -1)
            {
                perror("注册处理函数错误");
                exit(1);
            }

            close(fd_KHD);
        }
    }

    if (JinCheng_ID == 0) //子进程
    {
        while (1)
        {
            ZiJie_DaXiao = read(fd_KHD, &data, sizeof(data)); //读取客户端数据
            if (ZiJie_DaXiao == -1)
            {
                if (errno == EINTR)
                {
                    continue;
                }
                else
                {
                    perror("读取数据错误");
                    exit(1);
                }
            }
            sprintf(Show_Data, "这是%d号服务器子进程,接收到的数据是%s\n", getpid(), data);
            write(fd_KHD, Show_Data, strlen(Show_Data));        //发回给客户端
            write(STDOUT_FILENO, Show_Data, strlen(Show_Data)); //显示到终端
        }
    }
    return 0;
}

测试结果

 

Address already in use解决方法

netstat -apn  |  grep 端口号

批量杀进程

ps aux | grep 运行的文件 | awk '{print $2}' | xargs kill -9

测试代码2

单进程服务器与千级数量级的客户端建立连接。

/*
	CeShi2_FWQ.c
    单进程服务器与千级数量级的客户端建立连接
*/

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <semaphore.h>
#include <pthread.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <strings.h>
#include <signal.h>
#include <errno.h>
#include <sys/wait.h>
#include <time.h>

#define DuanKouHao 8080

void ChuLi_HanShu() //处理函数
{
    while ((waitpid(0, NULL, WNOHANG)) > 0) //非阻塞回收
        ;
    return;
}

int main(int argc, char *argv[])
{
    int fd_FWQ; //服务器文件描述符
    int fd_KHD; //客户端文件描述符
    int flag;
    struct sockaddr_in DiZhi_JieGou_FWQ; //服务器地址结构
    struct sockaddr_in DiZhi_JieGou_KHD; //客户端地址结构
    socklen_t KHD_DaXiao;                //客户端大小
    pid_t JinCheng_ID;                   //进程ID
    int ZiJie_DaXiao;                    //字节大小
    char data[1024];
    char Show_Data[1024]; //显示的数据
    clock_t start, stop;  //clock_t为clock()函数返回的变量类型
    double duration;

    bzero(&DiZhi_JieGou_FWQ, sizeof(DiZhi_JieGou_FWQ)); //地址结构清零

    fd_FWQ = socket(AF_INET, SOCK_STREAM, 0); //创建服务器套接字
    if (fd_FWQ == -1)
    {
        perror("创建服务器套接字错误");
        exit(1);
    }

    DiZhi_JieGou_FWQ.sin_family = AF_INET; //IPv4
    DiZhi_JieGou_FWQ.sin_port = htons(DuanKouHao);
    DiZhi_JieGou_FWQ.sin_addr.s_addr = htonl(INADDR_ANY);
    flag = bind(fd_FWQ, (struct sockaddr *)&DiZhi_JieGou_FWQ, sizeof(DiZhi_JieGou_FWQ)); //绑定服务器地址结构
    if (flag == -1)
    {
        perror("绑定服务器地址结构错误");
        exit(1);
    }

    //flag = listen(fd_FWQ, 10); //设置连接上限
    flag = listen(fd_FWQ, 100); //设置连接上限
    if (flag == -1)
    {
        perror("设置连接上限错误");
        exit(1);
    }

    KHD_DaXiao = sizeof(DiZhi_JieGou_KHD);
    flag = 0;
    start = clock();
    while (1)
    {
        fd_KHD = accept(fd_FWQ, (struct sockaddr *)&DiZhi_JieGou_KHD, &KHD_DaXiao); //建立客户端连接,阻塞等待连接
        if (fd_KHD == -1)
        {
            perror("建立客户端连接错误");
            exit(1);
        }
        flag++;
        printf("第%d次连接,客户端IP地址是:%s\n", flag,
               inet_ntop(AF_INET, &DiZhi_JieGou_KHD.sin_addr.s_addr, data, sizeof(DiZhi_JieGou_KHD))); //打印客户端信息

        close(fd_KHD);
        if (flag >= 2000)
        {
            break;
        }
    }
    stop = clock();
    duration = (double)(stop - start) / 1000.0; //CLK_TCK为clock()函数的时间单位,即时钟打点
    printf("时间:%f\n", duration);

    return 0;
}
/*
	CeShi2_KHD.c
    多进程客户端测试服务器连接代码
*/

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <semaphore.h>
#include <pthread.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <errno.h>

#define FWQ_IP "10.3.22.7" //服务器IP

int main(int argc, char *argv[])
{
    int fd_FWQ; //服务器
    int flag;
    struct sockaddr_in FWQ_DiZhi; //服务器地址
    int ZiFuShu;                  //字符数
    char data[1024];              //数据
    pid_t JinCheng_ID;            //进程ID

    FWQ_DiZhi.sin_family = AF_INET;                         //IPv4
    FWQ_DiZhi.sin_port = htons(8080);                       //端口号8080
    flag = inet_pton(AF_INET, FWQ_IP, &FWQ_DiZhi.sin_addr); //十进制IP转换网络IP
    if (flag == -1)
    {
        perror("十进制IP转换网络IP错误");
        exit(1);
    }

    flag = 0;
    while (1)
    {
        JinCheng_ID = fork(); //创建子进程
        if (JinCheng_ID < 0)
        {
            perror("创建子进程错误");
            exit(1);
        }
        else if (JinCheng_ID == 0) //子进程
        {
            break;
        }
        flag++;
        if (flag >= 2000)
        {
            break;
        }
    }

    if (JinCheng_ID == 0) //子进程
    {
        printf("这是%d子进程\n", getpid());
        fd_FWQ = socket(AF_INET, SOCK_STREAM, 0); //创建服务器套接字
        if (fd_FWQ == -1)
        {
            perror("创建服务器套接字错误");
            exit(1);
        }
        sleep(5);
        flag = connect(fd_FWQ, (struct sockaddr *)&FWQ_DiZhi, sizeof(FWQ_DiZhi));
        if (flag == -1)
        {
            perror("连接服务器错误");
            exit(1);
        }
        close(fd_FWQ);
    }

    while(1);

    return 0;
}
测试结果

同时连接服务器的客户端上限为10时。

同时连接服务器的客户端上限为100时。  

测试代码4

多线程服务器与千级数量级的客户端建立连接。

/*
	CeShi4_FWQ.c
   多线程并发服务器处理客户端数据,并发回去
*/

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <semaphore.h>
#include <pthread.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <strings.h>
#include <signal.h>
#include <errno.h>
#include <sys/wait.h>
#include <time.h>

#define DuanKouHao 8080               //端口号
#define XianCheng_XinXi_ShuLiang 2000 //线程信息数量
#define data_DaXiao 1024              //数据缓冲区大小

struct XianCheng_XinXi //线程信息
{
    struct sockaddr_in KHD_XinXi;
    int fd_KHD;
};

void *ChuLi_HanShu(void *arg) //处理函数
{
    int leng;
    char data[data_DaXiao];
    struct XianCheng_XinXi *xian_cheng_xin_xi = (struct XianCheng_XinXi *)arg;
    while (1)
    {
        leng = read(xian_cheng_xin_xi->fd_KHD, data, sizeof(data));
        if (leng == -1)
        {
            if (errno == EAGAIN || errno == EWOULDBLOCK)
            {
                continue;
            }
            else
            {
                break;
            }
        }
    }
    close(xian_cheng_xin_xi->fd_KHD);
    return (void *)0;
}

int main(int argc, char *argv[])
{
    int fd_FWQ; //服务器文件描述符
    int fd_KHD; //客户端文件描述符
    int flag;
    int flag1;
    struct sockaddr_in DiZhi_JieGou_FWQ; //服务器地址结构
    struct sockaddr_in DiZhi_JieGou_KHD; //客户端地址结构
    socklen_t KHD_DaXiao;                //客户端大小
    pthread_t XianCheng_ID;              //线程ID
    int ZiJie_DaXiao;                    //字节大小
    char data[1024];
    clock_t start, stop; //clock_t为clock()函数返回的变量类型
    double duration;
    struct XianCheng_XinXi xian_cheng_xin_xi[XianCheng_XinXi_ShuLiang];

    bzero(&DiZhi_JieGou_FWQ, sizeof(DiZhi_JieGou_FWQ)); //地址结构清零

    fd_FWQ = socket(AF_INET, SOCK_STREAM, 0); //创建服务器套接字
    if (fd_FWQ == -1)
    {
        perror("创建服务器套接字错误");
        exit(1);
    }

    DiZhi_JieGou_FWQ.sin_family = AF_INET; //IPv4
    DiZhi_JieGou_FWQ.sin_port = htons(DuanKouHao);
    DiZhi_JieGou_FWQ.sin_addr.s_addr = htonl(INADDR_ANY);
    flag = bind(fd_FWQ, (struct sockaddr *)&DiZhi_JieGou_FWQ, sizeof(DiZhi_JieGou_FWQ)); //绑定服务器地址结构
    if (flag == -1)
    {
        perror("绑定服务器地址结构错误");
        exit(1);
    }

    flag = listen(fd_FWQ, 128); //设置连接上限
    if (flag == -1)
    {
        perror("设置连接上限错误");
        exit(1);
    }

    KHD_DaXiao = sizeof(DiZhi_JieGou_KHD);
    flag1 = 0;
    start = clock();
    while (1)
    {
        fd_KHD = accept(fd_FWQ, (struct sockaddr *)&DiZhi_JieGou_KHD, &KHD_DaXiao); //建立客户端连接,阻塞等待连接
        if (fd_KHD == -1)
        {
            perror("建立客户端连接错误");
            exit(1);
        }

        flag1++;
        printf("第%d次连接,客户端IP地址是:%s\n", flag1,
               inet_ntop(AF_INET, &DiZhi_JieGou_KHD.sin_addr.s_addr, data, sizeof(DiZhi_JieGou_KHD))); //打印客户端信息

        xian_cheng_xin_xi[fd_KHD].KHD_XinXi = DiZhi_JieGou_KHD;
        xian_cheng_xin_xi[fd_KHD].fd_KHD = fd_KHD;

        pthread_create(&XianCheng_ID, NULL, ChuLi_HanShu, (void *)&xian_cheng_xin_xi[fd_KHD]);
        pthread_detach(XianCheng_ID);

        close(fd_KHD);

        if (flag1 >= 2000)
        {
            break;
        }
    }

    stop = clock();
    duration = (double)(stop - start) / 1000.0; //CLK_TCK为clock()函数的时间单位,即时钟打点
    printf("时间:%f\n", duration);

    return 0;
}
/*
	CeShi4_KHD.c
   多线程并发服务器处理客户端数据,并发回去
*/
/*
        测试1,多进程客户端测试服务器连接代码
*/

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <semaphore.h>
#include <pthread.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <errno.h>

#define FWQ_IP "10.3.22.7" //服务器IP

int main(int argc, char *argv[])
{
    int fd_FWQ; //服务器
    int flag;
    struct sockaddr_in FWQ_DiZhi; //服务器地址
    int ZiFuShu;                  //字符数
    char data[1024];              //数据
    pid_t JinCheng_ID;            //进程ID

    FWQ_DiZhi.sin_family = AF_INET;                         //IPv4
    FWQ_DiZhi.sin_port = htons(8080);                       //端口号8080
    flag = inet_pton(AF_INET, FWQ_IP, &FWQ_DiZhi.sin_addr); //十进制IP转换网络IP
    if (flag == -1)
    {
        perror("十进制IP转换网络IP错误");
        exit(1);
    }

    flag = 0;
    while (1)
    {
        JinCheng_ID = fork(); //创建子进程
        if (JinCheng_ID < 0)
        {
            perror("创建子进程错误");
            exit(1);
        }
        else if (JinCheng_ID == 0) //子进程
        {
            break;
        }
        flag++;
        if (flag >= 2000)
        {
            break;
        }
    }

    if (JinCheng_ID == 0) //子进程
    {
        printf("这是%d子进程\n", getpid());
        fd_FWQ = socket(AF_INET, SOCK_STREAM, 0); //创建服务器套接字
        if (fd_FWQ == -1)
        {
            perror("创建服务器套接字错误");
            exit(1);
        }
        sleep(10);
        flag = connect(fd_FWQ, (struct sockaddr *)&FWQ_DiZhi, sizeof(FWQ_DiZhi));
        if (flag == -1)
        {
            perror("连接服务器错误");
            exit(1);
        }
        //close(fd_FWQ);
    }

    //while (1);
    sleep(180);
    close(fd_FWQ);
    if(JinCheng_ID>0){
        sleep(30);
    }

    return 0;
}
测试结果

TCP状态转换

  • CLOSED:初始状态。

  • LISTEN:服务器端的某个SOCKET处于监听状态,可以接受连接。

  • SYN_SENT:客户端已发送SYN报文。

  • SYN_RCVD:接收到SYN报文,服务器端的SOCKET在建立TCP连接时的三次握手会话过程中的一个中间状态,很短暂。

  • ESTABLISHED:连接已经建立。

  • FIN_WAIT_1:等待对方的FIN报文,当socket在ESTABLISHED状态时,想主动关闭连接,向对方发送了FIN报文,此时该socket进入到FIN_WAIT_1状态。

  • FIN_WAIT_2:等待对方的FIN报文,socket只能接收数据,不能发。主动关闭链接的一方,发出FIN收到ACK以后进入该状态,称之为半连接或半关闭状态。

  • TIME_WAIT:收到了对方的FIN报文,并发送出了ACK报文,等2MSL后即可回到CLOSED可用状态。如果FIN_WAIT_1状态下,收到对方同时带 FIN标志和ACK标志的报文时,可以直接进入到TIME_WAIT状态,而无须经过FIN_WAIT_2状态。

  • CLOSING:双方都正在关闭SOCKET连接。

  • CLOSE_WAIT:在等待关闭。

  • LAST_ACK:被动关闭一方在发送FIN报文后,最后等待对方的ACK报文。当收到ACK报文后,即可以进入到CLOSED可用状态。

主动发起连接请求端

  1. CLOSE

  2. 发送SYN

  3. 进入SYN_SENT状态

  4. 接收ACK、SYN

  5. 发送ACK

  6. 进入ESTABLISHED状态(数据通信态)

主动关闭连接请求端

  1. ESTABLISHED状态(数据通信态)

  2. 发送FIN

  3. 进入FIN_WAIT_1状态

  4. 接收ACK

  5. 进入FIN_WAIT_2状态(半关闭)

  6. 接收对端发送FIN

  7. 回发ACK

  8. 进入TIME_WAIT状态(只有主动关闭连接方,会经历该状态)

  9. 等2MSL时长

  10. CLOSE

被动接收连接请求端

  1. CLOSE

  2. 进入LISTEN状态

  3. 接收SYN

  4. 发送ACK、SYN

  5. 进入SYN_RCVD状态

  6. 接收ACK

  7. 进入ESTABLISHED状态(数据通信态)

被动关闭连接请求端

  1. ESTABLISHED状态(数据通信态)

  2. 接收FIN

  3. 发送ACK

  4. 进入CLOSE_WAIT状态(说明对端【主动关闭连接端】处于半关闭状态)

  5. 发送FIN

  6. 进入LAST_ACK状态

  7. 接收ACK

  8. CLOSE

2MSL时长

一般为40s,保证最后一个 ACK 能成功被对端接收。一定出现在主动关闭连接请求端

端口复用

测试代码5

使用端口复用,解除服务器主动断开连接后的2MSL等待时长。

/*服务器端代码*/

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <semaphore.h>
#include <pthread.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <ctype.h>

int main(int argc, char *argv[])
{
    int flag;
    int fd_FWQ;                          //服务器文件描述符
    int fd_KFD;                          //客户端文件描述符
    char data[1024];                     //读取的数据
    int ZiJieShu;                        //字节数
    struct sockaddr_in DiZhi_JieGou_FWQ; //服务器地址结构
    struct sockaddr_in DiZhi_JieGou_KHD; //客户端地址结构
    socklen_t KeHuDuan_DaXiao;           //客户端大小
    char KHD_IP[1024];                   //客户端IP
    char FWQ_IP[1024];                   //服务器IP
    int opt = 1;

    bzero(&DiZhi_JieGou_FWQ, sizeof(DiZhi_JieGou_FWQ)); //地址结构清零

    fd_FWQ = socket(AF_INET, SOCK_STREAM, 0); //创建服务器套接字
    if (fd_FWQ == -1)
    {
        perror("创建服务器套接字错误");
        exit(1);
    }

    setsockopt(fd_FWQ, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); //端口复用

    DiZhi_JieGou_FWQ.sin_family = AF_INET;                                               //IPv4
    DiZhi_JieGou_FWQ.sin_port = htons(8080);                                             //端口号8080
    DiZhi_JieGou_FWQ.sin_addr.s_addr = htonl(INADDR_ANY);                                //获取系统中任意有效的IP地址
    flag = bind(fd_FWQ, (struct sockaddr *)&DiZhi_JieGou_FWQ, sizeof(DiZhi_JieGou_FWQ)); //绑定服务器的地址结构
    if (flag == -1)
    {
        perror("绑定服务器地址结构错误");
        exit(1);
    }
    printf("服务器IP:%s,端口号:%d\n",
           inet_ntop(AF_INET, &DiZhi_JieGou_FWQ.sin_addr.s_addr, FWQ_IP, sizeof(FWQ_IP)),
           ntohs(DiZhi_JieGou_FWQ.sin_port));

    flag = listen(fd_FWQ, 128); //设置连接服务器上限数
    if (flag == -1)
    {
        perror("设置连接上限数错误");
        exit(1);
    }

    KeHuDuan_DaXiao = sizeof(DiZhi_JieGou_KHD);
    fd_KFD = accept(fd_FWQ, (struct sockaddr *)&DiZhi_JieGou_KHD, &KeHuDuan_DaXiao); //阻塞监听客户端连接
    if (fd_KFD == -1)
    {
        perror("阻塞监听客户端连接错误");
        exit(1);
    }
    printf("客户端IP:%s,端口号:%d\n",
           inet_ntop(AF_INET, &DiZhi_JieGou_KHD.sin_addr.s_addr, KHD_IP, sizeof(KHD_IP)), //网络转换成十进制本地IP
           ntohs(DiZhi_JieGou_KHD.sin_port));                                             //网络转换成本地端口

    while (1)
    {
        ZiJieShu = read(fd_KFD, data, sizeof(data));
        write(STDOUT_FILENO, data, ZiJieShu); //终端显示
        write(fd_KFD, data, ZiJieShu);
    }

    close(fd_KFD);
    close(fd_FWQ);
    return 0;
}
测试结果

半关闭

通信双方中,只有一端关闭通信。在关闭多个文件描述符应用的文件时,采用全关闭方法。

man 2 shutdown

参数sockfd

文件描述符。

参数how

SHUT_RD:关闭读端

SHUT_WR:关闭写端

SHUT_RDWR:关闭读写端

select多路IO转换

借助内核, select 来监听, 客户端连接、数据通信事件。

FD_ZERO

清空一个文件描述符集合。

man FD_ZERO

参数set

文件描述符集合。

FD_SET

将待监听的文件描述符,添加到监听集合中。

man FD_SET

参数fd

文件描述符。

参数set

文件描述符集合。

用法:

fd_set rset;
FD_SET(3, &rset);
FD_SET(5, &rset);
FD_SET(6, &rset);

FD_CLR

将一个文件描述符从监听集合中移除。

man FD_CLR

参数fd

文件描述符。

参数set

文件描述符集合。

FD_ISSET

判断一个文件描述符是否在监听集合中。

man FD_ISSET 

参数fd

文件描述符。

参数set

文件描述符集合。

返回值

1:在

0:不在

用法:

fd_set rset;
int flag;
flag=FD_ISSET(4,&rset);

select

man select

参数nfds

监听的所有文件描述符中,最大文件描述符+1。

参数readfds

读,文件描述符监听集合。传入、传出参数。

参数writefds

写,文件描述符监听集合。传入、传出参数。

参数exceptfds

异常,文件描述符监听集合。传入、传出参数。

参数timeout

大于0:设置监听超时时长。

NULL:阻塞监听。

0:非阻塞监听,轮询。

返回值

大于0:所有监听集合(3个)中, 满足对应事件的总数。

0:没有满足监听条件的文件描述符。

-1:errno

优缺点

        缺点:监听上限受文件描述符限制,最大1024。检测满足条件的fd,自己添加业务逻辑,提高了编码难度。

        优点:跨平台。win、linux、macOS、Unix、类Unix、mips。

测试代码6

服务器利用select进行监听客户端。

/*服务器端代码*/

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <semaphore.h>
#include <pthread.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <errno.h>

int main(int argc, char *argv[])
{
    int fd_FWQ;                          //服务器文件描述符
    int fd_KHD;                          //客户端文件描述符
    int fd_Max;                          //最大的文件描述符
    struct sockaddr_in DiZhi_JieGou_FWQ; //服务器地址结构
    struct sockaddr_in DiZhi_JieGou_KHD; //客户端地址结构
    int opt = 1;
    int flag, i, leng;
    fd_set fd_all_JIHe;         //所有的文件描述符集合
    fd_set fd_Du_JiHe;          //读的文件描述符集合
    socklen_t JieGouTi_ChangDu; //结构体长度
    char data[1024];

    bzero(&DiZhi_JieGou_FWQ, sizeof(DiZhi_JieGou_FWQ)); //地址结构清零

    fd_FWQ = socket(AF_INET, SOCK_STREAM, 0); //创建服务器套接字
    if (fd_FWQ == -1)
    {
        perror("创建服务器套接字错误");
        exit(1);
    }

    setsockopt(fd_FWQ, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); //端口复用

    DiZhi_JieGou_FWQ.sin_family = AF_INET;                                               //IPv4
    DiZhi_JieGou_FWQ.sin_port = htons(8080);                                             //端口号
    DiZhi_JieGou_FWQ.sin_addr.s_addr = htonl(INADDR_ANY);                                //获取可用IP地址
    flag = bind(fd_FWQ, (struct sockaddr *)&DiZhi_JieGou_FWQ, sizeof(DiZhi_JieGou_FWQ)); //绑定服务器地址结构
    if (flag == -1)
    {
        perror("绑定服务器地址结构错误");
        exit(1);
    }

    flag = listen(fd_FWQ, 128); //设置连接服务器上限数
    if (flag == -1)
    {
        perror("设置连接服务器上限数错误");
        exit(1);
    }

    FD_ZERO(&fd_all_JIHe);        //清空文件描述符集合
    FD_SET(fd_FWQ, &fd_all_JIHe); //添加服务器文件描述符,监听服务器
    fd_Max = fd_FWQ;

    while (1)
    {
        fd_Du_JiHe = fd_all_JIHe;
        flag = select(fd_Max + 1, &fd_Du_JiHe, NULL, NULL, NULL); //监听
        if (flag == -1)
        {
            perror("监听错误");
            exit(1);
        }

        if (FD_ISSET(fd_FWQ, &fd_Du_JiHe))
        {
            JieGouTi_ChangDu = sizeof(DiZhi_JieGou_KHD);
            fd_KHD = accept(fd_FWQ, (struct sockaddr *)&DiZhi_JieGou_KHD, &JieGouTi_ChangDu); //与客户端连接,不会阻塞
            if (fd_KHD == -1)
            {
                perror("客户端建立错误");
                exit(1);
            }

            FD_SET(fd_KHD, &fd_all_JIHe); //添加连接好客户端的文件描述符,监听读事件

            if (fd_Max < fd_KHD) //最大的文件描述符小于连接上服务器的文件描述符
            {
                fd_Max = fd_KHD;
            }

            if (flag == 1) //说明只有一个文件描述符,是服务器的文件描述符
            {
                continue;
            }
        }

        for (i = fd_FWQ + 1; i <= fd_Max; i++)
        {
            if (FD_ISSET(i, &fd_Du_JiHe))
            {
                leng = read(i, &data, sizeof(data));
                if (leng == -1)
                {
                    if (errno == EINTR)
                    {
                        continue;
                    }
                    else
                    {
                        perror("读取文件错误");
                        exit(1);
                    }
                }
                else if (leng == 0) //客户端关闭
                {
                    close(i);
                    FD_CLR(i, &fd_all_JIHe); //移除客户端文件描述符
                }
                else //接收到数据
                {
                    write(i, "你好客户端,我是服务器,接收到的数据是:", sizeof("你好客户端,我是服务器,接收到的数据是:"));
                    write(i, data, leng);
                    write(STDOUT_FILENO, "接收到的数据是:", sizeof("接收到的数据是:"));
                    write(STDOUT_FILENO, data, leng);
                }
            }
        }
    }
    close(fd_FWQ);
    return 0;
}
测试结果

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/601755.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

浅谈自己用过最好用的AI工具概括

个人最经常用的AI工具的其实是Copilot&#xff0c;但是也有别的一些最好用的AI工具&#xff0c;包括&#xff1a; OpenAI GPT-3&#xff1a;这是一个自然语言生成模型&#xff0c;具有强大的语言理解和生成能力。它可以用于各种任务&#xff0c;如文字生成、自动回复和文本摘要…

1984. 学生分数的最小差值C++

给你一个 下标从 0 开始 的整数数组 nums &#xff0c;其中 nums[i] 表示第 i 名学生的分数。另给你一个整数 k 。 从数组中选出任意 k 名学生的分数&#xff0c;使这 k 个分数间 最高分 和 最低分 的 差值 达到 最小化 。 返回可能的 最小差值 。 示例 1&#xff1a; 输入&…

台灯的十大品牌有哪些?十大护眼灯品牌推荐

相信细心的家长已经发现&#xff0c;自家孩子随着步入更高的年级&#xff0c;每天晚上学习的时间也越来越晚了&#xff0c;而这个过程中必然少不了一盏好的台灯&#xff01; 市场上有不少网红代言的护眼灯&#xff0c;虽然它们销售量高&#xff0c;但其实缺乏专业技术和安全保障…

大数据Scala教程从入门到精通第三篇:Scala和Java的关系

一&#xff1a;Scala和Java的关系 1&#xff1a;详解 一般来说&#xff0c;学 Scala的人&#xff0c;都会 Java&#xff0c;而 Scala 是基于 Java 的&#xff0c;因此我们需要将 Scala和 Java 以及 JVM 之间的关系搞清楚&#xff0c;否则学习 Scala 你会蒙圈 Scala可以使用SDK…

【算法】基础算法004之前缀和

&#x1f440;樊梓慕&#xff1a;个人主页 &#x1f3a5;个人专栏&#xff1a;《C语言》《数据结构》《蓝桥杯试题》《LeetCode刷题笔记》《实训项目》《C》《Linux》《算法》 &#x1f31d;每一个不曾起舞的日子&#xff0c;都是对生命的辜负 前言 本篇文章为大家带来前缀和…

推荐一个gpt全能网站

进入后&#xff0c;里面是这样的 点开后&#xff0c;里面是这样的 你以为只有这些吗&#xff1f; 往下翻一翻&#xff0c;你会发现新大陆&#xff01;&#xff01; 在输入框的下面&#xff0c;有一个分类栏&#xff0c;鼠标移上去&#xff0c;下面就会给出一堆网站 光是gp…

(超简单)SpringBoot中简单用工厂模式来实现

简单讲述业务需求 业务需要根据不同的类型返回不同的用户列表&#xff0c;比如按角色查询用户列表、按机构查询用户列表&#xff0c;用户信息需要从数据库中查询&#xff0c;因为不同的类型查询的逻辑不相同&#xff0c;因此简单用工厂模式来设计一下&#xff1b; 首先新建一个…

为什么 ChatGPT 不火了?

不火了是有原因的&#xff0c;下面我来从大部分人拿到 ChatGPT 之后的两大痛点开始讲起&#xff1a; 很多朋友拿到 ChatGPT 后的第一个痛点就是&#xff1a;用的不好 你经常会感觉到 ChatGPT 回答的好空&#xff0c;没有太多参考价值。 而第二个痛点则是&#xff1a;无处去用…

数据结构复习/学习9--堆/堆实现/升降序建堆/top-k问题

一、堆与完全二叉树 1.堆的逻辑与物理结构 2.父节点与子节点的下标 3.大小根堆 二、堆的实现&#xff08;大根堆为例&#xff09; 注意事项总结&#xff1a; 注意堆中插入与删除数据的位置和方法与维持大根堆有序时的数据上下调整 三、堆排序 1.排升序建大堆效率高 注意事项…

Android 开机启动扫描SD卡apk流程源码分析

在开机的时候&#xff0c;装在SD卡的apk和装在系统盘的apk扫描过程不一样&#xff0c;系统盘apk在系统启动过程中扫描&#xff0c;而SD卡上的就不是&#xff0c;等系统启动好了才挂载、扫描&#xff0c;下面就说下SD扫描的流程&#xff1a; 在SystemServer启动MountService&am…

Golang | Leetcode Golang题解之第74题搜索二维矩阵

题目&#xff1a; 题解&#xff1a; func searchMatrix(matrix [][]int, target int) bool {m, n : len(matrix), len(matrix[0])i : sort.Search(m*n, func(i int) bool { return matrix[i/n][i%n] > target })return i < m*n && matrix[i/n][i%n] target }

【查找算法】之二分查找

一、算法介绍 二分查找&#xff0c;也称为折半查找&#xff0c;是一种在有序数组中查找特定元素的高效算法。对于包含 n 个元素的有序数组&#xff0c;二分查找的步骤如下&#xff1a; 确定搜索范围&#xff1a;首先&#xff0c;将要查找的元素与数组中间的元素进行比较。如果…

链舞算法谱---链表经典题剖析

前言&#xff1a;探究链表算法的奥秘&#xff0c;解锁编程新世界&#xff01; 欢迎来到我的链表算法博客&#xff0c;这将是您深入了解链表算法&#xff0c;提升编程技能的绝佳机会。链表作为数据结构的重要成员之一&#xff0c;其动态性和灵活性在实现某些功能上发挥不可替代的…

联发科技发布天玑9300+旗舰5G生成式AI芯片 | 最新快讯

5 月 7 日消息&#xff0c;联发科技今天举办了天玑开发者大会 2024。大会上&#xff0c;联发科技开启了“天玑 AI 先锋计划”&#xff0c;联合业界生态企业发布了《生成式 AI 手机产业白皮书》&#xff0c;分享了生成式 AI 端侧部署的解决方案“天玑 AI 开发套件”。同时&#…

界面组件DevExpress Reporting中文教程 - 如何按条件显示页面水印?

DevExpress Reporting是.NET Framework下功能完善的报表平台&#xff0c;它附带了易于使用的Visual Studio报表设计器和丰富的报表控件集&#xff0c;包括数据透视表、图表&#xff0c;因此您可以构建无与伦比、信息清晰的报表。 从防止未经授权的使用到建立所有权和真实性&am…

java语言数据结构(单链表)

前言 不得承认java应用的广泛&#xff0c;所以毅然决定java版本的数据结构和算法专题还是要坚决更新。每日更新2题&#xff0c;希望学习的小伙伴可以关注一波跟上&#xff0c;评论区欢迎讨论交流。 实现原理 节点&#xff08;Node&#xff09;&#xff1a;链表的基本构建单元…

【Git】Git学习-07:git diff查看差异

学习视频链接&#xff1a;【GeekHour】一小时Git教程_哔哩哔哩_bilibili 默认比较工作区和暂存区的差异 工作区和版本库的内容比较 git diff HEAD / git diff --staged 暂存区和版本库内容比较 git diff --cached 比较特定两个版本的不同 git diff 版本id 版本id HEAD表示提交的…

【Git】Git学习-17:git rebase,且解决合并冲突

学习视频链接&#xff1a;【GeekHour】一小时Git教程_哔哩哔哩_bilibili​编辑https://www.bilibili.com/video/BV1HM411377j/?vd_source95dda35ac10d1ae6785cc7006f365780 理论 git rebase 目标分支&#xff1a;把当前分支的提交&#xff0c;从与目标分支的共同主祖先处断开…

Ubuntu 24.04 LTS 安装 touchegg 开启触控板多指手势

文章目录 〇、概述一、安装 touchegg二、安装 gnome-shell 扩展 X11 Gestures三、安装可视化配置工具 touche 〇、概述 之前为了让笔记本支持多指手势&#xff0c;我安装的是 fusuma&#xff0c;安装教程详见 这篇文章 &#xff0c;考虑到 fusuma 安装过程繁琐且不支持可视化配…

Redis单机安装

1.编译 cd redis安装目录 makemake install2.修改配置文件redis.conf #端口修改 port 6379 #后台进程启动 yes daemonize yes # daemonize no #注释掉 为了可以远程连接 #bind 127.0.0.1 #设置密码 requirepass pwd3.启动 ./redis-server ../redis.conf查看进程 [rootlocal…