基本套接字编程(3) -- select篇

时间:2022-04-19 05:15:02

1. I/O复用

我们学习了I/o复用的基本知识,了解到目前支持I/O复用的系统调用有select、pselect、poll、epoll。而epoll技术以其独特的优势被越来越多的应用到各大企业服务器。(后面将有poll & epoll单独学习笔记)

基本概念

IO多路复用是指内核一旦发现进程指定的一个或者多个IO条件准备读取,它就通知该进程。IO多路复用适用如下场合:

(1)当客户处理多个描述字时(一般是交互式输入和网络套接口),必须使用I/O复用。

(2)当一个客户同时处理多个套接口时,而这种情况是可能的,但很少出现。
(3)如果一个TCP服务器既要处理监听套接口,又要处理已连接套接口,一般也要用到I/O复用。
(4)如果一个服务器即要处理TCP,又要处理UDP,一般要使用I/O复用。

(5)如果一个服务器要处理多个服务或多个协议,一般要使用I/O复用。



与多进程和多线程技术相比,I/O多路复用技术的最大优势是系统开销小,系统不必创建进程/线程,也不必维护这些进程/线程,从而大大减小了系统的开销。

2. select技术

select()函数确定一个或多个套接口的状态,本函数用于确定一个或多个套接口的状态,对每一个套接口,调用者可查询它的可读性、可写性及错误状态信息,用fd_set结构来表示一组等待检查的套接口,在调用返回时,这个结构存有满足一定条件的套接口组的子集,并且select()返回满足条件的套接口的数目。

2.1函数原型

该函数准许进程指示内核等待多个事件中的任何一个发送,并只在有一个或多个事件发生或经历一段指定的时间后才唤醒。函数原型如下:
#include <sys/select.h>
#include <sys/time.h> int select(int maxfdp1,fd_set *readset,fd_set *writeset,fd_set *exceptset,const struct timeval *timeout)
返回值:就绪描述符的数目,超时返回0,出错返回-1

函数参数介绍如下:

(1)第一个参数maxfdp1指定待测试的描述字个数,它的值是待测试的最大描述字加1(因此把该参数命名为maxfdp1),描述字0、1、2...maxfdp1-1均将被测试。因为文件描述符是从0开始的。

(2)中间的三个参数readset、writeset和exceptset指定我们要让内核测试读、写和异常条件的描述字。如果对某一个的条件不感兴趣,就可以把它设为空指针。struct fd_set可以理解为一个集合,这个集合中存放的是文件描述符,可通过以下四个宏进行设置:

          void FD_ZERO(fd_set *fdset);           //清空集合

          void FD_SET(int fd, fd_set *fdset);   //将一个给定的文件描述符加入集合之中

          void FD_CLR(int fd, fd_set *fdset);   //将一个给定的文件描述符从集合中删除

          int FD_ISSET(int fd, fd_set *fdset);   // 检查集合中指定的文件描述符是否可以读写 

(3)timeout告知内核等待所指定描述字中的任何一个就绪可花多少时间。其timeval结构用于指定这段时间的秒数和微秒数。

         struct timeval{

                   long tv_sec;   //seconds

                   long tv_usec;  //microseconds

          };

这个参数有三种可能:

(1)永远等待下去:仅在有一个描述字准备好I/O时才返回。为此,把该参数设置为空指针NULL。

(2)等待一段固定时间:在有一个描述字准备好I/O时返回,但是不超过由该参数所指向的timeval结构中指定的秒数和微秒数。

(3)根本不等待:检查描述字后立即返回,这称为轮询。为此,该参数必须指向一个timeval结构,而且其中的定时器值必须为0。

有关select更加详细的讲解请参考《Unix网络编程 -- 卷一》第六章 Page127 ~ 142;

2.2 select原理流程图


基本套接字编程(3) -- select篇

3. TCP回射程序实例

本例是基本套接字编程(1) -- tcp篇中回射程序的改写,其中server端采用select技术,实现I/O复用,可同时为多个客户程序服务!

3.1 server.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <time.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <fcntl.h> #define PORT 8888
#define MAX_LINE 2048
#define LISTENQ 20 int main(int argc , char **argv)
{
int i, maxi, maxfd, listenfd, connfd, sockfd; int nready , client[FD_SETSIZE]; ssize_t n, ret; fd_set rset , allset; char buf[MAX_LINE]; socklen_t clilen; struct sockaddr_in servaddr , cliaddr; /*(1) 得到监听描述符*/
listenfd = socket(AF_INET , SOCK_STREAM , 0); /*(2) 绑定套接字*/
bzero(&servaddr , sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(PORT); bind(listenfd , (struct sockaddr *)&servaddr , sizeof(servaddr)); /*(3) 监听*/
listen(listenfd , LISTENQ); /*(4) 设置select*/
maxfd = listenfd;
maxi = -1;
for(i=0 ; i<FD_SETSIZE ; ++i)
{
client[i] = -1;
}//for
FD_ZERO(&allset);
FD_SET(listenfd , &allset); /*(5) 进入服务器接收请求死循环*/
while(1)
{
rset = allset;
nready = select(maxfd+1 , &rset , NULL , NULL , NULL); if(FD_ISSET(listenfd , &rset))
{
/*接收客户端的请求*/
clilen = sizeof(cliaddr); printf("\naccpet connection~\n"); if((connfd = accept(listenfd , (struct sockaddr *)&cliaddr , &clilen)) < 0)
{
perror("accept error.\n");
exit(1);
}//if printf("accpet a new client: %s:%d\n", inet_ntoa(cliaddr.sin_addr) , cliaddr.sin_port); /*将客户链接套接字描述符添加到数组*/
for(i=0 ; i<FD_SETSIZE ; ++i)
{
if(client[i] < 0)
{
client[i] = connfd;
break;
}//if
}//for if(FD_SETSIZE == i)
{
perror("too many connection.\n");
exit(1);
}//if FD_SET(connfd , &allset);
if(connfd > maxfd)
maxfd = connfd;
if(i > maxi)
maxi = i; if(--nready < 0)
continue;
}//if for(i=0; i<=maxi ; ++i)
{
if((sockfd = client[i]) < 0)
continue;
if(FD_ISSET(sockfd , &rset))
{
/*处理客户请求*/
printf("\nreading the socket~~~ \n"); bzero(buf , MAX_LINE);
if((n = read(sockfd , buf , MAX_LINE)) <= 0)
{
close(sockfd);
FD_CLR(sockfd , &allset);
client[i] = -1;
}//if
else{
printf("clint[%d] send message: %s\n", i , buf);
if((ret = write(sockfd , buf , n)) != n)
{
printf("error writing to the sockfd!\n");
break;
}//if
}//else
if(--nready <= 0)
break;
}//if
}//for
}//while
}

3.2 client.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <time.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <fcntl.h> #define PORT 8888
#define MAX_LINE 2048 int max(int a , int b)
{
return a > b ? a : b;
} /*readline函数实现*/
ssize_t readline(int fd, char *vptr, size_t maxlen)
{
ssize_t n, rc;
char c, *ptr; ptr = vptr;
for (n = 1; n < maxlen; n++) {
if ( (rc = read(fd, &c,1)) == 1) {
*ptr++ = c;
if (c == '\n')
break; /* newline is stored, like fgets() */
} else if (rc == 0) {
*ptr = 0;
return(n - 1); /* EOF, n - 1 bytes were read */
} else
return(-1); /* error, errno set by read() */
} *ptr = 0; /* null terminate like fgets() */
return(n);
} /*普通客户端消息处理函数*/
void str_cli(int sockfd)
{
/*发送和接收缓冲区*/
char sendline[MAX_LINE] , recvline[MAX_LINE];
while(fgets(sendline , MAX_LINE , stdin) != NULL)
{
write(sockfd , sendline , strlen(sendline)); bzero(recvline , MAX_LINE);
if(readline(sockfd , recvline , MAX_LINE) == 0)
{
perror("server terminated prematurely");
exit(1);
}//if if(fputs(recvline , stdout) == EOF)
{
perror("fputs error");
exit(1);
}//if bzero(sendline , MAX_LINE);
}//while
} /*采用select的客户端消息处理函数*/
void str_cli2(FILE* fp , int sockfd)
{
int maxfd;
fd_set rset;
/*发送和接收缓冲区*/
char sendline[MAX_LINE] , recvline[MAX_LINE]; FD_ZERO(&rset);
while(1)
{
/*将文件描述符和套接字描述符添加到rset描述符集*/
FD_SET(fileno(fp) , &rset);
FD_SET(sockfd , &rset);
maxfd = max(fileno(fp) , sockfd) + 1;
select(maxfd , &rset , NULL , NULL , NULL); if(FD_ISSET(fileno(fp) , &rset))
{
if(fgets(sendline , MAX_LINE , fp) == NULL)
{
printf("read nothing~\n");
close(sockfd); /*all done*/
return ;
}//if
sendline[strlen(sendline) - 1] = '\0';
write(sockfd , sendline , strlen(sendline));
}//if if(FD_ISSET(sockfd , &rset))
{
if(readline(sockfd , recvline , MAX_LINE) == 0)
{ perror("handleMsg: server terminated prematurely.\n");
exit(1);
}//if if(fputs(recvline , stdout) == EOF)
{
perror("fputs error");
exit(1);
}//if
}//if
}//while
} int main(int argc , char **argv)
{
/*声明套接字和链接服务器地址*/
int sockfd;
struct sockaddr_in servaddr; /*判断是否为合法输入*/
if(argc != 2)
{
perror("usage:tcpcli <IPaddress>");
exit(1);
}//if /*(1) 创建套接字*/
if((sockfd = socket(AF_INET , SOCK_STREAM , 0)) == -1)
{
perror("socket error");
exit(1);
}//if /*(2) 设置链接服务器地址结构*/
bzero(&servaddr , sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(PORT);
if(inet_pton(AF_INET , argv[1] , &servaddr.sin_addr) < 0)
{
printf("inet_pton error for %s\n",argv[1]);
exit(1);
}//if /*(3) 发送链接服务器请求*/
if(connect(sockfd , (struct sockaddr *)&servaddr , sizeof(servaddr)) < 0)
{
perror("connect error");
exit(1);
}//if /*调用普通消息处理函数*/
str_cli(sockfd);
/*调用采用select技术的消息处理函数*/
//str_cli2(stdin , sockfd);
exit(0);
}

3.3 运行结果

服务器监听终端:

基本套接字编程(3) -- select篇

两个客户链接服务器:

基本套接字编程(3) -- select篇




注:以上部分理论内容来自参考博客,多谢原博主!

基本套接字编程(3) -- select篇的更多相关文章

  1. 基本套接字编程(7) -- udp篇

    1. UDP概述         UDP 是User Datagram Protocol的简称, 中文名是用户数据报协议,是OSI(Open System Interconnection,开放式系统互 ...

  2. 基本套接字编程(1) -- tcp篇

    1. Socket简介 Socket是进程通讯的一种方式,即调用这个网络库的一些API函数实现分布在不同主机的相关进程之间的数据交换. 几个定义: (1)IP地址:即依照TCP/IP协议分配给本地主机 ...

  3. 网络编程&lbrack;第二篇&rsqb;基于udp协议的套接字编程

    udp协议下的套接字编程 一.udp是无链接的    不可靠的 而上篇的tcp协议是可靠的,会有反馈信息来确认信息交换的完成与否 基于udp协议写成的服务端与客户端,各司其职,不管对方是否接收到信息, ...

  4. 入门级:怎么使用C&num;进行套接字编程(一)

    翻译一篇简单的文章学习下基础,此文针对我等对socket只听说未尝试阶段的水平. How to C# Socket programming C#通过他的命名空间像System.Net和System.N ...

  5. 【unix网络编程第三版】阅读笔记(二):套接字编程简介

    unp第二章主要将了TCP和UDP的简介,这些在<TCP/IP详解>和<计算机网络>等书中有很多细致的讲解,可以参考本人的这篇博客[计算机网络 第五版]阅读笔记之五:运输层,这 ...

  6. Python套接字编程(1)——socket模块与套接字编程

    在Python网络编程系列,我们主要学习以下内容: 1. socket模块与基本套接字编程 2. socket模块的其他网络编程功能 3. SocketServer模块与简单并发服务器 4. 异步编程 ...

  7. 非阻塞套接字编程, IO多路复用&lpar;epoll&rpar;

    非阻塞套接字编程: server端 import socket server = socket.socket() server.setblocking(False) server.bind(('', ...

  8. socket套接字编程 HTTP协议

    socket套接字编程  套接字介绍  1. 套接字 : 实现网络编程进行数据传输的一种技术手段  2. Python实现套接字编程:import  socket  3. 套接字分类 >流式套接 ...

  9. Linux Socket 原始套接字编程

    对于linux网络编程来说,可以简单的分为标准套接字编程和原始套接字编程,标准套接字主要就是应用层数据的传输,原始套接字则是可以获得不止是应用层的其他层不同协议的数据.与标准套接字相区别的主要是要开发 ...

随机推荐

  1. MINIX3 导读分析

    一个操作系统的分析是属于一个非常庞大的工程,操作系统就像是一个人造的 人,每一个模块想完全发挥功效,很有可能需要很多模块的支持才能够实现.所 以在分析 MINIX3 时,我认为同时看多个模块对于理解 ...

  2. linux框架之ibus

    框架与具体输入法安装 ibus是一个框架,安装好ibus框架后,只需要安装ibus平台下具体的输入法即可,海风或极点五笔,然后注销当前账户,重新登录,便可添加新安装的输入法 [root@localho ...

  3. Spark如何解决常见的Top N问题

    需求   假设我们有一张各个产品线URL的访问记录表,该表仅仅有两个字段:product.url,我们需要统计各个产品线下访问次数前10的URL是哪些?   解决方案   (1)模拟访问记录数据   ...

  4. WCF 服务端异常封装

    通常WCF服务端异常的详细信息只有在调试环境下才暴露出来,但我目前有需求需要将一部分异常的详细信息传递到客户端,又需要保证一定的安全性. 最简单的办法当然是在服务端将异常捕获后,序列化传给客户端,但这 ...

  5. CCF-201803-2 碰撞的小球

    问题描述 数轴上有一条长度为L(L为偶数)的线段,左端点在原点,右端点在坐标L处.有n个不计体积的小球在线段上,开始时所有的小球都处在偶数坐标上,速度方向向右,速度大小为1单位长度每秒.当小球到达线段 ...

  6. Linux下mysql开机自启动

    1,cd /etc/init.d/ 2,chmod +x mysql 3,chkconfig add mysql 4,chkconfig --list             显示服务列表 如果看到m ...

  7. Git实操

    使用git首先要理解工作区(working).暂存区(stage或者index).和版本库(repo区),很多命令都是和这三个概念相关的. git init 初始化git仓库,会生成默认的.git文件 ...

  8. centos7配置固定ip

    查看本机gateway netstat -rn (以0.0.0.0开始的行的gateway是默认网关) vi /etc/sysconfig/network-scripts/ifcfg-enp0s3 T ...

  9. mooctest项目总结 【转载】

    原文链接 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3 ...

  10. Android应用耗电量统计,无需USB连接

    Android应用耗电量统计一直是一个很头疼的问题,手工统计耗时太长,自动化统计又不是非常精准(执行自动化代码需要通过USB连接,而USB又会充电,这就造成统计数据不准).后来从前辈那里得知可以通过a ...