目录:
1.补充知识
2.简易版回射服务器的实现
3.select模型实现
4.poll模型实现
1.补充知识
ps -eo pid,ppid,sid,tty,pgrp,comm | grep -E 'bash|PID|server'
netstat -an | grep "5000"
strace -e trace=signal -p 1359
2.简易回射服务器的实现
简易版回射服务器:实现服务器和多个客户端的点对点反射,源代码下面;
说明:简易版服务器的问题在于当服务器的子进程退出时,由于客户端处于调用fget函数侦听键盘输入的阻塞状态,所以客户端会一直处于CLOSE_WAIT,服务端处于FIN_WAIT2状态(见重现步骤),
当我们在客户端敲入数据后,客户端会执行write函数将数据发送给服务端,但此时服务端的接收窗口已关闭,会发送RST消息告诉客户端已关闭,客户端收到RST消息应该关闭发送窗口,如果客户端继续发送数据给服务器,此时会触发SIGPIPE的信号,导致服务器异常退出。所以我们需要解决这两个问题;
解决问题的方法:1.I/O进行异步处理(select/poll/epoll);2.对SIGPIPE信号进行处理;
重现步骤:
1.利用ps获取服务端进程id
root@epc:~
PID PPID SID TT PGRP COMMAND
1999 1856 1856 pts/1 1999 server
2003 1999 1856 pts/1 1999 server
2.执行kill 2003杀掉子进程
3.查看tcp状态
root@epc:~
tcp 0 0 0.0.0.0:5000 0.0.0.0:* LISTEN
tcp 0 0 127.0.0.1:5000 127.0.0.1:45196 FIN_WAIT2
tcp 1 0 127.0.0.1:45196 127.0.0.1:5000 CLOSE_WAIT
root@epc:~
4.客户端在键盘输入数据;
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <errno.h>
#define SERV_PORT 5000
#define MAXLINE 64
#define handle_error(msg) \
do { perror(msg); exit(EXIT_FAILURE); } while(0)
#define handle_success(msg) \
do { printf(msg); exit(EXIT_SUCCESS); } while(0)
ssize_t readline(int fd, void *buf, size_t nBytes)
{
char *buf_p = (char*)buf;
size_t n;
int nread;
for(;;)
{
nread = recv(fd, buf_p, nBytes, MSG_PEEK);
if (nread<0)
{
if (errno==EINTR)
continue;
return -1;
}
else if (nread == 0)
return 0;
else
break;
}
for (n=1; n<=nread; n++)
{
if (buf_p[n-1] == '\n')
break;
}
for(;;)
{
nread = read(fd, buf_p, n);
if (nread<0)
{
if (errno==EINTR)
continue;
return -1;
}
else if (nread == 0)
return 0;
else
{
break;
}
}
*(buf_p+nread) = 0;
return nread;
}
void str_cli(FILE *fp, int sockfd)
{
char sendline[MAXLINE], recvline[MAXLINE];
while (fgets(sendline, MAXLINE, fp) != NULL){
write(sockfd, sendline, strlen(sendline));
if (readline(sockfd, recvline, MAXLINE) == 0)
handle_success("str_cli:server terminated prematurely");
fputs(recvline, stdout);
}
}
int main(int argc, char **argv)
{
int sockfd;
struct sockaddr_in servaddr;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERV_PORT);
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
connect(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr));
str_cli(stdin, sockfd);
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <wait.h>
#define SERV_PORT 5000
#define MAXLINE 64
#define handle_error(msg) \
do { perror(msg); exit(EXIT_FAILURE); } while(0)
#define handle_success(msg) \
do { printf(msg); exit(EXIT_SUCCESS); } while(0)
void sig_chld(int signo)
{
pid_t pid;
int stat;
while ( (pid = waitpid(-1, &stat, WNOHANG)) > 0)
printf("child %d terminated\n", pid);
return;
}
void str_echo(int sockfd)
{
int n;
char buf[MAXLINE];
again:
while ( (n = read(sockfd, buf, MAXLINE)))
write(sockfd, buf, n);
if (n < 0 && errno == EINTR)
goto again;
else if (n < 0)
printf("str_echo: read error");
}
int main(int argc, char **argv)
{
if (signal(SIGCHLD, sig_chld) == SIG_ERR){
handle_error("can't catch SIGCHLD");
}
signal(SIGPIPE, SIG_IGN);
int listenfd, connfd;
pid_t childpid;
socklen_t clilen;
struct sockaddr_in cliaddr, servaddr;
listenfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr));
listen(listenfd, 5);
for ( ; ; ){
clilen = sizeof(cliaddr);
if ( (connfd = accept(listenfd, (struct sockaddr *) &cliaddr, &clilen)) < 0){
if (errno == EINTR)
continue;
else{
handle_error("accept error\n");
}
}
if ((childpid = fork()) == 0){
close(listenfd);
str_echo(connfd);
exit(0);
}
close(connfd);
}
return 0;
}
3.select模型实现
Select函数在Socket编程中还是比较重要的,可是对于初学Socket的人来说都不太爱用Select写程序,他们只是习惯写诸如connect、 accept、recv或recvfrom这样的阻塞程序(所谓阻塞方式block,顾名思义,就是进程或是线程执行到这些函数时必须等待某个事件的发生,如果事件没有发生,进程或线程就被阻塞,函数不能立即返回)。可是使用Select就可以完成非阻塞(所谓非阻塞方式non-block,就是进程或线程执行此函数时不必非要等待事件的发生,一旦执行肯定返回,以返回值的不同来反映函数的执行情况,如果事件发生则与阻塞方式相同,若事件没有发生则返回一个代码来告知事件未发生,而进程或线程继续执行,所以效率较高)方式工作的程序,它能够监视我们需要监视的文件描述符的变化情况——读写或是异常。
#include <sys/select.h>
#include <sys/time.h>
int select(int maxfd, fd_set *readset, fd_set *writeset,
fd_set *exceptset, const struct timeval *timeout);
@select函数:select能够监视我们需要监视的文件描述符的变化情况
@int:错误返回-1,超时返回0。当关注的事件返回时,返回大于0的值,该值是发生事件的文件描述符数
@maxfd:监视对象文件描述符数量。
@readset:将所有关注“是否存在待读取数据”的文件描述符注册到fd_set变量,并传递其地址值
@writeset: 将所有关注“是否可传输无阻塞数据”的文件描述符注册到fd_set变量,并传递其地址值
@exceptset:将所有关注“是否发生异常”的文件描述符注册到fd_set变量,并传递其地址值
@timeout:调用select后,为防止陷入无限阻塞状态,传递超时信息
struct timeval {
long tv_sec;
long tv_usec;
};
FD_ZERO(fd_set *fdset):清空fdset与所有文件句柄的联系。
FD_SET(int fd, fd_set *fdset):建立文件句柄fd与fdset的联系。
FD_CLR(int fd, fd_set *fdset):清除文件句柄fd与fdset的联系。
FD_ISSET(int fd, fdset *fdset):检查fdset联系的文件句柄fd是否可读写,>0表示可读写。
struct fd_set一个存放文件描述符(file descriptor),即文件句柄的聚合,实际上是一long类型的数组,
每一个数组元素都能与一打开的文件句柄(不管是Socket句柄,还是其他文件或命名管道或设备句柄)建立联系,建立联系的工作由程序员完成;
#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<unistd.h>
#include<stdlib.h>
#include<errno.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<string.h>
#include<signal.h>
#define ERR_EXIT(m) \
do { \
perror(m); \
exit(EXIT_FAILURE); \
} while (0)
#define MAXLINE 1024
#define SERV_PORT 5000
ssize_t readn(int fd, void *buf, size_t nByte)
{
size_t nleft = nByte;
ssize_t nread = 0;
char * buf_p = (char*)buf;
while (nleft > 0)
{
nread = read(fd, buf_p, nleft);
if (nread<0 && errno==EINTR)
continue;
else if (nread == 0)
return 0;
else if (nread < 0)
return -1;
else
return nread;
nleft -= nread;
buf_p += nread;
}
return nByte;
}
ssize_t writen(int fd, void *buf, size_t nBytes)
{
size_t nleft = nBytes;
char *buf_p = (char*)buf;
int nwritten = 0;
while(nleft > 0)
{
nwritten = write(fd, buf_p, nleft);
if (nwritten<0 && errno==EINTR)
nwritten = 0;
else
return -1;
nleft -= nwritten;
buf_p += nwritten;
}
return nBytes;
}
int main(int argc, char **argv)
{
int i, maxi, maxfd, listenfd, connfd, sockfd;
int nready, client[FD_SETSIZE];
ssize_t n;
char buf[MAXLINE];
socklen_t clilen;
struct sockaddr_in cliaddr, servaddr;
if ((listenfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
ERR_EXIT("socket error");
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
int on = 1;
if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
ERR_EXIT("setsockopt error");
if (bind(listenfd, (struct sockaddr*)&servaddr,sizeof(servaddr)) < 0)
ERR_EXIT("bind error");
if (listen(listenfd, SOMAXCONN) < 0)
ERR_EXIT("listen error");
maxfd = listenfd;
maxi = -1;
for (i = 0; i < FD_SETSIZE; ++i)
client[i] = -1;
fd_set rset, allset;
FD_ZERO(&allset);
FD_SET(listenfd, &allset);
while(true)
{
rset = allset;
nready = select(maxfd+1, &rset, NULL, NULL, NULL);
if (nready < 0) {
if (errno == EINTR)
continue;
ERR_EXIT("select error");
}
if (FD_ISSET(listenfd, &rset)) {
clilen = sizeof(cliaddr);
connfd = accept(listenfd, (struct sockaddr *) &cliaddr, &clilen);
for (i = 0; i < FD_SETSIZE; ++i)
{
if (client[i] < 0) {
client[i] = connfd;
break;
}
}
if (i == FD_SETSIZE)
{
ERR_EXIT("too many clients");
exit(EXIT_FAILURE);
}
FD_SET(connfd, &allset);
if (connfd > maxfd)
maxfd = connfd;
if (i > maxi)
maxi = i;
if (--nready <= 0)
continue;
}
for (i = 0; i <= maxi; ++i) {
if ( (sockfd = client[i]) < 0)
continue;
if (FD_ISSET(sockfd, &rset))
{
if ( (n = read(sockfd, buf, MAXLINE)) == 0)
{
close(sockfd);
FD_CLR(sockfd, &allset);
client[i] = -1;
}
else if (n < 0)
ERR_EXIT("read error");
else
writen(sockfd, buf, n);
if (--nready <= 0)
break;
}
}
}
return 0;
}
#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<unistd.h>
#include<stdlib.h>
#include<errno.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<string.h>
#define ERR_EXIT(m) \
do { \
perror(m); \
exit(EXIT_FAILURE); \
} while (0)
#define MAXLINE 1024
#define SERV_PORT 5000
int main(void)
{
int sock;
if ((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
ERR_EXIT("socket error");
struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERV_PORT);
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
if (connect(sock, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)
ERR_EXIT("connect error");
struct sockaddr_in localaddr;
char cli_ip[20];
socklen_t local_len = sizeof(localaddr);
memset(&localaddr, 0, sizeof(localaddr));
if( getsockname(sock,(struct sockaddr *)&localaddr,&local_len) != 0 )
ERR_EXIT("getsockname error");
inet_ntop(AF_INET, &localaddr.sin_addr, cli_ip, sizeof(cli_ip));
printf("host %s:%d\n", cli_ip, ntohs(localaddr.sin_port));
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 (true)
{
FD_SET(fd_stdin, &rset);
FD_SET(sock, &rset);
nready = select(maxfd + 1, &rset, NULL, NULL, NULL);
if (nready < 0) {
if (errno == EINTR)
continue;
ERR_EXIT("select error");
}
if (FD_ISSET(sock, &rset))
{
int ret = read(sock, recvbuf, sizeof(recvbuf));
if (ret == -1)
ERR_EXIT("read error");
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;
write(sock, sendbuf, strlen(sendbuf));
memset(sendbuf, 0, sizeof(sendbuf));
}
}
close(sock);
return 0;
}
4.poll模型实现
select() 和 poll() 系统调用的本质一样,poll() 的机制与 select() 类似,与 select() 在本质上没有多大差别,管理多个描述符也是进行轮询,根据描述符的状态进行处理。
#include <poll.h>
int poll(struct pollfd * fdarray, unsigned long nfds, int timeout);
参数解释:
(1)fdarray:指向一个结构体数组的第0个元素的指针,每个数组元素都是一个struct pollfd结构,用于指定测试某个给定的fd的条件 ;
(2)nfds:表示fds结构体数组的长度 ;
(3)timeout:表示poll函数的超时时间,单位是毫秒 ;
函数功能:
监视并等待多个文件描述符的属性变化
函数返回值:
(1)返回值小于0,表示出错
(2)返回值等于0,表示poll函数等待超时
(3)返回值大于0,表示poll由于监听的文件描述符就绪返回,并且返回结果就是就绪的文件描述符的个数。
在该函数的第一个参数中,我们知道存放文件描述符的数组中每一个元素都是一个struct pollfd结构,那么pollfd结构到底是什么样的呢?我们来一起了解一下:
struct poolfd{
int fd;
short events;
short revents;
}
成员变量说明:
(1)fd:每一个 pollfd 结构体指定了一个被监视的文件描述符,可以传递多个结构体,指示 poll() 监视多个文件描述符。
(2)events:表示要告诉操作系统需要监测fd的事件(输入、输出、错误),每一个事件有多个取值
(3)revents:revents 域是文件描述符的操作结果事件,内核在调用返回时设置这个域。events 域中请求的任何事件都可能在 revents 域中返回。
事件的常用值有以下几种:
可读:
POLLIN: 有普通数据或者优先数据可读
POLLRDNORM: 有普通数据可读
POLLRDBAND: 有优先数据可读
POLLPRI: 有紧急数据可读
可写:
POLLOUT: 有普通数据可写
POLLWRNORM: 有普通数据可
POLLWRBAND: 有紧急数据可写
异常:
POLLERR: 有错误发生
POLLHUP: 有描述符挂起事件发生
POLLNVAL: 描述符非法
#include <limits.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <poll.h>
#include <unistd.h>
#define ERR_EXIT(m) \
do { \
perror(m); \
exit(EXIT_FAILURE); \
} while (0)
#define MAXLINE 1024
#define SERV_PORT 5000
#define OPEN_MAX 1024
#define INFTIM -1
ssize_t writen(int fd, void *buf, size_t nBytes)
{
size_t nleft = nBytes;
char *buf_p = (char*)buf;
int nwritten = 0;
while(nleft > 0)
{
nwritten = write(fd, buf_p, nleft);
if (nwritten<0 && errno==EINTR)
nwritten = 0;
else
return -1;
nleft -= nwritten;
buf_p += nwritten;
}
return nBytes;
}
int main(int argc, char **argv)
{
int i, maxi, listenfd, connfd, sockfd;
int nready;
ssize_t n;
char buf[MAXLINE];
socklen_t clilen;
struct pollfd client[OPEN_MAX];
struct sockaddr_in cliaddr, servaddr;
if ((listenfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
ERR_EXIT("socket error");
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
int on = 1;
if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
ERR_EXIT("setsockopt error");
if (bind(listenfd, (struct sockaddr*)&servaddr,sizeof(servaddr)) < 0)
ERR_EXIT("bind error");
if (listen(listenfd, SOMAXCONN) < 0)
ERR_EXIT("listen error");
client[0].fd = listenfd;
client[0].events = POLLIN;
for (i = 1; i < OPE_MAX; ++i)
client[i].fd = -1;
maxi = 0;
while (true)
{
nready = poll(client, maxi + 1, INFTIM);
if(nready < 0)
{
perror("poll error");
break;
}
else if(nready == 0)
{
continue;
}
if (client[0].revents & POLLIN)
{
clilen = sizeof(cliaddr);
connfd = accept(listenfd, (struct sockaddr *) &cliaddr, &clilen);
printf("new client\n");
for (i = 1; i < OPEN_MAX; ++i)
{
if (client[i].fd < 0)
{
client[i].fd = connfd;
break;
}
}
if (OPEN_MAX == i)
{
ERR_EXIT("too many clients");
}
client[i].events = POLLIN;
if (i > maxi)
maxi = i;
if (--nready <= 0)
continue;
}
for (i = 1; i <= maxi; ++i)
{
if ( (sockfd = client[i].fd) < 0)
continue;
if (client[i].revents & (POLLIN | POLLERR))
{
if ( (n = read(sockfd, buf, MAXLINE)) >= 0)
{
if (errno == ECONNRESET)
{
close(sockfd);
client[i].fd = -1;
}
else if (0 == n)
{
close(sockfd);
client[i].fd = -1;
} else
writen(sockfd, buf, n);
if (--nready <= 0)
break;
}
else
{
ERR_EXIT("read error");
}
}
}
}
return 0;
}
#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<unistd.h>
#include<stdlib.h>
#include<errno.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<string.h>
#include<poll.h>
#define ERR_EXIT(m) \
do { \
perror(m); \
exit(EXIT_FAILURE); \
} while (0)
#define MAXLINE 1024
#define SERV_PORT 5000
int main(void)
{
int sock;
if ((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
ERR_EXIT("socket error");
struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERV_PORT);
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
if (connect(sock, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)
ERR_EXIT("connect error");
struct sockaddr_in localaddr;
char cli_ip[20];
socklen_t local_len = sizeof(localaddr);
memset(&localaddr, 0, sizeof(localaddr));
if( getsockname(sock,(struct sockaddr *)&localaddr,&local_len) != 0 )
ERR_EXIT("getsockname error");
inet_ntop(AF_INET, &localaddr.sin_addr, cli_ip, sizeof(cli_ip));
printf("host %s:%d\n", cli_ip, ntohs(localaddr.sin_port));
struct pollfd p_fds[2];
int fd_stdin = fileno(stdin);
p_fds[0].fd = STDIN_FILENO;
p_fds[0].events = POLLIN;
p_fds[0].revents = 0;
p_fds[1].fd = sock;
p_fds[1].events = POLLIN;
p_fds[1].revents = 0;
int nready;
int maxfd;
if (fd_stdin > sock)
maxfd = fd_stdin;
else
maxfd = sock;
char sendbuf[1024] = {0};
char recvbuf[1024] = {0};
while (true)
{
nready = poll(p_fds, 2, -1);
if(nready < 0)
ERR_EXIT("poll");
else if(nready == 0)
{
printf("timeout\n");
continue;
}
else
{
if ((p_fds[1].revents&POLLIN) == POLLIN && p_fds[1].fd == sock)
{
int ret = read(sock, recvbuf, sizeof(recvbuf));
if (ret == -1)
ERR_EXIT("read error");
else if (ret == 0 || errno == ECONNRESET)
{
p_fds[1].fd = -1;
break;
}
fputs(recvbuf, stdout);
memset(recvbuf, 0, sizeof(recvbuf));
}
if ((p_fds[0].revents&POLLIN) == POLLIN && p_fds[0].fd == STDIN_FILENO)
{
if (fgets(sendbuf, sizeof(sendbuf), stdin) == NULL)
break;
write(sock, sendbuf, strlen(sendbuf));
memset(sendbuf, 0, sizeof(sendbuf));
}
}
}
close(sock);
return 0;
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)