linux---高并发服务器(网络)

多进程并发服务器

为提高服务器效率,服务器应能同时被多个客户端进程使用,且能处理多个用户请求,实际上,我们在生活、应用中接触到的服务器,都能实现并发功能。

在多进程并发服务器中,若有用户请求到达,服务器将会调用fork()函数,创建一个子进程,之后父进程将继续调用accept(),而子进程则去处理用户请求。

案例1:搭建多进程并发服务器,使服务器端可接收多个客户端的数据,并将接收到的数据转为大写,写回客户端;使客户端可向服务器发送数据,并将服务器返回的数据打印到终端。

服务端:

#include <arpa/inet.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/types.h>
#include "wrap.h"
#define MAXLINE 80						//缓冲数组大小
#define SERV_PORT 8000					//端口号
//子进程回收函数
void do_sigchild(int num)
{
	while (waitpid(0, NULL, WNOHANG) > 0);
}
int main()
{
	struct sockaddr_in servaddr, cliaddr;
	socklen_t cliaddr_len;
	int listenfd, connfd;
	char buf[MAXLINE];
	char str[INET_ADDRSTRLEN];
	int i, n;
	pid_t pid;
	struct sigaction newact;
	newact.sa_handler = do_sigchild;
	sigaction(SIGCHLD, &newact, NULL);		//信号捕获与处理(回收子进程)
	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);
		pid = fork();			//创建子进程
		if (pid == 0) {
			//子进程处理客户端请求
			Close(listenfd);
			while (1) {
				n = Read(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, &cliaddr.sin_addr, str, sizeof(str)),
					ntohs(cliaddr.sin_port));
				for (i = 0; i < n; i++)
					buf[i] = toupper(buf[i]);
				Write(connfd, buf, n);
			}
			Close(connfd);
			return 0;
		}
		else if (pid > 0) {
			Close(connfd);
		}
		else
			perr_exit("fork");
	}
	Close(listenfd);
	return 0;
}

服务器进程中的核心业务代码为第35~64行。
对用户而言,服务器需要一直保持运转,以便能及时与客户端连接,处理客户端请求。
因此,服务器的accept功能应处于while循环中。
当服务器通过AcceptO)成功与客户端连接后,服务器创建子进程,将请求处理功能交予子进程。
需要注意的是,此时父子进程打开了相同的文件描述符,因此在父进程中应调用Close()函数关闭由Accept()函数获取的文件描述符。

在进程机制中,子进程由父进程回收。通过对前面章节的学习,我们知道可以通过调用wait()、waitpid()函数或使用信号机制来回收子进程。
其中wait()函数用于等待回收子进程,若没有子进程终止,父进程将会阻塞,此时服务器将无法接收客户端请求,此种方式显然不合适:
若使用信号,子进程终止时产生的SIGCHLD信号会使父进程中断,进而使服务器的稳定性受到影响,因此信号机制也不适用。

程序fserver.c中选用waitpid()实现子进程的回收及资源释放。waitpid()函数采用非阻塞方式回收子进程,调用waitpid()函数不会使父进程阻塞,且当其第一个参数pid被设置为0时,可回收进程组中所有已终止的子进程。因此可搭配信号捕获函数sigaction(),捕获子进程终止时产生的SIGCHLD信号,在空闲时刻回收所有已终止的子进程。当然,若服务器中的子进程较多、也可创建一个子进程专门回收服务器中的其他子进程,以保证服务器的性能。

客户端:

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include "wrap.h"
#define MAXLINE 80							//缓冲数组大小
#define SERV_PORT 8000						//端口号
int main()
{
	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;
}

分别
在这里插入图片描述
关于warp二个文件见:
Linux 网络编程 wrap.c和wrap.h

在这里插入图片描述

在这里插入图片描述
说明:
(1)多进程并发服务器效率高且更加稳定,服务器中的进程不会受其它进程状态的影响;
(2)多进程并发服务器中进程数量受可打开文件描述符的限制;
(3)多进程并发服务器中进程数量受内存容量限制。

多线程并发服务器

考虑到每个进程可打开的文件描述符数量有限且进程占用资源较多,系统中进程的数量又受到内存大小的限制,若想在保证服务器效率的前提下降低服务器的消耗,可利用多线程机制搭建并发服务器。

多线程并发服务器与多进程并发服务器类似,不同的是当有请求到达时,服务器进程会创建一个子线程并使子线程处理客户端请求。

下面通过一个案例来展示使用多线程并发服务器实现网络通信的方法。

案例2:搭建多线程并发服务器,使服务器端可接收多个客户端的数据,并将接收到的数据转为大写,写回客户端;使客户端可向服务器发送数据,并将服务器返回的数据打印到终端。

服务端:

#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 8000							//端口号
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[383];
	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(&tid, NULL, do_work, (void*)&ts[i]);
		i++;
	}
	return 0;
}

客户端:

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include "wrap.h"
#define MAXLINE 80								//缓冲数组大小
#define SERV_PORT 8000							//端口号
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;
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
与多进程服务器相比:
(1)线程占用的空间资源大大减少,因此内存堆服务器的限制也被降低;
(2)多线程并发服务器稳定性较差。
因此在搭建服务器时,应从需求出发,选择更为合适的服务器。

I/O多路转接服务器

在这里插入图片描述

select

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
案例3:使用select模型搭建多路I/O转接服务器,使服务器可接收客户端数据,并将接收到的数据转为大写,写回客户端;使客户端可向服务器发送数据,并将服务器返回的数据打印到终端。

服务端:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "wrap.h"
#define MAXLINE 80						//缓冲数组大小
#define SERV_PORT 8000					//端口号
int main()
{
	int i, maxi, maxfd, listenfd, connfd, sockfd;
	int nready, client[FD_SETSIZE]; 	//FD_SETSIZE 默认为1024
	ssize_t n;
	fd_set rset, allset;
	char buf[MAXLINE];
	char str[INET_ADDRSTRLEN];			//#define INET_ADDRSTRLEN 16
	socklen_t cliaddr_len;
	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);		//默认最大128
	maxfd = listenfd;
	maxi = -1;
	//初始化监控列表
	for (i = 0; i < FD_SETSIZE; i++)
		client[i] = -1; 		//使用-1初始化client[]中元素
	FD_ZERO(&allset);
	FD_SET(listenfd, &allset); 	//将listenfd添加到文件描述符集中
	//循环监测处于连接状态进程的文件描述符
	for (;;) {
		//使用变量rset获取文件描述符集合
		rset = allset;
		//记录就绪进程数量
		nready = select(maxfd + 1, &rset, NULL, NULL, NULL);
		if (nready < 0)
			perr_exit("select error");
		if (FD_ISSET(listenfd, &rset)){//有新连接请求到达则进行连接便处理连接请求
			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));
			for (i = 0; i < FD_SETSIZE; i++)
			if (client[i] < 0) {
				client[i] = connfd; 	//将文件描述符connfd保存到client[]中
				break;
			}
			if (i == FD_SETSIZE) { 		//判断连接数是否已达上限
				fputs("too many clients\n", stderr);
				exit(1);
			}
			FD_SET(connfd, &allset); 	//添加新文件描述符到监控信号集中
			if (connfd > maxfd) 		//更新最大文件描述符
				maxfd = connfd;
			if (i > maxi) 				//更新client[]最大下标值
				maxi = i;
			//若无文件描述符就绪,便返回select,继续阻塞监测剩余的文件描述符
			if (--nready == 0)
				continue;
		}
		//遍历文件描述符集,处理已就绪的文件描述符
		for (i = 0; i <= maxi; i++) {
			if ((sockfd = client[i]) < 0)
				continue;
			if (FD_ISSET(sockfd, &rset)) {
				//n=0,client就绪但未读到数据,表示client将关闭连接
				if ((n = Read(sockfd, buf, MAXLINE)) == 0) {
					//关闭服务器端连接
					Close(sockfd);
					FD_CLR(sockfd, &allset); //清除集合中对应的文件描述符
					client[i] = -1;
				}
				else {						//处理获取的数据
					int j;
					for (j = 0; j < n; j++)
						buf[j] = toupper(buf[j]);
					Write(sockfd, buf, n);
				}
				if (--nready == 0)
					break;
			}
		}
	}
	Close(listenfd);
	return 0;
}

客户端:

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include "wrap.h"
#define MAXLINE 80					//缓冲数组大小
#define SERV_PORT 8000				//端口号
int main()
{
	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;
}

poll

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
案例4:使用poll模型搭建多路I/O转接服务器,使服务器可接收客户端数据,并将接收到的数据转为大写,写回客户端;使客户端可向服务器发送数据,并将服务器返回的数据打印到终端。

服务端:

#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 8000					//端口号
#define OPEN_MAX 1024					//最大打开文件描述符数量
int main()
{
	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);
	//初始化poll()的参数fds
	client[0].fd = listenfd;
	client[0].events = POLLRDNORM;	//设置listenfd监听普通读事件
	for (i = 1; i < OPEN_MAX; i++)
		client[i].fd = -1; 		//将client[]中其余元素的fd成员初始化为-1
	maxi = 0; 					//记录client[]数组有效元素中最大元素下标
	//使用poll()机制循环检测文件描述符集合
	for (;;) {
		nready = poll(client, maxi + 1, -1);	//阻塞等待请求到达
		//通过listenfd状态判断是否有客户端连接请求,如有则建立连接
		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));
			//将accept返回的connfd存放到client[]中的空闲位置
			for (i = 1; i < OPEN_MAX; i++){
				if (client[i].fd < 0) {
					client[i].fd = connfd;
					break;
				}
			}
			if (i == OPEN_MAX)
				perr_exit("too many clients");
			client[i].events = POLLRDNORM; 	//设置刚刚返回的connfd,监控读事件
			if (i > maxi)					//更新client[]中最大元素下标
				maxi = i;
			if (--nready <= 0) 				//若无就绪事件,回到poll阻塞
				continue;
		}
		//检测client[],处理有就绪事件的文件描述符
		for (i = 1; i <= maxi; i++){
			if ((sockfd = client[i].fd) < 0)
				continue;
			if (client[i].revents & (POLLRDNORM | POLLERR)) {
				if ((n = Read(sockfd, buf, MAXLINE)) < 0) {
					//比较errno,若为RST则表示连接中断
					if (errno == ECONNRESET){
						printf("client[%d] aborted connection\n", i);
						Close(sockfd);
						client[i].fd = -1;
					}
					else
						perr_exit("read error");
				}
				else if (n == 0) {//连接由客户端关闭
					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);
				}
				//当就绪文件描述符数量为0时,终止循环
				if (--nready <= 0)
					break;
			}
		}
	}
	return 0;
}

客户端:

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include "wrap.h"
#define MAXLINE 80						//缓冲数组大小
#define SERV_PORT 8000					//端口号
int main()
{
	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_create()

在这里插入图片描述
在这里插入图片描述

epoll_ctl()

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
epoll事件
在这里插入图片描述
用户数据变量
在这里插入图片描述
在这里插入图片描述

epoll_wait()

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
案例5:使用epoll模型搭建多路I/O转接服务器,使服务器可接收客户端数据,并将接收到的数据转为大写,写回客户端;使客户端可向服务器发送数据,并将服务器返回的数据打印到终端。

服务端:

#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 8000						//端口号
#define OPEN_MAX 1024						//最大打开文件描述符数量
int main()
{
	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);
	//初始化client集合
	for (i = 0; i < OPEN_MAX; i++)
		client[i] = -1;
	maxi = -1;							//初始化maxi
	efd = epoll_create(OPEN_MAX);		//创建epoll句柄
	if (efd == -1)
		perr_exit("epoll_create");
	//初始化tep
	tep.events = EPOLLIN;
	tep.data.fd = listenfd;
	//为服务器进程注册事件(listenfd)
	res = epoll_ctl(efd, EPOLL_CTL_ADD, listenfd, &tep);
	if (res == -1)
		perr_exit("epoll_ctl");
	for (;;) {
		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;
			//若fd为listenfd,表示有连接请求到达
			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));		//字节序转换
				//将accept获取到的文件描述符保存到client[]数组中
				for (j = 0; j < OPEN_MAX; j++)
				if (client[j] < 0) {
					client[j] = connfd;
					break;
				}
				if (j == OPEN_MAX)
					perr_exit("too many clients");
				if (j > maxi)
					maxi = j;					//更新最大文件描述符
				tep.events = EPOLLIN;
				tep.data.fd = connfd;
				//为新建立连接的进程注册事件
				res = epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &tep);
				if (res == -1)
					perr_exit("epoll_ctl");
			}
			else {//若fd不等于listenfd,表示就绪的是各路连接
				sockfd = ep[i].data.fd;
				n = Read(sockfd, buf, MAXLINE);
				if (n == 0) {//若读取的字符个数为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;
}

客户端

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include "wrap.h"
#define MAXLINE 80							//缓冲数组大小
#define SERV_PORT 8000						//端口号
int main()
{
	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的工作模式

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
案例6:搭建工作在边缘触发模式的epoll服务器,使服务器可接收并处理客户端发送的数据。

服务端:

#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 8000
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);
	//设置epoll为ET模式
	event.events = EPOLLIN | EPOLLET;
	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;
}

客户端:

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include "wrap.h"
#define MAXLINE 80							//缓冲数组大小
#define SERV_PORT 8000						//端口号
int main()
{
	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;
}

在这里插入图片描述
在这里插入图片描述

多学一招 线程池

在这里插入图片描述
在这里插入图片描述

仅仅做个记录,课件与源码东西见主页上传资料

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值