本文参考自徐晓鑫《后台开发》,记录之。

一、为什么要使用非阻塞I/O之select

初学socket的人可能不爱用select写程序,而习惯诸如connect、accept、recv/recvfrom这样的阻塞程序。

当让服务器同时为多个客户端提供一问一答服务时,很多程序员采用多线程/进程模型来解决。但是若同时响应成百上千的连接请求,无论是多进程还是多线程都会严重占据系统资源降低系统对外响应的效率。(“线程池”旨在降低创建和销毁线程的频率,“连接池”旨在尽量重用已有连接,二者都需要考虑面临的响应规模,即池的大小是有限的)。

高级程序员使用select就可以完成非阻塞方式工作的程序,它能够监视被监测文件描述符的变化情况。

使用select的事件驱动模型只用单线程(进程)执行,占用资源少,不消耗太多CPU资源,同时能为多客户端提供服务。当然select也有缺点如下:

每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大

每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大

select支持的文件描述符数量太小了,默认是1024

后面学习poll、epoll就是解决这个问题的,这个后面会了解到。

二、slect函数原型

int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval *timeout);

具体解释select的参数:

maxfdp是一个整数值,集合中所有文件描述符的范围,即所有文件描述符的最大值加1。

fd_set *readfds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的读变化的,即我们关心是否可以从这些文件中读取数据了,如果这个集合中有一个文件可读,select就会返回一个大于0的值,表示有文件可读;如果没有可读的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0;若发生错误返回负值。

fd_set *writefds是指向fd_set结构的指针,主要关心文件的写变化,即是否可写。

fd_set *errorfds用来监视文件错误异常

返回值:

正值表示准备就绪的描述符数, 0表示等待超时,负值表示select出错

三、使用select函数循环读取键盘输入

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

int main()

{

int keyboard;

int ret,i;

char c;

fd_set readfd;

struct timeval timeout;

keyboard = open("/dev/tty",O_RDONLY |O_NONBLOCK);

assert(keyboard>0);

while(1)

{

timeout.tv_sec = 5;

timeout.tv_usec = 0;

FD_ZERO(&readfd);

FD_SET(keyboard,&readfd);

ret = select(keyboard+1,&readfd,NULL,NULL,&timeout);

if(ret == -1)

perror("select error\n");

else if (ret) {

if(FD_ISSET(keyboard,&readfd)) {

i = read(keyboard,&c,1);

if('\n'== c)

continue;

printf("The input is %c\n",c);

if('q'==c)

break;

}

}

else if (ret ==0)

printf("time out\n");

}

return 0;

}

只要发现键盘输入字符,程序就输出对应字符。若超过5s不输入,打印time out。

四、使用select函数提高服务器处理能力

服务器端:

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#define DEFAULT_PORT 6666

int main( int argc, char ** argv){

int serverfd,acceptfd; /* 监听socket: serverfd,数据传输socket: acceptfd */

struct sockaddr_in my_addr; /* 本机地址信息 */

struct sockaddr_in their_addr; /* 客户地址信息 */

unsigned int sin_size, myport=6666, lisnum=10;

if ((serverfd = socket(AF_INET , SOCK_STREAM, 0)) == -1) {

perror("socket" );

return -1;

}

printf("socket ok \n");

my_addr.sin_family=AF_INET;

my_addr.sin_port=htons(DEFAULT_PORT);

my_addr.sin_addr.s_addr = INADDR_ANY;

bzero(&(my_addr.sin_zero), 0);

if (bind(serverfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr )) == -1) {

perror("bind" );

return -2;

}

printf("bind ok \n");

if (listen(serverfd, lisnum) == -1) {

perror("listen" );

return -3;

}

printf("listen ok \n");

fd_set client_fdset; /*监控文件描述符集合*/

int maxsock; /*监控文件描述符中最大的文件号*/

struct timeval tv; /*超时返回时间*/

int client_sockfd[5]; /*存放活动的sockfd*/

bzero((void*)client_sockfd,sizeof(client_sockfd));

int conn_amount = 0; /*用来记录描述符数量*/

maxsock = serverfd;

char buffer[1024];

int ret=0;

/*不断的查看是否有新的client连接;已连接的client是否有发送消息过来*/

while(1){

/*初始化文件描述符号到集合*/

FD_ZERO(&client_fdset);

/*加入服务器描述符*/

FD_SET(serverfd,&client_fdset);

/*设置超时时间*/

tv.tv_sec = 30; /*30秒*/

tv.tv_usec = 0;

/*把活动的句柄加入到文件描述符中*/

for(int i = 0; i < 5; ++i){

/*程序中Listen中参数设为5,故i必须小于5*/

if(client_sockfd[i] != 0){

FD_SET(client_sockfd[i], &client_fdset);

}

}

/*printf("put sockfd in fdset!\n");*/

/*select函数,根据返回值判断程序是否有异常*/

ret = select(maxsock+1, &client_fdset, NULL, NULL, &tv);

if(ret < 0){

perror("select error!\n");

break;

} else if(ret == 0){

printf("timeout!\n");

continue;

}

/*轮询各个(已连接上的client的)文件描述符有无可读(接收)数据,有就输出,没有或者异常时,关闭相应的client连接,并在集合里清理掉*/

for(int i = 0; i < conn_amount; ++i){

/*FD_ISSET检查client_sockfd是否可读写,>0可读写*/

if(FD_ISSET(client_sockfd[i], &client_fdset)){

printf("start recv from client[%d]:\n",i);

ret = recv(client_sockfd[i], buffer, 1024, 0);

if(ret <= 0){

printf("client[%d] close\n", i);

close(client_sockfd[i]);

FD_CLR(client_sockfd[i], &client_fdset);

client_sockfd[i] = 0;

}

else{

printf("recv from client[%d] :%s\n", i, buffer);

}

}

}

/*检查是否有新的连接,如果有,接收连接加入到client_sockfd中*/

if(FD_ISSET(serverfd, &client_fdset))

{

/*接受连接*/

struct sockaddr_in client_addr;

size_t size = sizeof(struct sockaddr_in);

int sock_client = accept(serverfd, (struct sockaddr*)(&client_addr), (unsigned int*)(&size));

if(sock_client < 0){

perror("accept error!\n");

continue;

}

/*把连接加入到文件描述符集合中*/

if(conn_amount < 5)

{

client_sockfd[conn_amount++] = sock_client;

bzero(buffer,1024);

strcpy(buffer, "this is server! welcome!\n");

send(sock_client, buffer, 1024, 0);

printf("new connection client[%d] %s:%d\n", conn_amount, inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));

bzero(buffer,sizeof(buffer));

ret = recv(sock_client, buffer, 1024, 0);

if(ret < 0){

perror("recv error!\n");

close(serverfd);

return -1;

}

printf("recv : %s\n",buffer);

//更新maxsock,因为下一次进入while循环调用时,需要传当前最大的fd值+1给select函数

if(sock_client > maxsock){

maxsock = sock_client;

}

else{

printf("max connections!!!quit!!\n");

break;

}

}

}

}

//最后,把已连接上的clent的fd和server自身的fd都关闭

for(int i = 0; i < 5; ++i){

if(client_sockfd[i] != 0){

close(client_sockfd[i]);

}

}

close(serverfd);

return 0;

}

客户端:

#include

#include

#include

#include

#include

#include

#include

#include

#include

#define DEFAULT_PORT 6666

int main( int argc, char * argv[]){

int connfd = 0;

int cLen = 0;

struct sockaddr_in client;

if(argc < 2){

printf(" Uasge: clientent [server IP address]\n");

return -1;

}

client.sin_family = AF_INET;

client.sin_port = htons(DEFAULT_PORT);

client.sin_addr.s_addr = inet_addr(argv[1]);

connfd = socket(AF_INET, SOCK_STREAM, 0);

if(connfd < 0){

perror("socket" );

return -1;

}

if(connect(connfd, (struct sockaddr*)&client, sizeof(client)) < 0){

perror("connect" );

return -1;

}

char buffer[1024];

bzero(buffer,sizeof(buffer));

recv(connfd, buffer, 1024, 0);

printf("recv : %s\n", buffer);

bzero(buffer,sizeof(buffer));

strcpy(buffer,"this is client!\n");

send(connfd, buffer, 1024, 0);

while(1){

bzero(buffer,sizeof(buffer));

scanf("%s",buffer);

int p = strlen(buffer);

buffer[p] = '\0';

send(connfd, buffer, 1024, 0);

printf("i have send buffer\n");

}

close(connfd);

return 0;

}

验证:

客户端1:

客户端2:

linux tcp ip c,Linux下TCP/IP编程--TCP实战(select)相关推荐

  1. 编写tcp服务器发送hex格式_Android网络编程-TCP/IP协议

    在Android网络编程-计算机网络基础一文中得知,IP协议属于网络层,TCP.UDP协议属于传输层. IP协议是TCP/IP协议族的动力,它为上层协议提供无状态.无连接.不可靠的服务. TCP协议是 ...

  2. python tcp不用循环监听_网络编程: TCP

    1. IP 地址 概念: 标识网络中设备的地址(需要联网才有没有联网, 是没有这个地址) 表现形式: ipv4 目前主要使用的, 点分十进制的格式,(192.168.3.43) 分为 4 段, 每段的 ...

  3. android设计ip输入框,Android下自定义IP控件

    在Android原生控件中,没有IP输入控件,于是自定义一个,如果有bug或者代码上的问题,欢迎大家提出,先谢谢大家. 控件代码如下: import android.content.Context; ...

  4. Linux——Linux驱动之设备树下platform总线驱动编写实战(手把手教你设备树下platform总线利用GPIO控制蜂鸣器完整实现过程)

    [系列专栏]:博主结合工作实践输出的,解决实际问题的专栏,朋友们看过来! <QT开发实战> <嵌入式通用开发实战>

  5. 多线程 python tcp 图片_Python第四周之网络编程TCP (服务器/客户端; 线程下载图片;线程版服务器和客户端(单人、多人)) UDP...

    # 网络编程 # 计算机网络, 把多台独立自主的计算机,连接到网络,实现资源的共享 # Internet网,(互联网)eniac 1946美国大学第一台电子计算机 # # 一个TCP报文除了包含要传输 ...

  6. linux socket ip层配置,Linux下Socket通信(TCP实现)

    近期在做的项目中,涉及到了进程间数据传输,系统的原本实现是通过管道,但是原有的实现中两个进程是在同一台机器,而且两个进程的关系为父子关系,而我们要做的是将其中一个进程移植到服务器上,因此两个进程要分开 ...

  7. linux 协议栈之socket,Linux TCP/IP 协议栈之 Socket 的实现分析(一)

    内核版本:2.6.37 参考[作者:kendo的文章(基于内涵版本2.6.12)] 第一部份 Socket套接字的创建 socket 并不是 TCP/IP协议的一部份. 从广义上来讲,socket 是 ...

  8. Linux Kernel TCP/IP Stack|Linux网络硬核系列

    大家好,我是Alex,今天给大家介绍Linux网络技术中最核心的部分--TCP/IP协议栈 . 我们先看一下抽象的网络协议栈模型 TCP/IP四层(参考)模型 再按分层思想看Linux内核协议栈实现框 ...

  9. Linux 网络编程——TCP/IP 数据包格式解析

    图中括号中的数字代表的是当前域所占的空间大小,单位是bit位. 黄色的是数据链路层的头部,一共14字节 绿色的部分是IP头部,一般是20字节 紫色部分是TCP头部,一般是20字节 最内部的是数据包内容 ...

  10. Linux下编写UDP/TCP版本的服务器和客户端的流程

    Linux下编写UDP/TCP版本的服务器和客户端的流程 文章目录 Linux下编写UDP/TCP版本的服务器和客户端的流程 一:UDP和TCP的区别 二.UDP编写服务器的步骤 三.UDP编写客户端 ...

最新文章

  1. Netbackup Status code 6解决思路
  2. vue修改节点class_Vue2.0 源码解读系列 来自 Vue 的神秘礼盒
  3. 《数学之美》——第三章 个人笔记
  4. 办公自动化-演练-从A表中提取数据整合到B表中-0223
  5. SQLyog下载地址—Mysql的可视化(建议收藏)
  6. 2019年9月全国程序员工资统计(参考)
  7. 爬虫日记(71):用OCR来对抗字体反爬
  8. 三大邮箱品牌:网易,腾讯,阿里说明
  9. java解压jar包的方法_Java 打包成jar包 和 解压jar包
  10. vue3和vue2不同点总结
  11. 如何将flv格式的视频转换为mp4格式
  12. 【VS】使用VS查看源代码
  13. abap获取日期_ABAP 日期时间函数
  14. DLS 深度受限搜索 狼羊 过河 问题 python 实现
  15. Qt安装包官方下载地址
  16. 殊途同归的两种角度理解岭回归(内含有sklearn例子)
  17. win10下loadrunner11安装与破解
  18. ps怎么给图片加透明边框
  19. h5页面跳转到微信小程序之利用URL Scheme接口
  20. 数字化时代,小程序平台促进银行线上金融业务发展

热门文章

  1. ActiveBpel部署运行BPEL流程实例
  2. C程序设计语言上机13,《高级语言程序设计》北大上机试题(十三)
  3. hadoop主节点切换_hadoop2.0 HA的主备自动切换
  4. zkServer.cmd 闪退
  5. 统计指定目录下的视频时长
  6. python django flask介绍_django和flask哪个值得研究学习
  7. sklearn机器学习实例
  8. 什么是html的混杂模式_HTML的完整形式是什么?
  9. python 示例_Python使用示例设置add()方法
  10. 定义整型数组_C++数组的定义与初始化(学习笔记:第6章 01)