本文参考IBM知识库的文章进行翻译修改
版权归原作者所有,如有任何问题请及时联系我们
本示例代码介绍如何用非阻塞socket和select() API,只用一个线程实现一个TCP服务器。
socket调用流如下图所示。

本示例代码调用包括:
1. socket()API创建一个套接字,指定使用TCP协议。
2. ioctlsocket()API 设置使用非阻塞模式。
3. bind()API 将套接字绑定到指定端口。
4. listen()API 允许服务器开始接收客户端连接。
5. accept()API 接受客户端连接。
6. select()API 查询指定套接字列表有哪些需要处理,返回值0表示超时,值-1表示调用失败WSAGetLastError()获取具体错误码,值n(>0)表示有n个套接字需要处理。
7. accept()和 recv() API 循环执行,直到返回EWOULDBLOCK错误码。
8. send()API发送接收到的数据。
9. closesocket()API关闭套接字。
具体API使用详见MSDN,对照示例代码去理解。
#include <stdio.h>
#include <stdlib.h>
#include <WinSock2.h>
#define SERVER_PORT 12345
#define TRUE 1
#define FALSE 0
void main(int argc, char *argv[])
{
u_int i;
int len, rc;
u_long on = 1;
int listen_sd, new_sd, current_sd;
int end_server = FALSE;
int close_conn;
char buffer[80];
struct sockaddr_in addr;
struct timeval timeout;
fd_set master_set, working_set;
/*************************************************************/
/* init WinSocket library */
/*************************************************************/
WSADATA wsa;
if (WSAStartup(MAKEWORD(2, 2), &wsa) != NO_ERROR)
{
perror("WSAStartup() failed");
exit(-1);
}
/*************************************************************/
/* Create an AF_INET6 stream socket to receive incoming */
/* connections on */
/*************************************************************/
listen_sd = socket(AF_INET, SOCK_STREAM, 0);
if (listen_sd < 0)
{
perror("socket() failed");
exit(-1);
}
/*************************************************************/
/* Set socket to be nonblocking. All of the sockets for */
/* the incoming connections will also be nonblocking since */
/* they will inherit that state from the listening socket. */
/*************************************************************/
rc = ioctlsocket(listen_sd, FIONBIO, &on);
if (rc < 0)
{
perror("ioctl() failed");
closesocket(listen_sd);
exit(-1);
}
/*************************************************************/
/* Bind the socket */
/*************************************************************/
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(SERVER_PORT);
rc = bind(listen_sd,
(struct sockaddr *)&addr, sizeof(addr));
if (rc < 0)
{
perror("bind() failed");
closesocket(listen_sd);
exit(-1);
}
/*************************************************************/
/* Set the listen back log */
/*************************************************************/
rc = listen(listen_sd, 32);
if (rc < 0)
{
perror("listen() failed");
closesocket(listen_sd);
exit(-1);
}
/*************************************************************/
/* Initialize the master fd_set */
/*************************************************************/
FD_ZERO(&master_set);
FD_SET(listen_sd, &master_set);
/*************************************************************/
/* Initialize the timeval struct to 3 minutes. If no */
/* activity after 3 minutes this program will end. */
/*************************************************************/
timeout.tv_sec = 3 * 60;
timeout.tv_usec = 0;
/*************************************************************/
/* Loop waiting for incoming connects or for incoming data */
/* on any of the connected sockets. */
/*************************************************************/
do
{
/**********************************************************/
/* Copy the master fd_set over to the working fd_set. */
/**********************************************************/
memcpy(&working_set, &master_set, sizeof(master_set));
/**********************************************************/
/* Call select() and wait 3 minutes for it to complete. */
/**********************************************************/
printf("Waiting on select()...\n");
rc = select(0, &working_set, NULL, NULL, &timeout);
/**********************************************************/
/* Check to see if the select call failed. */
/**********************************************************/
if (rc < 0)
{
perror(" select() failed");
break;
}
/**********************************************************/
/* Check to see if the 3 minute time out expired. */
/**********************************************************/
if (rc == 0)
{
printf(" select() timed out. End program.\n");
break;
}
for (i = 0; i <= working_set.fd_count; ++i)
{
current_sd = working_set.fd_array[i];
/****************************************************/
/* Check to see if this is the listening socket */
/****************************************************/
if (current_sd == listen_sd)
{
printf(" Listening socket is readable\n");
/*************************************************/
/* Accept all incoming connections that are */
/* queued up on the listening socket before we */
/* loop back and call select again. */
/*************************************************/
do
{
/**********************************************/
/* Accept each incoming connection. If */
/* accept fails with EWOULDBLOCK, then we */
/* have accepted all of them. Any other */
/* failure on accept will cause us to end the */
/* server. */
/**********************************************/
new_sd = accept(listen_sd, NULL, NULL);
if (new_sd < 0)
{
if (WSAGetLastError() != WSAEWOULDBLOCK)
{
perror(" accept() failed");
end_server = TRUE;
}
break;
}
/**********************************************/
/* Add the new incoming connection to the */
/* master read set */
/**********************************************/
printf(" New incoming connection - %d\n", new_sd);
FD_SET(new_sd, &master_set);
/**********************************************/
/* Loop back up and accept another incoming */
/* connection */
/**********************************************/
} while (new_sd != -1);
}
/****************************************************/
/* This is not the listening socket, therefore an */
/* existing connection must be readable */
/****************************************************/
else
{
printf(" Descriptor %d is readable\n", current_sd);
close_conn = FALSE;
/*************************************************/
/* Receive all incoming data on this socket */
/* before we loop back and call select again. */
/*************************************************/
do
{
/**********************************************/
/* Receive data on this connection until the */
/* recv fails with EWOULDBLOCK. If any other */
/* failure occurs, we will close the */
/* connection. */
/**********************************************/
rc = recv(current_sd, buffer, sizeof(buffer), 0);
if (rc < 0)
{
if (WSAGetLastError() != WSAEWOULDBLOCK)
{
perror(" recv() failed");
close_conn = TRUE;
}
break;
}
/**********************************************/
/* Check to see if the connection has been */
/* closed by the client */
/**********************************************/
if (rc == 0)
{
printf(" Connection closed\n");
close_conn = TRUE;
break;
}
/**********************************************/
/* Data was received */
/**********************************************/
len = rc;
printf(" %d bytes received\n", len);
/**********************************************/
/* Echo the data back to the client */
/**********************************************/
rc = send(current_sd, buffer, len, 0);
if (rc < 0)
{
perror(" send() failed");
close_conn = TRUE;
break;
}
} while (TRUE);
/*************************************************/
/* If the close_conn flag was turned on, we need */
/* to clean up this active connection. This */
/* clean up process includes removing the */
/* descriptor from the master set and */
/* determining the new maximum descriptor value */
/* based on the bits that are still turned on in */
/* the master set. */
/*************************************************/
if (close_conn)
{
closesocket(current_sd);
FD_CLR(current_sd, &master_set);
}
} /* End of existing connection is readable */
} /* End of loop through selectable descriptors */
} while (end_server == FALSE);
/*************************************************************/
/* Clean up all of the sockets that are open */
/*************************************************************/
for (i = 0; i <= master_set.fd_count; ++i)
{
closesocket(master_set.fd_array[i]);
}
}