小型tcp服务器--select
Posted xy913741894
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了小型tcp服务器--select相关的知识,希望对你有一定的参考价值。
在前面的博客中,我用多线程,多进程编写了一个小型服务器,之所以会使用多进程/多线程是因为服务器往往需要服务多个客户端,提高并发性和效率是十分重要的,然而缺点是系统开销较大,系统需要创建多个进程/线程。
然而,现实生活中,一个程序,或者一个网络服务器的真正的性能瓶颈往往在IO,事实上,网络IO进行读写数据之时,往往因为就绪条件不满足而出于等待的状态,这大大就降低了我们性能。
对于网络IO,可分为两步:
I/O请求分两步:
1)进行等待:等待读或者写事件准备就绪。
2)进行数据搬迁:将数据从存储介质拷贝到内存缓冲区,此时数据已经准备好了,可以被用户应用程序进行读写,进行用户应用程序拷贝内核缓冲区中的数据到用户缓冲区。
为了提高性能,如果让等待的时间少一点就好了。IO复用(多路转接)正是这样的一种计数帮我们减少等待时间,提高性能的技术。那么它是基于什么样的原理呢?
I/O多路复用就是通过一种机制,一个进程可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。
但select,pselect,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间。
打个比方,就好比钓鱼,本来是拿一个鱼竿钓鱼,现在我拿100个鱼竿钓鱼,是不是等待的时间相对就少,钓到鱼的可能性就大了。
今天,我们主要实现一个基于select的tcp服务器,其实和之前多线程多进程服务器很相似的~
makefile
.PHONY:all
all:server_select client_select
client_select:client_select.c
gcc -o $@ $^
server_select:server_select.c
gcc -o $@ $^
.PHONY:clean
clean:
rm -f server_select
server
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define N (sizeof(fd_set)*8)
int fd_array[N];
static void usage(const char* s)
printf("correct usage : %s [local_ip] [local_port]\\n", s);
int get_socket(const char* ip, const char* port)
int sock_fd = socket(AF_INET, SOCK_STREAM, 0);
if (sock_fd < 0)
fprintf(stderr, "socket failure\\n");
exit(1);
int opt = 1;
if (setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0)
fprintf(stderr, "setsockopt failure\\n");
exit(2);
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_port = htons(atoi(port));
inet_aton(ip, &local.sin_addr);
// int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
//
if (bind(sock_fd, (struct sockaddr*)&local, sizeof(local)) < 0)
fprintf(stderr, "bind failure\\n");
exit(2);
if (listen(sock_fd, 6) < 0)
fprintf(stderr, "listen failure\\n");
exit(3);
return sock_fd;
int main(int argc, char* argv[])
if (argc != 3)
usage(argv[0]);
exit(0);
int sock_fd = get_socket(argv[1], argv[2]);
int index = 0;
for (; index < N; ++index)
fd_array[index] = -1;
fd_array[0] = sock_fd;
int max_fd = -1;
struct timeval timeout = 5, 0; //3s
// void FD_CLR(int fd, fd_set *set);
// int FD_ISSET(int fd, fd_set *set);
// void FD_SET(int fd, fd_set *set);
// void FD_ZERO(fd_set *set);
//
while (1)
fd_set my_fd_set;
FD_ZERO(&my_fd_set);
int i = 0;
for (;i < N; i++)
if (fd_array[i] > 0)
FD_SET(fd_array[i], &my_fd_set);
if (fd_array[i] > max_fd)
max_fd = fd_array[i];
// int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
int flag = select(max_fd+1, &my_fd_set, NULL, NULL, 0);//最后一个参数表示是否阻塞等,0表示阻塞,timeout表示timeout时间等待
switch (flag)
case 0:
printf("timeout...\\n");
break;
case -1:
printf("select error...\\n");
break;
default:
for (i = 0; i < N; i++)
if (fd_array[i] < 0)
continue;
//找到一个有效的fd进行操作
if (i == 0 && FD_ISSET(sock_fd, &my_fd_set))
//int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
struct sockaddr_in client;
socklen_t len = sizeof(client);
int new_fd = accept(sock_fd, (struct sockaddr*)&client, &len);
if (new_fd < 0)
//fprintf(stderr, "accept failure\\n");
continue;
char* ip = inet_ntoa(client.sin_addr);
int port = ntohs(client.sin_port);
printf("get a new client %s:%d\\n", ip, port);
int j = 1;
for (; j < N; j++)
if (fd_array[j] == -1)
break;
if (j == N)
printf("server is full\\n");
close(new_fd);
else
fd_array[j] = new_fd;
else if (i > 0 && FD_ISSET(fd_array[i], &my_fd_set)) //如果是普通套接字
//读取client数据
char buf[1024];
ssize_t s = read(fd_array[i], buf, sizeof(buf)-1);
if (s > 0)
buf[s] = '\\0';
printf("client # %s\\n", buf);
write(fd_array[i], buf, strlen(buf));
else if (s == 0)
printf("client quits\\n");
close(fd_array[i]);
fd_array[i] = -1;
else
fprintf(stderr, "read error\\n");
else
//default
break;
return 0;
client
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
static void usage(const char* s)
printf("correct usage : %s [remote_ip] [remote_port]\\n", s);
int get_socket(const char* ip, const char* port)
int sock_fd = socket(AF_INET, SOCK_STREAM, 0);
if (sock_fd < 0)
fprintf(stderr, "socket failure\\n");
exit(1);
struct sockaddr_in remote;
remote.sin_family = AF_INET;
remote.sin_port = htons(atoi(port));
inet_aton(ip, &remote.sin_addr);
// int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
int flag = connect(sock_fd, (struct sockaddr*)&remote, sizeof(remote));
if (flag < 0)
fprintf(stderr, "connect failure\\n");
exit(2);
return sock_fd;
int main(int argc, char* argv[])
if (argc != 3)
usage(argv[0]);
exit(0);
int sock_fd = get_socket(argv[1], argv[2]);
//利用重定向输出到网络
//dup()
char buf[1024];
while (1)
memset(buf, '\\0', sizeof(buf));
printf("please enter # ");
fflush(stdout);
ssize_t s = read(0, buf, sizeof(buf)-1);
if (s > 0)
buf[s-1] = '\\0';
write(sock_fd, buf, strlen(buf));
s = read(sock_fd, buf, sizeof(buf)-1);
if (s > 0)
printf("server echo # %s\\n", buf);
写完select之后,我们总结一下它的优缺点:
优点:
系统开销小,不必创建多线程多进程;
属于IO复用的一种,因此效率比较好,IO的等待时间相对减小了
select目前几乎在所有的平台上支持,其良好跨平台支持也是它的一个优点
缺点:
单个进程可监视的fd数量被限制,默认是1024
需要维护一个用来存放大量fd的数据结构(比如数组),这样会使得用户空间和内核空间在传递该结构时复制开销大
每次调用fd都是线性扫描的,当fd个数很多的时候,效率不高
以上是关于小型tcp服务器--select的主要内容,如果未能解决你的问题,请参考以下文章