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狀態消失就能夠重啓服務器。socket

/*設置地址重複使用*/
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\n",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);
                }
        }

啓動服務器端,使用多個客戶端進行鏈接,能夠看到服務器可以同時處理多個鏈接:
code

實現一個P2P簡單聊天程序

爲了實現聊天的功能,客戶端與服務器端都須要有一個進程來讀取鏈接,另外一個進程來處理鍵盤輸入。使用fork()來完成這個簡單的聊天程序。
客戶端程序:blog

//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("鏈接成功\n");
        }
        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("鏈接關閉\n");
                                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\n",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("對方關閉\n");
                                break;
                        }
                        fputs(recvbuf,stdout);
                }
                kill(pid,SIGUSR1);
                exit(EXIT_SUCCESS);
        }
        /*關閉套接字*/
        close(listenfd);
        close(conn);
        return 0;
相關文章
相關標籤/搜索