4.Linux网络编程-select和poll模型

2023-05-16

目录:
1.补充知识
2.简易版回射服务器的实现
3.select模型实现
4.poll模型实现

1.补充知识

#显示进程的pid:
ps -eo pid,ppid,sid,tty,pgrp,comm | grep -E 'bash|PID|server'
#显示TCP的状态
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:~# ps -eo pid,ppid,sid,tty,pgrp,comm | grep -E 'PID|server'
   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:~# netstat -an|grep 5000
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)

/**
readline-读取一行数据,以'\n'为分界符;
返回值:-1表示返回错误,0表示收到FIN信号,>0表示数据的长度
说明:此函数实现了低性能的读取行数据的功能;
@ssize_t:返回读的长度 -1失败
@fd:文件描述符
@buf:待写数据首地址
@nByte:待写长度
 */
//ssize_t readline(int fd, void *buf, size_t nByte)
//{
//    ssize_t n, nread;
//    char c;
//    char* ptr = (char*)buf;
//    for (n = 1; n < nByte; n++){
//    again:
//        if ( (nread = read(fd, &c, 1)) == 1){
//            *ptr++ = c;
//            if (c == '\n')
//                break;  // newline is stored
//        } else if (nread == 0){
//            *ptr = 0;
//            return (n - 1); // EOF, n - 1 bytes
//        } else {
//            if (errno == EINTR)
//                    goto again;
//            return -1; /// error, errno set by read()
//        }
//    }
//    *ptr = 0;
//    return n;
//}


//readline-读取一行数据,以'\n'为分界符;
//返回值:-1表示返回错误,0表示收到FIN信号,>0表示数据的长度
//说明:此函数实现了高性能的读取行数据的功能;
//@ssize_t:返回读的长度 -1失败
//@fd:文件描述符
//@buf:待写数据首地址
//@nByte:待写长度
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);  // do it all
    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)

/*
 * sig_chld:信号的处理函数,触发时用来处理僵尸进程
 * 作用:子进程退出后会发送SIGCHLD信号给父进程,系统默认行为是维持子进程保持僵死状态不退出;
 * @signo:捕获的信号;
*/
void sig_chld(int signo)
{
    pid_t pid;
    int stat;
    // 用waitpid不用wait因为可以通过WNOHANG选项告知waitpid在尚有未终止的子进程在运行时不要阻塞
    while ( (pid = waitpid(-1, &stat, WNOHANG)) > 0)
        printf("child %d terminated\n", pid);// printf() isn't suitable for use here
    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);	//处理服务器子进程关闭后,客户端连续发送多次数据,产生SIGPIPE信号,导致服务器挂掉;
    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) // accept 在阻塞的时候被signal打断会返回EINTR错误存于errno
                continue; // back to for()
            else{
            	handle_error("accept error\n");
            }
        }

        if ((childpid = fork()) == 0){ // child
            close(listenfd); // close listening socket
            str_echo(connfd);// process the request
            exit(0);
        }
        close(connfd); // parent closes connected socket
    }
    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

//readn函数
//说明:此函数解决了粘包的数据处理;
//@ssize_t:-1表示返回错误,0表示收到FIN信号,>0表示数据的长度
//@fd:文件描述符
//@buf:待写数据首地址
//@nByte:待读长度
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;
}

//writen函数
//说明:此函数解决了缓冲区数据发送溢出的处理;
//@ssize_t:-1表示返回错误,0表示收到FIN信号,>0表示数据的长度
//@fd:文件描述符
//@buf:待写数据首地址
//@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;

	//1.创建套接字
    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);

	//2.设置套接字属性
    int on = 1;
    if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
    	ERR_EXIT("setsockopt error");

    //3.绑定
    if (bind(listenfd, (struct sockaddr*)&servaddr,sizeof(servaddr)) < 0)
    	ERR_EXIT("bind error");

    //4.监听
    if (listen(listenfd, SOMAXCONN) < 0) //listen应在socket和bind之后,而在accept之前
    	ERR_EXIT("listen error");

	maxfd = listenfd; // initialize
	maxi = -1;  // index into client[] array
	for (i = 0; i < FD_SETSIZE; ++i)
		client[i] = -1; // -1 indicates available entry

	fd_set rset, allset;
	FD_ZERO(&allset);
	FD_SET(listenfd, &allset);
	while(true)
	{
		rset = allset; //rset被select调用后值会改变
		nready = select(maxfd+1, &rset, NULL, NULL, NULL);
        if (nready < 0) {
            if (errno == EINTR)
                continue;
            ERR_EXIT("select error");
        }

		if (FD_ISSET(listenfd, &rset)) { // new client connection
			clilen = sizeof(cliaddr);
			connfd = accept(listenfd, (struct sockaddr *) &cliaddr, &clilen);

			for (i = 0; i < FD_SETSIZE; ++i)
			{
				if (client[i] < 0) {
					client[i] = connfd; // save descriptor
					break;
				}
			}
			if (i == FD_SETSIZE)
			{
				ERR_EXIT("too many clients");
				exit(EXIT_FAILURE);
			}
			FD_SET(connfd, &allset); // add new descriptor to set
			if (connfd > maxfd)
				maxfd = connfd; // for select

			if (i > maxi)
				maxi = i; // max index in client[i] array

			if (--nready <= 0)
				continue; // no more readable descriptors
		}
		for (i = 0; i <= maxi; ++i) { // check all clients for data
			if ( (sockfd = client[i]) < 0)
				continue;
			if (FD_ISSET(sockfd, &rset))
			{
				if ( (n = read(sockfd, buf, MAXLINE)) == 0)
				{
					// connection closed by client
					//shutdown(sockfd, SHUT_WR);	//关闭可写,但还可以接收客户端数据,避免服务器异常;
					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; // no more readable descriptors
			}
		}
	}

    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)
        //  listenfd = socket(AF_INET, SOCK_STREAM, 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); //select返回表示检测到可读事件
        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:          描述符非法
//利用poll实现回射服务器
#include <limits.h> // for OPEN_MAX
#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

//writen函数
//说明:此函数解决了缓冲区数据发送溢出的处理;
//@ssize_t:-1表示返回错误,0表示收到FIN信号,>0表示数据的长度
//@fd:文件描述符
//@buf:待写数据首地址
//@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, listenfd, connfd, sockfd;
	int nready;

	ssize_t n;
	char buf[MAXLINE];
	socklen_t clilen;
	struct pollfd client[OPEN_MAX];
	struct sockaddr_in cliaddr, servaddr;

	//1.创建套接字
	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);

	//2.设置套接字属性
	int on = 1;
	if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
		ERR_EXIT("setsockopt error");

	//3.绑定
	if (bind(listenfd, (struct sockaddr*)&servaddr,sizeof(servaddr)) < 0)
		ERR_EXIT("bind error");

	//4.监听
	if (listen(listenfd, SOMAXCONN) < 0) //listen应在socket和bind之后,而在accept之前
		ERR_EXIT("listen error");

	client[0].fd = listenfd;
	client[0].events = POLLIN;
	for (i = 1; i < OPE_MAX; ++i)
		client[i].fd = -1;  // indicates available entry
	maxi = 0;  // max index into client[] array
	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)
		{ // new client connection
			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; // save descriptor
					break;
				}
			}
			if (OPEN_MAX == i)
			{
				ERR_EXIT("too many clients");
			}
			client[i].events = POLLIN;
			if (i > maxi)
				maxi = i;  // max index in client[] array

			if (--nready <= 0)
				continue; // no more readable descriptors
		}

		for (i = 1; i <= maxi; ++i)
		{ // check all clients for data
			if ( (sockfd = client[i].fd) < 0)
				continue;
			if (client[i].revents & (POLLIN | POLLERR))
			{
				if ( (n = read(sockfd, buf, MAXLINE)) >= 0)
				{
					if (errno == ECONNRESET)
					{
						// connection reset by client
						close(sockfd);
						client[i].fd = -1;
					}
					else if (0 == n)
					{
						// connection closed by client
						close(sockfd);
						client[i].fd = -1;
					} else
						writen(sockfd, buf, n);
					if (--nready <= 0)
						break; // no more readable descriptors
				}
				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)
        //  listenfd = socket(AF_INET, SOCK_STREAM, 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(使用前将#替换为@)

4.Linux网络编程-select和poll模型 的相关文章

  • 数据结构_练习 第7章  查找

    1 xff0e 选择题 xff08 1 xff09 对 n 个元素的表做顺序查找时 xff0c 若查找每个元素的概率相同 xff0c 则平均查找长度为 xff08 xff09 A xff0e n 1 2 B xff0e n 2 C xff0
  • 数据结构_练习 第8章 排序

    1 xff0e 选择题 xff08 1 xff09 从未排序序列中依次取出元素与已排序序列中的元素进行比较 xff0c 将其放入已排序序列的正确位置上的方法 xff0c 这种排序方法称为 xff08 xff09 A xff0e 归并排序 B
  • Java GUI小程序--画板

    画板 一个可以绘画的简单绘图软件 本文用两个类来实现画板的基本功能 xff08 源代码在文章最后面 xff09 画板制作分两个部分 xff1a xff08 一 xff09 界面布局 xff08 二 xff09 实现功能 小知识 xff1a
  • Java_关于垃圾回收机制(GC)的六个知识

    1 垃圾回收机制 xff1a 垃圾回收是一种动态存储管理技术 xff0c 它自动地释放不再被程序引用的对象 xff0c 按照特定的垃圾收集算法来实现资源自动回收的功能 当一个对象不再被引用的时候 xff0c 内存回收它占领的空间 xff0c
  • 树莓派 Nginx+php7搭建网站

    1 6 安装Nginx 43 php7 并且启动 sudo apt get update sudo apt get install nginx sudo apt get install php7 0 fpm php7 0 cli php7
  • Python的GUI编程(一)Label(标签)

    常用Python GUI库有 xff1a 1 Tkinter 2 WxPython 3 PyQT 4 pyGtk 5 Jython 6 MFC 7 PythonCard 8 Dabo 9 AnyGui 10 WPY 11 IronPytho
  • 使用 Drools 规则引擎实现业务逻辑

    要求施加在当今软件产品上的大多数复杂性是行为和功能方面的 xff0c 从而导致组件实现具有复杂的业务逻辑 实现 J2EE 或 J2SE 应用程序中业务逻辑最常见的方法是编写 Java 代码来实现需求文档的规则和逻辑 在大多数情况下 xff0
  • Python的GUI编程(九)Menu(菜单)OptionMenu(为可选菜单)

    在用户界面程序中 菜单以图标和文字的方式展示可用选项 用鼠标选择一个选项 程序的某个行为既被触发 这种行为通常包括比如 打开 保存文件 退出程序 等功能 上下文菜单是一种根据用户当前所在程序位置 上下文 动态生成的菜单 简单程序 xff1a
  • Linux同时使用无线和有线网络

    有时候我们可能同时插入了无线网卡和有线网络 xff0c 但是默认是通过有线网络连接的外网 如果同时需要使用无线网络连接外网以及使用有线网络进行高速局域网连接的话需要将默认网关设置成无线网络 xff1a 使用ip route show分别查看
  • 【Python】计算两个日期相差天数

    Python 计算两个日期相差天数
  • 《Zoom to learn Learn to zoom》学习笔记

    创新点 xff1a 使用真实原始LR和HR图像作为数据进行训练网络 xff0c 区别于其他大部分网络 xff08 用HR图和经过HR图进行下采样的LR图作为网络数据进行训练 xff09 采用由CX loss 改进的CoBi loss作为网络
  • cmake安装以及更新

    直接install cmake wget https cmake org files v3 6 cmake 3 6 2 tar gz span class token function tar span zxvf cmake 3 6 2 t
  • manjaro安装以及配置、安装输入法、向日葵、anaconda、pycharm、QQ

    为什么不用Ubuntu xff0c 用manjaro就不说了 xff0c 直接上步骤吧 xff01 1 做个优盘 找个优盘 xff0c 最好是3 0接口吧 xff0c 我用的2 0有点慢 先下载系统 xff0c 找个自己喜欢的风格 Manj
  • Python编程中的常见语句

    4 1 if条件判断语句 4 1 1 if条件判断语句单分支 单分支格式 xff1a if 判断条件 xff1a 语句块1 else xff1a 语句块2 例 xff1a name 61 input 39 请输入您的用户名 39 if na
  • Linux下创建新用户及用户权限

    一 用户创建 增加用户 1 在root权限下 xff1b 命令 xff1a useradd 43 用户名 xff0c 它不会在 home目录下创建同名文件夹 xff0c 也没有创建密码 xff0c 因此利用这个用户登录系统 xff0c 是登
  • cacert.pem是怎么来的

    小弟最近在搞支付宝支付接口 xff0c 碰到个问题 xff0c help 我看demo中有下面一行代码 xff1a PHP code 1 2 3 ca证书路径地址 xff0c 用于curl中ssl校验 请保证cacert pem文件在当前文
  • mapping文件的编写

    mapping文件的编写 xff08 以及实体类与xml中类型的对应关系 xff09 2017年02月18日 11 31 36 转角人生 阅读数 xff1a 2918 标签 xff1a mapping文件 更多 个人分类 xff1a map
  • java-兔子繁殖问题

    題目 xff1a 古典问题 xff1a 有一对兔子 xff0c 从出生后第3个月起每个月都生一对兔子 xff0c 小兔子长到第三个月后每个月又生一 对兔子 xff0c 假如兔子都不死 xff0c 问每个月的兔子总数为多少 xff1f 64
  • Nginx服务器的安装部署和框架简介

    Nginx服务器的安装部署 1 如何获取Nginx服务器安装文件 Nginx服务器的软件版本包括 Windows版 和 Linux版俩种 官网下载地址为http nginx org en download html 网页上提供了Nginx服
  • error - problem conecting

    解决的办法是在树莓派里安装如下模块 xff1a sudo apt get install tightvncserver 接着reboot重启 xff0c 重新连接即可

随机推荐

  • SQL 日期函数应用实例!!

    sql view plain copy select convert varchar 10 getdate 120 只返回当前日期 xff0c 且为2012 12 12格式 xff08 最有用 xff09 sql view plain co
  • 使用docker-compose搭建Harbor私有仓库

    一 安装docker compose 1 下载docker compose的最新版本 curl L 34 https github com docker compose releases download 1 22 0 docker com
  • Zabbix安装部署

    一 服务端安装配置 1 环境检查 root 64 m01 cat etc redhat release CentOS Linux release 7 4 1708 Core root 64 m01 uname r 3 10 0 693 el
  • Docker安装部署

    一 安装 devicemapper 存储驱动 使用说明 xff1a 为了在生产级别的环境中使用 docker 运行环境 xff0c 必须使用 direct lvm 模式来运行devicemapper 存储驱动 这种模式使用块设备来创建 th
  • MySql安装部署

    下载并安装MySQL官方的 Yum Repository wget i c http dev mysql com get mysql57 community release el7 10 noarch rpm 使用上面的命令就直接下载了安装
  • Nginx安装部署

    Nginx 安装配置 Nginx 34 engine x 34 是一款是由俄罗斯的程序设计师Igor Sysoev所开发高性能的 Web和 反向代理 服务器 xff0c 也是一个 IMAP POP3 SMTP 代理服务器 在高连接并发的情况
  • Rancher安装部署

    直接通过docker镜像来运行我们的rancher xff0c 首先 xff0c 先从镜像中心下载rancher镜像 xff0c 如果是1 x系列的 xff0c 镜像名为rancher server xff0c 而2 x是rancher r
  • form表单提交onclick和onsubmit进行表单验证

    onsubmit只能表单上使用 提交表单前会触发 onclick是按钮等控件使用 用来触发点击事件 在提交表单前 xff0c 一般都会进行数据验证 xff0c 可以选择在submit按钮上的onclick中验证 也可以在onsubmit中验
  • iperf3网络测试工具

    一 iperf能用来做什么 测量网络带宽和网络质量提供网络延迟抖动 数据包丢失率 最大传输单元等统计信息 二 iperf3主要功能介绍 TCP 测试网络带宽支持多线程 xff0c 在客户端与服务端支持多重连接报告MSS MTU值的大小支持T
  • C++多线程5-单例模式详解

    单例模式 xff1a 只允许创建一个类对象 xff0c 实现的关键是将构造函数变为私有 单例模式有几种实现方式 xff1a 懒汉模式饿汉模式线程安全模式 锁实现和call once实现 局部静态变量模式 1 懒汉模式 当需要使用类对象时 x
  • c++多线程1-多线程的创建

    什么是多线程 xff1f 我们可以理解为一个线程执行一个代码段 xff0c 所以多个线程就是执行多个代码段 xff0c 如果当一个线程结束后 xff0c 进程就退出了 xff0c 这个线程我们称之为主线程 每个进程可以有一个或一个以上的线程
  • c++多线程2-线程参数传递需要注意的几个问题

    一 线程的初始化参数需要注意以下几个问题 xff1a 1 回调函数使用引用参数接收值时 xff0c 必须声明为const xff0c 否则报错 xff1b xff08 线程基于数据安全保护的考虑 xff09 2 回调函数必须声明为指针 xf
  • c++11-智能指针

    c 43 43 智能指针 为了更安全地管理动态内存 xff0c c 43 43 11引入了智能指针 xff0c 提供了包括shared ptr unique ptr weak ptr三种不同类型的智能指针 目录结构 xff1a 一 三种指针
  • C++多线程3-共享数据操作保护

    目录 xff1a 1 多线程操作共享数据引出的问题 2 解决多线程操作共享数据的方法 xff1a 锁 3 互斥量mutex的概念和用法 4 lock普通锁的用法 5 lock guard类模板的用法 6 死锁的概念和解决 7 unique
  • C++多线程4-unique_lock详解

    unique lock和lock guard都是可以自动解锁的类 xff0c 但是lock guard更加高效体现在永远在析构函数中解锁 xff0c 而unique lock更加灵活 xff0c 但执行效率会比lock guard低一些 x
  • C++多线程6-条件变量

    1 条件变量 std condition variable是多线程中经常用到的一个类 xff0c 它的头文件为condition variable 它常用的成员函数包括 xff0c wait notify one notify all等 它
  • Centos6.5系统升级软件操作文档

    为什么要用Centos6 5系统 xff1f 答 xff1a 因为计算板官方推荐Centos6 5 为什么要升级软件 xff1f 答 xff1a 软件需支持C 43 43 11相关库 1 系统信息 系统版本 xff1a CentOS 6 5
  • c++11多线程7-异步线程

    异步线程 异步线程的引入解决了线程有依赖关系的情景 c 43 43 11提供了std async xff0c std packaged task xff0c std promise xff0c 三种方法 1 std async std as
  • HTML中meta标签如何正确使用

    HTML中 lt meta gt 标签如何正确使用 如果我们在浏览器中按下F12或者Ctrl 43 shift 43 J xff0c 便可以打开开发者工具 xff0c 在element中即可看到 lt head gt 元素中有不少 lt m
  • 4.Linux网络编程-select和poll模型

    目录 xff1a 1 补充知识 2 简易版回射服务器的实现 3 select模型实现 4 poll模型实现 1 补充知识 span class token comment 显示进程的pid xff1a span span class tok