Linux系统编程_高并发服务器

  • 多进程并发
  • 多线程并发
  • 多路I\O转接
    select
    poll
    epoll

多进程并发服务器

  • 设计思路:
    1)服务器阻塞接收若干个客户端的连接请求,一旦有一个客户端connect,则服务器端fork一个子进程与之连接。
    2)在服务器端,子进程的任务是接收并处理客户端的数据,而父进程的任务是监听新的客户端请求(accept),创建子进程,并负责回收子进程(可通过处理SIGCHLD信号的方式)。
  • 示例程序如下:
//server.c
#include <stdio.h>
#include <ctype.h>
#include <arpa/inet.h>
#include <signal.h>
#include <unistd.h>
#include <strings.h>
#include <stdlib.h>
#include <sys/wait.h>
#include "wrap.h"

#define SERV_PORT 6666 
//#define SERV_IP "127.0.0.1"

void handle_child(int signo) {
   
        while (waitpid(0, NULL, WNOHANG) > 0); 

        return ;
}

int main() {
   
        int lfd, cfd;
        struct sockaddr_in serv_addr, clie_addr;
        socklen_t clie_addr_len;
        pid_t pid;
        int n, i;
        char buf[BUFSIZ], clie_IP[BUFSIZ];

        lfd = Socket(AF_INET, SOCK_STREAM, 0);

        bzero(&serv_addr, sizeof(serv_addr));
        serv_addr.sin_family = AF_INET;
        serv_addr.sin_port = htons(SERV_PORT); //convert to net bytes order
        serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
        //inet_pton(AF_INET, (const char *)SERV_IP, &serv_addr.sin_addr.s_addr);
        Bind(lfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));

        Listen(lfd, 128);

        while(1) {
   
                clie_addr_len = sizeof(clie_addr);
                cfd = Accept(lfd, (struct sockaddr *)&clie_addr, &clie_addr_len);
                printf("client IP: %s, port: %d\n",
                        inet_ntop(AF_INET, &clie_addr.sin_addr.s_addr, clie_IP, sizeof(clie_IP)), //inet_ntop returns the third argument
                        ntohs(clie_addr.sin_port)); //convert net bytes order to host bytes order

                pid = fork();
                if (pid < 0) {
   
                        perror("fork error");
                        exit(1);
                } else if (pid == 0) {
    //child
                        close(lfd); //lfd is useless for child now
                        break; //get out of the loop to go on
                } else if (pid > 0) {
    //parent
                        close(cfd); //cfd is useless for parent now
                        signal(SIGCHLD, handle_child); //wait for children's end
                }
        }

        if (pid == 0) {
    //child
                while (1) {
   
                        n = Read(cfd, buf, sizeof(buf));
                        if (n == -1) {
    //other errors except for signal interruption
                                perror("read error");
                                exit(1);
                        } else if (n == 0) {
    //client close, read over
                                close(cfd);
                                return 0;
                        } else {
    //loop to handle data
                                for(i = 0; i < n; i++)
                                        buf[i] = toupper(buf[i]);
                                Write(cfd, buf, n);
                                Write(STDOUT_FILENO, buf, n); //print to screen
                        }
                }
        }

        return 0;
}

多线程并发服务器

  • 思路与多进程并发相同,区别仅在于将多进程中的创建子进程改为多线程中的创建子线程。
  • 示例程序如下:
//server.c
#include <stdio.h>
#include <fcntl.h>
#include <ctype.h>
#include <pthread.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>

#include "wrap.h"

#define SERV_PORT 7777
#define SERV_IP "127.0.0.1"
#define MAXLINE 8192

struct s_info {
    //bind a client's sockaddr and its file descriptor
        struct sockaddr_in clie_addr;
        int cfd;
};

void *child_thrd(void *arg) {
    //start routine of child thread
        int n, i;
        struct s_info *ts = (struct s_info *)arg;
        char buf[MAXLINE];
        char str[INET_ADDRSTRLEN]; //cmd:"[+d": #define INET_ADDRSTRLEN 16

        while (1) {
   
                n = Read(ts->cfd, buf, MAXLINE);
                if (n == 0) {
    //client close, read over
                        printf("the client %d closed ...\n", ts->cfd);
                        break; //get out of the loop and close cfd
                }
                printf("received from client IP: %s at port: %d, thread: %lu\n",
                        inet_ntop(AF_INET, &(*ts).clie_addr.sin_addr, str, sizeof(str)),
                        ntohs((*ts).clie_addr.sin_port),
                        pthread_self());

                for (i = 0; i < n; i++)
                        buf[i] = toupper(buf[i]);
                Write(ts->cfd, buf, n);
                Write(STDOUT_FILENO, buf, n);
        }
        Close(ts->cfd); //if n == 0
        return (void *)0;
}

int main() {
   
        int lfd, cfd;
        struct sockaddr_in serv_addr, clie_addr;
        socklen_t clie_addr_len;
        pthread_t tid;
        int i = 0;
        struct s_info ts[256]; //max number of threads to be created

        lfd = Socket(AF_INET, SOCK_STREAM, 0);

        bzero(&serv_addr, sizeof(serv_addr));
        serv_addr.sin_family = AF_INET;
        serv_addr.sin_port = htons(SERV_PORT);
        //serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
        inet_pton(AF_INET, (const char *)SERV_IP, &serv_addr.sin_addr.s_addr);
        Bind(lfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));

        Listen(lfd, 128);
        printf("Accepting client connect ...\n");

        while (1) {
   
                bzero(&clie_addr, sizeof(clie_addr));
                clie_addr_len = sizeof(clie_addr);
                cfd = Accept(lfd, (struct sockaddr *)&clie_addr, &clie_addr_len);
                ts[i].clie_addr = clie_addr;
                ts[i].cfd = cfd;

                // error handling if numbers of new threads is up to the limit

                pthread_create(&tid, NULL, child_thrd, (void *)&ts[i]);
                pthread_detach(tid); 
                //child thread detachs(in case that a zombie thread is created), no need for pthread_join
                i++;
        }

        return 0;
}
  • 但是,不论是多进程版还是多线程版的并发服务器模型,它们的问题都是无法处理大量客户端的连接和数据处理的请求,因为每当有一个新的客户端发起连接请求,服务器端都要创建一个子进程或子线程与之对应,来响应这个客户端。而一个进程或一个线程均要占用一定的CPU资源,具体地说,当有大量的子进程或子线程存在时,CPU要不停地在它们之间切换,这要消耗大量的CPU资源。因此,更有效的方法是借助多路I\O转接。

多路I\O转接

多路I\O转接服务器的主旨思想是,不再由应用程序服务器端自己监听客户端的连接请求,取而代之由内核代替监听。一旦内核监听到来自客户端的事件,它就会通知服务器端来处理,从而将服务器端从阻塞监听中解放出来。

select

  • 一旦调用了select函数,该函数会驱使内核帮助服务器监听客户端请求,因此可以笼统地说是使用select函数来监听客户端。
  • int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout)
    参数1:nfds为监控的文件描述符集里最大文件描述符加1,因为此参数会告诉内核检测前多少个文件描述符
    参数2:readfds为监控有读数据到达的文件描述符集合,传入传出参数
    参数3:writefds为监控写数据到达的文件描述符集合,传入传出参数
    参数4:exceptfds为监控异常发生到达的文件描述符集合,传入传出参数
    上述三个监控事件集中不关心的事件集可传NULL
    参数5:timeout为定时阻塞监控时间:NULL,永远等下去;设置timeval,等待固定时间;设置timeval里时间均为0,检查描述字后立即返回,轮询
struct timeval {
   
	long tv_sec; /* seconds */
	long tv_usec; /* microseconds */
};
  • 另有四个配套函数:
void FD_CLR(int fd, fd_set *set);//把文件描述符集合里fd清0
int  FD_ISSET(int fd, fd_set *set);//测试文件描述符集合里fd是否置1
void FD_SET(int fd, fd_set *set);//把文件描述符集合里fd数据位置1
void FD_ZERO(fd_set *set);//把文件描述符集合里所有位清0,常用于初始化
  • 示例程序:
//server.c
#include <sys/socket.h>
#include <stdlib.h>
#include <unistd.h>
#include <ctype.h>
#include <sys/select.h>
#include <arpa/inet.h>
#include <strings.h>
#include <stdio.h>
#include "wrap.h"

#define SERV_PORT 7777 
#define SERV_IP "127.0.0.1"

#define MAXLINE 4096

int main() {
   
        int lfd, cfd, maxfd, sockfd;
        struct sockaddr_in serv_addr, clie_addr;
        socklen_t clie_addr_len;
        int client[FD_SETSIZE]; //restore fds having connection with server
        fd_set rset; //read file descriptor set
		fd_set allset; //listening set of select function(backup set of rset)
        int nready; //return value of select
        char buf[MAXLINE], str[INET_ADDRSTRLEN];
        int maxi, i, j;
        int n
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值