6.并发服务器 以及 与之对应的客户端

该代码大致过程,客户端连接服务器,服务器接收链接后,将创建子进程。客户端从终端输入字符,字符会被送到服务器的子进程,子进程得到字符后,再将字符回馈给客户端,客户端将其显示出来。代码比较长因为加入了信号对死亡子进程的处理。

[root@liumengli net]# cat echo_server.c
#include "/programe/net/head.h"
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "signal.h"
#include "sys/types.h"
#include "unistd.h"

#define LISTENQ 10

void str_echo(int);
void install();//安装对由子进程死亡发出的SIGCHLD信号的处理,如果不做处理子进程会变成僵尸进程
void my_op(int, siginfo_t *, void *);
//IP地址被我硬编码到了代码中,端口由输入参数指定
int main(int argc, char ** argv) {
        int listenfd, connfd;
        pid_t   child_pid;
        socklen_t child_len;
        struct sockaddr_in child_socket, serv_socket;


        listenfd = socket(AF_INET, SOCK_STREAM, 0);

        bzero(&serv_socket, sizeof(serv_socket));
        serv_socket.sin_family = AF_INET;
        serv_socket.sin_addr.s_addr = htonl(INADDR_ANY);
        serv_socket.sin_port = htons(atoi(argv[1]));
        bind(listenfd, (struct sockaddr *)&serv_socket, sizeof(serv_socket));
        install();
        listen(listenfd, LISTENQ);
        for(;;) {
                child_len = sizeof(child_socket);
                connfd = accept(listenfd, (struct sockaddr *)&child_socket, &child_len);
                if(connfd == -1) //值得注意的地方
                        continue;
                if((child_pid = fork()) == 0) {
                        printf("my pid is:%d\n", getpid());
                        close(listenfd);/值得注意的地方
                        str_echo(connfd);
                        close(connfd);/值得注意的地方
                        exit(0);
                }
                close(connfd);
        }
}

void str_echo(int connfd) {
        ssize_t n;
        char buf[100];
        for(;;) {
                if((n = read(connfd, buf, 100)) == 0)
                        return;
                write(connfd, buf, n);
        }
}

void install() { //安装对死亡子进程的信号
        struct sigaction act, old_act;
        sigemptyset(&act.sa_mask);
        act.sa_flags = SA_SIGINFO;
        act.sa_sigaction = my_op;

        if(sigaction(SIGCHLD, &act, &old_act) < 0) {
                printf("install signal failed\n");
                exit(1);
        }
}

void my_op(int signum, siginfo_t * info, void * myact) {//对死亡子进程处理,防止其变成僵尸进程
        pid_t pid;
        int stat;

        pid = wait(&stat);
        printf("%d process terminated\n", pid);
        return;
}

[root@liumengli net]# cat echo_client.c

#include "/programe/net/head.h"
#include "stdio.h"
#include "stdlib.h"
#include "string.h"

int main(int argc, char ** argv) {
        int sockfd;
        struct sockaddr_in serv_socket;
        char buf[100];

        if(argc != 2) {
                printf("please input port");
                exit(1);
        }

        sockfd = socket(AF_INET, SOCK_STREAM, 0);
        bzero(&serv_socket, sizeof(serv_socket));
        serv_socket.sin_family = AF_INET;
        serv_socket.sin_port = htons(atoi(argv[1]));
        inet_pton(AF_INET, "192.168.1.235", &serv_socket.sin_addr);
        connect(sockfd, (struct sockaddr_in *)&serv_socket, sizeof(serv_socket));
        int n = read(0, buf, 100);//在linux中0是标准输入
        buf[n] = '\0';
        write(sockfd, buf, n + 1);
        read(sockfd, buf, sizeof(buf));
        printf("%s\n", buf);
        close(sockfd);
        exit(0);
}

代码不是非常难,如果有linux下信号处理和I/O操作的编程经验,这个程序不难理解。

服务器端,首先是定义自己的监听套接口,然后调用listen函数监听,主进程在accept处会由于完成链接队列中没有链接而被挂起,等客户端connect后,主进程会从accept处返回,主进程会调用fork创建一个子进程,子进程开始对链接connfd进行服务。子进程在完成服务后,调用exit死亡,死亡的子进程会想父进程发送SIGHCLD信号,此时父进程会因循环继续挂起在accept处。挂起的父进程会对信号捕获,并调用my_op函数处理信号。一个子进程就彻底处理完毕。

扫描二维码关注公众号,回复: 822818 查看本文章

客户端,在connect服务器后,进程会在read(0, buf, 100)处等待客户从键盘输入(0就是代表标准输入,默认情况下是键盘。以回车键标志输入结束)。完毕后,通过write(sockfd, buf, n+1)发送给服务器,服务器会返回数据,通过read读取服务器返回数据,并将数据打印。

几个值得注意的地方:

1. close(listenfd);/值得注意的地方
str_echo(connfd);
close(connfd);/值得注意的地方

在linux下,任何一个I/O打开,只是在第一次为这个I/O创建描述结构,之后每次的open都只是对这个描述结构引用,并给该引用计数加1,close只会将共享计数减1,等共享计数减成0以后才会将描述结构删除。子进程在创建时候会继承父进程的所有资源,包括其打开的文件和链接,因此所有的链接和文件描述结构的共享计数都会加1,监听接口listenfd共享计数和链接connfd共享计数都会被加1,子进程关闭listenfd和connfd只是减1共享计数。(貌似不自己手动close,子进程在死亡后也会减1,不过我不能确定,最好自己手动添加close)。

2.

if(connfd == -1) //值得注意的地方
                        continue;

有些系统调用可能永远阻塞系统称之为慢系统调用,也就是这类调用可能永远无法返回,比如accept,只要没有客户端connect那么这类调用就可能永远不会返回,进程将一直被阻塞,同样后面的read也是这种类型。这儿有个原则:当一个进程被慢系统调用阻塞的时候捕获到一个信号,等到信号处理程序返回时,系统调用可能返回一个EINTR错误。只所以采用可能这个词,是因为某些内核会自动重启这些调用,从而使的主程序将继续在这个系统调用处阻塞。当然不是所有内核都会这么处理,所以后面我们要加上检查语句。

猜你喜欢

转载自memorymyann.iteye.com/blog/625536