Socket 基礎程式設計(二)
在上一篇部落格中,我們總結了基本的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 <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#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; i<SOCKMAXCONN-1; 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 <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#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; i<random_times; i++)
{
if (-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;
}