linux 網絡編程之信號機制

調試輸出函數

這裏利用宏來實現debug的相關輸出
git

int dbg_level = -1;
#define print_dbg(level, ...)						\
	({								\
	if (level <= dbg_level) {					\
		fprintf(stderr, "%s, %u, %s: ",				\
			__FILE__, __LINE__, __func__);			\
		fprintf(stderr, ##__VA_ARGS__);				\
	}								\
	})

基本的socket編程

前面有講過基本的網絡編程,主要是利用socket的相關函數進行實現,其大致框架以下
github

int sockfd = socket(PF_INET, SOCK_STREAM, 0); // 建立套接字
if(sockfd < 0)
{
    print_dbg(0, "create socket error\n");
    return;
}

struct sockaddr_in server_addr;
bzero(&server_addr, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(port);
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
if(bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr) < 0)
{
    print_dbg(0, "Failed to bind port:%d\n", port);
    return;
}

if(listen(sockfd, SOMAXCONN) < 0)
{
    print_dbg(0, "Listen error\n");
    return;
}

while(1)
{
    clientfd = accept(sockfd, (struct sockaddr *)&client_addr, NULL);
    if(clientfd < 0)
    {
        if(error == EINTR) // 信號中斷處理
            continue;
        else
        {
            print_dbg(0, "accept dumpt\n");
            break;
        }
    }
    
    int pid = fork();
    if(pid < 0)
    {
        print_dbg(0, "create child process error\n");
        close(clientfd);
        continue;
    }
    else if(pid == 0)
    {
        ...
        exit(0);
    }
    ...
}

擴充的socket編程

  1.  首先是對socket的一些可能出現錯誤的函數進行再封裝處理,好比accept可能出現多種錯誤,若只是簡單的退出,則對服務器的安全性將有很大的挑戰;這裏對於最多見的信號中斷處理,進行了再次封裝編程

 clientfd = accept(sockfd, (struct sockaddr *)&client_addr, NULL);
    if(clientfd < 0)
    {
        if(error == EINTR) // 信號中斷處理
            continue;
        else
        {
            print_dbg(0, "accept dumpt\n");
            break;
        }
    }

從上面的代碼能夠看出,當錯誤號爲EINTR時,忽略掉再次進入循環;再例如出現EPERM,防火牆問題時,也能夠作出相應的提示安全

2. 除了accept以外,對於send,recv函數一樣須要再次封裝處理,以確保能正確的接收到數據和正確的發送完數據.下面給出了最多見的錯誤處理,特別是對於信號中斷,通常都有作出要求服務器

char client_ip[16];
int recvn(int fd, char *buf, size_t len, int flag)
{
	int size = recv(fd, buf, len, flag);
	if (size < 0)
	{
		if (errno == EINTR)
			return recvn(fd, buf, len, flag);
		else
		{
			record_log("receive data from ip:%s error\n", client_ip);
			print_dbg(0, "receive data from ip:%s error\n", client_ip);
			return -errno;
		}
	}
	return size;
}

int sendn(int fd, const char *buf, size_t len, int flag)
{
	int size = send(fd, buf, len, flag);
	if (size < 0)
		if (errno == EINTR)
			return sendn(fd, buf, len, flag);
		else
		    goto ERROR;
	if(size != len)
	    goto ERROR;
	    
	return size;
	
	ERROR:
        record_log("send data to ip:%s error\n", client_ip);
	print_dbg(0, "receive data from ip:%s error\n", client_ip);
	return -errno;		
}

信號與進程通訊

若是咱們考慮的服務器是隻容許順序的請求,即一個請求處理完畢以後再響應另外一個請求;若當前正在處理一個client的請求時,此時接受到其餘client的請求,這裏能夠利用信號來實現屏蔽處理。網絡

思路:利用全局變量busy來決定在while(1)循環中是否接受客戶端處理請求框架

        當busy爲1時,表示再也不接受client請求;當busy爲0時,表示能夠接受client請求,並標記busy爲1;
socket

        當與client創建鏈接的子進程結束後,要求將busy設置爲0
函數

        故流程爲:       測試

signal(SIGCHLD, signa_handler); // 註冊信號處理函數
while(1)
{
    clientfd = accept(...);
    ...
    
    if(busy == 1)
    {
        print_dbg(0, "socket busy\n");
        close(clientfd);
        continue;
    }
    busy = 1;
    int pid = fork();
    if(pid < 0) {}
    else if(pid == 0) 
    {
        child_process(clientfd);
        exit(0); // 求捕獲子進程結束信號,在處理函數中將busy復位爲0
    }
}

        

這裏主要簡單的使用signal函數結合waitpid來實現這個要求

  1. signal函數

  2. 函數原型爲: 

    #include <signal.h>
    typedef void (*sighandler_t)(int);
    sighandler_t signal(int signum, sighandler_t handler);

  3. 結合本博客主題,這裏簡單說明本函數具體使用的方式,首先是建一個信號處理函數,用於處理信號發生時所須要作的操做,具體代碼以下:

    int busy = 0;
    static void signal_handler(int sig)
    {
    	int stat;
    	pid_t pid;
    
    	while ((pid = waitpid(-1, &stat, WNOHANG)) > 0)
    		;
    	busy = 0;
    }

  4. waitpid 來處理子進程結束問題

        函數原型,

    #include<sys/types.h>
    #include<sys/wait.h>
    pid_t waitpid(pid_t pid,int * status,int options);

        其中參數參考百科,但須要注意的是上面的代碼,在信號處理函數中,下面這行是保障子進程結束不會僵死的關鍵

while ((pid = waitpid(-1, &stat, WNOHANG)) > 0);

    參數 status 能夠設成 NULL。參數 pid 爲欲等待的子進程識別碼,

    其餘數值意義以下:

    pid<-1 等待進程組識別碼爲 pid 絕對值的任何子進程。

    pid=-1 等待任何子進程,至關於 wait()。

    pid=0 等待進程組識別碼與目前進程相同的任何子進程。

    pid>0 等待任何子進程識別碼爲 pid 的子進程。

    參數options提供了一些額外的選項來控制waitpid,參數 option 能夠爲 0 或能夠用"|"運算符把它們鏈接起來使用,好比:

    ret=waitpid(-1,NULL,WNOHANG | WUNTRACED);

    若是咱們不想使用它們,也能夠把options設爲0,如:

    ret=waitpid(-1,NULL,0);

    WNOHANG 若pid指定的子進程沒有結束,則waitpid()函數返回0,不予以等待。若結束,則返回該子進程的ID。

    WUNTRACED 若子進程進入暫停狀態,則立刻返回,但子進程的結束狀態不予以理會。WIFSTOPPED(status)宏肯定返回值是否對應與一個暫停子進程。

最後源代碼能夠參考:https://github.com/liuyueyi/sa/blob/master/server.c

測試

服務器首先接受一個R\T的字符,用於判斷是接受數據仍是發送數據

  1. 啓動服務器

  2. 開啓一個終端輸入: nc 127.0.0.1 10033

    輸入: R [換行] 其餘的一些數據

  3. 再開啓一個終端輸入:  nc 127.0.0.1 10033

  4.  服務器提示socket busy,並關閉鏈接
相關文章
相關標籤/搜索