linux网络编程:使用单进程实现多客户端通信
服务端:
//回射服务器 //避免僵尸进程 #include "unistd.h" #include "sys/types.h" #include "sys/socket.h" #include "sys/wait.h" #include "netinet/in.h" #include "arpa/inet.h" #include "stdlib.h" #include "stdio.h" #include "errno.h" #include "string.h" #include "signal.h" #define ERR_EXIT(m) \ do \ { \ perror(m); \ exit(EXIT_FAILURE); \ }while(0) ssize_t readn(int fd , void *buf, size_t count) { size_t nleft = count; //剩余字节数 //在32位系统中size_t是4字节的,而在64位系统中,size_t是8字节的 增加可移植性 ssize_t nread;//读到的字节数 //ssize_t:这个数据类型用来表示可以被执行读写操作的数据块的大小,它表示的是sign size_t类型的。 char *bufp = (char*)buf; while (nleft > 0) { if ((nread = read(fd , bufp ,nleft)) < 0) { if (errno == EINTR) continue; return -1; } else if (nread == 0) //对等方关闭 return count - nleft; bufp += nread; nleft -= nread; } return count; } ssize_t writen(int fd , const void *buf , size_t count) { size_t nleft = count; //剩余字节数 ssize_t nwritten; char *bufp = (char*)buf; while (nleft > 0) { if ((nwritten = write(fd , bufp ,nleft)) < 0) { if (errno == EINTR) continue; return -1; } else if (nwritten == 0) //对等方关闭 continue; bufp += nwritten; nleft -= nwritten; } return count; } ssize_t recv_peek(int sockfd , void *buf , size_t len) { while (1) { int ret = recv(sockfd , buf , len , MSG_PEEK); if (ret == -1 && errno == EINTR) continue; return ret; } } ssize_t readline(int sockfd , void *buf , size_t maxline) { int ret; int nread; char *bufp = buf; int nleft = maxline; while (1) { ret = recv_peek(sockfd , bufp , nleft); if (ret < 0 ) return ret; else if (ret == 0) return ret; nread = ret; int i; for (i=0;i<nread;i++) { if (bufp[i] == '\n') { ret = readn(sockfd,bufp,i+1); // i+1 包括 \n if (ret != i + 1) exit(EXIT_FAILURE); return ret; } } if (nread > nleft) exit(EXIT_FAILURE); nleft -= nread; ret = readn(sockfd , bufp , nread); if (ret != nread) exit(EXIT_FAILURE); bufp += nread; } return -1; //运行到这里就出错了 } void echo_srv(int conn) { char recvbuf[1024]; int n; while(1) { memset(&recvbuf,0,sizeof(recvbuf)); int ret = readline(conn,recvbuf,1024); //扑捉客户端关闭 if (ret == -1) ERR_EXIT("readline"); if (ret == 0) { printf("clientclose\n"); break; } fputs(recvbuf,stdout); writen(conn,recvbuf,strlen(recvbuf)); } } void handle_sigchld(int sig) { // wait(NULL); while (waitpid(-1,NULL,WNOHANG) > 0);//waitpid()会暂时停止目前进程的执行,直到有信号来到或子进程结束。 } int main(int argc, char const *argv[]) { // signal(SIGCHLD,SIG_IGN);//避免僵尸进程 signal(SIGCHLD,handle_sigchld);//避免僵尸进程 int listenfd; if ((listenfd = socket(PF_INET,SOCK_STREAM,IPPROTO_TCP)) < 0) //IPv4为PF_INET,IPv6为PF_INET6;TCP传输时为SOCK_STREAM, UDP位SOCK_DGRAM ERR_EXIT("socket"); struct sockaddr_in servaddr; memset(&servaddr,0,sizeof(servaddr)); servaddr.sin_family = AF_INET; //设置地址家族 协议族,在socket编程中只能是AF_INET servaddr.sin_port = htons(5188); //设置端口 servaddr.sin_addr.s_addr = htonl(INADDR_ANY); //设置地址 // servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); // inet_aton("127.0.0.1",&servaddr.sin_addr); int on = 1; if (setsockopt(listenfd, SOL_SOCKET , SO_REUSEADDR, &on , sizeof(on)) < 0)//setsockopt()函数,用于任意类型、任意状态套接口的设置选项值。 ERR_EXIT("setsockopt"); if (bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr)) < 0) ERR_EXIT("bind"); /*定义函数:int bind(int sockfd, struct sockaddr * my_addr, int addrlen); 函数说明:bind()用来设置给参数sockfd 的socket 一个名称. 此名称由参数my_addr 指向一sockaddr 结构,对于不同的socket domain 定义了一个通用的数据结构 */ if (listen(listenfd,SOMAXCONN) < 0) ERR_EXIT("listen"); struct sockaddr_in peeraddr; socklen_t peerlen = sizeof(peeraddr); int conn; // pid_t pid; // while (1) // { // if ((conn = accept(listenfd,(struct sockaddr*)&peeraddr,&peerlen)) < 0) // ERR_EXIT("accept"); // /* // 定义函数:int accept(int s, struct sockaddr * addr, int * addrlen); // 函数说明:accept()用来接受参数s 的socket 连线. 参数s 的socket 必需先经bind()、listen()函数处理过, // 当有连线进来时accept()会返回一个新的socket 处理代码, 往后的数据传送与读取就是经由新的socket处理, // 而原来参数s 的socket 能继续使用accept()来接受新的连线要求. 连线成功时, 参数addr // 所指的结构会被系统填入远程主机的地址数据, 参数addrlen 为scokaddr 的结构长度. 关于机构sockaddr 的定义请参考bind(). // */ // printf("ip=%s port=%d\n", inet_ntoa(peeraddr.sin_addr) , ntohs(peeraddr.sin_port)); // //inet_ntoa 将网络地址转换成“.”点隔的字符串格式。 // //ntohs 本函数将一个16位数由网络字节顺序转换为主机字节顺序。 // // pid = fork(); // if (pid == -1) // ERR_EXIT("fork"); // if (pid == 0) // { // close(listenfd); // echo_srv(conn); // exit(EXIT_SUCCESS); // }else // close(conn); // } int client[FD_SETSIZE];//保存客户端文件描述符 int maxi = 0; int i; for(i=0 ; i < FD_SETSIZE;i++) client[i] = -1; int nready; int maxfd = listenfd; fd_set rset; fd_set allset; FD_ZERO(&rset); FD_ZERO(&allset); FD_SET(listenfd,&allset); while (1) { rset = allset; nready = select(maxfd+1,&rset,NULL,NULL,NULL); if (nready == -1) { if (errno == EINTR) continue; ERR_EXIT("select"); } if (nready == 0) continue; if (FD_ISSET(listenfd,&rset)) { peerlen = sizeof(peeraddr); conn = accept(listenfd,(struct sockaddr*)&peeraddr,&peerlen); if (conn == -1) ERR_EXIT("accept"); for (i=0;i<FD_SETSIZE;i++) { if (client[i] < 0) { client[i] = conn; if (i > maxi) maxi = i; break; } } if (i == FD_SETSIZE) { fprintf(stderr,"too many clients\n"); exit(EXIT_FAILURE); } printf("ip=%s port=%d\n", inet_ntoa(peeraddr.sin_addr) , ntohs(peeraddr.sin_port)); FD_SET(conn,&allset); if (conn > maxfd) maxfd = conn; if (--nready <= 0) continue; } for(i=0;i<=maxi;i++) { conn = client[i]; if (conn == -1) continue; if (FD_ISSET(conn,&rset)) { char recvbuf[1024] = {0}; int ret = readline(conn,recvbuf,1024); //扑捉客户端关闭 if (ret == -1) ERR_EXIT("readline"); if (ret == 0) { printf("clientclose\n"); FD_CLR(conn,&allset); client[i] = -1; } fputs(recvbuf,stdout); writen(conn,recvbuf,strlen(recvbuf)); if (--nready <= 0) break; } } } return 0; }
客户端:
//回射客户端 #include "unistd.h" #include "sys/types.h" #include "sys/socket.h" #include "netinet/in.h" #include "arpa/inet.h" #include "stdlib.h" #include "stdio.h" #include "errno.h" #include "string.h" #include "signal.h" #define ERR_EXIT(m) \ do \ { \ perror(m); \ exit(EXIT_FAILURE); \ }while(0) ssize_t readn(int fd , void *buf, size_t count) { size_t nleft = count; //剩余字节数 ssize_t nread;//读到的字节数 char *bufp = (char*)buf; while (nleft > 0) { if ((nread = read(fd , bufp ,nleft)) < 0) { if (errno == EINTR) continue; return -1; } else if (nread == 0) //对等方关闭 return count - nleft; bufp += nread; nleft -= nread; } return count; } ssize_t writen(int fd , const void *buf , size_t count) { size_t nleft = count; //剩余字节数 ssize_t nwritten; char *bufp = (char*)buf; while (nleft > 0) { if ((nwritten = write(fd , bufp ,nleft)) < 0) { if (errno == EINTR) continue; return -1; } else if (nwritten == 0) //对等方关闭 continue; bufp += nwritten; nleft -= nwritten; } return count; } ssize_t recv_peek(int sockfd , void *buf , size_t len) { while (1) { int ret = recv(sockfd , buf , len , MSG_PEEK); if (ret == -1 && errno == EINTR) continue; return ret; } } ssize_t readline(int sockfd , void *buf , size_t maxline) { int ret; int nread; char *bufp = buf; int nleft = maxline; while (1) { ret = recv_peek(sockfd , bufp , nleft); if (ret < 0 ) return ret; else if (ret == 0) return ret; nread = ret; int i; for (i=0;i<nread;i++) { if (bufp[i] == '\n') { ret = readn(sockfd,bufp,i+1); // i+1 包括 \n if (ret != i + 1) exit(EXIT_FAILURE); return ret; } } if (nread > nleft) exit(EXIT_FAILURE); nleft -= nread; ret = readn(sockfd , bufp , nread); if (ret != nread) exit(EXIT_FAILURE); bufp += nread; } return -1; //运行到这里就出错了 } void echo_cli(int sock) { // char sendbuf[1024] = {0}; // char recvbuf[1024] = {0}; // while (fgets(sendbuf,sizeof(sendbuf),stdin) != NULL) // { // writen(sock,sendbuf,strlen(sendbuf)); //包头加包体 // int ret = readline(sock ,recvbuf,sizeof(recvbuf)); // //扑捉客户端关闭 // if (ret == -1) // ERR_EXIT("readline"); // else if (ret == 0) // { // printf("client close\n"); // break; // } // fputs(recvbuf,stdout); // memset(sendbuf , 0 , sizeof(sendbuf)); // memset(recvbuf , 0 , sizeof(recvbuf)); // } // close(sock); fd_set rset; FD_ZERO(&rset); int nready; int maxfd; int fd_stdin = fileno(stdin); if (fd_stdin > sock) maxfd = fd_stdin; else maxfd = sock; char sendbuf[1024] = {0}; char recvbuf[1024] = {0}; while (1) { FD_SET(fd_stdin,&rset); FD_SET(sock,&rset); nready = select(maxfd+1,&rset,NULL,NULL,NULL); if (nready == -1) ERR_EXIT("select"); if (nready == 0) continue; if (FD_ISSET(sock,&rset)) { int ret = readline(sock ,recvbuf,sizeof(recvbuf)); //扑捉客户端关闭 if (ret == -1) ERR_EXIT("readline"); else if (ret == 0) { printf("server close\n"); break; } fputs(recvbuf,stdout); memset(recvbuf , 0 , sizeof(recvbuf)); } if (FD_ISSET(fd_stdin,&rset)) { if (fgets(sendbuf,sizeof(sendbuf),stdin) == NULL) break; writen(sock,sendbuf,strlen(sendbuf)); memset(sendbuf,0,sizeof(sendbuf)); } } close(sock); } void handle_sigpipe(int sig) { printf("recv a sig=%d\n",sig); } int main(void) { signal(SIGPIPE,handle_sigpipe); int sock; if ((sock = socket(PF_INET,SOCK_STREAM,IPPROTO_TCP)) < 0) //IPv4为PF_INET,IPv6为PF_INET6;TCP传输时为SOCK_STREAM, UDP位SOCK_DGRAM ERR_EXIT("socket"); 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"); //设置地址 if (connect(sock,(struct sockaddr*)&servaddr,sizeof(servaddr)) < 0) ERR_EXIT("connect"); struct sockaddr_in localaddr; socklen_t addrlen = sizeof(localaddr); if (getsockname(sock , (struct sockaddr*)&localaddr , &addrlen) < 0 ) ERR_EXIT("getsockname"); printf("ip=%s port=%d\n", inet_ntoa(localaddr.sin_addr) , ntohs(localaddr.sin_port)); echo_cli(sock); return 0; }