如果你运行过(一)中的范例,你会发现,客户端与服务端未建立连接前会在accpet与connect之间等待,建立后进行读写时,不论是客户端还是服务端都会在read()处等待另一方发送。本节让我们来了解以下他们的阻塞等待原理。

阻塞机制

connnect()

客户端通过connect()主动向服务端建立连接,并等待服务端的反馈,实现三次握手。范例(一)中,我们的服务端都是稳定运行的,一旦客户端发起连接,就会快速响应,connect也随之会快速返回结果给客户。但如果listen()中监听队列已满,服务器迟迟不反馈给客户端,那connet()又会如何让处理呢?我们说过,connect()使一个发起三次握手阻塞函数,调用它会每隔一段时间发起一个SYN包,直至服务器反馈,若超过一定时间仍未收到服务器反馈则会显示响应超时。

accpet()

上节我们说过,connect与listen交互建立连接后,产生套接字会放入一个队列中,供accept取用。accept作用就是查看该队列是否有新生成的套接字,若有则立即返回,无则阻塞等待。

read()

read()的阻塞机制在于调用它时会询问内核缓存是否有数据可读入,若无则会等待内核缓存准备好数据直至递送给read()

非阻塞实现

我们可以看出,这些阻塞函数会一直等待结果,在阻塞期间程序会不能做其他的事,这样会使我们的时间利用率降低,因此我们可以将他们设置未非阻塞,在未等到结果时可以让程序做其他的事。

实现原理

轮子阻塞还是非阻塞的属性其实与函数本身无关,而是取决于放入的套接字,默认情况下,socket是阻塞式的,因此只需要改变socket本身的属性,就可以实现轮子的非阻塞调用了。

设置为非阻塞

1.创建时添入SOCK_NONBLOCK设置

int socket(int domain, int type, int protocol);
int s = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP);

2.使用fcntl函数设置

fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFL, 0) | O_NONBLOCK);

3.使用ioctl函数设置

ioctl(sockfd, FIONBIO, 1); //1:非阻塞 0:阻塞

当我们将套接字设置为非阻塞后,accept与read会进行查询,若无结果或出错都会迅速返回。而connect较为特殊,如果我们调用非阻塞connect后,它会发送一个SYN包而迅速返回,那么之后的三次握手是否建立成功,就需要我们使用poll函数或者select函数检测套接字的状态是否可写,监测超时时间可以由我们自己决定。

服务端
#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>

unsigned short server_Port = 1234; // 服务器端口
const char *IP = "192.168.80.132"; // 服务器IP

/**字母转大写*/
void upper(char *buf)
{
    char *pointer = buf;
    while (*pointer != '\n')
    {
        if (*pointer >= 'a' && *pointer <= 'z')
            *pointer -= 32;
        pointer++;
    }
    return;
}

int main()
{
    // 创建套接字
    int server_Socket = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0); // 设置为非阻塞
    struct sockaddr_in server_Addr;
    server_Addr.sin_family = AF_INET;
    server_Addr.sin_port = htons(server_Port);
    int flag = inet_aton(IP, &(server_Addr.sin_addr));
    if (!flag)
    {
        perror("IP transfer failed!\n");
    }

    // 绑定服务器IP地址端口号
    bind(server_Socket, (struct sockaddr *)&server_Addr, sizeof(server_Addr));
    listen(server_Socket, 0);

    while (1)
    {
        struct sockaddr_in client_Addr;
        int addrlen = sizeof(client_Addr);
        int conn_Socket = accept(server_Socket, (struct sockaddr *)&client_Addr, &addrlen);
        if (conn_Socket == -1)
        {
            continue;
        } // 若未有连接就位,迅速返回错误结果

        // 提取客户端信息
        char *client_Ip = inet_ntoa(client_Addr.sin_addr);
        unsigned short client_Port = ntohs(client_Addr.sin_port);
        printf("客户端: %s:%hu已连接!\n", client_Ip, client_Port);
        ioctl(conn_Socket, FIONBIO, 1);
        while (1)
        {

            char buf[100];
            memset(buf, '\0', 100);
            int count = read(conn_Socket, buf, 99);
            if (count == -1) // 若未有数据则返回报错
            {
                continue;
            }
            else if (count == 0)
            {
                printf("客户端: %s:%hu断开连接!\n", client_Ip, client_Port);
                close(conn_Socket);
                break;
            }
            else
            {
                printf("客户端: %s:%hu消息:%s", client_Ip, client_Port, buf);
                upper(buf);
                write(conn_Socket, buf, 99);
            }
        }
    }

    return 0;
}
客户端
include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <stdlib.h>
#include <poll.h>
#include <errno.h>
#include <fcntl.h>

unsigned short server_Port = 1234; // 服务器端口
const char *IP = "192.168.80.132"; // 服务器IP
int main()
{
    // 创建套接字
    int client_Socket = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
    struct sockaddr_in server_Addr;
    server_Addr.sin_family = AF_INET;
    server_Addr.sin_port = htons(server_Port);
    int flag = inet_aton(IP, &(server_Addr.sin_addr));
    if (!flag)
    {
        perror("IP transfer failed!\n");
    }
    flag = connect(client_Socket, (struct sockaddr *)&server_Addr, sizeof(server_Addr));//非阻塞连接
    if (flag = -1)
    {
        struct pollfd fds[1];
        fds[0].fd = client_Socket;
        fds[0].events = POLLOUT;
        int result = poll(fds, 1, 3000); // 设置检测套接字的时间
        if (result == 0)
        {
            printf("Poll timeout\n");//若仍变为可写则超时
            exit(0);
        }
        else if (result > 0)
        {
            if (fds[0].revents == POLLOUT)
                printf("Poll successfully\n");//检测套接字状态变化是否为可写
        }
        else
        {
            printf("Poll error\n");//检测出错
        }
    }

    while (1)
    {
        // 终端输入
        printf("请输入消息:");
        char buf[100];
        fgets(buf, 99, stdin);

        // input end quit!
        if (strncmp(buf, "end", 3) == 0)
        {
            printf("用户退出!\n");
            close(client_Socket);
            break;
        }

        write(client_Socket, buf, 99);
        int count = -1;
        while (count == -1)
        {
            count = read(client_Socket, buf, 99);
        }
        if (count == 0)
        {
            printf("与服务器断开连接!\n");
            close(client_Socket);
            break;
        }
        else
        {
            printf("服务端返回消息:%s", buf);
        }
    }
    return 0;
}
posted on 2023-04-23 09:59  Aspirants  阅读(358)  评论(0)    收藏  举报