用TCP/TP进行网际互连(6)
———— 利用I/O复用完成单进程并发服务器的处理
1、实现原理
1)创建套接字并将其绑定到这个服务的熟知端口上,将该套接字加到一个表中,该表中的项是可以进行I/O的描述符。
2)使用select在已经有的套接字上等待I/O。
3)如果最初的套接字准备就绪,使用accept获得下一个连接,并将这个新的套接字加入到表中,该表中的项是可以进行I/O的描述符。
4)如果最初的套接字以外的套接字就绪,就使用recv或read获得下一个请求,构造响应,用send或者write将响应发回给客户。
5)继续按照以上的步骤2进行处理。
2、代码实现
- 服务器:
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#define PORT 6000
#define LISTENQ 20
#define BUFFSIZE 4096
#define FILE_NAME_MAX_SIZE 512
int echo(int sc){
//process_conn_server(sc); /*处理连接*/
ssize_t size = 0;
char buffer[1024]; /*数据的缓冲区*/
size = read(sc, buffer, 1024); /*从套接字中读取数据放到 缓冲区buffer中*/
write(sc, buffer, strlen(buffer)+1);/*发给客户端*/
return size;
}
int passiveTCP (const char*service){
//Create socket
int sockfd,connfd;
struct sockaddr_in svraddr,clientaddr;
bzero(&svraddr,sizeof(svraddr));
svraddr.sin_family=AF_INET;
svraddr.sin_addr.s_addr=htonl(INADDR_ANY);
svraddr.sin_port=htons(PORT);
sockfd=socket(AF_INET,SOCK_STREAM,0);
if(sockfd<0)
{
perror("socket");
exit(1);
}
//bind
if(bind(sockfd,(struct sockaddr*)&svraddr,sizeof(svraddr))<0)
{
perror("bind");
exit(1);
}
//listen
if(listen(sockfd,LISTENQ)<0)
{
perror("listen");
exit(1);
}
return sockfd;
}
int main(int argc, char *argv[])
{
char *service = "echo";/* service name or port number*/
struct sockaddr_in fsin; /* the from address of a client */
int msock; /* master server socket */
fd_set rfds; /* read file descriptor set */
fd_set afds; /* active file descriptor set */
unsigned int alen; /* from-address length */
int fd, nfds;
msock = passiveTCP(service);
nfds = getdtablesize();
FD_ZERO(&afds);
FD_SET(msock, &afds);
while (1) {
memcpy(&rfds, &afds, sizeof(rfds));
if (select(nfds, &rfds, (fd_set *)0, (fd_set *)0,(struct timeval *)0) < 0)
//errexit("select: %s\n", strerror(errno));
printf("select error");
if (FD_ISSET(msock, &rfds)) {
int ssock;
alen = sizeof(fsin);
ssock = accept(msock, (struct sockaddr *)&fsin,&alen);
if (ssock < 0)
//errexit("accept: %s\n",strerror(errno));
printf("accept error");
FD_SET(ssock, &afds);
}
for (fd=0; fd<nfds; ++fd)
if (fd != msock && FD_ISSET(fd, &rfds))
if (echo(fd) == 0) {
close(fd);
FD_CLR(fd, &afds);
}
}
}
- 客户端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>
#define PORT 6000 /*侦听端口地址*/
#define IP "127.0.0.1" /*服务器IP地址*/
int main(int argc, char *argv[])
{
int s; /*s为socket描述符*/
struct sockaddr_in server_addr; /*服务器地址结构*/
s = socket(AF_INET, SOCK_STREAM, 0); /*建立一个流式套接字 */
if(s < 0){ /*出错*/
printf("socket error\n");
return -1;
}
/*设置服务器地址*/
bzero(&server_addr, sizeof(server_addr)); /*清零*/
server_addr.sin_family = AF_INET; /*协议族*/
server_addr.sin_addr.s_addr = htonl(INADDR_ANY); /*本地地址*/
server_addr.sin_port = htons(PORT); /*服务器端口*/
/*将用户输入的字符串类型的IP地址转为整型*/
inet_pton(AF_INET, IP, &server_addr.sin_addr);
/*连接服务器*/
connect(s, (struct sockaddr*)&server_addr, sizeof(struct sockaddr));
//process_conn_client(s); /*客户端处理过程*/
ssize_t size = 0;
char buffer[1024]; /*数据的缓冲区*/
for(;;){ /*循环处理过程*/
/*从标准输入中读取数据放到缓冲区buffer中*/
size = read(0, buffer, 1024);
if(size > 0){ /*读到数据*/
write(s, buffer, size); /*发送给服务器*/
size = read(s, buffer, 1024); /*从服务器读取数据*/
write(1, buffer, size); /*写到标准输出*/
}
}
close(s); /*关闭连接*/
return 0;
}
3、运行效果
服务器:
打开三个客户端同时连接:
可以看到,服务器可以对这三个客户端并发提供服务。
附上我的实验代码,有兴趣可以拿来看一下:
https://github.com/KevinBetterQ/Network-programming/tree/master/select