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

2023-05-16

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

一、为什么要使用非阻塞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。

70

四、使用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;

}

验证:

70

客户端1:

70

客户端2:

70

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

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

  • 启动jetty服务器时出现NoClassDefFoundError

    我正在尝试在码头服务器中托管我的网络应用程序 spring 我将 war 文件复制到 jetty 服务器中的 webapp 文件夹中 我并不是想嵌入jetty服务器 而是试图在jetty内托管应用程序 如tomcat 我没有安装jetty
  • 两个http请求可以合并在一起吗?如果可以的话,nodeJS服务器如何处理呢?

    昨天我做了一些关于 NodeJS 的演讲 有人问我以下问题 我们知道nodeJS是一个单线程服务器 多个请求是 到达服务器并将所有请求推送到事件循环 如果什么 两个请求同时到达服务器 服务器将如何处理 处理这种情况 我猜到了一个想法并回复如
  • 获取当前时间(以小时和分钟为单位)

    我正在尝试从系统收集信息 并且需要获取当前时间 以小时和分钟为单位 目前我有 date awk print 4 输出如下 16 18 54 怎样才能把秒数去掉呢 提供格式字符串 date H M Running man date将给出所有格
  • Kubernetes Pod 已终止 - 退出代码 137

    我需要一些关于 k8s 1 14 和在其上运行 gitlab 管道所面临的问题的建议 许多作业都会抛出退出代码 137 错误 我发现这意味着容器突然终止 集群信息 库伯内特版本 1 14 使用的云 AWS EKS 节点 C5 4xLarge
  • PyGTK+3(PyGObject)创建屏幕截图?

    我过去 3 天在 google 上搜索 如何使用 PyGTK 3 创建屏幕截图 有关于 pyqt pygtk 2 wx 和 PIL 的 gallizion 教程 顺便说一句 我不需要 scrot imlib2 imagemagick 等外部
  • Ubuntu 上的 Docker 无法连接到本地主机,但可以连接到其 IP

    我运行的是 Ubuntu 18 04 uname r 5 3 0 46 generic 我已经安装了docker docker version Docker version 19 03 8 build afacb8b7f0 我有一个简单的
  • 为所有图像添加前缀(递归)

    我有一个包含 5000 多张图像的文件夹 全部带有 JPG 扩展名 我想要做的就是递归地向所有图像添加 thumb 前缀 我发现了一个类似的问题 重命名文件和目录 添加前缀 https stackoverflow com questions
  • 为什么 Solaris 汇编器生成的机器代码与 GNU 汇编器在这里不同?

    我为 amd64 编写了这个小汇编文件 对于这个问题来说 代码的作用并不重要 globl fib fib mov edi ecx xor eax eax jrcxz 1f lea 1 rax ebx 0 add rbx rax xchg r
  • vm.dirty_ratio 和 vm.dirty_background_ratio 之间的区别?

    我目前正在试验中找到的内核参数 proc sys vm 尤其dirty ratio and dirty background ratio 内核文档对两者都有以下解释 脏背景比例 包含 以包含空闲页面的总可用内存的百分比表示 和可回收页 后台
  • 有没有比使用 backtrace() 更便宜的方法来查找调用堆栈的深度?

    我的日志记录代码使用的返回值回溯 http linux die net man 3 backtrace确定当前堆栈深度 出于漂亮的打印目的 但我可以从分析中看到这是一个相当昂贵的调用 我不认为有更便宜的方法吗 请注意 我不关心帧地址 只关心
  • 更改 Amazon RDS MYSQL Linux 服务器的 innodb_log_file_size 变量值

    我们正在使用 Amazon RDS linux 服务器作为 MYSQL 更改 my cnf 文件变量值的方法是什么 我正在尝试更改 innodb log file size 变量 您能告诉我哪一个是最好的改变方式吗 所以请帮我解决这个问题
  • 在 UNIX 时间戳 Shell/Bash 中将日期与时区转换

    我需要将日期从格式为 yyyy mm dd hh mm ss TZ 的字符串转换为 UNIX 时间 TZ 时区 到目前为止我所做的是将没有时区的 yyyy mm dd hh mm ss 格式的日期转换为时间戳 dateYMD 2019 2
  • 为什么分配大块内存会失败,而重新分配小块内存却不会失败

    这段代码的结果是x指向一块大小为 100GB 的内存 include
  • __libc_start_main 发生了什么?

    我真的很想理解从高级代码到可执行文件的步骤 但是遇到了一些困难 我写了一个空的int main C 文件并尝试通过以下方式破译反汇编objdump d 这是发生的事情 in start 设置对齐方式 将参数压入堆栈 调用 libc star
  • AMQP如何克服直接使用TCP的困难?

    AMQP如何克服直接使用TCP发送消息时的困难 或者更具体地说 在发布 订阅场景中 在 AMQP 中 有一个代理 该代理接收消息 然后完成将消息路由到交换器和队列的困难部分 您还可以设置持久队列 即使客户端断开连接 也可以为客户端保存消息
  • 使用 Shell 脚本提供密码

    我已将客户端和服务器设置为无密码登录 就像无密码登录一样 通过将服务器的 RSA 密钥复制到所有客户端的 root ssh id rsa pub 来实现 但这是我手动完成的 我喜欢使用 shell 脚本自动执行此过程 并通过脚本向计算机提供
  • 什么是接口标识符

    我有一台笔记本电脑 使用一个或多个网络适配器连接到我组织的网络 我正在尝试编写一个工具来持续监控每个网络的连接状态和连接质量 然而 我的网络知识有限 术语让我感到困惑 特别是查找所有网络适配器 有人建议我使用命令ifconfig它给了我所谓
  • 远程 ssh 命令:第一个回显输出丢失

    我试图通过 ssh 1 liner 调用在远程机器上运行多个命令 方法是将它们指定为传递给 bash c 的分号分隔字符串 它适用于某些情况 但不适用于其他情况 看一下这个 Note the echo 1 output is lost ba
  • 使用无效命令进行 fork 会导致 valgrind 中的内存泄漏

    我有以下代码 它在分叉内执行无效命令 以下代码在 valgrind 中返回内存泄漏 include
  • 将一个文件写入.c中的另一个文件

    我有一个读取文件然后将其内容复制到另一个文件的代码 我需要使其仅复制每 20 个符号 然后跳过 10 个符号 然后再次跳过 20 个符号 依此类推 我必须使用 lseek 函数 但我不知道如何将所有这些放入循环中来执行此操作 main ar

随机推荐