socket編程之併發服務器(CS模型改進2)

    承接之前博客的基於linux系統的CS模型實現,這裏再修改,CS模型中的服務器是迭代服務器,每次只能服務一個客戶,我們並不希望整個服務器被一個客戶單獨長期佔用,而是希望服務多個客戶,這裏就用到了fork一個子進程來服務每個客戶。這也是fork兩個典型用法之一。

fork函數用法:

#include <unistd.h>
pid_t fork(void);
返回:在子進程爲0,父進程返回子進程ID

理解fork最困難的地方:調用一次,返回兩次,它在調用進程(父進程)中返回子進程ID,子進程返回爲0。
fork之後的特性:父進程中調用fork之前的所有描述符在fork返回之後由子進程分享。我們的併發服務器就是利用了這個特點。

併發服務器原理講解

我們用fork實現併發服務器的核心代碼:
while(1)
    {
        if((conn=accept(listenfd,(struct sockaddr*)&peeraddr,&peerlen))<0)
            ERR_EXIT("accept");

        printf("ip=%s port=%d\n",inet_ntoa(peeraddr.sin_addr),ntohs(peeraddr.sin_port));
        pid=fork();
        if(pid==-1)
            ERR_EXIT("fork");
        if(pid==0)
        {
            close(listenfd);
            do_service(conn);
            exit(EXIT_SUCCESS);//將子進程退出,要不它會fork()
        }
        else
            close(conn);
    }
當一個連接建立,accept返回,服務器接着fork,然後由子程序服務客戶,父進程(也就是服務器)等待另一個連接(通過listenfd),既然新的客戶由子進程提供服務,父進程就關閉已連接套接字,這裏可能會有很多朋友疑惑,父進程關閉了套接字,子進程是否會收到影響,其實是不會的,這裏的close只是將引用-1,子進程可以繼續使用。

我們可以在子進程中顯示的關閉已連接套接字,這一點並非必須,因爲下一個語句exit,它的部分工作就是關閉所有由內核打開的描述符。

程序service:

#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>

#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#define ERR_EXIT(m) \
    do \
    { \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while(0)
void do_service(int conn)
{
    char recvbuf[1024];
        while(1){
                    memset(recvbuf,0,sizeof(recvbuf));//初始化recvbuf
                    int ret=read(conn,recvbuf,sizeof(recvbuf));//函數從打開的文件,設備中讀取數據,返回讀取的字節數。
                    if(ret==0)
                    {
                        printf("client_close\n");
                        break;
                    }
                    else if(ret==-1)
                    {
                        ERR_EXIT("read");
                    }
                    fputs(recvbuf,stdout);
                    write(conn,recvbuf,ret);//buf中數據被複制到了TCP發送緩衝區
                }
}
int main(void)
{
    int listenfd;
    /*if((listenfd=socket(AF_INET,SOCK_STEAM,IPPOTO_TCP))<0) */
    if((listenfd=socket(AF_INET,SOCK_STREAM,0))<0)
        ERR_EXIT("socket");

    //IPV4地址結構
    struct sockaddr_in servaddr;
    memset(&servaddr,0,sizeof(servaddr));
    servaddr.sin_family=AF_INET;//地址家族
    servaddr.sin_port=htons(5188);//端口,主機轉網絡
    /*servaddr.sin_addr.s_addr=inet_addr("127.0.0.1");*/
    /*inet_aton("127.0.0.1",&servaddr.sin_addr);*/
    servaddr.sin_addr.s_addr=htonl(INADDR_ANY);
    //地址reuseaddr
    int on=1;
    if(setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on))<0)
        ERR_EXIT("setsockopt");

    if(bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr))<0)
        ERR_EXIT("bind");


    if(listen(listenfd,SOMAXCONN)<0)
        ERR_EXIT("listen");

    struct sockaddr_in peeraddr;
    socklen_t peerlen =sizeof(peeraddr);//typedef int socklen_t
    int conn;//已連接套接字
    pid_t pid;
    while(1)
    {
        if((conn=accept(listenfd,(struct sockaddr*)&peeraddr,&peerlen))<0)
            ERR_EXIT("accept");

        printf("ip=%s port=%d\n",inet_ntoa(peeraddr.sin_addr),ntohs(peeraddr.sin_port));
        pid=fork();
        if(pid==-1)
            ERR_EXIT("fork");
        if(pid==0)
        {
            close(listenfd);
            do_service(conn);
            exit(EXIT_SUCCESS);//將子進程退出,要不它會fork()
        }
        else
            close(conn);
    }
    return 0;
}

cilent:

#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>

#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#define ERR_EXIT(m) \
    do \
    { \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while(0)
int main()
{
    int sock;
    /*if((listenfd=socket(AF_INET,SOCK_STEAM,IPPOTO_TCP))<0) */
    if((sock=socket(AF_INET,SOCK_STREAM,0))<0)
        ERR_EXIT("socket");

    //IPV4地址結構
    struct sockaddr_in servaddr;
    memset(&servaddr,0,sizeof(servaddr));
    servaddr.sin_family=AF_INET;//地址家族
    servaddr.sin_port=htons(5188);//端口,主機轉網絡
    servaddr.sin_addr.s_addr=inet_addr("127.0.0.1");
    /*inet_aton("127.0.0.1",&servaddr.sin_addr);*/


    if(connect(sock,(struct sockaddr*)&servaddr,sizeof(servaddr))<0)
        ERR_EXIT("connect");

    char sendbuf[1024]={0};
    char recvbuf[1024]={0};
    while(fgets(sendbuf,sizeof(sendbuf),stdin) !=NULL){
        write(sock,sendbuf,strlen(sendbuf));//發送
        read(sock,recvbuf,sizeof(recvbuf));//接受
        fputs(recvbuf,stdout);
        memset(sendbuf,0,sizeof(sendbuf));
        memset(recvbuf,0,sizeof(recvbuf));
    }
    close(sock);
    return 0;


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