1. 程式人生 > >Socket 基礎程式設計(二)

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;  
}