为了实现tcp服务器能够同时处理多个服务器,除了使用多进程、多线程的方法以外,还可以借助select和设置套接字为NONBLOCK,以防止服务器阻塞在某个函数上。
代码如下
头文件
#ifndef _TCP_SELECT_H_
#define _TCP_SELECT_H_
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h> //文件io read write close
#include <netinet/in.h>//socket() struct sockaddr_in htonl htons
#include <netinet/ip.h>//socket()
#include <arpa/inet.h>//inetaddr() inet_pton()
#include <sys/time.h>
#include <errno.h>
#include <fcntl.h>
#define SERV_PORT 5001
#define SERV_IP_ADDR "192.168.152.130"
typedef int data_t;
typedef struct node_t
{
data_t data;
struct node_t * next;
}linknode_t, * linklist_t;
extern int FD_SET_LINKLIST(linklist_t H,fd_set * prset);
extern int LINKLIST_INSERT(linklist_t H,int newfd);
extern void LINKLIST_DESTORY(linklist_t H);
extern int find_maxfd(linklist_t H);
#endif
服务器端:使用链表对已经建立连接的套接字进行维护
#include "tcp_select.h"
int main()
{
int fd = -1;
//创建socket
fd = socket(AF_INET,SOCK_STREAM,0);//流式套接字-TCP
//设置套接字为非阻塞
int flags = fcntl(fd,F_GETFL,0);
fcntl(fd,F_SETFL,flags|O_NONBLOCK);
if(fd == -1)
{
perror("socket");
exit(-1);
}
//2绑定socket
//2.1填充struct sockaddr_in
struct sockaddr_in sin;
bzero(&sin,sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(SERV_PORT);//网络字节序的端口号
#if 1
sin.sin_addr.s_addr = htonl(INADDR_ANY);//代替某个IP inet_addr(SERV_IP_ADDR) 让服务器能绑定在任意IP
#else
if(inet_pton(AF_INET,SERV_IP_ADDR,(void *)&sin.sin_addr.s_addr) != 1)
{
perror("inet_pton");
exit(-1);
}
#endif
//2.2开始绑定socket就是给fd带上属性
if(bind(fd,(const struct sockaddr *)&sin,sizeof(sin)) < 0)
{
perror("bind");
exit(-1);
}
//3 调用listen()将主动套接字变成被动套接字
if(listen(fd,5) == -1)
{
perror("listen");
exit(-1);
}
//链表存储已经建立连接的newfd
linklist_t H;
if((H = (linklist_t)malloc(sizeof(linknode_t))) == NULL)
{
perror("malloc");
exit(-1);
}
H->next = NULL;
char buf[BUFSIZ];
bzero(buf,sizeof(buf));
int ret;
fd_set rset;
struct timeval tout;
int maxfd = -1;
int newfd = -1;
struct sockaddr_in cin;
socklen_t addrlen = sizeof(cin);
linklist_t p = H;
linklist_t q;
char ipv4_addr[16];
int res;
while(1)
{
//IO复用集合预处理
maxfd = find_maxfd(H);
FD_ZERO(&rset);
FD_SET_LINKLIST(H,&rset);
tout.tv_sec = 0;
tout.tv_usec = 0;
res = select(maxfd+1, &rset,NULL,NULL,&tout);
if(res == -1)
{
perror("select");
exit(-1);
}
if (res == 0)
{
printf("no socket ready to read\n");
}
p = H;
while(p->next)
{
if(FD_ISSET(p->next->data,&rset))
{
do
{
bzero(buf,sizeof(buf));
ret = read(p->next->data,buf,BUFSIZ-1);
puts("test");
}while(ret < 0 && EINTR == errno);
if(ret < 0 )
{
printf("fd=%d: read failed\n",p->next->data);
p = p->next;
continue;
}
if(strncmp(buf,"quit\n",4) == 0 || !ret)
{
printf("client(fd=%d) is exiting\n",p->next->data);
FD_CLR(p->next->data,&rset);
q = p->next;
p->next = p->next->next;
free(q);
continue;
}
write(p->next->data,buf,BUFSIZ);
}
p = p->next;
}
newfd = accept(fd,(struct sockaddr *)&cin,&addrlen);
if (newfd == -1 && errno == EAGAIN)
{
puts("no client connections yet");
continue;
}
else if(newfd == -1)
{
perror("accept");
exit(-1);
}
if(!inet_ntop(AF_INET,(void *)&cin.sin_addr.s_addr,ipv4_addr,sizeof(cin)))
{
perror("inet_ntop");
exit(-1);
}
if(newfd > 0 )
{
if(LINKLIST_INSERT(H,newfd) == -1)
printf("client(%s:%d)(fd=%d) failed to connect\n",ipv4_addr,ntohs(cin.sin_port),newfd);
else
printf("client(%s:%d)(fd=%d) is connected\n",ipv4_addr,ntohs(cin.sin_port),newfd);
}
}
close(newfd);
close(fd);
LINKLIST_DESTORY(H);
return 0;
#if 0
//5通过newfd进行cs通信
char buf[BUFSIZ];
bzero(buf,BUFSIZ);
int ret;
FILE * fp;
fp = fopen("./readme.txt","a+");
while(1)
{
while((ret = read(newfd,buf,BUFSIZ)) > 0)
{
if(strncasecmp(buf,"quit\n",4) == 0)
{
printf("client is exiting\n");
goto _exit;
}
fputs(buf,fp);
}
if(ret == 0 )
{
break;
}
else
{
perror("read");
break;
}
}
_exit:
//6关闭套接子
close(newfd);
close(fd);
fclose(fp);
return 0;
#endif
}
int FD_SET_LINKLIST(linklist_t H,fd_set * prset)
{
linklist_t p = H;
while(p->next)
{
FD_SET(p->next->data,prset);
p = p->next;
}
return 0;
}
int LINKLIST_INSERT(linklist_t H,int newfd)
{
linklist_t p ;
if((p = (linklist_t)malloc(sizeof(linknode_t))) == NULL)
{
perror("malloc");
return -1;
}
//链表头部插入
p->data = newfd;
p->next = H->next;
H->next = p;
return 0;
}
void LINKLIST_DESTORY(linklist_t H)
{
linklist_t p;
while(H)
{
p = H;
H = p->next;
free(p);
}
}
int find_maxfd(linklist_t H)
{
linklist_t p = H->next;
int maxfd = 0;
for(;p != NULL;p = p->next)
{
if(p->data > maxfd)
maxfd = p->data;
}
return maxfd;
}
客户端
#include "tcp_select.h"
int main()
{
int fd = -1;
//创建socket
fd = socket(AF_INET,SOCK_STREAM,0);//流式套接字-TCP
if(fd == -1)
{
perror("socket");
exit(-1);
}
//2 connect()申请连接
//填充sockaddr_in()结构体
struct sockaddr_in sin;
bzero(&sin,sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(SERV_PORT);//网络字节序的端口号
#if 0
sin.sin_addr.s_addr = inet_addr(SERV_IP_ADDR);
#else
if(inet_pton(AF_INET,SERV_IP_ADDR,(void *)&sin.sin_addr.s_addr) != 1)
{
perror("inet_pton");
exit(-1);
}
#endif
//申请连接
if((connect(fd,(const struct sockaddr *)&sin,sizeof(sin))) < 0)
{
perror("connect");
exit(-1);
}
//
char buf[BUFSIZ];
fd_set rset;
int maxfd = fd;
int ret;
struct timeval tout;
while(1)
{
//对集合进行预操作
FD_ZERO(&rset);
FD_SET(fd,&rset);//将套接字加入到IO复用集合中
FD_SET(0,&rset);//将标准输入流加入到io复用集合中
tout.tv_sec = 5;//填充struct timeval 结构体
tout.tv_usec = 0;
//使用select进行多路IO选择
select(maxfd+1, &rset,NULL,NULL,&tout);
//当标准输入流有输入时进行处理
if(FD_ISSET(0,&rset))
{
do
{
ret = read(0,buf,BUFSIZ);
}while(ret < 0 && EINTR == errno);//工程写法
if(ret < 0 )
{
perror("read");
continue;
}
if(!ret)
continue;
#if 0
if(fgets(buf,BUFSIZ,stdin) == NULL)
{
continue;
}
#endif
if(write(fd,buf,BUFSIZ) < 0 )
{
perror("write() to socket");
continue;
}
if(!strncasecmp(buf,"quit\n",4))
{
printf("client is exiting\n");
break;
}
}
if(FD_ISSET(fd,&rset))
{
do
{
bzero(buf,sizeof(buf));
ret = read(fd,buf,BUFSIZ);
}while(ret < 0 && EINTR == errno);//工程写法
if(ret < 0 )
{
perror("read");
continue;
}
if(!ret)//服务器被关闭
{
printf("server is shutdown\n");
break;
}
fprintf(stdout,"server: %s\n",buf);
}
}
//关闭套接字
close(fd);
return 0;
}
参考链接
http://velep.com/archives/1137.html
https://blog.youkuaiyun.com/u014453898/article/details/53888800