C++ 高并发服务器

4、高并发服务器

1)多进程并发服务器

大致思路:

1.  Socket();      创建 监听套接字 lfd

2.  Bind();        绑定地址结构 Struct scokaddr_in addr;

3.  Listen();
4.while(1){
	 cfd = Accpet();
	 pid = fork();
     if(pid == 0) {      子进程 read(cfd) --- 小-》大 --- 	
         write(cfd)
         close(lfd);      关闭用于连接的套接字 lfd
         read();
         小---大
         write();
      }else if(pid > 0){
		 close(cfd);  关闭用于通信的套接字 cfd
         contiue;
       }
  }
5.子进程:
	close(lfd);
	read();
	小---大
	write();
  父进程:
  	close(cfd);
  	注册信号捕捉函数: SIGCHLD
  	在回调函数中,完成子进程回收
  		while(waitpid());

使用多进程并发服务器时要考虑以下几点:

  • 父进程最大文件描述个数(父进程中需要close关闭accept返回的新文件描述符)

  • 系统内创建进程个数(与内存大小相关)

  • 进程创建过多是否降低整体服务性能(进程调度)

多进程并发服务器实现:

wrap.h wrap.c 为错误封装函数

①wrap.h
#ifndef __WRAP_H_
#define __WRAP_H_
void sys_err(const char *s);
int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr);
int Bind(int fd, const struct sockaddr *sa, socklen_t salen);
int Connect(int fd, const struct sockaddr *sa, socklen_t salen);
int Listen(int fd, int backlog);
int Socket(int family, int type, int protocol);
ssize_t Read(int fd, void *ptr, size_t nbytes);
ssize_t Write(int fd, const void *ptr, size_t nbytes);
int Close(int fd);
ssize_t Readn(int fd, void *vptr, size_t n);
ssize_t Writen(int fd, const void *vptr, size_t n);
ssize_t my_read(int fd, char *ptr);
ssize_t Readline(int fd, void *vptr, size_t maxlen);
#endif
②wrap.c
#include <stdio.h>
#include <ctype.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <pthread.h>
#include <signal.h>
#include <errno.h>
#include "wrap.h"
void sys_err(const char *s)
{
	perror(s);
	exit(1);
}
int Socket(int family, int type, int protocol)
{
	int n;
	if ( (n = socket(family, type, protocol)) < 0)
		sys_err("socket error");
	return n;
}
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
			sys_err("accept error");
	}
	return n;
}
int Bind(int fd, const struct sockaddr *sa, socklen_t salen)
{
	int n;
	if ((n = bind(fd, sa, salen)) < 0)
		sys_err("bind error");
	return n;
}
int Connect(int fd, const struct sockaddr *sa, socklen_t salen)
{
	int n;
	if ((n = connect(fd, sa, salen)) < 0)
		sys_err("connect error");
	return n;
}
int Listen(int fd, int backlog)
{
	int n;
	if ((n = listen(fd, backlog)) < 0)
		sys_err("listen 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;
}
int Close(int fd)
{
	int n;
	if ((n = close(fd)) == -1)
		sys_err("close error");
	return n;
}
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;
}

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;
}
③server.c
#include <stdio.h>
#include <ctype.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <pthread.h>
#include <signal.h>
#include <errno.h>

#include "wrap.h"

#define MAXLINE 80
#define SERV_PORT 9527

void catch_child(int signum)
{
	while((waitpid(0, NULL, WNOHANG)) > 0);
	return ;
}

int main(int argc, char *argv[])
{
    //创建socket
    int lfd = 0;
    lfd = Socket(AF_INET, SOCK_STREAM, 0);  

    //bind绑定ip和端口
    struct sockaddr_in serv_addr;

	//memset(&srv_addr, 0, sizeof(srv_addr));   //将地址结构清零
	bzero(&serv_addr, sizeof(serv_addr));

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(SERV_PORT);
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    Bind(lfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));

    //设置监听个数
    Listen(lfd, 128);

    //accept
	struct sockaddr_in clit_addr;
    socklen_t clit_addr_len = sizeof(clit_addr); //创建客户端地址
    int cfd = 0;
	while(1){
		cfd = Accept(lfd, (struct sockaddr *)&clit_addr, &clit_addr_len);
		
		int ret;
		pid_t pid;
		pid = fork();
		if(pid < 0){
			sys_err("fork error");
		}else if(pid == 0){
			close(lfd);
			break;
		}else{
			struct sigaction act;    //设置信号捕捉

			act.sa_handler = catch_child;
			sigemptyset(&act.sa_mask);
			act.sa_flags = 0;
			
			sigaction(SIGCHLD, &act, NULL);
			if(ret != 0){
				sys_err("sigaction error");
			}
			close(cfd);
			continue;
		}
	}
	
	char buf[MAXLINE];
	int ret, i, pid;
	if(pid == 0){
		for(;;){
			ret = read(cfd, buf, sizeof(buf));
			if(ret == 0){
				close(cfd);
				exit(1);
			}

			for(i = 0; i < ret; i++){
				buf[i] = toupper(buf[i]);
			}
		
			write(cfd, buf, ret);
			write(STDOUT_FILENO, buf, ret);
		}
	}
    
   return 0;
}

④client.c
#include <stdio.h>
#include <ctype.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>

#include "wrap.h"

#define MAXLINE 80
#define SERV_PORT 9527

int main(int argc, char *argv[])
{
    int cfd, ret;
    cfd = Socket(AF_INET, SOCK_STREAM, 0);

    //创建服务器地址结构
    struct sockaddr_in serv_addr;  //服务器地址结构
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(SERV_PORT); //设置端口
    //inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr.s_addr);
    inet_pton(AF_INET, "IP地址", &serv_addr.sin_addr.s_addr);

    //连接服务端
    Connect(cfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));

    //与服务端通信逻辑
    char buf[MAXLINE];
    int n;
    while (fgets(buf, MAXLINE, stdin) != NULL) {
	    Write(cfd, buf, strlen(buf));
		n = Read(cfd, buf, MAXLINE);
		if (n == 0) {
			printf("the other side has been closed.\n");
			break;
		} else
			Write(STDOUT_FILENO, buf, n);
	}

}

2)多线程并发服务器

大致思路:

	1.  `Socket();`       创建 监听套接字 lfd

	2.  `Bind();`           绑定地址结构 `Struct scokaddr_in addr;`

	3.  `Listen();`
4.while(1){
	cfd = Accept(lfd, )
	pthread_create(&tid, NULL, tfn, NULL);
    pthread_detach(tid);  //pthread_join(tid, void **);新线程--专用于回收子线程
}
5.子线程:
	void *tfn(void *arg)
	{
		close(lfd);
		read(cfd);
		小---大
		write(cfd);
	}

多进程并发服务器实现:

添加wrap.h wrap.c 为错误封装函数,内容同上

①server.c
#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>

#include "wrap.h"
#define MAXLINE 80
#define SERV_PORT 6666

struct s_info {
	struct sockaddr_in cliaddr;
	int connfd;
};
void *do_work(void *arg)
{
	int n,i;
	struct s_info *ts = (struct s_info*)arg;
	char buf[MAXLINE];
	char str[INET_ADDRSTRLEN];
	/* 可以在创建线程前设置线程创建属性,设为分离态,哪种效率高内? */
	pthread_detach(pthread_self());
	while (1) {
		n = Read(ts->connfd, buf, MAXLINE);
		if (n == 0) {
			printf("the other side has been closed.\n");
			break;
		}
		printf("received from %s at PORT %d\n",
				inet_ntop(AF_INET, &(*ts).cliaddr.sin_addr, str, sizeof(str)),
				ntohs((*ts).cliaddr.sin_port));
		for (i = 0; i < n; i++)
			buf[i] = toupper(buf[i]);
		Write(ts->connfd, buf, n);
	}
	Close(ts->connfd);
}

int main(void)
{
	struct sockaddr_in servaddr, cliaddr;
	socklen_t cliaddr_len;
	int listenfd, connfd;
	int i = 0;
	pthread_t tid;
	struct s_info ts[256];

	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, 20);

	printf("Accepting connections ...\n");
	while (1) {
		cliaddr_len = sizeof(cliaddr);
		connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
		ts[i].cliaddr = cliaddr;
		ts[i].connfd = connfd;
		/* 达到线程最大数时,pthread_create出错处理, 增加服务器稳定性 */
		pthread_create(&tid, NULL, do_work, (void*)&ts[i]);
		i++;
	}
	return 0;
}

②client.c
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include "wrap.h"
#define MAXLINE 80
#define SERV_PORT 6666
int main(int argc, char *argv[])
{
	struct sockaddr_in servaddr;
	char buf[MAXLINE];
	int sockfd, n;

	sockfd = Socket(AF_INET, SOCK_STREAM, 0);

	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
	servaddr.sin_port = htons(SERV_PORT);

	Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

	while (fgets(buf, MAXLINE, stdin) != NULL) {
		Write(sockfd, buf, strlen(buf));
		n = Read(sockfd, buf, MAXLINE);
		if (n == 0)
			printf("the other side has been closed.\n");
		else
			Write(STDOUT_FILENO, buf, n);
	}
	Close(sockfd);
	return 0;
}

3)多路I/O转接服务器

原理:

借助内核,select 来监听。客户端连接、数据通信事件

image-20230311165543825

① select 函数
#include<sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

int select(int nfds, fd_set *readfds, fd_set *writefds, 
           fd_set *excepfds, struct timeval *timeout);

	nfds: 		监听的所有文件描述符中,最大文件描述符+1
	readfds:	读 文件描述符监听集合,传入传出参数
	writefds:	写 文件描述符监听集合,传入传出参数
	exceptfds:	异常 文件描述符监听集合,传入传出参数
	timeout:	定时阻塞监控时间,3种情况
				1.NULL,永远等下去
				2.设置timeval,等待固定时间
				3.设置timeval里时间均为0,检查描述字后立即返回,轮询
     返回值:
        		1. >0 :所有监听集合(3个)中,满足对应事件的总数
                2.  0 :没有满足监听条件的文件描述符
                3. -1 :error
        
struct timeval {
	long tv_sec; /* seconds */
	long tv_usec; /* microseconds */
};

/*把文件描述符集合里所有位清0(清空一个文件描述符集合)*/
void FD_ZERO(fd_set *set); 
	fd_set rset;
	FD_ZERO(&rset);

/*把文件描述符集合里fd位置1(将待监听的文件描述符添加到监听集合)*/
void FD_SET(int fd, fd_set *set); 
	FD_SET(3, &rset);	FD_SET(4, &rset);	FD_SET(5, &rset);

/*把文件描述符集合里fd清0(将一个文件描述符从集合中移除)*/
void FD_CLR(int fd, fd_set *set); 
	FD_CLR(4, &rset);

/*测试文件描述符集合里fd是否置1(判断一个文件描述符是否在监听集合中)*/
int FD_ISSET(int fd, fd_set *set); 
	返回值:在->1  不在->0
	FD_ISSET(4, &rset)

思路分析:

  • lfd = socket(); 创建套接字

  • bind(); 绑定地址结构

  • listen(); 设置监听上限

  • fd_set rset, allset; 创建r监听集合

  • FD_ZERO(&allset); 将r监听集合清空

  • FD_SET(lfd, &allset) 将 lfd 添加到读集合

  • while(1){
    	rset = allset;    //保存监听集合
    	ret = select(lfd+1, &rset, NULL, NULL, NULL); //监听文件描述符集合对应事件
    	if(ret > 0){             //有监听的描述符满足对应事件
    		if(FD_ISSET(lfd, &rset)){      //1 在。 0 不在
     	       cfd = accept();             //建立连接,返回用于通信的文件描述符
     	       FD_SET(cfd, &allset);       //添加到通信描述符集合中
    	    }
            for(i = lfd + 1; i < Max文件描述符; i++){
    			FD_ISSET(i, &rset);     //有read,write事件
                read();
                小——大
                write();
            }
    	}
    }
    

实现代码:

同样要包含上述错误处理函数文件wrap.h wrap.c

server.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <ctype.h>

#include "wrap.h"

#define SERV_PORT 6666

int main(int argc, char *argv[])
{
    int listenfd, connfd;

	struct sockaddr_in clie_addr, serv_addr;
	socklen_t clie_addr_len;

	listenfd = Socket(AF_INET, SOCK_STREAM, 0);  

    int opt = 1;
    setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
	bzero(&serv_addr, sizeof(serv_addr));    //将地址结构清零

	serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(SERV_PORT);
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
	Bind(listenfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));

	Listen(listenfd, 128);  

	fd_set rset, allset;             //定义 读集合,备份集合allset
	int ret, maxfd = 0, n, i, j;
	char buf[BUFSIZ];
	maxfd = listenfd;                //最大文件描述符

	FD_ZERO(&allset);                //清空 监听集合
	FD_SET(listenfd, &allset);       //将待监听fd添加到监听集合

	while(1){
		rset = allset;               //备份
		ret = select(maxfd + 1, &rset, NULL, NULL, NULL);   //使用selcet监听
		if(ret < 0){
			sys_err("select error");
		}

		if(FD_ISSET(listenfd, &rset)){
			clie_addr_len = sizeof(clie_addr);
			connfd = Accept(listenfd, (struct sockaddr *)&clie_addr, &clie_addr_len);//建立连接 ---不会阻塞

			FD_SET(connfd, &allset);  //将新生产的fd,添加到监听集合中,监听数据读事件
			if(maxfd < connfd)        //修改maxfd
				maxfd = connfd;
			
			if(ret == 1)       //说明select 只返回一个,并且是listenfd,后续执行无需执行
				continue;
		}

		for(i = listenfd + 1; i <= maxfd; i++){   //处理满足读事件的 fd
			if(FD_ISSET(i, &rset)){               //找到满足读事件的那个 fd
				n = read(i, buf, sizeof(buf));
				if(n == 0){                       //检测到客户端已经关闭连接
					Close(i);
					FD_CLR(i, &allset);           //将关闭的fd,移除监听集合
				}else if(n == -1){
					sys_err("read error");
				}

				for(j = 0; j < n; j++){
					buf[j] = toupper(buf[j]);

				write(i, buf, n);
				write(STDOUT_FILENO, buf, n);
				}
			}
		}
	}

	Close(listenfd);

    return 0;
}

client.c

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include "wrap.h"

#define MAXLINE 80
#define SERV_PORT 6666

int main(int argc, char *argv[])
{
	struct sockaddr_in servaddr;
	char buf[MAXLINE];
	int sockfd, n;

	sockfd = Socket(AF_INET, SOCK_STREAM, 0);

	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
	servaddr.sin_port = htons(SERV_PORT);

	Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

	while (fgets(buf, MAXLINE, stdin) != NULL) {
		Write(sockfd, buf, strlen(buf));
		n = Read(sockfd, buf, MAXLINE);
		if (n == 0)
			printf("the other side has been closed.\n");
		else
			Write(STDOUT_FILENO, buf, n);
	}
	Close(sockfd);
	return 0;
}

select 优缺点

缺点:

  • 监听上限受文件描述符限制。最大 1024 。
  • 检测满足条件的fd,自己添加业务逻辑提高小。提高了编码难度

select是轮询方式,可以使用自定义数组,防止遍历1024个文件描述符,指定需要监听的cfd。

优点:

  • 服务端接入少
  • 跨平台。win、Linux、macOS、类Unix、mips

基于select的I/O复用技术速度慢的原因

  • 调用select函数后常见的针对所有文件描述符的循环语句(轮询方式)
  • 每次调用select函数时都需要向该函数传递监视对象信息

② poll 函数

相对于select改进了一点点,但很少用

函数原型

#include <poll.h>
int poll(struct pollfd *fds, nfs_t nfds, int timeout);
	fds:监听的文件描述符[数组]
         struct pollfd  {
         	 int fd :  	    待监听的文件描述符
             short events : 待监听的文件描述符对应的监听事件
             				取值:POLLIN、POLLOUT、POLLERR
             short revents: 传入时给0。如果满足对应事件的话,返回 非0->POLLIN、POLLOUT、POLLERR
         }

	nfds:监听数组的实际有效监听个数
        
    timeout: >0 : 超时时长。单位:毫秒
              -1 : 阻塞等待
               0 :不阻塞 

    返回值:返回满足监听事件的文件描述符总个数
poll 优缺点

优点:

  • 自带数组结构。可以将 监听事件集合 和 返回时间集合 分离
  • 拓展 监听上线。超出1024 限制

缺点:

  • 不能跨平台。Linux
  • 无法直接定位满足监听事件的文件描述符,编码难度大

突破1024文件描述符限制:

cat /proc/sys/fs/file-max   //当前计算机所能打开的最大文件个数。受硬件影响
ulimit -a   //当前用户下的进程,默认打开文件描述符个数,缺省1024
修改:
    打开 sudo vi/etc/security/limits.conf  写入
    * soft nofile 65536    //设置默认值,可以直接借助命令修改[注销用户,使其生效]
    * hard nofile 100000   //命令修改上限
//复习read函数返回值:
	>0 :实际读到的字节数
    =0 :socket中,表示对端关闭。close()
    -1 : 如果 errno == EINTR 被异常终止,需要重新启动
         如果 errno == EAGIN 或 EWOULDBLOCK 以非阻塞方式读数据,但是没有数据,需要再次读
         如果 errno == ECONNRESET 说明连接被重置。需要 close(),移除监听队列

server.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <poll.h>
#include <errno.h>
#include "wrap.h"

#define MAXLINE 80
#define SERV_PORT 6666
#define OPEN_MAX 1024

int main(int argc, char *argv[])
{
	int i, j, maxi, listenfd, connfd, sockfd;
	int nready;
	ssize_t n;
	char buf[MAXLINE], str[INET_ADDRSTRLEN];
	socklen_t clilen;
	struct pollfd client[OPEN_MAX];
	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, 20);

	client[0].fd = listenfd;
	client[0].events = POLLRDNORM; 					/* listenfd监听普通读事件 */

	for (i = 1; i < OPEN_MAX; i++)
		client[i].fd = -1; 							/* 用-1初始化client[]里剩下元素 */
	maxi = 0; 										/* client[]数组有效元素中最大元素下标 */

	for ( ; ; ) {
		nready = poll(client, maxi+1, -1); 			/* 阻塞 */
		if (client[0].revents & POLLRDNORM) { 		/* 有客户端链接请求 */
			clilen = sizeof(cliaddr);
			connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &clilen);
			printf("received from %s at PORT %d\n",
					inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
					ntohs(cliaddr.sin_port));
			for (i = 1; i < OPEN_MAX; i++) {
				if (client[i].fd < 0) {
					client[i].fd = connfd; 	/* 找到client[]中空闲的位置,存放accept返回的connfd */
					break;
				}
			}

			if (i == OPEN_MAX)
				sys_err("too many clients");

			client[i].events = POLLRDNORM; 		/* 设置刚刚返回的connfd,监控读事件 */
			if (i > maxi)
				maxi = i; 						/* 更新client[]中最大元素下标 */
			if (--nready <= 0)
				continue; 						/* 没有更多就绪事件时,继续回到poll阻塞 */
		}
		for (i = 1; i <= maxi; i++) { 			/* 检测client[] */
			if ((sockfd = client[i].fd) < 0)
				continue;
			if (client[i].revents & (POLLRDNORM | POLLERR)) {
				if ((n = Read(sockfd, buf, MAXLINE)) < 0) {
					if (errno == ECONNRESET) { /* 当收到 RST标志时 */
						/* connection reset by client */
						printf("client[%d] aborted connection\n", i);
						Close(sockfd);
						client[i].fd = -1;
					} else {
						sys_err("read error");
					}
				} else if (n == 0) {
					/* connection closed by client */
					printf("client[%d] closed connection\n", i);
					Close(sockfd);
					client[i].fd = -1;
				} else {
					for (j = 0; j < n; j++)
						buf[j] = toupper(buf[j]);
						Writen(sockfd, buf, n);
				}
				if (--nready <= 0)
					break; 				/* no more readable descriptors */
			}
		}
	}
	return 0;
}

client.c

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include "wrap.h"

#define MAXLINE 80
#define SERV_PORT 6666

int main(int argc, char *argv[])
{
	struct sockaddr_in servaddr;
	char buf[MAXLINE];
	int sockfd, n;

	sockfd = Socket(AF_INET, SOCK_STREAM, 0);

	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
	servaddr.sin_port = htons(SERV_PORT);

	Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

	while (fgets(buf, MAXLINE, stdin) != NULL) {
		Write(sockfd, buf, strlen(buf));
		n = Read(sockfd, buf, MAXLINE);
		if (n == 0)
			printf("the other side has been closed.\n");
		else
			Write(STDOUT_FILENO, buf, n);
	}
	Close(sockfd);
	return 0;
}
③epoll 函数

epoll是Linux下多路复用IO接口select/poll的增强版本

目前epoll是Linux大规模并发网络程序中的热门首选模型

epoll函数刚好能够克服select函数不合理的地方。

epoll 优点(高效,能突破1024文件描述符)

  • 无需编写以监视状态变化为目的的针对所有文件描述符的循环语句
  • 调用对应于select函数的epoll_wait函数时无需每次传递监视信息

epoll 缺点

  • 不能跨平台

突破1024上限同poll

Epoll相对select/poll的优势:
  • Epoll 没有最大并发连接的限制,上限是最大可以打开文件的数目,这个数字一般远大于 2048, 一般来说这个数目和系统内存关系很大 ,具体数目可以 cat /proc/sys/fs/file-max[599534] ,并且现在服务器的内存都很大,所以这个不是问题。

  • 效率提升,epoll对于句柄事件的选择不是遍历的,是事件响应的,就是句柄上事件来就马上选择出来,不需要遍历整个句柄链表,因此效率非常高,内核将句柄用红黑树保存的,IO效率不随FD数目增加而线性下降。

  • 内存拷贝, select让内核把 FD 消息通知给用户空间的时候使用了内存拷贝的方式,开销较大,但是Epoll 在这点上使用了共享内存的方式,这个内存拷贝也省略了。

epoll服务器端实现需要的三个函数

epoll_create:创建保存epoll文件描述符的空间

epoll_ctl:向空间注册并注销文件描述符

epoll_wait:与select类似,等待文件描述符发生变化

头文件:#include<sys/epoll.h>

epoll_create 函数

调用epoll_create函数时创建的文件描述符保存空间叫做“epoll例程”,有些情况名称会有所不同。size并非用来决定epoll例程的大小,而仅供操作系统参考

//创建epoll例程(句柄)——文件描述符保存空间
int epoll_create(int size);  

	size:创建的红黑树的监听节点数量(仅供内核参考)。

	返回值:指向新创建的红黑树的根节点的 fd(epoll文件描述符)
		  失败:-1 error

epoll_ctl 函数

生成例程后,应在其内部注册监视对象文件描述符,此时使用epoll函数

//操作监听红黑树(控制某个epoll监控的文件描述符上的事件:注册、修改、删除)
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); 

	epfd:指向新创建的红黑树的根节点的 fd (epoll文件描述符)

	op:对该监听红黑树所作的操作
		EPOLL_CTL_ADD 添加fd 到监听红黑树
		EPOLL_CTL_MOD 修改fd 在监听红黑树上的监听事件
		EPOLL_CTL_DEL 将一个fd 从监听红黑树上摘下(取消监听)

	fd :待监听的fd

	event:
		   本质 struct epoll_event 结构体、地址
		   成员events: EPOLLIN / EPOLLOUT / EPOLLERR
           成员data 联合体:
           	 	int fd;  //对应监听事件的fd
				void* ptr;
struct evt{  //使ptr自动回调(反应堆)
    int fd;
    void(*func)(int fd);
}*ptr
				unit32_t u32;
				unit64_t u64;

//返回值:成功 0;  失败:-1 error
/*EPOLLIN :	表示对应的文件描述符可以读(包括对端SOCKET正常关闭)
EPOLLOUT:	表示对应的文件描述符可以写
EPOLLPRI:	表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来)
EPOLLERR:	表示对应的文件描述符发生错误
EPOLLHUP:	表示对应的文件描述符被挂断;
EPOLLET: 	将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)而言的
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里*/

例:epoll_ctl(A, EPOLL_CTL_ADD, B, C)

表示:“epoll例程A中注册文件描述符B,主要目的是监视参数C中的事件”

epoll_wait 函数

//等待所监控文件描述符上有事件的产生,类似于select()调用。
int epoll_wait(int epfd, struct epoll_event *event, int maxevents, int timeout); 
	
	epfd:指向新创建的红黑树的根节点的 fd (epoll文件描述符)
        
    events:传出参数,[简单看作一个数组],满足监听条件的那些 fd 结构体
        
    maxevents:数组 元素的总个数  1024
        struct epoll_event events[1024]
            
    timeout: 
		-1:阻塞
         0:不阻塞
        >0:超时时间(毫秒)
            
    返回值:
        >0:满足监听的总个数。可以用作循环上限
         0:没有fd满足监听事件
        -1:失败  error

epoll实现多路IO转接思路:

  • lfd = socket();创建socket,得到监听文件描述符

  • setsockopt();设置端口复用

  • bind();绑定

  • listen();监听

  • int epfd = epoll_create(1024);      //epfd:创建一棵epoll树(红黑树)
    
  • //tep:用来设置单个fd属性,ep是epoll_wait()传出的满足监听事件的数组
    struct epoll_event teo, ep[1024]; 
    tep.events = EPOLLIN;
    tep.data.fd = lfd;
    
    epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &tep);  //将lfd添加到监听红黑树上
    
  • while(1){
        ret = epoll_wait(epfd, ep, 1024, -1);  //实施监听
        for(i = 0; i < ret; i++){
            if(ep[i].data.fd == lfd){  //lfd满足读事件,有新的客户端发起连接请求
                cfd = Accept();
                tep.events = EPOLLIN;  //初始化 cfd的属性
                epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &tep);
            }else{   //cfd们 满足读事件, 有客户端写数据来
                n = read(ep[i].data.fd, buf, sizeof(buf));
                if(n == 0){
                    close(ep[i].data.fd);
                    //将关闭的cfd,从监听树上摘下
                    epoll_ctl(epfd, EPOLL_CTL_DEL, ep[i].data.fd, NULL);
                }else if(n > 0){
                    小——大
                    write(ep[i].data.fd, buf, n);
                }
            }
        }
    }
    

添加wrap.h wrap.c 为错误封装函数,内容同上

server.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <errno.h>
#include "wrap.h"

#define MAXLINE 80
#define SERV_PORT 6666
#define OPEN_MAX 1024

int main(int argc, char *argv[])
{
	int i, j, maxi, listenfd, connfd, sockfd;
	int nready, efd, res;
	ssize_t n;
	char buf[MAXLINE], str[INET_ADDRSTRLEN];
	socklen_t clilen;
	int client[OPEN_MAX];
	struct sockaddr_in cliaddr, servaddr;
	struct epoll_event tep, ep[OPEN_MAX];

	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, 20);

	for (i = 0; i < OPEN_MAX; i++)
		client[i] = -1;
	maxi = -1;

	efd = epoll_create(OPEN_MAX);
	if (efd == -1)
		perr_exit("epoll_create");

	tep.events = EPOLLIN; tep.data.fd = listenfd;

	res = epoll_ctl(efd, EPOLL_CTL_ADD, listenfd, &tep);
	if (res == -1)
		perr_exit("epoll_ctl");

	while (1) {
		nready = epoll_wait(efd, ep, OPEN_MAX, -1); /* 阻塞监听 */
		if (nready == -1)
			perr_exit("epoll_wait");

		for (i = 0; i < nready; i++) {
			if (!(ep[i].events & EPOLLIN))
				continue;
			if (ep[i].data.fd == listenfd) {
				clilen = sizeof(cliaddr);
				connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &clilen);
				printf("received from %s at PORT %d\n", 
						inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)), 
						ntohs(cliaddr.sin_port));
				for (j = 0; j < OPEN_MAX; j++) {
					if (client[j] < 0) {
						client[j] = connfd; /* save descriptor */
						break;
					}
				}

				if (j == OPEN_MAX)
					perr_exit("too many clients");
				if (j > maxi)
					maxi = j; 		/* max index in client[] array */

				tep.events = EPOLLIN; 
				tep.data.fd = connfd;
				res = epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &tep);
				if (res == -1)
					perr_exit("epoll_ctl");
			} else {
				sockfd = ep[i].data.fd;
				n = Read(sockfd, buf, MAXLINE);
				if (n == 0) {
					for (j = 0; j <= maxi; j++) {
						if (client[j] == sockfd) {
							client[j] = -1;
							break;
						}
					}
					res = epoll_ctl(efd, EPOLL_CTL_DEL, sockfd, NULL);
					if (res == -1)
						perr_exit("epoll_ctl");

					Close(sockfd);
					printf("client[%d] closed connection\n", j);
				} else {
					for (j = 0; j < n; j++)
						buf[j] = toupper(buf[j]);
					Writen(sockfd, buf, n);
				}
			}
		}
	}
	close(listenfd);
	close(efd);
	return 0;
}

client.c

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include "wrap.h"

#define MAXLINE 80
#define SERV_PORT 6666

int main(int argc, char *argv[])
{
	struct sockaddr_in servaddr;
	char buf[MAXLINE];
	int sockfd, n;

	sockfd = Socket(AF_INET, SOCK_STREAM, 0);

	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
	servaddr.sin_port = htons(SERV_PORT);

	Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

	while (fgets(buf, MAXLINE, stdin) != NULL) {
		Write(sockfd, buf, strlen(buf));
		n = Read(sockfd, buf, MAXLINE);
		if (n == 0)
			printf("the other side has been closed.\n");
		else
			Write(STDOUT_FILENO, buf, n);
	}

	Close(sockfd);
	return 0;
}

4)epol 进阶

​ epoll除了提供select/poll 那种IO事件的电平触发(Level Triggered)外,还提供了边沿触发(Edge Triggered),这就使得用户空间程序有可能缓存IO状态,减少epoll_wait/epoll_pwait的调用,还提高应用程序效率。

epoll事件有两种模型:

  • Edge Triggered(ET)边缘触发只有数据到来才会触发,不管缓冲区是否还有数据
  • Level Triggered(LT)水平触发只要有数据都会触发

思考如下步骤:

  1. 假定我们已经把一个用来管道中读取数据的文件描述符(rfd)添加到epoll描述符
  2. 管道的另一端写入了2KB的数据
  3. 调用epoll_wait,并且它会返回rfd,说明它已经准备好读取操作
  4. 读取1KB数据
  5. 调用epoll_wait……

在这个过程中,有两种工作模式:

① ET 模式

ET 模式(Edge Triggere):边沿触发

缓冲区剩余未读尽的数据不会导致epoll_wait返回。新的读写事件满足才会触发。

ET模式是一种高效模式,但只支持 非阻塞模式。非阻塞模式就要用忙轮询

struct epoll_event event;
event.events = EPOLLIN | EPOLLET;
epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &event);
int flg = fcntl(cfd, F_GETFL);
flg |= O_NONBLOCK;
fcntl(cfd, F_SETFL, flg);
② LT 模式

LT 模式(Level Triggered):水平触发——默认采用方式

缓冲区剩余未读尽的数据会导致epoll_wait返回。

与ET模式不同的是,以LT方式调用epoll接口的时候,它就相当于一个速度比较快的poll,无论后面的数据是否被使用。

示例:

event.events = EPOLLIN | EPOLLET; //ET 边沿触发

event.events = EPOLLIN; //LT 水平触发

【epoll为什么要有EPOLLET触发模式?】:

如果采用EPOLLLT模式的话,系统中一旦有大量你不需要读写的就绪文件描述符,它们每次调用epoll_wait都会返回,这样会大大降低处理程序检索自己关心的就绪文件描述符的效率.。而采用EPOLLET这种边缘触发模式的话,当被监控的文件描述符上有可读写事件发生时,epoll_wait()会通知处理程序去读写。如果这次没有把数据全部读写完(如读写缓冲区太小),那么下次调用epoll_wait()时,它不会通知你,也就是它只会通知你一次,直到该文件描述符上出现第二次可读写事件才会通知你!!!这种模式比水平触发效率高,系统不会充斥大量你不关心的就绪文件描述符。

实例一:

基于管道epoll ET触发模式

#include <stdio.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <errno.h>
#include <unistd.h>

#define MAXLINE 10

int main(int argc, char *argv[])
{
	int efd, i;
	int pfd[2];
	pid_t pid;
	char buf[MAXLINE], ch = 'a';

	pipe(pfd);
	pid = fork();
	if (pid == 0) {
		close(pfd[0]);
		while (1) {
			for (i = 0; i < MAXLINE/2; i++)
				buf[i] = ch;
			buf[i-1] = '\n';
			ch++;

			for (; i < MAXLINE; i++)
				buf[i] = ch;
			buf[i-1] = '\n';
			ch++;

			write(pfd[1], buf, sizeof(buf));
			sleep(2);
		}
		close(pfd[1]);
	} else if (pid > 0) {
		struct epoll_event event;
		struct epoll_event resevent[10];
		int res, len;
		close(pfd[1]);

		efd = epoll_create(10);
		/* event.events = EPOLLIN; */
		event.events = EPOLLIN | EPOLLET;		/* ET 边沿触发 ,默认是水平触发 */
		event.data.fd = pfd[0];
	epoll_ctl(efd, EPOLL_CTL_ADD, pfd[0], &event);

		while (1) {
			res = epoll_wait(efd, resevent, 10, -1);
			printf("res %d\n", res);
			if (resevent[0].data.fd == pfd[0]) {
				len = read(pfd[0], buf, MAXLINE/2);
				write(STDOUT_FILENO, buf, len);
			}
		}
		close(pfd[0]);
		close(efd);
	} else {
		perror("fork");
		exit(-1);
	}
	return 0;
}

实例二:

基于网络C/S模型的epoll ET触发模式

server.c

#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/epoll.h>
#include <unistd.h>

#define MAXLINE 10
#define SERV_PORT 8080

int main(void)
{
	struct sockaddr_in servaddr, cliaddr;
	socklen_t cliaddr_len;
	int listenfd, connfd;
	char buf[MAXLINE];
	char str[INET_ADDRSTRLEN];
	int i, efd;

	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, 20);

	struct epoll_event event;
	struct epoll_event resevent[10];
	int res, len;
	efd = epoll_create(10);
	event.events = EPOLLIN | EPOLLET;		/* ET 边沿触发 ,默认是水平触发 */

	printf("Accepting connections ...\n");
	cliaddr_len = sizeof(cliaddr);
	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));

	event.data.fd = connfd;
	epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &event);

	while (1) {
		res = epoll_wait(efd, resevent, 10, -1);
		printf("res %d\n", res);
		if (resevent[0].data.fd == connfd) {
			len = read(connfd, buf, MAXLINE/2);
			write(STDOUT_FILENO, buf, len);
		}
	}
	return 0;
}

client.c

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>

#define MAXLINE 10
#define SERV_PORT 8080

int main(int argc, char *argv[])
{
	struct sockaddr_in servaddr;
	char buf[MAXLINE];
	int sockfd, i;
	char ch = 'a';

	sockfd = socket(AF_INET, SOCK_STREAM, 0);

	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
	servaddr.sin_port = htons(SERV_PORT);

	connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

	while (1) {
		for (i = 0; i < MAXLINE/2; i++)
			buf[i] = ch;
		buf[i-1] = '\n';
		ch++;

		for (; i < MAXLINE; i++)
			buf[i] = ch;
		buf[i-1] = '\n';
		ch++;

		write(sockfd, buf, sizeof(buf));
		sleep(10);
	}
	Close(sockfd);
	return 0;
}

实例三:

基于网络C/S非阻塞模型的epoll ET触发模式

server.c

#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/epoll.h>
#include <unistd.h>
#include <fcntl.h>

#define MAXLINE 10
#define SERV_PORT 8080

int main(void)
{
	struct sockaddr_in servaddr, cliaddr;
	socklen_t cliaddr_len;
	int listenfd, connfd;
	char buf[MAXLINE];
	char str[INET_ADDRSTRLEN];
	int i, efd, flag;

	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, 20);

	struct epoll_event event;
	struct epoll_event resevent[10];
	int res, len;
	efd = epoll_create(10);
	/* event.events = EPOLLIN; */
	event.events = EPOLLIN | EPOLLET;		/* ET 边沿触发 ,默认是水平触发 */

	printf("Accepting connections ...\n");
	cliaddr_len = sizeof(cliaddr);
	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));

	flag = fcntl(connfd, F_GETFL);
	flag |= O_NONBLOCK;
	fcntl(connfd, F_SETFL, flag);
	event.data.fd = connfd;
	epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &event);

	while (1) {
		printf("epoll_wait begin\n");
		res = epoll_wait(efd, resevent, 10, -1);
		printf("epoll_wait end res %d\n", res);

		if (resevent[0].data.fd == connfd) {
			while ((len = read(connfd, buf, MAXLINE/2)) > 0)
				write(STDOUT_FILENO, buf, len);
		}
	}
	return 0;
}

client.c

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>

#define MAXLINE 10
#define SERV_PORT 8080

int main(int argc, char *argv[])
{
	struct sockaddr_in servaddr;
	char buf[MAXLINE];
	int sockfd, i;
	char ch = 'a';

	sockfd = socket(AF_INET, SOCK_STREAM, 0);

	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
	servaddr.sin_port = htons(SERV_PORT);

	connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

	while (1) {
		for (i = 0; i < MAXLINE/2; i++)
			buf[i] = ch;
		buf[i-1] = '\n';
		ch++;

		for (; i < MAXLINE; i++)
			buf[i] = ch;
		buf[i-1] = '\n';
		ch++;

		write(sockfd, buf, sizeof(buf));
		sleep(10);
	}
	Close(sockfd);
	return 0;
}
③ epoll反应堆
epoll_create(); // 创建监听红黑树
epoll_ctl(); // 向书上添加监听fd
epoll_wait(); // 监听
有监听fd事件发送--->返回监听满足数组--->判断返回数组元素--->
lfd满足accept--->返回cfd---->read()读数据--->write()给客户端回应。
epoll_create(); // 创建监听红黑树
epoll_ctl(); // 向书上添加监听fd
epoll_wait(); // 监听
有客户端连接上来--->lfd调用acceptconn()--->将cfd挂载到红黑树上监听其读事件--->
epoll_wait()返回cfd--->cfd回调recvdata()--->将cfd摘下来监听写事件--->
epoll_wait()返回cfd--->cfd回调senddata()--->将cfd摘下来监听读事件--->...--->
#include <stdio.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>

#define MAX_EVENTS 1024 /*监听上限*/
#define BUFLEN  4096    /*缓存区大小*/
#define SERV_PORT 6666  /*端口号*/

void recvdata(int fd,int events,void *arg);
void senddata(int fd,int events,void *arg);

/*描述就绪文件描述符的相关信息*/
struct myevent_s
{
    int fd;             //要监听的文件描述符
    int events;         //对应的监听事件,EPOLLIN和EPLLOUT
    void *arg;          //指向自己结构体指针
    void (*call_back)(int fd,int events,void *arg); //回调函数
    int status;         //是否在监听:1->在红黑树上(监听), 0->不在(不监听)
    char buf[BUFLEN];   
    int len;
    long last_active;   //记录每次加入红黑树 g_efd 的时间值
};

int g_efd;      //全局变量,作为红黑树根
struct myevent_s g_events[MAX_EVENTS+1];    //自定义结构体类型数组. +1-->listen fd


/*
 * 封装一个自定义事件,包括fd,这个fd的回调函数,还有一个额外的参数项
 * 注意:在封装这个事件的时候,为这个事件指明了回调函数,一般来说,一个fd只对一个特定的事件
 * 感兴趣,当这个事件发生的时候,就调用这个回调函数
 */
void eventset(struct myevent_s *ev, int fd, void (*call_back)(int fd,int events,void *arg), void *arg)
{
    ev->fd = fd;
    ev->call_back = call_back;
    ev->events = 0;
    ev->arg = arg;
    ev->status = 0;
    if(ev->len <= 0)
    {
        memset(ev->buf, 0, sizeof(ev->buf));
        ev->len = 0;
    }
    ev->last_active = time(NULL); //调用eventset函数的时间
    return;
}

/* 向 epoll监听的红黑树 添加一个文件描述符 */
void eventadd(int efd, int events, struct myevent_s *ev)
{
    struct epoll_event epv={0, {0}};
    int op = 0;
    epv.data.ptr = ev; // ptr指向一个结构体(之前的epoll模型红黑树上挂载的是文件描述符cfd和lfd,现在是ptr指针)
    epv.events = ev->events = events; //EPOLLIN 或 EPOLLOUT
    if(ev->status == 0)       //status 说明文件描述符是否在红黑树上 0不在,1 在
    {
        op = EPOLL_CTL_ADD; //将其加入红黑树 g_efd, 并将status置1
        ev->status = 1;
    }
    if(epoll_ctl(efd, op, ev->fd, &epv) < 0) // 添加一个节点
        printf("event add failed [fd=%d],events[%d]\n", ev->fd, events);
    else
        printf("event add OK [fd=%d],events[%0X]\n", ev->fd, events);
    return;
}

/* 从epoll 监听的 红黑树中删除一个文件描述符*/
void eventdel(int efd,struct myevent_s *ev)
{
    struct epoll_event epv = {0, {0}};
    if(ev->status != 1) //如果fd没有添加到监听树上,就不用删除,直接返回
        return;
    epv.data.ptr = NULL;
    ev->status = 0;
    epoll_ctl(efd, EPOLL_CTL_DEL, ev->fd, &epv);
    return;
}

/*  当有文件描述符就绪, epoll返回, 调用该函数与客户端建立链接 */
void acceptconn(int lfd,int events,void *arg)
{
    struct sockaddr_in cin;
    socklen_t len = sizeof(cin);
    int cfd, i;
    if((cfd = accept(lfd, (struct sockaddr *)&cin, &len)) == -1)
    {
        if(errno != EAGAIN && errno != EINTR)
        {
            sleep(1);
        }
        printf("%s:accept,%s\n",__func__, strerror(errno));
        return;
    }
    do
    {
        for(i = 0; i < MAX_EVENTS; i++) //从全局数组g_events中找一个空闲元素,类似于select中找值为-1的元素
        {
            if(g_events[i].status ==0)
                break;
        }
        if(i == MAX_EVENTS) // 超出连接数上限
        {
            printf("%s: max connect limit[%d]\n", __func__, MAX_EVENTS);
            break;
        }
        int flag = 0;
        if((flag = fcntl(cfd, F_SETFL, O_NONBLOCK)) < 0) //将cfd也设置为非阻塞
        {
            printf("%s: fcntl nonblocking failed, %s\n", __func__, strerror(errno));
            break;
        }
        eventset(&g_events[i], cfd, recvdata, &g_events[i]); //找到合适的节点之后,将其添加到监听树中,并监听读事件
        eventadd(g_efd, EPOLLIN, &g_events[i]);
    }while(0);

    printf("new connect[%s:%d],[time:%ld],pos[%d]",inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), g_events[i].last_active, i);
    return;
}

/*读取客户端发过来的数据的函数*/
void recvdata(int fd, int events, void *arg)
{
    struct myevent_s *ev = (struct myevent_s *)arg;
    int len;

    len = recv(fd, ev->buf, sizeof(ev->buf), 0);    //读取客户端发过来的数据

    eventdel(g_efd, ev);                            //将该节点从红黑树上摘除

    if (len > 0) 
    {
        ev->len = len;
        ev->buf[len] = '\0';                        //手动添加字符串结束标记
        printf("C[%d]:%s\n", fd, ev->buf);                  

        eventset(ev, fd, senddata, ev);             //设置该fd对应的回调函数为senddata    
        eventadd(g_efd, EPOLLOUT, ev);              //将fd加入红黑树g_efd中,监听其写事件    

    } 
    else if (len == 0) 
    {
        close(ev->fd);
        /* ev-g_events 地址相减得到偏移元素位置 */
        printf("[fd=%d] pos[%ld], closed\n", fd, ev-g_events);
    } 
    else 
    {
        close(ev->fd);
        printf("recv[fd=%d] error[%d]:%s\n", fd, errno, strerror(errno));
    }   
    return;
}

/*发送给客户端数据*/
void senddata(int fd, int events, void *arg)
{
    struct myevent_s *ev = (struct myevent_s *)arg;
    int len;

    len = send(fd, ev->buf, ev->len, 0);    //直接将数据回射给客户端

    eventdel(g_efd, ev);                    //从红黑树g_efd中移除

    if (len > 0) 
    {
        printf("send[fd=%d], [%d]%s\n", fd, len, ev->buf);
        eventset(ev, fd, recvdata, ev);     //将该fd的回调函数改为recvdata
        eventadd(g_efd, EPOLLIN, ev);       //重新添加到红黑树上,设为监听读事件
    }
    else 
    {
        close(ev->fd);                      //关闭链接
        printf("send[fd=%d] error %s\n", fd, strerror(errno));
    }
    return ;
}

/*创建 socket, 初始化lfd */

void initlistensocket(int efd, short port)
{
    struct sockaddr_in sin;

    int lfd = socket(AF_INET, SOCK_STREAM, 0);
    fcntl(lfd, F_SETFL, O_NONBLOCK);                //将socket设为非阻塞

    memset(&sin, 0, sizeof(sin));               //bzero(&sin, sizeof(sin))
    sin.sin_family = AF_INET;
    sin.sin_addr.s_addr = INADDR_ANY;
    sin.sin_port = htons(port);

    bind(lfd, (struct sockaddr *)&sin, sizeof(sin));

    listen(lfd, 20);

    /* void eventset(struct myevent_s *ev, int fd, void (*call_back)(int, int, void *), void *arg);  */
    eventset(&g_events[MAX_EVENTS], lfd, acceptconn, &g_events[MAX_EVENTS]);    

    /* void eventadd(int efd, int events, struct myevent_s *ev) */
    eventadd(efd, EPOLLIN, &g_events[MAX_EVENTS]);  //将lfd添加到监听树上,监听读事件

    return;
}

int main()
{
    int port=SERV_PORT;

    g_efd = epoll_create(MAX_EVENTS + 1); //创建红黑树,返回给全局 g_efd
    if(g_efd <= 0)
            printf("create efd in %s err %s\n", __func__, strerror(errno));
    
    initlistensocket(g_efd, port); //初始化监听socket
    
    struct epoll_event events[MAX_EVENTS + 1];  //定义这个结构体数组,用来接收epoll_wait传出的满足监听事件的fd结构体
    printf("server running:port[%d]\n", port);

    int checkpos = 0;
    int i;
    while(1)
    {
    /*    long now = time(NULL);
        for(i=0; i < 100; i++, checkpos++)
        {
            if(checkpos == MAX_EVENTS);
                checkpos = 0;
            if(g_events[checkpos].status != 1)
                continue;
            long duration = now -g_events[checkpos].last_active;
            if(duration >= 60)
            {
                close(g_events[checkpos].fd);
                printf("[fd=%d] timeout\n", g_events[checkpos].fd);
                eventdel(g_efd, &g_events[checkpos]);
            }
        } */
        //调用eppoll_wait等待接入的客户端事件,epoll_wait传出的是满足监听条件的那些fd的struct epoll_event类型
        int nfd = epoll_wait(g_efd, events, MAX_EVENTS+1, 1000);
        if (nfd < 0)
        {
            printf("epoll_wait error, exit\n");
            exit(-1);
        }
        for(i = 0; i < nfd; i++)
        {
		    //evtAdd()函数中,添加到监听树中监听事件的时候将myevents_t结构体类型给了ptr指针
            //这里epoll_wait返回的时候,同样会返回对应fd的myevents_t类型的指针
            struct myevent_s *ev = (struct myevent_s *)events[i].data.ptr;
            //如果监听的是读事件,并返回的是读事件
            if((events[i].events & EPOLLIN) &&(ev->events & EPOLLIN))
            {
                ev->call_back(ev->fd, events[i].events, ev->arg);
            }
            //如果监听的是写事件,并返回的是写事件
            if((events[i].events & EPOLLOUT) && (ev->events & EPOLLOUT))
            {
                ev->call_back(ev->fd, events[i].events, ev->arg);
            }
        }
    }
    return 0;
}

④ 线程池并发服务器

线程池,模块分析:

  • main()

    • 创建线程池
    • 向线程池中添加任务,借助回调处理任务
    • 销毁线程池
  • pthreadpoll_create()

    • 创建线程池结构体 指针
    • 初始化线程池结构体(N个成员变量)
    • 创建N个任务线程
    • 创建1个管理者线程
    • 失败时,销毁开辟的所有空间(释放)
  • threadpoll_thread()

    • 进入子线程回调函数
    • 接收参数 void *arg --> poll结构体
    • 加锁 --> lock --> 整个结构体锁
    • 判断条件变量 --> wait
  • adjust_thread()

    • 进入管理者线程回调函数
    • 接收参数 void *arg --> poll结构体
    • 加锁 --> lock --> 整个结构体锁
    • 获取管理线程池要用到的变量。task_num,live_num,busy_num
    • 根据既定算法,使用上述3变量,判断是否应该创建、销毁线程池中 指定步长的线程
  • threadpoll_add()

    • 总功能

      • 模拟产生任务 num[20]

      • 设置回调函数,处理任务 sleep(1)代表处理完成

    • 内部实现

      • 初始化 任务队列结构体成员。 回调函数function, arg
      • 利用环形队列机制,实现添加任务。借助队尾指针挪移%实现
      • 唤醒阻塞在 条件变量上的线程
      • 解锁
  • 从3. 中的wait之后继续执行,处理任务

    • 加锁
    • 获取 任务处理回调函数,及参数
    • 利用环形队列机制,实现处理任务。借助队头指针挪移%实现
    • 唤醒阻塞在 条件变量 上的 server
    • 解锁
    • 加锁
    • 改忙线程数++
    • 解锁
    • 执行处理任务的线程
    • 加锁
    • 改忙线程数--
    • 解锁
  • 创建 销毁线程

    • 管理者线程根据task_num,live_num,busy_num

    • 根据既定算法,使用上述3变量,判断是否应该创建、销毁线程池中 指定步长的线程

    • 如果满足 创建条件

      • pthread_create(); 回调 任务线程函数 live_num++
    • 如果满足 销毁条件

      • signal 给 阻塞在条件变量上的线程 发送 假条件满足信号
      • 跳转至 --170 wait阻塞线程会被 假信号 唤醒。 判断:wait_exit_thr_num > 0 pthread_exit();

⑤ UDP 服务器

TCP通信和UDP通信各自的缺点:

  • TCP:面向连接的,可靠数据包传输。 对于不稳定的网络层,采用完全弥补的通信方式。丢包重传。

    • 优点:稳定
      • 数据流量稳定、速度稳定、顺序 稳定
    • 缺点:
      • 传输速度慢,效率低,开销大
    • 使用场景:
      • 数据完整性要求较高,不追求效率
      • 大数据传输、文件传输
  • UDP:无连接的,不可靠的数据报传递。 对于不稳定的网络层,采用完全不弥补的通信方式。默认还原网络状况

    • 优点:
      • 传输速度快,效率高,开销小
    • 缺点:不稳定
      • 数据流量不稳定、速度不稳定、顺序不稳定
    • 使用场景:
      • 时效性要求较高的场合、稳定性其次
      • 游戏、视频会议、视频电话 腾讯、华为、阿里 --- 应用层数据校验协议,弥补UDP的不足

UDP实现 C/S 模型:

recv()/send()  //只能用于TCP通信。代替read,write
accept();   //Connect()被舍弃

server:
    lfd = socekt(AF_INET, SOCK_DGRAM, 0);   //SOCK_DGRAM--报式协议
	bind();
	listen();   //可有可无
	while(1){
        read(cfd, buf, )    //read被替换成recvfrom()--涵盖accept传出地址结构
        小--大
            write();  //被替换成--sendto();
    }
	close();

client:
	connfd = socket(AF_INET, SOCK_DGRAM, 0);
	sendto("服务器地址结构", 地址结构大小);
	recvfrom();
	写到屏幕
    close();	

recvfrom 函数

#include<sys/types.h>
#include<sys/socket.h>

ssize_t recv(int sockfd, void *buf, size_t len, int flags);

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                struct sockaddr *src_addr, socklen_t *addr);
参数:
    sockfd:套接字
    buf:缓冲区地址
    len:缓冲区大小
    flags:0
    src_addr:(struct sockaddr*)&addr 传出。 对端地址结构
    addrlen:传入传出
返回值:
     成功接收数据字节数。失败:-1 errno   0:对端关闭       

sendto 函数

#include<sys/types.h>
#include<sys/socket.h>

ssize_t send(int sockfd, const void *buf, size_t len, int flags);

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, 
				const struct sockaddr *dest_addr, socklen_t addrlen);
参数:
	sockfd:套接字
    buf:存储数据的缓冲区
    len:数据长度
    flags:0
    dest_addr:(struct sockaddr*)&addr 传入。 目标地址结构
    addrlen:传入传出
返回值:
     成功接收数据字节数。失败:-1 errno

UDP实现的并发服务器和客户端:

由于UDP不需要维护连接,程序逻辑简单了很多,但是UDP协议是不可靠的,保证通讯可靠性的机制需要在应用层实现。

编译运行server,在两个终端里各开一个client与server交互,看看server是否具有并发服务的能力。用Ctrl+C关闭server,然后再运行server,看此时client还能否和server联系上。和前面TCP程序的运行结果相比较,体会无连接的含义。

server.c

#include <string.h>
#include <netinet/in.h>
#include <stdio.h>
#include <unistd.h>
#include <strings.h>
#include <arpa/inet.h>
#include <ctype.h>

#define MAXLINE 80
#define SERV_PORT 6666

int main(void)
{
	struct sockaddr_in servaddr, cliaddr;
	socklen_t cliaddr_len;
	int sockfd;
	char buf[MAXLINE];
	char str[INET_ADDRSTRLEN];
	int i, n;

	sockfd = socket(AF_INET, SOCK_DGRAM, 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(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
	printf("Accepting connections ...\n");

	while (1) {
		cliaddr_len = sizeof(cliaddr);
		n = recvfrom(sockfd, buf, MAXLINE,0, (struct sockaddr *)&cliaddr, &cliaddr_len);
		if (n == -1)
			perror("recvfrom error");
		printf("received from %s at PORT %d\n", 
				inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
				ntohs(cliaddr.sin_port));
		for (i = 0; i < n; i++)
			buf[i] = toupper(buf[i]);

		n = sendto(sockfd, buf, n, 0, (struct sockaddr *)&cliaddr, sizeof(cliaddr));
		if (n == -1)
			perror("sendto error");
	}
	close(sockfd);
	return 0;
}

client.c

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <strings.h>
#include <ctype.h>

#define MAXLINE 80
#define SERV_PORT 6666

int main(int argc, char *argv[])
{
	struct sockaddr_in servaddr;
	int sockfd, n;
	char buf[MAXLINE];

	sockfd = socket(AF_INET, SOCK_DGRAM, 0);

	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
	servaddr.sin_port = htons(SERV_PORT);

	while (fgets(buf, MAXLINE, stdin) != NULL) {
		n = sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr *)&servaddr, sizeof(servaddr));
		if (n == -1)
			perror("sendto error");
		n = recvfrom(sockfd, buf, MAXLINE, 0, NULL, 0);
		if (n == -1)
			perror("recvfrom error");
		write(STDOUT_FILENO, buf, n);
	}
	close(sockfd);
	return 0;
}

懂得利用TCP socket 代码 重编写UDP socket

⑥ socket IPC(本地套接字)

​ IPC:pipe、fifo、mmap、信号、本地套(domain)---CS模型

对比网络编程TCP C/S 模型,注意以下几点:

1、int socket(int domain, int type, int protocol)
	参数:
		domian:AF_INET -> AF_UNIX/AF_LOCAL
		type:SOCK_STREAM/SOCK_DGRAM 都可以
            
2、地址结构:sockaddr_in 
    --> sockaddr_un
            
    	struct sockaddr_in srv_addr; 
	--> sockaddr_un srv_addr
            
		srv_addr.sin_family = AF_INET; 
	--> srv_addr.sun_family = AF_UNIX;

		srv_addr.sin_port = htons(8888);
	--> strcpy(srv_addr.sun_path, "srv.socket")
            
		srv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
	--> len = offsetof(struct sockaddr_un, sun_path)+strlen("srv.socket");

		bind(fd, (struct sockaddr *)&arv_addr, sizeof(srv_addr));		
	--> bind(fd, (struct sockaddr *)&arv_addr, len);	

3、bind()函数调用成功,会创建一个socket。因此为保证bind成功,通常我们在bain之前,可以使用unlink("srv.socket");

4、客户端不能依赖“隐式绑定”。并且应该在通信建立过程中,创建且初始化2个结构地址
    1)client_addr --> bind()
    2)server_addr --> connect()
对比网络套接字地址结构和本地套接字地址结构:
struct sockaddr_in {
__kernel_sa_family_t sin_family;    /* Address family */  	地址结构类型
__be16 sin_port;					 /* Port number */		端口号
struct in_addr sin_addr;			/* Internet address */	IP地址
};
struct sockaddr_un {
__kernel_sa_family_t sun_family;   /* AF_UNIX */	地址结构类型
char sun_path[UNIX_PATH_MAX]; 		/* pathname */	socket文件名(含路径)
};
以下程序将UNIX Domain socket绑定到一个地址。
	size = offsetof(struct sockaddr_un, sun_path) + strlen(un.sun_path);
	#define offsetof(type, member) ((int)&((type *)0)->MEMBER)

server.c

#include <stdlib.h>
#include <stdio.h>
#include <stddef.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>

#define QLEN 10
/*
* Create a server endpoint of a connection.
* Returns fd if all OK, <0 on error.
*/
int serv_listen(const char *name)
{
	int fd, len, err, rval;
	struct sockaddr_un un;

	/* create a UNIX domain stream socket */
	if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
		return(-1);
	/* in case it already exists */
	unlink(name); 			

	/* fill in socket address structure */
	memset(&un, 0, sizeof(un));
	un.sun_family = AF_UNIX;
	strcpy(un.sun_path, name);
	len = offsetof(struct sockaddr_un, sun_path) + strlen(name);

	/* bind the name to the descriptor */
	if (bind(fd, (struct sockaddr *)&un, len) < 0) {
		rval = -2;
		goto errout;
	}
	if (listen(fd, QLEN) < 0) { /* tell kernel we're a server */
		rval = -3;
		goto errout;
	}
	return(fd);

errout:
	err = errno;
	close(fd);
	errno = err;
	return(rval);
}
int serv_accept(int listenfd, uid_t *uidptr)
{
	int clifd, len, err, rval;
	time_t staletime;
	struct sockaddr_un un;
	struct stat statbuf;

	len = sizeof(un);
	if ((clifd = accept(listenfd, (struct sockaddr *)&un, &len)) < 0)
		return(-1); /* often errno=EINTR, if signal caught */

	/* obtain the client's uid from its calling address */
	len -= offsetof(struct sockaddr_un, sun_path); /* len of pathname */
	un.sun_path[len] = 0; /* null terminate */

	if (stat(un.sun_path, &statbuf) < 0) {
		rval = -2;
		goto errout;
	}
	if (S_ISSOCK(statbuf.st_mode) == 0) {
		rval = -3; /* not a socket */
		goto errout;
	}
	if (uidptr != NULL)
		*uidptr = statbuf.st_uid; /* return uid of caller */
	/* we're done with pathname now */
	unlink(un.sun_path); 
	return(clifd);

errout:
	err = errno;
	close(clifd);
	errno = err;
	return(rval);
}
int main(void)
{
	int lfd, cfd, n, i;
	uid_t cuid;
	char buf[1024];
	lfd = serv_listen("foo.socket");

	if (lfd < 0) {
		switch (lfd) {
			case -3:perror("listen"); break;
			case -2:perror("bind"); break;
			case -1:perror("socket"); break;
		}
		exit(-1);
	}
	cfd = serv_accept(lfd, &cuid);
	if (cfd < 0) {
		switch (cfd) {
			case -3:perror("not a socket"); break;
			case -2:perror("a bad filename"); break;
			case -1:perror("accept"); break;
		}
		exit(-1);
	}
	while (1) {
r_again:
		n = read(cfd, buf, 1024);
		if (n == -1) {
		if (errno == EINTR)
		goto r_again;
	}
	else if (n == 0) {
		printf("the other side has been closed.\n");
		break;
	}
	for (i = 0; i < n; i++)
		buf[i] = toupper(buf[i]);
		write(cfd, buf, n);
	}
	close(cfd);
	close(lfd);
	return 0;
}

client.c

#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <errno.h>

#define CLI_PATH "/var/tmp/" /* +5 for pid = 14 chars */
/*
* Create a client endpoint and connect to a server.
* Returns fd if all OK, <0 on error.
*/
int cli_conn(const char *name)
{
	int fd, len, err, rval;
	struct sockaddr_un un;

	/* create a UNIX domain stream socket */
	if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
		return(-1);

	/* fill socket address structure with our address */
	memset(&un, 0, sizeof(un));
	un.sun_family = AF_UNIX;
	sprintf(un.sun_path, "%s%05d", CLI_PATH, getpid());
	len = offsetof(struct sockaddr_un, sun_path) + strlen(un.sun_path);

	/* in case it already exists */
	unlink(un.sun_path); 
	if (bind(fd, (struct sockaddr *)&un, len) < 0) {
		rval = -2;
		goto errout;
	}

	/* fill socket address structure with server's address */
	memset(&un, 0, sizeof(un));
	un.sun_family = AF_UNIX;
	strcpy(un.sun_path, name);
	len = offsetof(struct sockaddr_un, sun_path) + strlen(name);
	if (connect(fd, (struct sockaddr *)&un, len) < 0) {
		rval = -4;
		goto errout;
	}
return(fd);
	errout:
	err = errno;
	close(fd);
	errno = err;
	return(rval);
}
int main(void)
{
	int fd, n;
	char buf[1024];

	fd = cli_conn("foo.socket");
	if (fd < 0) {
		switch (fd) {
			case -4:perror("connect"); break;
			case -3:perror("listen"); break;
			case -2:perror("bind"); break;
			case -1:perror("socket"); break;
		}
		exit(-1);
	}
	while (fgets(buf, sizeof(buf), stdin) != NULL) {
		write(fd, buf, strlen(buf));
		n = read(fd, buf, sizeof(buf));
		write(STDOUT_FILENO, buf, n);
	}
	close(fd);
	return 0;
}

本地套接字和网络套接字实现对比:

  • 通信对象:
    • 本地套接字是让单机中的进程进行通信;
    • 网络套接字是让网络中的进程进行通信;
  • 通信方式:
    • 网络套接字是通过绑定ip和端口;
    • 本地套接字是绑定套接字文件的路径名。

除了以上两点外,基本相同。

本地套和网络套对比2


posted @ 2023-08-22 17:38  洋綮  阅读(245)  评论(0)    收藏  举报