`
memorymyann
  • 浏览: 270783 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

8.并发服务器 以及 与之对应的客户端3 select函数

阅读更多

    之前代码仍然存在一些比较重要的问题,当我们客户端进程阻塞于从标准输入获取数据的时候n = read(connfd, buf, 100)。 如果出现了意外,比如说我们手动kill掉服务器对客户端的服务进程,此时服务器就会向客户端接口发送fin字节,表示服务器连接已经关闭.其意思就是服务器不会再向客户端发送数据,当然在这里进程已经被KILL,客户端阻塞在标准输入出,不会去对套接口的FIN字节做出处理,这就会引发不特定情况出现。我这里环境是fedora,当我KILL掉服务器进程后,再从客户端输入数据,仍然可以发送,并正确回显(这让我很意外,至今没找出原因)。当我再次输入时候,进程会终止(原因是第2次输入会接受SIGPIPE信号,此信号默认操作会终止进程)。

 

  为了避免以上所说的情况,我们需要有这样一个能力:如果一个或者多个I/O条件满足时,我们就要被通知到,也就是I/O复用,下面的select函数就可以帮助我们实现这种功能。(请先看代码后面的内容,如果有兴趣,再回头看代码,直接看代码比较累)。

 

[root@liumengli net]# cat chat_client.c
#include "/programe/net/head.h"
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "sys/select.h"

#define MAXSIZE 100

int main(int argc, char ** argv) {
        int sockfd;
        struct sockaddr_in serv_socket;
        int maxfdpl;
        char send[MAXSIZE], recv[MAXSIZE];

        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 *)&serv_socket, sizeof(serv_socket));
        for(;;) {
                fd_set rset;
                FD_ZERO(&rset);
                FD_SET(1, &rset);
                FD_SET(sockfd, &rset);
                maxfdpl = sockfd + 1;
                select(maxfdpl, &rset, NULL, NULL, NULL); //标记1
                if(FD_ISSET(sockfd, &rset)) {
                        int n = read(sockfd, recv, MAXSIZE);

                        if(!n) {
                                printf("server closed\n");
                                break;
                        }
                        recv[n] = '\0';
                        printf("get message from server:%s\n", recv);
                }
                if(FD_ISSET(1, &rset)) {
                        int n = read(1, send, MAXSIZE);
                        send[n] = '\0';
                        write(sockfd, send, n);
                }
        }
        close(sockfd);
        exit(0);
}

 

 

[root@liumengli net]# cat chat_server.c
#include "/programe/net/head.h"
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "unistd.h"
#include "sys/wait.h"
#include "sys/select.h"
#include "sys/time.h"

#define MAXSIZE 100
#define LISTENQ 10

int main(int argc, char ** argv) {
        int listenfd, connfd;
        socklen_t client_len;
        struct sockaddr_in client_socket, serv_socket;
        char send[MAXSIZE + 1], recv[MAXSIZE + 1];


        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));
        listen(listenfd, LISTENQ);
        client_len = sizeof(client_socket);
        connfd = accept(listenfd, (struct sockaddr *)&client_socket, &client_len);
        for(;;) {
                fd_set rest;
                FD_ZERO(&rest);
                FD_SET(1, &rest);
                FD_SET(connfd, &rest);
                int maxfdpl = 2;
                if(maxfdpl < connfd + 1)
                        maxfdpl = connfd + 1;
                int flag = select(maxfdpl, &rest, NULL, NULL, NULL); //标记2
                if(flag <= 0) {
                        printf("some error happend, sorry");
                } else {
                        if(FD_ISSET(1, &rest)) {
                                int n = read(1, send, MAXSIZE);
                                send[n] = '\0';
                                write(connfd, send, n);
                        }
                        if(FD_ISSET(connfd, &rest)) {
                                int n = read(connfd, recv, MAXSIZE);
                                if(n == 0) {
                                        printf("client closed\n");
                                        break;
                                }
                                recv[n] = '\0';
                                printf("get message:%s", recv);
                        }
                }

        }
}

 

这里我们关心的是代码

fd_set rest;

FD_ZERO(&rest);
FD_SET(1, &rest);
FD_SET(connfd, &rest);
int maxfdpl = 2;
if(maxfdpl < connfd + 1)
          maxfdpl = connfd + 1;

int flag = select(maxfdpl, &rest, NULL, NULL, NULL); //标记2

对于select函数,我们从代码的过程来解释比较简单。

首先我们定义了一个fd_set的对象rest(引用了面向对象的语句).FD_ZERO是一个宏操作,是将该对象清0(不清0会有不可预料的情况发生)。然后我们FD_SET(1, &rest)表示我们关心1号描述字,具体关心1号描述字什么,后面会看到。以此类推FD_SET(connfd, &rest)表示我们关心通信套接口connfd。暂时不看maxfdpl,后面就是 int flag = select(maxfdpl, &rest, NULL, NULL, NULL);之前我们说过rest关注1号和connfd号描述字,1号代表的是标准输入,connfd当然就是我们的通信套接口。select第2个参数的意思是关注的内容是否可读。总体解释下就是:我们现在关注标注输入文件是否可读或者通信套接口 是否可读。

 

所以上面的select语句执行后效果就是,当前进程挂起,一旦标准输入可以读,或者套接口可以读,则进程继续执行,也就是从select处返回。

 

再来总体解释下select函数,最后一个参数就是等待时间,如果时间到进程继续执行,返回值为0。倒数第2个参数,就是我们关心的描述字是否有异常情况处理,有则返回,无则挂起,返回大于0。倒数第3个参数是我们关系的描述字是否可写,可以则返回,不可以则挂起,返回大于0.倒数第4个参数则是我们关心的描述字是否可读,可以则返回,不可以则挂起,返回0,至于第一个参数,由于我们可能关心很多事情,操作系统为我们定义了1024个描述字,但我们常常用不了这么多,因此你必须告诉操作系统,你关心的描述字最大是多少,一提高效率,所以它就是你关系的描述字的最大值+1,至于+1则很好理解,因为如果你关心的是0号描述字(也就是标准输入),则你必须加1,因为数组长度是1.

 

对于套接口可读,只要满足下面条件就表示可读:

1.套接口接收缓冲区的数据大于接受缓冲区低潮限度当前值(默认是1,可以设置)。

2.链接的一方关闭(接受到了FIN的TCP链接)(这也就是为什么我们代码中要检测接受到的字符是否长度为0.个人觉得这应该属于异常情况,应该将该套接口的描述字加入到异常既倒数第2个描述字,可惜不是)。

3.如果套接口是监听套接口,则当完成链接数非0时候,就可读,因此我们可以将前面的accpet代码改成,

fd_set temp;
FD_ZERO(&temp);
FD_SET(listenfd, &temp);
int temp1 = listenfd + 1;
int flag = select(temp1, &temp, NULL, NULL, NULL);
if(flag <= 0) {
        printf("some error happen\n");
        exit(1);
}
printf("some connect to me\n");
connfd = accept(listenfd, (struct sockaddr *)&client_socket, &client_len);
printf("get connect\n");

可能有人觉得多次一举,当你要检测多个I/O接口时候,重要性就会凸显出来。代码运行时候可以看出2条打印语句很快被执行,accept处不会阻塞。当然不会,因为之前的select已经检测。

4.如果套接口有错误需要处理,此时select不阻塞,返回一个-1

 

对于网络套接口可写,满足下面情况即可:

1.套接口发送缓冲区可用字节数大于等于发送缓冲去低潮限度的当前值(默认是2048,也是可以设置的)。

2.链接写的一方关闭(和上面一样就是就是受到了FIN),此时再对此套接口写就会出现SIGPIPE信号。

3.套接口有错误需要处理。

 

再者和之前一样,进程在调用select被阻塞后,仍然会被信号唤醒(这是当然的,即使你没有捕获此信号),当然大多数信号默认操作会终止进程,但某些信号不会,比如你自己定义的,而且不终止进程的信号,此时也会从select返回,虽然信号提供了重启系统调用的操作,但不是所有的内核都对此有实现,因此在select返回时候,请务必检查返回值。再去检测你设置的fd_set.

分享到:
评论

相关推荐

    tcp并发服务器

    TCP并发服务器是网络编程中的一个重要概念,它是指服务器可以同时处理多个客户端连接请求的机制。在高并发场景下,为了高效地服务大量客户端,服务器必须具备处理并发的能力。本篇将详细探讨TCP并发服务器的工作原理...

    一个服务器和一个客户端,用来展示 网络套接字和select等函数的使用,双方可以通信.zip

    4. **多路复用技术——select函数**: - `select`函数是多路复用I/O的一种方式,它可以同时监控多个套接字,判断哪些已经准备好进行读写操作,从而提高程序的效率和并发处理能力。 - 在C#中,虽然没有直接对应的`...

    select多个客户端连接

    在IT行业中,网络编程是不可或缺的一部分,特别是在服务器端开发中,如何有效地管理多个客户端的并发连接是一项关键任务。"select"函数就是解决这个问题的一种方法,它允许程序同时监控多个文件描述符(通常为套接字...

    TDengine Windows客户端和服务器

    3. 执行SQL语句进行数据操作,如CREATE DATABASE创建数据库,INSERT INTO插入数据,以及SELECT查询数据等。 4. 在完成数据操作后,记得关闭连接以释放资源。 TDengine的一大特色是其内置的流式计算引擎,能实现数据...

    select_TCP_server.rar_select TCP_服务器 select

    在“用select实现TCP并发服务器.doc”文档中,可能会详细讲解这个过程,包括具体的代码示例,如何初始化描述符集合,如何处理读写事件,以及如何处理异常和错误情况。 需要注意的是,select模型在处理大量连接时...

    epoll函数实现多客户端并发

    在C语言网络编程中,结合`socket`、`accept`、`read`、`write`等函数,我们可以构建出基于`epoll`的高并发服务器。通常,服务器会在监听套接字上注册`EPOLLIN`事件,当新的客户端连接到达时,`epoll_wait()`会返回...

    完成端口 服务器+客户端

    IOCP服务器的核心在于创建一个或多个完成端口对象,并将套接字与之关联。当一个I/O操作完成时,系统会将结果放入完成端口,等待线程处理。服务器通常会有一个或多个工作线程循环地调用`GetQueuedCompletionStatus`...

    Win32 Socket 基于Select的服务程序

    本文将重点探讨基于Select函数的Win32 Socket服务程序设计,以及如何利用Select来管理多个并发连接。 Select函数在Winsock编程中起着核心作用,它允许程序同时监听多个套接字的状态,有效地处理I/O复用问题。Select...

    TCP并发服务器模型-多线程TCP服务器

    3. **监听连接**:通过调用select或poll函数来监听连接请求。这些函数可以监控多个套接字,当有连接请求到达时,它们会返回相应的套接字描述符。 4. **处理连接**:当有新的连接请求时,accept函数会返回一个新的套...

    以前写过的linux下tcp多客户端通信程序

    总之,这个项目展示了如何在Linux下构建一个简单的TCP服务器,以同时处理多个客户端的连接请求,以及对应的TCP客户端程序,用于与服务器进行数据交换。通过深入理解这些代码和相关网络编程概念,开发者可以进一步...

    205-ESP32_SDK开发-TCP服务器(select方式,支持多连接,高速高并发传输) - 杨奉武 - 博客园1

    select允许程序监控多个文件描述符(在TCP服务器中,通常对应客户端的套接字),等待任意一个描述符就绪(可读或可写)时进行相应处理。这种方式提高了服务器的并发能力,减少了资源浪费,提高了系统效率。 首先,...

    VC++实现文件下载(客户端和服务器)

    3. 调用`select`函数,传入读、写和错误的fd_set,以及超时时间。 4. `select`返回后,检查哪个SOCKET发生了事件,对每个事件执行相应操作(如读取、写入)。 5. 循环这个过程,直到所有数据传输完毕。 六、文件...

    epoll服务器、客户端模型

    服务器端可能需要维护一个会话池,每个连接对应一个会话,记录客户端的状态信息。客户端则可能需要解析服务器返回的数据,执行相应的业务逻辑,如登录验证、数据查询等。 总结起来,"epoll服务器、客户端模型"是一...

    多线程实现并发TCP服务器..ppt

    在多线程并发服务器中,每个连接可能对应一个独立的线程,从而实现对多个客户端请求的同时响应。 4. **Linux下的TCP并发服务器设计模式** - **多进程模式**: 当服务器接收到新的连接请求时,通过`fork()`创建...

    优化后的select多路IO服务器模型

    `client.c`则可能是客户端的实现,它模拟了与优化后的`select`服务器的交互,测试服务器的性能和正确性。 `makefile`是构建系统的一部分,它定义了编译和链接这些源文件的规则,以生成可执行的服务器和客户端程序。...

    select函数总结

    《select函数总结——深入解析与应用实践》 在操作系统中,多路复用技术是实现高并发、高效网络编程的关键。其中,`select`函数作为经典的I/O多路复用模型,广泛应用于各种网络服务程序中。本文将对`select`函数...

    select_IO_model.rar_select模型_socket select

    对应的"test5selectclient"、"test6selectclient"和"test4selectclient"文件则可能包含了客户端的实现,客户端也需要使用select模型来等待服务器的响应,或者在准备好发送数据时与服务器通信。 **应用场景**: - *...

    c语言套接字用select实现多用户连接

    3. 调用`select()`:传入待监控的描述符集合、可读集合、可写集合和异常集合,以及超时时间。 4. 处理结果:`select`返回后,检查哪些描述符准备好进行读、写或异常操作。 三、`select`实现多用户连接 在服务器端,...

    单线程单进程服务器(select版本)

    在该模型中,服务器在一个单独的线程和进程中运行,通过`select`函数来监听多个客户端的连接请求。当有新的连接或数据可读时,`select`会返回相应的文件描述符,服务器随即处理这些事件。这样的设计简化了代码结构,...

    socket_select.zip

    《基于select的IO复用:实现单服务器多客户端网络通信》 在计算机网络编程中,高效地处理多个并发连接是一项重要任务。Linux操作系统提供了一种称为`select`的系统调用,它允许程序同时监控多个文件描述符(FD),...

Global site tag (gtag.js) - Google Analytics