0
点赞
收藏
分享

微信扫一扫

Simple Linux C Socket (06): select

概述

前面几篇讲述了Linux C网络编程的基础入门和提升,实现了多进程网络编程和多线程网络编程,这两者都适用于客户端量比较小(几十)、或业务不太复杂(简单的数据交互)的场景(不知道理解的对不对)

当并发量提升时,会出现系统资源占用过大(CPU使用率过大、内存占用过多),然后需要考虑进程间通信或线程间资源调度的情况,提升程序复杂性。

此时可考虑使用Linux C提供的IO多路复用技术,即select/poll/epoll。IO多路复用可实现单进程(线程)处理多并发量的业务场景,且不会占用太高的系统资源(类似于一个熟练工,工作效率等价于多个普通工人),且其是单进程(线程)的,可以不用考虑并发编程的各种问题(通信、资源调度、锁等等)(当然,可以使用多进程(线程)+IO多路复用),可以说是服务端网络编程的优先选项。

当然,IO压力大或系统资源耗尽的情况,只能考虑扩容、集群、分布式等其他方向的知识了。

本文代码仅考虑select模型,poll与epoll可自行学习。

原理

可借助Linux C提供的select( 内核 )函数,实现一个进程监听多个IO事件( 网络连接、读、写等 ),以实现单进程网络服务程序,可同时处理多个客户端的IO事件的目的。

原理是,服务程序把每个客户端连接放到一个集合中(Socket连接集合),每次有事件发生,即遍历该集合,以确定如何做出应对。

其中:

  • fd_set为Socket连接(fd)集合;
  • FD_ZERO用于清空集合
  • FD_SET用于把指定fd放入集合中;
  • FD_ISSET用于判断某fd是否存在于集合中;
  • FD_CLR用于把指定fd移出集合;

代码

  • 服务端(server.c)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <unistd.h>
#include <arpa/inet.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/select.h>

int main()
{
// create socket
int listenFd;
if ((listenFd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
perror("socket");
return -1;
}

// bind
struct sockaddr_in serverAddr;
memset(&serverAddr, 0, sizeof(serverAddr));
serverAddr.sin_family = AF_INET;
serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
serverAddr.sin_port = htons(9999);
if (bind(listenFd, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) != 0)
{
perror("bind");
return -1;
}

// listen
if (listen(listenFd, 5) != 0)
{
perror("listen");
return -1;
}
printf("server start running...\n");

// select init
fd_set fdSet;
FD_ZERO(&fdSet);
FD_SET(listenFd, &fdSet);
int maxFd = listenFd; //记录fdSet中最大值

// select run
while (1)
{
fd_set tmpFdSet = fdSet; //原因:调用select函数后,fdSet内容更新

// select
int retSelect = select(maxFd + 1, &tmpFdSet, NULL, NULL, NULL);
if (retSelect < 0)
{
printf("select() failed...\n");
perror("select");
break;
}

// timeout
if (retSelect == 0)
{
printf("select() timeout...\n");
continue;
}

// check all client socket event
for (int eventFd = 0; eventFd <= maxFd; eventFd++)
{
// check if eventFd in fd_set
if (FD_ISSET(eventFd, &tmpFdSet) <= 0)
continue;

// new client connect event
if (eventFd == listenFd)
{
struct sockaddr_in clientAddr;
socklen_t clientAddrLen = sizeof(clientAddr);
int clientSockFd = accept(listenFd, (struct sockaddr *)&clientAddr, &clientAddrLen);
if (clientSockFd < 0)
{
printf("accept() failed...\n");
continue;
}

printf("client(socketFd=%d) connect ...", clientSockFd);

// add new client fd in fdSet
FD_SET(clientSockFd, &fdSet);
if (maxFd < clientSockFd)
maxFd = clientSockFd;
}
// read or disconnect event
else
{
char buffer[1024];
memset(buffer, 0, sizeof(buffer));

// read data from client
ssize_t retRead = read(eventFd, buffer, sizeof(buffer));

// error or disconnect
if (retRead <= 0)
{
printf("client(socketFd=%d) disconnect...\n", eventFd);
close(eventFd);
FD_CLR(eventFd, &fdSet); // attention: update fdSet

// update maxFd
if (maxFd == eventFd)
{
for (int n = maxFd; n > 0; n--)
{
if (FD_ISSET(n, &fdSet))
{
maxFd = n;
break;
}
}
}
}
else
{
// print receive data
printf("recv(clientSockFd=%d, dataSize=%ld): %s\n", eventFd, retRead, buffer);

// send same data to client
write(eventFd, buffer, strlen(buffer));
}
}
}
}

return 0;
}

  • 客户端(client.c)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>

int main()
{
// new socket
int sockFd = socket(AF_INET, SOCK_STREAM, 0);
if (sockFd < 0)
{
perror("socket");
return -1;
}

// connect
struct sockaddr_in serverAddr;
memset(&serverAddr, 0, sizeof(serverAddr));
serverAddr.sin_family = AF_INET;
serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
serverAddr.sin_port = htons(9999);
if (connect(sockFd, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) != 0)
{
printf("connect(127.0.0.1:9999) failed...\n");
close(sockFd);
return -1;
}
printf("connect success...\n");

// send and recv data
char buffer[1024];
for (int n = 0; n <= 10; n++)
{
memset(buffer, 0, sizeof(buffer));
sprintf(buffer, "%02d--%s", n, "hahahahahaha");
if (write(sockFd, buffer, strlen(buffer)) <= 0)
{
printf("write() failed...\n");
close(sockFd);
return -1;
}

memset(buffer, 0, sizeof(buffer));
if (read(sockFd, buffer, sizeof(buffer)) <= 0)
{
printf("read() failed...\n");
close(sockFd);
return -1;
}

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

close(sockFd);
return 0;
}


举报

相关推荐

0 条评论