多进程服务端的缺点和解决方法
多进程服务端只要有客户端连接请求就会创建新进程。但是因为创建进程时需要付出极大的代价,需要大量的运算和内存空间,由于每个进程都具有独立的内存空间,所以相互间的数据交换也要求采用相对复杂的方法(IPC属于相对复杂的通信方法)。
而IO复用技术,可以在不创建进程的同时向多个客户端提供服务。
复用
select函数可以将多个文件描述符集中到一起同一监视,监视内容如下:
- 是否存在套接字接收数据
- 无需阻塞传输数据的套接字有哪些
- 哪些套接字发生了异常
设置文件描述符
在fd_set变量中注册或更改值的宏操作:
FD_ZERO(fd_set *fdset); // 初始化所有位为0
FD_SET(int fd, fd_set *fdset); // 注册文件描述符的信息
FD_CLR(int fd, fd_set *fdset); // 清楚文件描述符的信息
FD_ISSET(int fd, fd_set *fdset); // 若包含文件描述符fd,则返回真
设置检查范围及超时
// Linux
#include <sys/select.h>
#include <sys/time.h>
int select(int maxfd, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timeval *timeout);
// 成功时返回大于0的值,失败时返回-1
// maxfd - 监视对象文件描述符数量
// readset - 将所有关注“是否存在待读取数据”的文件描述符注册到fd_set型变量,并传递其地址值。
// writeset - 将所有关注“是否可传输无阻塞数据”的文件描述符注册到fd_set型变量,并传递其地址值
// exceptset - 将所有关注“是否发生异常”的文件描述符注册到fd_set型变量,并传递其地址值
// timeout - 调用select函数后,为防止陷入无限阻塞的状态,传递超时信息
// Windows
#include <winsock2.h>
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *excepfds, const struct timeval *timeout);
// 成功时返回0,失败时返回-1
Windows下的IO复用服务器端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winsock2.h>
#define BUF_SIZE 1024
void ErrorHandling(const char *message);
int main(int argc, char *argv[])
{
WSADATA wsaData;
SOCKET hServSock, hClntSock;
SOCKADDR_IN servAdr, clntAdr;
TIMEVAL timeout;
fd_set reads, cpyReads;
int adrSz;
int strLen, fdNum, i;
char buf[BUF_SIZE] = {0};
if(argc != 2){
printf("Usage %s <port>\n", argv[0]);
exit(1);
}
if(WSAStartup(MAKEWORD(2, 2), &wsaData) != 0){
ErrorHandling("WSAStartup() error!");
}
hServSock = socket(PF_INET, SOCK_STREAM, 0);
memset(&servAdr, 0, sizeof(servAdr));
servAdr.sin_family = AF_INET;
servAdr.sin_addr.s_addr = htonl(INADDR_ANY);
servAdr.sin_port = htons(atoi(argv[1]));
if(bind(hServSock, (SOCKADDR*)&servAdr, sizeof(servAdr)) == SOCKET_ERROR){
ErrorHandling("bind() error!");
}
if(listen(hServSock, 5) == SOCKET_ERROR){
ErrorHandling("listen() error!");
}
FD_ZERO(&reads);
FD_SET(hServSock, &reads);
while (1)
{
cpyReads = reads;
timeout.tv_sec = 5;
timeout.tv_usec = 5000;
if((fdNum = select(0, &cpyReads, 0, 0, &timeout)) == SOCKET_ERROR){
break;
}
if(fdNum == 0){
continue;
}
for (i = 0; i < reads.fd_count; i++)
{
if(FD_ISSET(reads.fd_array[i], &cpyReads))
{
if(reads.fd_array[i] == hServSock)
{
adrSz = sizeof(clntAdr);
hClntSock = accept(hServSock, (SOCKADDR*)&clntAdr, &adrSz);
FD_SET(hClntSock, &reads);
printf("connected client: %d \n", hClntSock);
}else{
strLen = recv(reads.fd_array[i], buf, BUF_SIZE - 1, 0);
if(strLen == 0){
FD_CLR(reads.fd_array[i], &reads);
closesocket(cpyReads.fd_array[i]);
printf("closed client: %d\n", cpyReads.fd_array[i]);
}else{
send(reads.fd_array[i], buf, strLen, 0);
}
}
}
}
}
closesocket(hServSock);
WSACleanup();
return 0;
}
void ErrorHandling(const char *message){
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}