班级:09计应1班      姓名:于青林     学号:0906041017



前言:服务器与多个客户端建立链接,当客户端向服务器发送数据时,服务器会将这些数据转发给所有与它取得链接的客户端,即实现一个群发的效果。


------------------------------------ service.c ------------------------------------


/*------------------------------------------------------------------------------------

 注释:server.c是服务器的程序,接受客户端的链接请求并将客户端发来的数据转发给所有的客户端;

执行指令使server.c变为可执行文件:

[root@localhost ~]#gcc service.c -o service

-------------------------------------------------------------------------------------*/


/* server.c */

#include                                  

#include                                

#include                                

#include                            

#include "wrap.h"                                  


#define MAXLINE 80                                

#define SERV_PORT 8000                            


int main(int argc, char **argv) {                  

        int i, maxi, maxfd, listenfd, connfd, sockfd;  

        int nready, client[FD_SETSIZE];         //client     [FD_SETSIZE] 存放有数据请求的客户端;              

        ssize_t n;                                      

        fd_set rset, allset;                            

        char buf[MAXLINE];                              

        char str[INET_ADDRSTRLEN];                      

        socklen_t cliaddr_len;                          

        struct sockaddr_in      cliaddr, servaddr;


        listenfd = Socket(AF_INET, SOCK_STREAM, 0);    //socket()打开一个网络通讯端口,返回一个套接字描述符给listenfd;


        bzero(&servaddr, sizeof(servaddr));             //首先将结构体清零;

        servaddr.sin_family      = AF_INET;             //设置地址类型为AF_INET;

        servaddr.sin_addr.s_addr = htonl(INADDR_ANY);   //转换ip地址字节序,网络地址为INADDR_ANY,这个宏表示本地任意IP地址

        servaddr.sin_port        = htons(SERV_PORT);    //转换端口的字节序。到目前为止,结构体servaddr的设置已经完毕;


        int opt = 1;

        setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));    //允许创建端口号相同但IP地址不同的多个socket描述符


        Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));//将所监听的端口号与服务器的地址、端口绑定;


        Listen(listenfd, 20);      //listen()声明服务器处于监听状态,并且最多允许有20个客户端处于连接待状态;


        maxfd = listenfd;                    //将所监听的最大的套接字描述符赋给maxfd;

        maxi = -1;                                    

        for (i = 0; i < FD_SETSIZE; i++)                

                client[i] = -1;        //for循环将client[i]的值设为-1,client[i]在下文中用来保存套接字描述符,这样可以把建立数据请求的端口赋给最前面的client[i];

        FD_ZERO(&allset);           //将allset套接字描述符集清空。

        FD_SET(listenfd, &allset);  //向allset套接字描述符集中添加服务器所监听到的端口(即listenfd所接受到的请求);


        for ( ; ; ) {               //for 用于循环接受有数据请求,要与服务器交互的client的端口(用select这个系统调用);                    

                rset = allset;          // 把allset套接字描述符集的内容赋给rset;

                nready = select(maxfd+1, &rset, NULL, NULL, NULL);   // 调用select将rset有效的套接字描述符的个数给nready;

                if (nready < 0)                        //select调用出错时,会返回一个负数给nready。该语句判断select是否调用成功。

                        perr_exit("select error");                


                if (FD_ISSET(listenfd, &rset)) {   //判断listenfd所接受到的客户端的请求是否在rset集合中,这是一个监听到的客户端与所监听客户端中有数据请求的客户端的一个比对,测试该数据请求的客户端是否在监听的队列中。

                        cliaddr_len = sizeof(cliaddr);  //把cliaddr结构体的长度赋给cliaddr_len,作为缓冲区的长度。

                        connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);  //接受客户端的连接请求,与客户端建立连接。


                        printf("received from %s at PORT %d\n",

                               inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),

                               ntohs(cliaddr.sin_port));           //打印客户端的ip地址和端口号。


                        for (i = 0; i < FD_SETSIZE; i++)           //for循环,i作为client的下标,表示放有数据请求的客户端的最小索引

                                if (client[i] < 0) {                    

                                        client[i] = connfd;                  

                                        break;                   //把有数据请求的客户端的套接字描述符放置到client[i]中最靠前的位置。

                                }

                        if (i == FD_SETSIZE) {        //若i以达到最大值FD_SETSIZE,则表示有数据请求的客户端已达到FD_SETSIZE

                                fputs("too many clients\n", stderr);    

                                exit(1);                                

                        }


                        FD_SET(connfd, &allset);         //向allset套接字描述符集中添加与服务器建立连接并有数据请求的客户端端口;

                        if (connfd > maxfd)                        

                                maxfd = connfd;         //若此时建立并有数据请求的客户端已大于原来的套接字描述符最大值则用connfd从置maxfd;

                        if (i > maxi)                              

                                maxi = i;                   //maxi 表示已用的 client[i]的最大索引


                        if (--nready == 0)                        

                                continue;                  //若--nready为0,则表示当前的套接字描述符集中只有listenfd这个监听的描述符,没有客户端的数据请求端口,则进行下一轮的select循环;

                }


                for (i = 0; i <= maxi; i++) {        //for循环处理有数据请求的客户端;

                        if ( (sockfd = client[i]) < 0)  //把client[i]中存放的客户端的套接字描述符赋给sockfd。若小于0,表示这个client[i]中没有套接字描述符。

                                continue;                   //继续执行for循环,查找数据请求的客户端;                      

                        if (FD_ISSET(sockfd, &rset)) {  //测试sockfd是否在rset这个描述符集中;

                                if ( (n = Read(sockfd, buf, MAXLINE)) == 0) {    // 读入客户端的数据,若n等于0表示客户端已经关闭了连接;


                                        Close(sockfd);                     //客户端关闭连接了,服务器也关闭与客户端相应的连接;

                                        FD_CLR(sockfd, &allset);          //清空与客户端连接的套接字描述符在allset集中;

                                        client[i] = -1;                   //同时将放置这个客户端套接字的数组位置设为-1,用来存放下一次的客户端数据请求的描述符;

                                } else {                            //若n不为0,则处理客户端的数据请求;

                                        for (i = 0; i <= maxi; i++)        

                                                Write(client[i], buf, n);        // 把 buf写回与服务器相连的每一个的客户

                                }


                                if (--nready == 0)      

                                        break;  //再次判断nready,若--nready为0,则表示当前的套接字描述符集中只有listenfd这个监听的描述符,没有客户端的数据请求端口,则进行下一轮的select循环;

                        }

                }

        }

}



------------------------------------ client.c ------------------------------------


/*-------------------------------------------------------------------------------------------

注释:client.c是客户端的程序,向服务器发送请求建立链接,并向服务器发送数据,让服务器转发给所有的客户端。

执行指令使client.c变为可执行文件:

[root@localhost pop3]# gcc client.c -0 client

--------------------------------------------------------------------------------------------*/


/* client.c */

#include                                

#include                                

#include                                

#include                            

#include "wrap.h"                                  


#define MAXLINE 80                                

#define SERV_PORT 8000                            


int main(int argc, char *argv[])                  

{

        struct sockaddr_in servaddr;                  

        char buf[MAXLINE];                              

        int sockfd, n;                                  


        sockfd = Socket(AF_INET, SOCK_STREAM, 0);       //socket()打开一个网络通讯端口,返回一个套接字描述符给sockfd;


        bzero(&servaddr, sizeof(servaddr));             //首先将结构体清零;

        servaddr.sin_family = AF_INET;                  //设置地址类型为AF_INET;

        inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);   // 由服务器端IP 转化为网络字节序保存在servaddr.sin_addr

        servaddr.sin_port = htons(SERV_PORT);          //转换端口的字节序。


        Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); //与服务器建立链接;

while(1)                                          

{       pid_t pid;                                      

        char *message;                                  

        int n;                                        

        pid = fork();                       //调用fork()创建一个子进程;

        if (pid < 0) {                     //调用fork时出错;

                perror("fork failed");                      

                exit(1);                                    

        }

        if (pid == 0) {                 //若pid == 0则是子进程

                        n = Read(sockfd, buf, MAXLINE);           //把服务器上的数据读到buf中;

                if (n == 0)                                  //若n等于0表示服务器已经关闭了连接;

                        printf("the servre has closed.\n");

                else                                      //即n>0,从服务器上读数据成功;

                        Write(STDOUT_FILENO, buf, n);         //把buf中的数据写到显示器上;

                }

        else{                                    //若pid >0则是父进程  

                        fgets(buf, MAXLINE, stdin);               //用fgets从键盘读数据到buf中;

                        Write(sockfd, buf, strlen(buf));             //把buf中的数据写给服务器;


           }


}


        Close(sockfd);     //关闭与服务器建立的链接;

        return 0;                                      


------------------------------------wrap.h ------------------------------------


/*-------------------------------------------------------------------------------------------

注释:service.c  client.c都包含了wrap.h这个头文件;


--------------------------------------------------------------------------------------------*/

#include

#include

#include


void perr_exit(const char *s)

{

        perror(s);

        exit(1);

}


int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr)

{

        int n;


again:

        if ( (n = accept(fd, sa, salenptr)) < 0) {

                if ((errno == ECONNABORTED) || (errno == EINTR))

                        goto again;

                else

                        perr_exit("accept error");

        }

        return n;

}


void Bind(int fd, const struct sockaddr *sa, socklen_t salen)

{

        if (bind(fd, sa, salen) < 0)

                perr_exit("bind error");

}


void Connect(int fd, const struct sockaddr *sa, socklen_t salen)

{

        if (connect(fd, sa, salen) < 0)

                perr_exit("connect error");

}


void Listen(int fd, int backlog)

{

        if (listen(fd, backlog) < 0)

                perr_exit("listen error");

}


int Socket(int family, int type, int protocol)

{

        int n;


        if ( (n = socket(family, type, protocol)) < 0)

                perr_exit("socket error");

        return n;

}


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;

}


void Close(int fd)

{

        if (close(fd) == -1)

                perr_exit("close error");

}

ssize_t Readn(int fd, void *vptr, size_t n)

{

        size_t  nleft;

        ssize_t nread;

        char   *ptr;


        ptr = vptr;

        nleft = n;

        while (nleft > 0) {

                if ( (nread = read(fd, ptr, nleft)) < 0) {

                        if (errno == EINTR)

                                nread = 0;

                        else

                                return -1;

                } else if (nread == 0)

                        break;


                nleft -= nread;

                ptr += nread;

        }

        return n - nleft;

}


ssize_t Writen(int fd, const void *vptr, size_t n)

{

        size_t nleft;

        ssize_t nwritten;

        const char *ptr;


        ptr = vptr;

        nleft = n;

        while (nleft > 0) {

                if ( (nwritten = write(fd, ptr, nleft)) <= 0) {

                        if (nwritten < 0 && errno == EINTR)

                                nwritten = 0;

                        else

                                return -1;

                }


                nleft -= nwritten;

                ptr += nwritten;

        }

        return n;

}

static ssize_t my_read(int fd, char *ptr)

{

        static int read_cnt;

        static char *read_ptr;

        static char read_buf[100];


        if (read_cnt <= 0) {

        again:

                if ( (read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0) {

                        if (errno == EINTR)

                                goto again;

                        return -1;

                } else if (read_cnt == 0)

                        return 0;

                read_ptr = read_buf;

        }

        read_cnt--;

        *ptr = *read_ptr++;

        return 1;

}


ssize_t Readline(int fd, void *vptr, size_t maxlen)

{

        ssize_t n, rc;

        char    c, *ptr;


        ptr = vptr;

        for (n = 1; n < maxlen; n++) {

                if ( (rc = my_read(fd, &c)) == 1) {

                        *ptr++ = c;

                        if (c  == '\n')

                                break;

                } else if (rc == 0) {

                        *ptr = 0;

                        return n - 1;

                } else

                        return -1;

        }

        *ptr  = 0;

        return n;

}



/*------------------------------------------------------------------------------------

   执行结果:

服务器端:

[root@localhost Desktop]# ./server

received from 127.0.0.1 at PORT 40555

received from 127.0.0.1 at PORT 40556

received from 218.198.18.30 at PORT 44625


客户端:

客户端1

[root@localhost Desktop]# ./client

nihao          客户端1的输入;  

nihao                 接受服务器转发的自己的数据;

good job                   接受服务器转发的客户端2的数据;

hello world             接受服务器转发的客户端3的数据;


客户端2

[root@localhost Desktop]# ./client

nihao                接受服务器转发的客户端1的数据;

good job                  客户端2的输入;

good job                  接受服务器转发的自己的数据;

hello world       接受服务器转发的客户端3的数据;


客户端3

[root@localhost Desktop]# ./client

nihao                接受服务器转发的客户端1的数据;

good job                  接受服务器转发的客户端2的数据;

hello world       客户端3的输入;

hello world       接受服务器转发的自己的数据;

-------------------------------------------------------------------------------------*/