【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;
}
测试结果

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

因心,三人水

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值