热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

基本套接字编程(3)

1.IO复用我们学习了Io复用的基本知识,了解到目前支持IO复用的系统调用有select、pselect、poll、epoll。而epoll技术以其独特的优势被越来越多的应用到各大企业服务器。
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 
#include

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

3.1 server.c

#include 
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include


#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{
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{
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 
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include

#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_tn, rc;
charc, *ptr;

ptr = vptr;
for (n = 1; 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 ");
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 运行结果

服务器监听终端:


两个客户链接服务器:



GitHub源码地址


注:以上部分理论内容来自参考博客,多谢原博主!
推荐阅读
  • 本文介绍了Python高级网络编程及TCP/IP协议簇的OSI七层模型。首先简单介绍了七层模型的各层及其封装解封装过程。然后讨论了程序开发中涉及到的网络通信内容,主要包括TCP协议、UDP协议和IPV4协议。最后还介绍了socket编程、聊天socket实现、远程执行命令、上传文件、socketserver及其源码分析等相关内容。 ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 本文介绍了如何使用Express App提供静态文件,同时提到了一些不需要使用的文件,如package.json和/.ssh/known_hosts,并解释了为什么app.get('*')无法捕获所有请求以及为什么app.use(express.static(__dirname))可能会提供不需要的文件。 ... [详细]
  • 本文详细介绍了GetModuleFileName函数的用法,该函数可以用于获取当前模块所在的路径,方便进行文件操作和读取配置信息。文章通过示例代码和详细的解释,帮助读者理解和使用该函数。同时,还提供了相关的API函数声明和说明。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • 在说Hibernate映射前,我们先来了解下对象关系映射ORM。ORM的实现思想就是将关系数据库中表的数据映射成对象,以对象的形式展现。这样开发人员就可以把对数据库的操作转化为对 ... [详细]
  • eclipse学习(第三章:ssh中的Hibernate)——11.Hibernate的缓存(2级缓存,get和load)
    本文介绍了eclipse学习中的第三章内容,主要讲解了ssh中的Hibernate的缓存,包括2级缓存和get方法、load方法的区别。文章还涉及了项目实践和相关知识点的讲解。 ... [详细]
  • 本文详细介绍了Linux中进程控制块PCBtask_struct结构体的结构和作用,包括进程状态、进程号、待处理信号、进程地址空间、调度标志、锁深度、基本时间片、调度策略以及内存管理信息等方面的内容。阅读本文可以更加深入地了解Linux进程管理的原理和机制。 ... [详细]
  • 前景:当UI一个查询条件为多项选择,或录入多个条件的时候,比如查询所有名称里面包含以下动态条件,需要模糊查询里面每一项时比如是这样一个数组条件:newstring[]{兴业银行, ... [详细]
  • Go GUIlxn/walk 学习3.菜单栏和工具栏的具体实现
    本文介绍了使用Go语言的GUI库lxn/walk实现菜单栏和工具栏的具体方法,包括消息窗口的产生、文件放置动作响应和提示框的应用。部分代码来自上一篇博客和lxn/walk官方示例。文章提供了学习GUI开发的实际案例和代码示例。 ... [详细]
  • 本文讨论了clone的fork与pthread_create创建线程的不同之处。进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合。在调用系统调用fork创建一个进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性。但是二者之间的通讯需要通过专门的通讯机制,另外通过fork创建子进程系统开销很大。因此,在某些情况下,使用clone或pthread_create创建线程可能更加高效。 ... [详细]
  • 本文介绍了Python爬虫技术基础篇面向对象高级编程(中)中的多重继承概念。通过继承,子类可以扩展父类的功能。文章以动物类层次的设计为例,讨论了按照不同分类方式设计类层次的复杂性和多重继承的优势。最后给出了哺乳动物和鸟类的设计示例,以及能跑、能飞、宠物类和非宠物类的增加对类数量的影响。 ... [详细]
  • 本文讨论了在手机移动端如何使用HTML5和JavaScript实现视频上传并压缩视频质量,或者降低手机摄像头拍摄质量的问题。作者指出HTML5和JavaScript无法直接压缩视频,只能通过将视频传送到服务器端由后端进行压缩。对于控制相机拍摄质量,只有使用JAVA编写Android客户端才能实现压缩。此外,作者还解释了在交作业时使用zip格式压缩包导致CSS文件和图片音乐丢失的原因,并提供了解决方法。最后,作者还介绍了一个用于处理图片的类,可以实现图片剪裁处理和生成缩略图的功能。 ... [详细]
  • Java自带的观察者模式及实现方法详解
    本文介绍了Java自带的观察者模式,包括Observer和Observable对象的定义和使用方法。通过添加观察者和设置内部标志位,当被观察者中的事件发生变化时,通知观察者对象并执行相应的操作。实现观察者模式非常简单,只需继承Observable类和实现Observer接口即可。详情请参考Java官方api文档。 ... [详细]
author-avatar
爱笑的美美6_833
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有