博客搬家:https://blog.youkuaiyun.com/zheyufuck/article/details/52549889
使用socket和select实现并发型服务器
本文介绍使用select实现并发型服务器的实战,若有错误之处,还请不吝指点。
1. socket介绍
- socket系统调用包括了:socket()、bind()、listen()、accept()、connect()。
- socket()系统调用创建一个新的socket文件描述符。
- bind()系统调用将socke()返回的文件描述符绑定到一个特定的地址,使得客户端能够定位到该socket上。
- listen()监听接入连接,允许一个流socket接受来自其他的socket连接。
- accept()在监听流socket上接受来自一个对等应用程序的接入。
connect()建立与另一个socket之间的连接。
2. select介绍
select函数原型:int select(int nfds,fd_set *readfds,fd_set *writefds,fd_set *exceptfds,struct timeval *timeout);
- readfds是用来检测是否有输入就绪的文件描述符集合。
- writefds是用来检测是否有输出就绪的文件描述集合。
- exceptfds是用来检测异常情况是否发生的文件描述符。
- timeout用来控制select()阻塞行为的。设置为NULL时,select()会一直阻塞。又或者指向一个timeval结构体:
struct timeval{
time_t tv_sec;//second
suseconds_t tv_usec;//Microsecond(long int)
}
如果结构体timeval的两个域都为0,此时select不会阻塞,只是简单地轮询指定的文件描述符集合,看看是否有就绪的文件描述符并立即返回。
所有关于文件描述符集合的操作都是通过四个宏来完成的:
- void FD_ZERO(fd_set * fdset);//将fdset所指向的集合初始化为空
- void FD_SET(int fd,fd_set * fdset);//将文件描述符fd添加到右fdset所指向的集合中
- void FD_CLR(int fd ,fd_set * fdset);//将文件描述符fd从fdset集合中移除。
- int FD_ISSET(int fd ,fd_set *fdset);//如果文件描述符fd是fdset所指向集合的成员,则返回TRUE。
文件描述符集合有个最大容量限制,由常量FD_SETSIZE来决定。
3. 并发型服务器的实现
- 创建一个socket:
int sfd = socket(AF_INET,SOCK_STREAM,0);
- 将socket绑定到地址:
bind(sfd,(struct sockaddr *)(&addr) ,sizeof(addr));
struct sockaddr是通用的socket地址结构,而bind()中addr是IPv4 socket的地址结构struct sockaddr_in,因此需进行类型转换。struct sockaddr_in结构体:
struct sockaddr_in{
sa_family_t sin_family;//协议族
in_port_t sin_port;//端口号
struct in_addr sinaddr;//IPv4地址
unsigned char _pad[X];
};
设置接入监听连接:
listen(sfd,3);//3为最大请求连接数
接受连接 :
accept(sfd,(struct sockaddr *)&cli_addr,&cli_len);
- select()实现并发通信:至此一个流式socket创建基本完成,但此时还无法实现并发通信。实现并发通信的有很多,例如:使用多线程、多进程、select、epoll等。在这里我使用select实现并发通信。
在接入监听连接listen()之后使用select()进行轮询是否有读写事件触发,实现并发通信。
server.c
#include<stdio.h>
#include<stdlib.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/socket.h>
#include<string.h>
#include<sys/time.h>
#include<sys/select.h>
#include<unistd.h>
#define MAXSOCK 10
#define BUFSIZE 1024
int fd_sock[MAXSOCK];//存放所有的接入的socket文件描述符
void initSrv();
int main(){
initSrv();
return 0;
}
void initSrv(){
int sfd = socket(AF_INET,SOCK_STREAM,0);//创建socket
if(sfd == -1)
{
printf("start socket error\n");
return;
}
printf("socket ok \n");
/*配置IPv4地址信息*/
struct sockaddr_in addr;
memset(&addr,0,sizeof(struct sockaddr_in));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(7777);
int bfd;
if((bfd = bind(sfd,(struct sockaddr *)(&addr) ,sizeof(addr)) )== -1)//绑定到固定ip端口
{
printf("bind error \n");
return;
}
printf("bind ok ,ip is:%s,port is %d\n",inet_ntoa(addr.sin_addr),ntohs(addr.sin_port));
if(listen(sfd,3) == -1)//接入监听
{
printf("listen error\n");
return;
}
printf("listen ok\n");
/*配置地址,用于存储accept()返回的地址信息*/
socklen_t cli_len;
struct sockaddr_in cli_addr;
memset(&cli_addr,0,sizeof(cli_addr));
cli_len = sizeof(cli_addr);
ssize_t num;
fd_set fdsr;//创建select()中的文件描述符集合
struct timeval tv;//select()中超时参数的结构体
int maxsock = sfd;//定义select中文件描述符的最大值
int i;
int ready,ret;
int curr_count = 0;
int curr_user = 0;
int * amount = &curr_count;
char buf[BUFSIZE];
while(1)
{
/*操作select()中文件描述符集合*/
FD_ZERO(&fdsr);
FD_SET(sfd,&fdsr);
tv.tv_sec = 30;
tv.tv_usec = 0;
//将活动的文件描述符添加到文件描述符集合中
for(i = 0;i<MAXSOCK;i++){
if(fd_sock[i] != 0)
FD_SET(fd_sock[i],&fdsr);
}
ready = select(maxsock + 1,&fdsr,NULL,NULL,&tv);//调用select
if(ready == -1){
printf("select error\n");
break;
}
else if(ready == 0){
printf("select timeout\n");
continue;
}
/*查询listen()监听事件是否有被触发即是否有连接要接入*/
if(FD_ISSET(sfd,&fdsr))
{
int cfd = accept(sfd,(struct sockaddr *)&cli_addr,&cli_len);
if(cfd == -1)
{
printf("accept error\n");
continue;
}
/*将接受接入的连接的文件描述符添加到文件描述符集合*/
if(curr_count < MAXSOCK )
{
fd_sock[curr_count++] = cfd;
printf("fd is %d\tip is: %s,port is %d\n",cfd,inet_ntoa(cli_addr.sin_addr),ntohs(cli_addr.sin_port));
printf("在线用户有%d个\n",curr_count);
send(cfd,"welcome to connect\n",19,0);
if(cfd > maxsock)
maxsock = cfd;
}
else {
printf("已达到连接最大请求\n");
send(cfd,"conn false",10,0);
close(cfd);
}
}
//检查客户端是否发来消息
for(i = 0;i<curr_count;i++)
{
/*查询是哪个文件描述符被触发*/
if(FD_ISSET(fd_sock[i],&fdsr)){
/*读取客户端发来的信息*/
ret = recv(fd_sock[i],buf,sizeof(buf),0);
if(ret < 0){
printf("client %d is closed\n",i);
close(fd_sock[i]);
FD_CLR(fd_sock[i],&fdsr);
fd_sock[i] = 0;
*amount -= 1;
printf("当前在线用户有%d个\n",*amount);
}
else if(ret == 0){
printf("客户端断开连接\n");
close(fd_sock[i]);
FD_CLR(fd_sock[i],&fdsr);
fd_sock[i] = 0;
*amount-= 1;
printf("当前在线用户有%d个\n",*amount);
}
else{
if(ret < BUFSIZE)
memset(&buf[ret],'\0',1);
printf("client %d send %s\n",i,buf);
/*将消息转发给目标客户端*/
char split[] = "@";
char * src = strtok(buf,split);
char * msg = strtok(NULL,split);
send(atoi(src),msg,strlen(msg),0);
}
send(fd_sock[i],"serve recv succeed\n",19,0);
}
}
}
}
运行测试如下图所示:
- 服务器端:
telnet测试:
- 服务器端:
参考书:Linux /UNIX 系统编程手册