服务器模型、网络超时检测

服务器模型

循环服务器

每次只能处理一个客户端,当前客户端退出后,才能处理下一个客户端。
(循环处理客户端,因此不能做耗时动作。)

练习: 实现 TCP 全双工

利用进程实现

利用线程实现

并发服务器

同一时刻可以响应多个客户端的请求。

多进程实现并发(不建议)

// ser_pro.c

// cli_pro.c

多线程实现并发

每来一个客户端连接,开一个子线程来专门处理客户端的数据,实现简单,资源占用少。

// ser_thr.c

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

void *handler(void *arg){

    int fd = *(int *)arg;
    char buf[256] = {};
    while (1){
        int receriver = recv(fd, buf, sizeof(buf), 0);
        if (receriver < 0){
            perror("Failed to receive");
            return NULL;
        } else if (receriver == 0){
            printf("Client exited. \n");
            break;
        } else {
            printf("%s\n", buf);
        }
    }
    close(fd);
    pthread_exit(NULL);
}

int main(int argc, char const *argv[])
{
    if (argc != 2){
        printf("Please input %s <port>. \n", argv[0]);
        return -1;
    }
    
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0){
        perror("Failed to create a socket");
        return -1;
    }
    printf("sockfd: %d\n", sockfd);

    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(atoi(argv[1]));
    saddr.sin_addr.s_addr = inet_addr("0.0.0.0");

    if (bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0){
        perror("Failed to bind");
        return -1;
    }

    if (listen(sockfd, 6) < 0){
        perror("Failed to listen");
        return -1;
    }

    struct sockaddr_in caddr;
    socklen_t length = sizeof(caddr);

    char buf[256] = {};
    while (1){

        int accfd = accept(sockfd, (struct sockaddr *)&caddr, &length);
        if (accfd < 0){
            perror("Failed to accept");
            return -1;
        }
        printf("Client IPv4: %s\t\tport: %d\n", 
               inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port));

        pthread_t tid;
        pthread_create(&tid, NULL, handler, &accfd);
        pthread_detach(tid);

    }

    close(sockfd);    
    return 0;
}
// cli_thr.c

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

void *mythread_recv(void *arg)
{
    int fd = *((int *)arg);
    char buf[256] = {};
    while (1){
        int receriver = recv(fd, buf, sizeof(buf), 0);
        if (receriver < 0){
            perror("recv is err:");
            return NULL;
        } else {
            printf("%s\n", buf);
        }
    }
    pthread_exit(NULL);
}

int main(int argc, const char *argv[])
{
    if (argc != 3){
        printf("Please input %s <ip> <port>. \n", argv[0]);
        return -1;
    }

    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0){
        perror("Failed to create a socket");
        return -1;
    }

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

    if (connect(sockfd, (struct sockaddr *)&caddr, sizeof(caddr)) < 0){
        perror("Failed to connect");
        return -1;
    }

    pthread_t tid;
    pthread_create(&tid, NULL, mythread_recv, &sockfd);
    pthread_detach(tid);

    char buf[128] = "";
    while (1){
        fgets(buf, sizeof(buf), stdin);
        if (buf[strlen(buf)-1] == '\n')
            buf[strlen(buf)-1] = '\0';

        if (!strcmp(buf, "quit")){
            printf("Client exited. \n");
            break;
        }

        send(sockfd, buf, sizeof(buf), 0);
    }
    close(sockfd);
    return 0;
}

实现效果如下:
在这里插入图片描述

// server.c

#include "mymacro.h"

linklist_t ph;

void *server_send(void *arg){

    linklist_t p;
    char buf[256] = {};

    while (1){
        fgets(buf, sizeof(buf), stdin);
        if (buf[strlen(buf)-1] == '\n')
            buf[strlen(buf)-1] = '\0';

        p = (linklist_t)arg;
        while (p->next){
            p = p->next;
            send(p->fd, buf, sizeof(buf), 0);
        }
    }
    // pthread_exit(NULL);			// 服务器端不需要退出此线程
}

void *server_recv(void *arg){

    int fd = *(int *)arg;
    char buf[256] = {};
    while (1){
        int receriver = recv(fd, buf, sizeof(buf), 0);
        if (receriver < 0){
            perror("Failed to receive");
            return NULL;
        } else if (receriver == 0){
            DeleteFromLinkedList(ph, fd);
            printf("Client[%d] exited. \n", fd);
            break;
        } else {
            printf("Client[%d]: %s\n", fd, buf);
        }
    }
    close(fd);
    pthread_exit(NULL);
}

int main(int argc, char const *argv[])
{
    if (argc != 2){
        printf("Please input %s <port>. \n", argv[0]);
        return -1;
    }
    
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0){
        perror("Failed to create a socket");
        return -1;
    }
    // printf("sockfd: %d\n", sockfd);

    ADDRIN saddr, caddr;
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(atoi(argv[1]));
    saddr.sin_addr.s_addr = inet_addr("0.0.0.0");
    socklen_t length = sizeof(caddr);

    if (bind(sockfd, (ADDR *)&saddr, sizeof(saddr)) < 0){
        perror("Failed to bind");
        return -1;
    }

    if (listen(sockfd, 6) < 0){
        perror("Failed to listen");
        return -1;
    }

    ph = CreateLinkedList();
    head = ph;
    char buf[256] = {};
    pthread_t tid_recv, tid_send;
    pthread_create(&tid_send, NULL, server_send, head);
    pthread_detach(tid_send);

    while (1){
        int accfd = accept(sockfd, (ADDR *)&caddr, &length);
        if (accfd < 0){
            perror("Failed to accept");
            return -1;
        }
        // printf("Client IPv4: %s\t\tport: %d\n", 
        //        inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port));

        InsertIntoLinkedList(head, accfd);
        ShowLinkedList(head);

        pthread_create(&tid_recv, NULL, server_recv, &accfd);
        pthread_detach(tid_recv);
    }

    close(sockfd);    
    return 0;
}
```c
// client.c

#include "mymacro.h"

void *client_recv(void *arg)
{
    int fd = *((int *)arg);
    char buf[256] = {};
    while (1){
        int receriver = recv(fd, buf, sizeof(buf), 0);
        if (receriver < 0){
            perror("recv is err:");
            return NULL;
        } else {
            printf("Server: %s\n", buf);
        }
    }
    pthread_exit(NULL);
}

int main(int argc, const char *argv[])
{
    if (argc != 3){
        printf("Please input %s <ip> <port>. \n", argv[0]);
        return -1;
    }

    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0){
        perror("Failed to create a socket");
        return -1;
    }

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

    if (connect(sockfd, (struct sockaddr *)&caddr, sizeof(caddr)) < 0){
        perror("Failed to connect");
        return -1;
    }

    pthread_t tid;
    pthread_create(&tid, NULL, client_recv, &sockfd);
    pthread_detach(tid);

    char buf[256] = {};
    while (1){
        fgets(buf, sizeof(buf), stdin);
        if (buf[strlen(buf)-1] == '\n')
            buf[strlen(buf)-1] = '\0';

        if (!strcmp(buf, "quit")){
            printf("Client exited. \n");
            break;
        }

        send(sockfd, buf, sizeof(buf), 0);
    }
    
    close(sockfd);
    return 0;
}
// mymacro.h

#ifndef __MYMACRO_H_
#define __MYMACRO_H_

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h> 
#include <sys/socket.h> 
#include <netinet/in.h>    
#include <arpa/inet.h>
#include <netinet/ip.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include <pthread.h>
#include <fcntl.h>
#include <sys/stat.h>

#define N 32

typedef struct sockaddr_in ADDRIN;
typedef struct sockaddr ADDR;

typedef struct linkedlist{
    int fd;
    struct linkedlist *next;
} linknode_t, *linklist_t;

linklist_t head;


/* *************** 以下函数可写到一个新的.c文件中 **************** */

linklist_t CreateLinkedList(){

    linklist_t p = (linklist_t)malloc(sizeof(linknode_t));
    if (!p){
        perror("Failed to create a linked list");
        return NULL;
    }

    p->next = NULL;
    return p;
}

int InsertIntoLinkedList(linklist_t p, int data){

    linklist_t pnw = (linklist_t)malloc(sizeof(linknode_t));
    if (!pnw){
        perror("Failed to create a new node");
        return -1;
    }

    pnw->fd = data;

    while (p->next)
        p = p->next;
    
    p->next = pnw;
    pnw->next = NULL;
    return 0;
}

int DeleteFromLinkedList(linklist_t p, int data){

    while (p->next){
        if (p->next->fd == data){
            linklist_t pdel = p->next;
            p->next = pdel->next;
            free(pdel);
            pdel = NULL;
        }
        else                // 必须加,否则段错误
            p = p->next;
    }

    return 0;
}

void ShowLinkedList(linklist_t p){
    p = p->next;
    while (p){
        printf("%d ", p->fd);
        p = p->next;
    }
    putchar(10);
}

#endif

实现效果如下:
在这里插入图片描述

IO 多路复用

借助 select, poll, epoll 机制,将新连接的客户端描述符添加到描述符表中,只需要一个线程即可处理所有的客户端连接,在嵌入式开发中应用广泛,但代码复杂度较高。
( 点此跳转到 IO 多路复用 )
在这里插入图片描述

总结
在这里插入图片描述

网络超时检测

使用原因
避免进程在没有数据时无休止阻塞,当到达设定的时间, 进程从原操作返回,继续执行下面的代码。

通过函数参数设置超时

select 超时检测

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

// 无超时

	fd_set readfds, tempfds;		// 1. 先构造一张有关文件描述符的表
    FD_ZERO(&readfds);				// 2. 清空表
    FD_SET(0, &readfds);			// 3. 将关心的文件描述符添加到表中
    FD_SET(fd, &readfds);
    int maxfd = fd;

    char buf[256] = {};
    while (1){
        tempfds = readfds;			// 4. 备份表
        select(maxfd+1, &tempfds, NULL, NULL, NULL);	// 5. 调用 select 函数

        if (FD_ISSET(0, &tempfds)){						// 6. 产生事件,进行相应处理
            fgets(buf, sizeof(buf), stdin);
            if (buf[strlen(buf)-1] == '\n')
                buf[strlen(buf)-1] = '\0';
            printf("Keyboard: %s\n", buf);
        }
        if (FD_ISSET(fd, &tempfds)){
            int len = read(fd, buf, sizeof(buf));
            buf[len] = '\0';
            printf("Mouse: %s\n", buf);
        }
    }
// 添加超时

	fd_set readfds, tempfds;		// 1. 先构造一张有关文件描述符的表
    FD_ZERO(&readfds);				// 2. 清空表
    FD_SET(0, &readfds);			// 3. 将关心的文件描述符添加到表中
    FD_SET(fd, &readfds);
    int maxfd = fd;

    char buf[256] = {};
    while (1){
        tempfds = readfds;			// 4. 备份表
        struct timeval tiv = {3, 0};		// 3s + 0 ms
        int ret = select(maxfd+1, &tempfds, NULL, NULL, &tiv);	// 5. 调用 select 函数
        if (ret < 0){
            perror("Failed to select");
            return -1;
        } else if (ret == 0){
            printf("Timeout! \n");
            continue;
        } else {}

        if (FD_ISSET(0, &tempfds)){						// 6. 产生事件,进行相应处理
            fgets(buf, sizeof(buf), stdin);
            if (buf[strlen(buf)-1] == '\n')
                buf[strlen(buf)-1] = '\0';
            printf("Keyboard: %s\n", buf);
        }
        if (FD_ISSET(fd, &tempfds)){
            int len = read(fd, buf, sizeof(buf));
            buf[len] = '\0';
            printf("Mouse: %s\n", buf);
        }
    }

在这里插入图片描述

poll 超时检测

在这里插入图片描述

// 无超时

    struct pollfd fds[64];
    fds[0].fd = 0;
    fds[0].events = POLLIN;
    fds[1].fd = sockfd;
    fds[1].events = POLLIN;    
    int last = 1;

    char buf[256] = {};
    int accfd, receiver;
    struct sockaddr_in caddr;
    int length = sizeof(caddr);
    while (1){
        int getpoll = poll(fds, last+1, -1);
        if (getpoll < 0){
            perror("Failed to poll");
            return -1;
        }
        for (int i = 0; i < last+1; i++){
            if (fds[i].revents == POLLIN){
                if (fds[i].fd == 0){
                    /* 相应处理 */
                }
                if (fds[i].fd == sockfd){
                    /* 相应处理 */
                }
            }
        }
    }
// 添加超时

    struct pollfd fds[64];
    fds[0].fd = 0;
    fds[0].events = POLLIN;
    fds[1].fd = sockfd;
    fds[1].events = POLLIN;    
    int last = 1;

    char buf[256] = {};
    int accfd, receiver;
    struct sockaddr_in caddr;
    int length = sizeof(caddr);
    while (1){
        int getpoll = poll(fds, last+1, 3000);			// 设置 3000ms 超时检测
        if (getpoll < 0){
            perror("Failed to poll");
            return -1;
        } else if (getpoll == 0){
            printf("Timeout! \n");
            continue;
        } else {}
        
        for (int i = 0; i < last+1; i++){
            if (fds[i].revents == POLLIN){
                if (fds[i].fd == 0){
                    /* 相应处理 */
                }
                if (fds[i].fd == sockfd){
                    /* 相应处理 */
                }
            }
        }
    }

epoll 超时检测

在这里插入图片描述

// 无超时

	struct epoll_event ev, eves[16];

    ev.data.fd = 0;
    ev.events = EPOLLIN | EPOLLET;
    epoll_ctl(epfd, EPOLL_CTL_ADD, 0, &ev);

    ev.data.fd = sockfd;
    ev.events = EPOLLIN | EPOLLET;
    epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev);

    char buf[256] = {};
    while (1){ 
        int num = epoll_wait(epfd, eves, 16, -1);
        if (num < 0){
            perror("Failed to epwait");
            return -1;
        }
        for (int i = 0; i < num; i++){
            if (eves[i].data.fd == 0){
                /* 相应处理 */
            }
            if (eves[i].data.fd == sockfd){
                /* 相应处理 */
            }
        }
    }
// 添加超时

	struct epoll_event ev, eves[16];

    ev.data.fd = 0;
    ev.events = EPOLLIN | EPOLLET;
    epoll_ctl(epfd, EPOLL_CTL_ADD, 0, &ev);

    ev.data.fd = sockfd;
    ev.events = EPOLLIN | EPOLLET;
    epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev);

    char buf[256] = {};
    while (1){ 
        int num = epoll_wait(epfd, eves, 16, 3000);			// 设置 3000ms 超时检测
        if (num < 0){
            perror("Failed to epwait");
            return -1;
        } else if (num == 0){
            printf("Timeout! \n");
            continue;
        } else {}
        
        for (int i = 0; i < num; i++){
            if (eves[i].data.fd == 0){
                /* 相应处理 */
            }
            if (eves[i].data.fd == sockfd){
                /* 相应处理 */
            }
        }
    }

setsockopt() 设置套接字属性

#include<sys.socket.h>  
#include<sys/types.h> 
#include<sys/time.h>
int setsockopt(int sockfd, int level, int optname, void *optval, socklen_t optlen);

功能:获得/设置套接字属性
参数:
    sockfd:	套接字描述符
    level:		协议层
    optname:	选项名
    optval:		选项值
    optlen:		选项值大小指针
返回值:		成功: 0                
			失败: -1

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

端口与地址复用

// 端口与地址复用 server.c

	int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0){
        perror("Failed to create a socket");
        return -1;
    }
    printf("sockfd: %d\n", sockfd);

    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(atoi(argv[1]));
    saddr.sin_addr.s_addr = inet_addr("0.0.0.0");

	int op = 1;			// 非 0 即可
	setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &op, sizeof(op));

    if (bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0){
        perror("Failed to bind");
        return -1;
    }

超时检测,打断接下来的阻塞

	while (1){

        struct timeval tiv = {2, 500};			// 设置 2.5s 超时检测
        setsockopt(acceptfd, SOL_SOCKET, SO_RCVTIMEO, &tiv, sizeof(tiv));
        
        int recvbyte = recv(acceptfd, buf, sizeof(buf), 0);        
        if (recvbyte < 0){
            perror("Failed to receive");
            // return -1;			// 注释掉,每2.5s接收不到客户端消息,打印一次上一行代码
        } else if (recvbyte == 0){
            printf("Client exited\n");
            break;
        } else {
            if (!strcmp(buf, "quit")){
                flag = 1;
                break;
            }
            printf("%s\n", buf);
        }
    }

在这里插入图片描述

alarm() 定时器 + sigaction() 修改信号的行为

alarm() 定时器

若 alarm(n); 则 n 秒后,会有 SGIALRM信号 产生,从而终止程序。

// 程序退出

alarm(2);

while (1){
    
}
// 2s后,程序退出,打印 “闹钟” 或 "Alarm clock"

但将 alarm(n); 置于死循环中,则不会终止程序。

while (1){
    alarm(2);
}
// 死循环
// 程序退出

char buf[64] = {};
while (1){
    alarm(2);
    fgets(buf, sizeof(buf), stdin);		// 有阻塞		// 不输入任何内容
}
// 2s后,程序退出,打印 “闹钟” 或 "Alarm clock"

sigaction 修改信号的行为

#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

功能:对接收到的指定信号处理   
参数: 1. signum  信号	
      2. act:设置新行为   oldact:设置旧行为 
返回值: 成功: 0
        失败: -1


    结构体如下:  
           struct sigaction {     
                void (*sa_handler)(int); 			// 函数指针
                其他的结构体成员如mark(信号集),flag(对信号的标记)都不常用
           };

==================== 需要定义一个函数 ====================

                void handler(int sig)
                {
                    printf("timeout .....\n");
                }   

在这里插入图片描述

超时检测

#include <stdio.h>
#include <signal.h>
#include <unistd.h>

void handler(int sig){
    printf("Timeout! \n");
}

int main(int argc, char const *argv[])
{
    struct sigaction act;
    sigaction(SIGALRM, NULL, &act);     // 获取 SIGALRM信号 原来的属性
    act.sa_handler = handler;           // 修改属性
    sigaction(SIGALRM, &act, NULL);     // 写回属性

    char buf[64] = {};
    while (1){
        alarm(2);						// 2s后,产生一个 SIGALRM信号
        if (fgets(buf, sizeof(buf), stdin) == NULL){
            perror("Failed to get buffer");
            continue;
        }
        printf("Nothing. \n");
    }
    return 0;
}

运行结果如下:
在这里插入图片描述
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值