在上一篇博客中,我们总结了基本的Socket C/S结构的用法。但该实例仅仅限于1vs1的C/S交互中,当我们需要处理多对一的交互时,服务器就必须支持并发处理。
我们知道在服务器accept函数收到建联请求后,会反馈一个新的fd用于与客户端交互,此时通常我们就可以新起线程专门处理与该客户端的交互,而主线程则返回继续进行accept阻塞监听。
但这样的代价是明显的,线程的损耗不光是资源占用的问题,还涉及到CPU的线程处理切换,所以更恰当的做法应是基于单线程的方法,实现并发处理。
int select( int nfds, fd_set FAR* readfds, fd_set * writefds, fd_set * exceptfds, const struct timeval * timeout);
nfds:是一个整数值,是指集合中所有文件描述符的范围,即所有文件描述符的最大值加1,不能错!在Windows中这个参数的值无所谓,可以设置不正确。
readfds:(可选)指针,指向一组等待可读性检查的套接口。
writefds:(可选)指针,指向一组等待可写性检查的套接口。
exceptfds:(可选)指针,指向一组等待错误检查的套接口。
timeout:select()最多等待时间,对阻塞操作则为NULL。
select函数,是我们常用的非阻塞态函数,它会持续监测观察列表中的readfds, writefds 与 exceptfds,当其中的fd状态改变时,select就会返回。这就避免了socket函数中的accept监听、recv等的阻塞状态。
新的实现对原有代码进行了改善,首先是服务器端采用select的方式,监听所有的建联socket的fd与服务器端bind的fd的可读状态,当存在可读时,则对所有fd的状态进行遍历和处理。
客户端侧,利用fork函数生成多个子进程,根据进程号决定间隔时长,从而模拟多用户的多次交互行为。
Server:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define SUCCESS 0
#define SERVERPORT 8888
#define SOCKMAXCONN 1024
struct fd_node
{
int fd;
struct fd_node *next;
};
struct fd_node *free_list = NULL;
struct fd_node *work_list = NULL;
int init_node_list()
{
struct fd_node *curr = NULL;
struct fd_node *temp = NULL;
free_list = (struct fd_node*)malloc(sizeof(struct fd_node));
free_list->fd = -1;
free_list->next = NULL;
work_list = (struct fd_node*)malloc(sizeof(struct fd_node));
work_list->fd = -1;
work_list->next = NULL;
curr = free_list;
for (int i=0; i1; i++)
{
temp = (struct fd_node*)malloc(sizeof(struct fd_node));
temp->fd = -1;
temp->next = NULL;
curr->next = temp;
curr = temp;
}
return SUCCESS;
}
int free_node_list()
{
struct fd_node *curr = NULL;
struct fd_node *temp = NULL;
curr = free_list;
while (curr)
{
temp = curr;
curr = curr->next;
free(temp);
}
curr = work_list;
while (curr)
{
temp = curr;
curr = curr->next;
free(temp);
}
}
int main()
{
char buffer[1024] = {0};
fd_set readfds;
int client_conn = 0;
struct sockaddr_in client_socket_addr;
struct sockaddr_in server_socket_addr;
socklen_t length = sizeof(client_socket_addr);
//////////////////////////////////////////////////////////////////////////////
// struct sockaddr_in {
// __kernel_sa_family_t sin_family; /* Address family */
// __be16 sin_port; /* Port number */
// struct in_addr sin_addr; /* Internet address */
// unsigned char __pad[__SOCK_SIZE__ - sizeof(short int) - sizeof(unsigned short int) - sizeof(struct in_addr)];
// };
///////////////////////////////////////////////////////////////////////////////
server_socket_addr.sin_family = AF_INET;
server_socket_addr.sin_port = htons(SERVERPORT);
server_socket_addr.sin_addr.s_addr = htonl(INADDR_ANY);
//////////////////////////////////////////////////////////////////////////////
// Create a new socket of type TYPE in domain DOMAIN, using
// protocol PROTOCOL. If PROTOCOL is zero, one is chosen automatically.
// Returns a file descriptor for the new socket, or -1 for errors.
// extern int socket (int __domain, int __type, int __protocol) __THROW;
///////////////////////////////////////////////////////////////////////////////
int server_socket_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
///////////////////////////////////////////////////////////////////////////////
// Give the socket FD the local address ADDR (which is LEN bytes long).
// extern int bind (int __fd, __CONST_SOCKADDR_ARG __addr, socklen_t __len)
///////////////////////////////////////////////////////////////////////////////
if (SUCCESS != bind(server_socket_fd, (struct sockaddr *)&server_socket_addr, sizeof(server_socket_addr)))
{
perror ("Bind Socket Failed: ");
goto exit;
}
///////////////////////////////////////////////////////////////////////////////
// Prepare to accept connections on socket FD.
// N connection requests will be queued before further requests are refused.
// Returns 0 on success, -1 for errors.
// extern int listen (int __fd, int __n) __THROW;
///////////////////////////////////////////////////////////////////////////////
if(SUCCESS != listen(server_socket_fd, SOCKMAXCONN))
{
perror ("Listen Socket Failed: ");
goto exit;
}
#ifdef NOBLOCK
init_node_list();
for (;;)
{
int max_fd = server_socket_fd;
struct fd_node *curr = work_list;
FD_ZERO (&readfds);
FD_SET (server_socket_fd, &readfds);
int i = 0;
while (curr->next)
{
max_fd = server_socket_fd>curr->fd ? server_socket_fd : curr->fd;
FD_SET (curr->fd, &readfds);
curr = curr->next;
++i;
}
printf ("Monitor number is : %d \n", i);
if (select(max_fd+1, &readfds, NULL, NULL, NULL)<0)
{
perror("Select Failed: ");
}
if (FD_ISSET(server_socket_fd, &readfds))
{
FD_CLR(server_socket_fd, &readfds);
curr = free_list;
if (NULL == curr)
{
printf("Reach MAX FD number. \n");
continue;
}
curr->fd = accept(server_socket_fd, (struct sockaddr*)&client_socket_addr, &length);
free_list = curr->next;
curr->next = work_list;
work_list = curr;
printf ("Add a new fd: %d \n", curr->fd);
}
curr = work_list;
while (curr->next)
{
if (FD_ISSET(curr->fd, &readfds))
{
memset(buffer, 0, sizeof(buffer));
int client_len = recv(curr->fd, buffer, sizeof(buffer), 0);
if (-1 == client_len)
{
perror ("Recv Socket Failed: ");
}
if (0 == strncmp(buffer, "BYE", 4))
{
printf ("Del fd: %d \n", curr->fd);
struct fd_node *temp = work_list;
if (temp == curr)
{
work_list = temp->next;
}
else
{
while (temp->next != curr)
{
temp = temp->next;
}
temp->next = curr->next;
}
curr->next = free_list;
free_list = curr;
FD_CLR(curr->fd, &readfds);
close(curr->fd);
continue;
}
if (-1 == send(curr->fd, buffer, client_len, 0))
{
perror ("Send Socket Failed: ");
}
}
curr = curr->next;
}
}
free_node_list();
#else
for (;;)
{
client_conn = accept(server_socket_fd, (struct sockaddr*)&client_socket_addr, &length);
if (-1 == client_conn)
{
perror ("Connect Socket Failed: ");
goto exit;
}
int client_len = recv(client_conn, buffer, sizeof(buffer), 0);
if (-1 == client_len)
{
perror ("Recv Socket Failed: ");
goto exit;
}
if (-1 == send(client_conn, buffer, client_len, 0))
{
perror ("Send Socket Failed: ");
goto exit;
}
printf("Received Request: %s !\n", buffer);
close(client_conn);
}
#endif
goto exit;
exit:
close(client_conn);
close(server_socket_fd);
return SUCCESS;
}
Client:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define SUCCESS 0
#define SERVERPORT 8888
int main()
{
int fork_times = 0;
int random_times = 0;
char buffer[1024] = {0};
char *send_msg = "Hello World!";
struct sockaddr_in server_socket_addr = {0};
socklen_t server_addr_length = sizeof(server_socket_addr);
server_socket_addr.sin_family = AF_INET;
server_socket_addr.sin_port = htons(SERVERPORT);
if( inet_pton(AF_INET, "127.0.0.1", &server_socket_addr.sin_addr) <= 0)
{
perror ("Inet_pton Socket Failed: ");
goto exit;
}
do{
pid_t process = fork();
if (0 == process)
{
random_times = getpid()%5;
break;
}
else
{
printf ("%d create process %d \n", getpid(), process);
usleep(10);
}
} while (++fork_times < 1026);
printf ("%d Create sub Process random: %d \n", getpid(), random_times);
int client_socket_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(SUCCESS != connect (client_socket_fd, (struct sockaddr*)&server_socket_addr, server_addr_length))
{
perror ("Connect Socket Failed: ");
goto exit;
}
for (int i=0; iif (-1 == send(client_socket_fd, send_msg, strlen("Hello World!"), 0))
{
perror ("Send Socket Failed: ");
goto exit;
}
int client_len = recv(client_socket_fd, buffer, sizeof(buffer), 0);
if (-1 == client_len)
{
perror ("Recv Socket Failed: ");
goto exit;
}
sleep(random_times);
printf("[%4d:%4d] Received Response: %s !\n", getpid(), i, buffer);
}
send(client_socket_fd, "BYE", strlen("BYE"), 0);
goto exit;
exit:
close(client_socket_fd);
return SUCCESS;
}