linux 多進程/多線程併發服務器簡單實現

首先補充一個關於read函數的點:
如果順利read()會返回實際讀到的字節數,最好能將返回值與參數count作比較,若返回的字節數比要求讀取的字節數少,則說明讀到了文件尾部。當有錯誤發生時則返回-1,錯誤代碼存入errno中,而文件讀寫位置則無法預期。
read 返回值:
1.> 0實際讀到的字節數
2.= 0數據讀完(讀到文件、管道、socket 末尾–對端關閉)
3.-1 異常
3.1errno == EINTR 說明此調用被信號中斷 重啓/quit
3.2errno == EAGAIN (EWOULDBLOCK) 非阻塞方式讀即使用不可阻斷I/O 時(O_NONBLOCK),若無數據可讀取則返回此值。
3.其他值 出現錯誤。–perror exit。

因此,最好對原有的read/write函數重新包裝一下:

ssize_t Read(int fd, void *ptr, size_t nbytes)
{
	ssize_t n;
again:
	if ( (n = read(fd, ptr, nbytes)) == -1) {
		if (errno == EINTR)
			goto again;
		else
			return -1;
	}
	return n;
}
ssize_t Write(int fd, const void *ptr, size_t nbytes)
{
	ssize_t n;
again:
	if ( (n = write(fd, ptr, nbytes)) == -1) {
		if (errno == EINTR)
			goto again;
		else
			return -1;
	}
	return n;
}

多進程併發服務器實現

其實多進程併發的重點在於分工:父進程只負責監聽,並在connect成功後創建新的子進程。而子進程則只負責通信。最後,對於子進程的回收,採用信號機制,子進程正常結束後會向父進程發送SIGCHLD信號,所以最好的回收辦法就是在父進程中註冊信號捕捉函數。
server.c

#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/wait.h>
#include <ctype.h>
#include <unistd.h>

#include "wrap.h"

#define MAXLINE 8192
#define SERV_PORT 6666

void do_sigchild(int num)
{
    while (waitpid(0, NULL, WNOHANG) > 0)
	{
	}

}

int main(void)
{
    struct sockaddr_in servaddr, cliaddr;
    socklen_t cliaddr_len;
    int listenfd, connfd;
    char buf[MAXLINE];
    char str[INET_ADDRSTRLEN];
    int i;
    pid_t pid;
    struct sigaction newact;

    newact.sa_handler = do_sigchild;
    sigemptyset(&newact.sa_mask);
    newact.sa_flags = 0;
    sigaction(SIGCHLD, &newact, NULL);

    listenfd = Socket(AF_INET, SOCK_STREAM, 0);

    int opt = 1;
    setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));//允許端口複用(在bind函數前調用)

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(SERV_PORT);

    Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

    Listen(listenfd, 20);

    printf("Accepting connections ...\n");
    while (1) 
	{
        cliaddr_len = sizeof(cliaddr);
        connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
        pid = fork();
        if (pid == 0)//子進程
		{
            Close(listenfd);
            while (1) 
			{
                int n = Read(connfd, buf, MAXLINE);
                if (n == 0) //說明客戶端關閉通信套接字,那麼當前子進程跳出循環並結束(會向父進程發出SIGCHLD信號)
				{
                    printf("the other side has been closed.\n");
                    break;
                }
                printf("received from %s at PORT %d\n",
                        inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
                        ntohs(cliaddr.sin_port));

                for (i = 0; i < n; i++)
                    buf[i] = toupper(buf[i]);

                Write(STDOUT_FILENO, buf, n);
                Write(connfd, buf, n);
            }
            Close(connfd);
            return 0;
        } 
		else if (pid > 0) //父進程
		{
            Close(connfd);
        }  
		else
            perr_exit("fork");
    }
    return 0;
}

client.c

/* client.c */
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include "wrap.h"

#define MAXLINE 8192
#define SERV_PORT 6666

int main(int argc, char *argv[])
{
    struct sockaddr_in servaddr;
    char buf[MAXLINE];
    int sockfd, n;

    sockfd = Socket(AF_INET, SOCK_STREAM, 0);

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
    servaddr.sin_port = htons(SERV_PORT);

    Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

    while (fgets(buf, MAXLINE, stdin) != NULL) 
	{
        Write(sockfd, buf, strlen(buf));
        n = Read(sockfd, buf, MAXLINE);
        if (n == 0) // //說明服務器端關閉了通信套接字
		{
            printf("the other side has been closed.\n");
            break;
        }
        else
            Write(STDOUT_FILENO, buf, n);
    }

    Close(sockfd);

    return 0;
}


在這裏插入圖片描述
可以看到,子進程成功被回收,並沒有成爲殭屍進程。

多線程併發服務器實現

多線程的回收只需要detach就可以了。

#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <ctype.h>
#include <unistd.h>
#include <fcntl.h>

#include "wrap.h"

#define MAXLINE 8192
#define SERV_PORT 6666

struct s_info 
{                     //定義一個結構體, 方便直接打包傳參給線程函數
    struct sockaddr_in cliaddr;
    int connfd;
};

void *do_work(void *arg)
{
    int n,i;
    struct s_info *ts = (struct s_info*)arg;
    char buf[MAXLINE];
    char str[INET_ADDRSTRLEN];      //#define INET_ADDRSTRLEN 16  可用"[+d"查看

    while (1) 
	{
        n = Read(ts->connfd, buf, MAXLINE);                     //讀客戶端
        if (n == 0)
        {
            printf("the client %d closed...\n", ts->connfd);
            break;                                              //跳出循環,關閉cfd
        }
        printf("received from %s at PORT %d\n",
                inet_ntop(AF_INET, &(*ts).cliaddr.sin_addr, str, sizeof(str)),
                ntohs((*ts).cliaddr.sin_port));                 //打印客戶端信息(IP/PORT)

        for (i = 0; i < n; i++) 
            buf[i] = toupper(buf[i]);                           //小寫-->大寫

        Write(STDOUT_FILENO, buf, n);                           //寫出至屏幕
        Write(ts->connfd, buf, n);                              //回寫給客戶端
    }
    Close(ts->connfd);

    return (void *)0;
}

int main(void)
{
    struct sockaddr_in servaddr, cliaddr;
    socklen_t cliaddr_len;
    int listenfd, connfd;
    pthread_t tid;
    struct s_info ts[256];      //根據最大線程數創建結構體數組.
    int i = 0;

    listenfd = Socket(AF_INET, SOCK_STREAM, 0);                     //創建一個socket, 得到lfd

    bzero(&servaddr, sizeof(servaddr));                             //地址結構清零
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);                   //指定本地任意IP
    servaddr.sin_port = htons(SERV_PORT);                           //指定端口號 6666

    Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); //綁定

    Listen(listenfd, 128);      //設置同一時刻鏈接服務器上限數

    printf("Accepting client connect ...\n");

    while (1) 
	{
        cliaddr_len = sizeof(cliaddr);
        connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);   //阻塞監聽客戶端鏈接請求
        ts[i].cliaddr = cliaddr;
        ts[i].connfd = connfd;

        /* 達到線程最大數時,pthread_create出錯處理, 增加服務器穩定性 */
        pthread_create(&tid, NULL, do_work, (void*)&ts[i]);
        pthread_detach(tid);                                                    //子線程分離,防止僵線程產生.
        i++;
    }

    return 0;
}


在這裏插入圖片描述
可見,不會出現殭屍線程。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章