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;


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