linux网络编程:使用单进程实现多客户端通信

时间:2024-04-28 13:58:16

服务端:

//回射服务器
//避免僵尸进程
#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() 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 > )
{
if ((nread = read(fd , bufp ,nleft)) < )
{
if (errno == EINTR)
continue;
return -;
}
else if (nread == ) //对等方关闭
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 > )
{
if ((nwritten = write(fd , bufp ,nleft)) < )
{
if (errno == EINTR)
continue;
return -;
}
else if (nwritten == ) //对等方关闭
continue;
bufp += nwritten;
nleft -= nwritten;
}
return count;
} ssize_t recv_peek(int sockfd , void *buf , size_t len)
{
while ()
{
int ret = recv(sockfd , buf , len , MSG_PEEK);
if (ret == - && 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 ()
{
ret = recv_peek(sockfd , bufp , nleft);
if (ret < )
return ret;
else if (ret == )
return ret;
nread = ret;
int i;
for (i=;i<nread;i++)
{
if (bufp[i] == '\n')
{
ret = readn(sockfd,bufp,i+); // i+1 包括 \n
if (ret != i + )
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 -; //运行到这里就出错了
} void echo_srv(int conn)
{
char recvbuf[];
int n;
while()
{
memset(&recvbuf,,sizeof(recvbuf));
int ret = readline(conn,recvbuf,);
//扑捉客户端关闭
if (ret == -)
ERR_EXIT("readline");
if (ret == )
{
printf("clientclose\n");
break;
}
fputs(recvbuf,stdout);
writen(conn,recvbuf,strlen(recvbuf));
}
} void handle_sigchld(int sig)
{
// wait(NULL);
while (waitpid(-,NULL,WNOHANG) > );//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)) < ) //IPv4为PF_INET,IPv6为PF_INET6;TCP传输时为SOCK_STREAM, UDP位SOCK_DGRAM
ERR_EXIT("socket");
struct sockaddr_in servaddr;
memset(&servaddr,,sizeof(servaddr));
servaddr.sin_family = AF_INET; //设置地址家族 协议族,在socket编程中只能是AF_INET
servaddr.sin_port = htons(); //设置端口
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 = ;
if (setsockopt(listenfd, SOL_SOCKET , SO_REUSEADDR, &on , sizeof(on)) < )//setsockopt()函数,用于任意类型、任意状态套接口的设置选项值。
ERR_EXIT("setsockopt"); if (bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr)) < )
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) < )
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 = ;
int i;
for(i= ; i < FD_SETSIZE;i++)
client[i] = -; int nready;
int maxfd = listenfd;
fd_set rset;
fd_set allset;
FD_ZERO(&rset);
FD_ZERO(&allset);
FD_SET(listenfd,&allset);
while ()
{
rset = allset;
nready = select(maxfd+,&rset,NULL,NULL,NULL);
if (nready == -)
{
if (errno == EINTR)
continue;
ERR_EXIT("select");
}
if (nready == )
continue;
if (FD_ISSET(listenfd,&rset))
{
peerlen = sizeof(peeraddr);
conn = accept(listenfd,(struct sockaddr*)&peeraddr,&peerlen);
if (conn == -)
ERR_EXIT("accept");
for (i=;i<FD_SETSIZE;i++)
{
if (client[i] < )
{
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 <= )
continue;
} for(i=;i<=maxi;i++)
{
conn = client[i];
if (conn == -)
continue;
if (FD_ISSET(conn,&rset))
{
char recvbuf[] = {};
int ret = readline(conn,recvbuf,);
//扑捉客户端关闭
if (ret == -)
ERR_EXIT("readline");
if (ret == )
{
printf("clientclose\n");
FD_CLR(conn,&allset);
client[i] = -;
}
fputs(recvbuf,stdout);
writen(conn,recvbuf,strlen(recvbuf)); if (--nready <= )
break;
}
}
}
return ;
}

客户端:

//回射客户端

#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() ssize_t readn(int fd , void *buf, size_t count)
{
size_t nleft = count; //剩余字节数
ssize_t nread;//读到的字节数
char *bufp = (char*)buf; while (nleft > )
{
if ((nread = read(fd , bufp ,nleft)) < )
{
if (errno == EINTR)
continue;
return -;
}
else if (nread == ) //对等方关闭
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 > )
{
if ((nwritten = write(fd , bufp ,nleft)) < )
{
if (errno == EINTR)
continue;
return -;
}
else if (nwritten == ) //对等方关闭
continue;
bufp += nwritten;
nleft -= nwritten;
}
return count;
} ssize_t recv_peek(int sockfd , void *buf , size_t len)
{
while ()
{
int ret = recv(sockfd , buf , len , MSG_PEEK);
if (ret == - && 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 ()
{
ret = recv_peek(sockfd , bufp , nleft);
if (ret < )
return ret;
else if (ret == )
return ret;
nread = ret;
int i;
for (i=;i<nread;i++)
{
if (bufp[i] == '\n')
{
ret = readn(sockfd,bufp,i+); // i+1 包括 \n
if (ret != i + )
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 -; //运行到这里就出错了
} 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[] = {};
char recvbuf[] = {};
while ()
{
FD_SET(fd_stdin,&rset);
FD_SET(sock,&rset);
nready = select(maxfd+,&rset,NULL,NULL,NULL);
if (nready == -)
ERR_EXIT("select");
if (nready == )
continue;
if (FD_ISSET(sock,&rset))
{
int ret = readline(sock ,recvbuf,sizeof(recvbuf));
//扑捉客户端关闭
if (ret == -)
ERR_EXIT("readline");
else if (ret == )
{
printf("server close\n");
break;
}
fputs(recvbuf,stdout);
memset(recvbuf , , sizeof(recvbuf));
} if (FD_ISSET(fd_stdin,&rset))
{
if (fgets(sendbuf,sizeof(sendbuf),stdin) == NULL)
break;
writen(sock,sendbuf,strlen(sendbuf));
memset(sendbuf,,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)) < ) //IPv4为PF_INET,IPv6为PF_INET6;TCP传输时为SOCK_STREAM, UDP位SOCK_DGRAM
ERR_EXIT("socket");
struct sockaddr_in servaddr;
memset(&servaddr,,sizeof(servaddr));
servaddr.sin_family = AF_INET; //设置地址家族
servaddr.sin_port = htons(); //设置端口
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); //设置地址 if (connect(sock,(struct sockaddr*)&servaddr,sizeof(servaddr)) < )
ERR_EXIT("connect"); struct sockaddr_in localaddr;
socklen_t addrlen = sizeof(localaddr);
if (getsockname(sock , (struct sockaddr*)&localaddr , &addrlen) < )
ERR_EXIT("getsockname");
printf("ip=%s port=%d\n", inet_ntoa(localaddr.sin_addr) , ntohs(localaddr.sin_port));
echo_cli(sock);
return ;
}