• Socket编程实践(3) 多连接服务器实现与简单P2P聊天程序例程


    SO_REUSEADDR选项

    在上一篇文章的最后我们贴出了一个简单的C/S通信的例程。在该例程序中,使用"Ctrl+c"结束通信后,服务器是无法立即重启的,如果尝试重启服务器,将被告知:

    bind: Address already in use

    原因在于服务器重新启动时需要绑定地址:

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

    而这个时候网络正处于TIME_WAIT的状态,只有在TIME_WAIT状态退出后,套接字被删除,该地址才能被重新绑定。TIME_WAIT的时间是两个MSL,大约是1~4分钟。若每次服务器重启都需要等待TIME_WAIT结束那就太不合理了,好在选项SO_REUSEADDR能够解决这个问题。
    服务器端尽可能使用REUSEADD,在bind()之前调用setsockopt来设置SO_REUSEADDR套接字选项,使用SO_REUSEADDR选项可以使不必等待TIME_WAIT状态消失就可以重启服务器。

    
    /*设置地址重复使用*/
    int on = 1; //on为1表示开启
    if(setsockopt(listenfp ,SOL_SOCKET,SO_REUSEADDR,&on,sieof(on))<0)
        ERR_EXIT("setsockopt error");
    

    处理多客户的服务器

    在上一篇文章例程中,服务器端只能够连接一个客户端,并不能处理多个客户端的连接。原因在于服务器使用accept从已连接队列中获取一个连接后,便进入了对该连接的服务中,处于while循环状态。当一个新的客户端连接已经放入已连接队列时,服务器并不能执行到accpet的代码去获取队列中的连接。
    为了解决这个问题,我们可以fork()一个子进程,让子进程来处理一个客户端的连接,而父进程循环执行accept的代码,获取新的连接:

            int conn ;
            while(1)
            {
                    conn = accept(listenfd,(struct sockaddr*)&peeraddr,&peerlen);
                    if(conn <0)
                            ERR_EXIT("accept error");
                    else
                           printf("连接到服务器的客户端的IP地址是:%s,端口号是:%d
    ",inet_ntoa(peeraddr.sin_addr),htons(peeraddr.sin_port));
                    pid_t pid ;
                    pid = fork();//创建一个新进程
                    if (pid ==0) //子进程
                    {
                            close(listenfd); //子进程不需要监听套接字,将其关闭
                            /*循环获取数据、发送数据*/
                            char recvbuf[1024];
                            while(1)
                            {
                                    memset(recvbuf,0,sizeof(recvbuf));
                                    int ret = read(conn,recvbuf ,sizeof(recvbuf));
                                    fputs(recvbuf,stdout);
                                    write(conn,recvbuf,sizeof(recvbuf));
                            }
                            exit(EXIT_SUCCESS);
                    }
                    if(pid >0) //父进程
                    {
                            close(conn);//父进程无需该连接套接字,它的任务是执行accept获取连接      
                    }
                    else 
                    {
                            close(conn);
                    }
            }
    

    启动服务器端,使用多个客户端进行连接,可以看到服务器能够同时处理多个连接:

    实现一个P2P简单聊天程序

    为了实现聊天的功能,客户端与服务器端都需要有一个进程来读取连接,另一个进程来处理键盘输入。使用fork()来完成这个简单的聊天程序。
    客户端程序:

    //p2pcli.c
    #include<stdio.h>
    #include<stdlib.h>
    #include<errno.h>
    #include<signal.h>
    #include<arpa/inet.h>
    #include<netinet/in.h>
    #include<sys/types.h>
    #include<sys/socket.h>
    #include<string.h>
    #define ERR_EXIT(m)
            do 
            {
                    perror(m);
                    exit(EXIT_FAILURE);
            }while(0)
    void handler()
    {
            exit(EXIT_SUCCESS);
    }
    int main()
    {
            /*创建一个套接字*/
            int sock = socket(AF_INET,SOCK_STREAM,0);
            if(sock == -1)
            {
                    ERR_EXIT("socket");
            }
            /*定义一个地址结构*/
            struct sockaddr_in servaddr;
            memset(&servaddr,0,sizeof(servaddr));
            servaddr.sin_family = AF_INET;
            servaddr.sin_port = htons(5888);
            servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
            /*进行连接*/
            if(connect(sock,(struct sockaddr*)&servaddr,sizeof(servaddr))<0)
            {
                    ERR_EXIT("connect");
            }
            else
            {
                    printf("连接成功
    ");
            }
            pid_t pid ;
            pid = fork();
            if(pid == -1)
                    ERR_EXIT("fork");
            if(pid == 0) //子进程复制接收数据并显示出来
            {
                    char recvbuf[1024]={0};
                    while(1)
                    {
                            memset(recvbuf,0,sizeof(recvbuf));
                            int ret = read(sock ,recvbuf,sizeof(recvbuf));
                            if(ret == -1)
                            {
                                    ERR_EXIT("read");
                            }
                            if(ret == 0) //连接关闭
                            {
                                    printf("连接关闭
    ");
                                    kill(getppid(),SIGUSR1);
                                    break;
                            }
                            else
                            {
                                    printf("接收到信息:");
                                    fputs(recvbuf,stdout);
                            }
                    }
            }
            else //父进程负责从键盘接收输入并发送
            {
                    signal(SIGUSR1,handler);
                    char sendbuf[1024]={0}  ;
                    while(fgets(sendbuf,sizeof(sendbuf),stdin)!=NULL)
                    {
                            write(sock,sendbuf,strlen(sendbuf));
                            memset(&sendbuf,0,sizeof(sendbuf));
                    }
            }
            close(sock);
            return 0;
    }
    

    服务器端程序:

    // p2pser.c
    #include<stdio.h>
    #include<stdlib.h>
    #include<errno.h>
    #include<signal.h>
    #include<arpa/inet.h>
    #include<netinet/in.h>
    #include<sys/types.h>
    #include<sys/socket.h>
    #include<string.h>
    #define ERR_EXIT(m)
            do 
            {
                    perror(m);
                    exit(EXIT_FAILURE);
            }while(0)
    /*信号处理函数*/
    void handler(int sig)
    {
            exit(EXIT_SUCCESS);
    }
    int main()
    {
            /* 创建一个套接字*/
            int listenfd= socket(AF_INET ,SOCK_STREAM,0);
            if(listenfd==-1)
                    ERR_EXIT("socket");
            /*定义一个地址结构并填充*/
            struct sockaddr_in addr;
            addr.sin_family = AF_INET;   //协议族为ipv4
            addr.sin_port = htons(5888); //绑定端口号
            addr.sin_addr.s_addr = htonl(INADDR_ANY);//主机字节序转为网络字节序
            /*重复使用地址*/
            int on = 1;
            if(setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on))<0)
            {
                    ERR_EXIT("setsockopt");
            }
            /*将套接字绑定到地址上*/
            if(bind(listenfd,(const struct sockaddr *)&addr ,sizeof(addr))==-1)
            {
                    ERR_EXIT("bind");
            }
            /*监听套接字,成为被动套接字*/
            if(listen(listenfd,SOMAXCONN)<0)
            {
                    ERR_EXIT("Listen");
            }
            struct sockaddr_in peeraddr;
            socklen_t peerlen = sizeof(peeraddr);
            int conn ;
            conn = accept(listenfd,(struct sockaddr*)&peeraddr,&peerlen);
           if(conn <0)
                ERR_EXIT("accept error");
            else
                printf("连接到服务器的客户端的IP地址是:%s,端口号是:%d
    ",inet_ntoa(peeraddr.sin_addr),htons(peeraddr.sin_port));
            pid_t pid ;
            pid = fork();//创建一个新进程
            if(pid == -1)
            {
                    ERR_EXIT("fork");
            }
            if(pid == 0)//子进程
            {
                    signal(SIGUSR1,handler);
                    char sendbuf[1024] = {0};
                    while(fgets(sendbuf,sizeof(sendbuf),stdin)!=NULL)
                    {
                            write(conn,sendbuf,sizeof(sendbuf));
                            memset(sendbuf,0,sizeof(sendbuf));
                    }
                    exit(EXIT_SUCCESS);
            }
            else    //父进程 用来获取数据
            {
                    char recvbuf [1024]={0};
                    while(1)
                    {
                            memset(recvbuf,0,sizeof(recvbuf));
                            int ret = read(conn ,recvbuf,sizeof(recvbuf));
                            if(ret == -1)
                            {
                                    ERR_EXIT("read");
                            }
                            if(ret == 0) //对方已关闭 
                            {
                                    printf("对方关闭
    ");
                                    break;
                            }
                            fputs(recvbuf,stdout);
                    }
                    kill(pid,SIGUSR1);
                    exit(EXIT_SUCCESS);
            }
            /*关闭套接字*/
            close(listenfd);
            close(conn);
            return 0;
    
  • 相关阅读:
    构建之法阅读心得(九)
    构建之法阅读心得(八)
    构建之法阅读心得(七)
    构建之法阅读心得(六)
    构建之法阅读心得(五)
    构建之法阅读心得(四)
    一组阶段小记之读构建之法(三)
    暑期学习总结
    软工综合实践 学习笔记02
    软工综合实践 学习笔记01
  • 原文地址:https://www.cnblogs.com/QG-whz/p/5435396.html
Copyright © 2020-2023  润新知