网络编程:C/S模型 - TCP

本文详细介绍了一种高效的网络I/O模型epoll的实现方法,包括水平触发(LT)和边沿触发(ET)两种模式,以及如何结合非阻塞IO进行多路复用,提高服务器性能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

一、代码结构

二、敲代码:服务器-客户端

三、epoll_server:水平触发LT + 多触发读read

四、epoll_server:边沿触发ET + 非阻塞IO循环(read)


一、代码结构

1、客户端

clie_fd = socket(AF_INET, STREAM, 0);

connect(clie_fd, );


write();

read();


close();

2、服务端

serv_fd = socket(AF_INET, STREAM, 0);

bind();

listen(serv_fd, );


read();

write();


close();

二、敲代码:服务器-客户端

1、服务端

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

#define serv_ip     "127.0.0.1"
#define serv_port   6666

int main()
{
	int serv_fd, clie_fd;

	struct sockaddr_in serv_addr, clie_addr;                           //设置服务器地址
	socklen_t clie_addr_len;

	char buf[BUFSIZ];
	char quitbuf[20];
	int n = 0, i = 0;
	char clie_ipbuf[15];
	
	serv_fd = socket(AF_INET, SOCK_STREAM, 0);                          //创建socket

	serv_addr.sin_family = AF_INET;
	serv_addr.sin_port = htons(serv_port);
	inet_pton(AF_INET, serv_ip, &serv_addr.sin_addr.s_addr);
	//serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);

	bind(serv_fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));   //绑定端口和ip到服务器套接字
	
	listen(serv_fd, 128);                                              //设置最大同时连接数

	clie_addr_len = sizeof(clie_addr);
	clie_fd = accept(serv_fd, (struct sockaddr *)&clie_addr, &clie_addr_len);          //阻塞
	
	printf("client ip: %s, client port: %d\n", inet_ntop(AF_INET, &clie_addr.sin_addr.s_addr, clie_ipbuf, (socklen_t)sizeof(clie_ipbuf)), ntohs(clie_addr.sin_port));
	
	while(1)
	{
		//读取
		memset(buf, 0, sizeof(buf));
		n = read(clie_fd, buf, sizeof(buf));
		if(n < 0)
		{
			perror("read error");
   			exit(1);                
		}

		//处理
		printf("%s\n", buf);
		for(i=0; i<n; i++)	
		{
			buf[i] = toupper(buf[i]);
		}

		//回写
		write(clie_fd, buf, n);
	}

	close(serv_fd);
	close(clie_fd);

	printf("server close...\n");

	return 0;
}

2、客户端

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


#define serv_ip     "127.0.0.1"
#define serv_port   6666

int main()
{
	int clie_fd;
	struct sockaddr_in serv_addr;                                       //设置服务器地址
	socklen_t serv_addr_len;
	char buf[BUFSIZ];
	int n = 0;

	clie_fd = socket(AF_INET, SOCK_STREAM, 0);                          //创建socket

	serv_addr.sin_family = AF_INET;
	serv_addr.sin_port = htons(serv_port);
	//inet_pton(AF_INET, clie_ip, &clie_addr.sin_addr.s_addr);
	serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);              //

	serv_addr_len = sizeof(serv_addr);
	connect(clie_fd, (struct sockaddr *)&serv_addr, serv_addr_len);    //连接
	
	read(clie_fd, buf, sizeof(buf));
	printf("%s\n", buf);

	while(1)
	{
		//从屏幕读
		memset(buf, 0, sizeof(buf));
		fgets(buf, sizeof(buf), stdin);       //fgts()读一行  hello --->   "hello\n\0"
		buf[strlen(buf)-1] = 0;		          //将\n --->  \0
		
		if(strcmp(buf, "quit") == 0) {break;}
				
		//写
		write(clie_fd, buf, strlen(buf));
		
		//读取
		n = read(clie_fd, buf, sizeof(buf));
		
		//打印处理
		printf("%s\n", buf);
	}
	
	close(clie_fd);
	printf("client close...\n");
		
	return 0;
}

三、epoll_server:水平触发LT + 多触发读read

#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <string.h>
#include <strings.h>
#include <sys/wait.h>
#include <sys/epoll.h>

#define serv_ip     "127.0.0.1"
#define serv_port   6666
#define OPEN_MAX    1000

void perr_exit(const char *arg)
{
	printf("%s", arg);
	exit(1);
}

int main()
{
	int lfd, cfd, epfd;                       //文件描述符
	struct sockaddr_in serv_addr, clie_addr;  //地址
	socklen_t clie_addr_len;
	char clie_ipbuf[16];
	char buf[BUFSIZ];
	int n = 0, i = 0, res = 0, nready = 0 ;
	struct epoll_event evt, evts[OPEN_MAX];   //事件
	
	lfd = socket(AF_INET, SOCK_STREAM, 0);                                       //创建socket
	
	int opt = 1;
	setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));	             //端口复用,不受2MSL时间影响
	
	serv_addr.sin_family = AF_INET;                                              //server地址
	serv_addr.sin_port = htons(serv_port);
	inet_pton(AF_INET, serv_ip, &serv_addr.sin_addr.s_addr);
	//serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);

	res = bind(lfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));           //绑定端口和ip到服务器套接字
	if(res == -1) { perr_exit("bind error\n"); }

	listen(lfd, 128);                                                            //设置最大同时连接数
	
	epfd = epoll_create(OPEN_MAX);                                               //创建 EPOLL 模型,epfd 指向红黑树树根
	if(epfd == -1) { perr_exit("epoll_create error\n"); }            
	
	evt.events = EPOLLIN;                                                        //文件描述符的事件
	evt.data.fd = lfd;
	
	res = epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &evt);                             //添加 listen 事件
	if(res == -1) { perr_exit("epoll_ctl_add error\n"); }
	
	while(1)
	{
		nready = epoll_wait(epfd, evts, OPEN_MAX, -1);		                               //内核阻塞
		if(nready == -1) { perr_exit("epoll_wait error\n"); }
		
		for(i=0; i<nready; i++)
		{
			if(!(evts[i].events & EPOLLIN)) { continue; }                                  //过滤非读事件
			
			if(evts[i].data.fd == lfd)                                           //监听事件
			{
				clie_addr_len = sizeof(clie_addr);
				cfd = accept(lfd, (struct sockaddr *)&clie_addr, &clie_addr_len);          //非阻塞
				
				//打印
				printf("connected to client: %s, %d\n", inet_ntop(AF_INET, &clie_addr.sin_addr.s_addr, clie_ipbuf, (socklen_t)sizeof(clie_ipbuf)), ntohs(clie_addr.sin_port));
				//回写
				sprintf(buf, "connected to server: %s, %d", serv_ip, serv_port);
				write(cfd, buf, strlen(buf));
				
				evt.events = EPOLLIN;
				evt.data.fd = cfd;
				
				res = epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &evt);                           //添加事件
				if(res == -1) { perr_exit("epoll_ctl_add error\n"); }
			}
			else                                                               //读事件
			{
				cfd = evts[i].data.fd; //获取文件描述符
				
				memset(buf, 0, sizeof(buf));
				n = read(cfd, buf, sizeof(buf));  //读文件
				if(n < 0)            //出错
				{ 
					perr_exit("read error\n");
					close(cfd);
				}
				else if(n == 0)      //客户端断开
				{
					//break;
					printf("disconnected to client: %s, %d\n", inet_ntop(AF_INET, &clie_addr.sin_addr.s_addr, clie_ipbuf, (socklen_t)sizeof(clie_ipbuf)), ntohs(clie_addr.sin_port));

					res = epoll_ctl(epfd, EPOLL_CTL_DEL, cfd, NULL);         //事件删除
					if(res == -1) { perr_exit("epoll_ctl_del error\n"); }
					close(cfd);                                              //文件关闭
				}


				//处理,对应不同的终端,可以执行不同的程序,可以fork子进程
				if(buf[0]=='0'){
					printf("terminal 0\n");
				}
				printf("%s\n", buf);
				for(i=0; i<n; i++)	
				{
					buf[i] = toupper(buf[i]);
				}
				//写
				write(cfd, buf, n);
			}
		}
	}

	close(lfd);
	
	printf("server close...\n");

	return 0;
}


//ps -aux | grep server
//cat /proc/sys/fs/file-max            //查看一个进程可以打开的文件描述符上限  93881
//sudo vi /etc/security/limits.conf    //在文件尾写下如下设置,soft 软限制,hard 硬限制
/*         <type>  <item>    <value>
 *          soft   nofile    65536
 *          hard   nofile    100000
 */

 

四、epoll_server:边沿触发ET + 非阻塞IO循环(read)

#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <string.h>
#include <strings.h>
#include <sys/wait.h>
#include <sys/epoll.h>
#include <fcntl.h>


#define serv_ip     "127.0.0.1"
#define serv_port   6666
#define OPEN_MAX    1000
#define MAX_LINE    5

void perr_exit(const char *arg)
{
	printf("%s", arg);
	exit(1);
}

int main()
{
	int lfd, cfd, epfd;                       //文件描述符
	struct sockaddr_in serv_addr, clie_addr;  //地址
	socklen_t clie_addr_len;
	char clie_ipbuf[16];
	char buf[BUFSIZ];
	int n = 0, i = 0, res = 0, nready = 0, flag;
	struct epoll_event evt, evts[OPEN_MAX];   //事件
	
	
	lfd = socket(AF_INET, SOCK_STREAM, 0);                                       //创建socket
	
	int opt = 1;
	setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));	             //端口复用,不受2MSL时间影响
	
	serv_addr.sin_family = AF_INET;                                              //server地址
	serv_addr.sin_port = htons(serv_port);
	inet_pton(AF_INET, serv_ip, &serv_addr.sin_addr.s_addr);
	//serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);

	res = bind(lfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));           //绑定端口和ip到服务器套接字
	if(res == -1) { perr_exit("bind error\n"); }

	listen(lfd, 128);                                                            //设置最大同时连接数
	
	epfd = epoll_create(OPEN_MAX);                                               //创建 EPOLL 模型,epfd 指向红黑树树根
	if(epfd == -1) { perr_exit("epoll_create error\n"); }            
	
	//evt.events = EPOLLIN;                                                        //文件描述符的事件,默认为水平触发
	evt.events = EPOLLIN | EPOLLET;                                              //边沿触发
	evt.data.fd = lfd;
	
	res = epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &evt);                             //添加 listen 事件
	if(res == -1) { perr_exit("epoll_ctl_add error\n"); }
	
	while(1)
	{
		nready = epoll_wait(epfd, evts, OPEN_MAX, -1);		                               //内核阻塞
		if(nready == -1) { perr_exit("epoll_wait error\n"); }
		
		for(i=0; i<nready; i++)
		{
			if(!(evts[i].events & EPOLLIN)) { continue; }                                  //过滤非读事件
			
			if(evts[i].data.fd == lfd)                                           //监听事件
			{
				clie_addr_len = sizeof(clie_addr);
				cfd = accept(lfd, (struct sockaddr *)&clie_addr, &clie_addr_len);          //非阻塞
				
				//打印
				printf("connected to client: %s, %d\n", inet_ntop(AF_INET, &clie_addr.sin_addr.s_addr, clie_ipbuf, (socklen_t)sizeof(clie_ipbuf)), ntohs(clie_addr.sin_port));
				//回写
				sprintf(buf, "connected to server: %s, %d", serv_ip, serv_port);
				write(cfd, buf, strlen(buf));
				
				//修改 cfd 为非阻塞读
				flag = fcntl(cfd, F_GETFL);   //获得flag
				flag |= O_NONBLOCK;
				fcntl(cfd, F_SETFL, flag);    //设置文件为非阻塞
				
				evt.events = EPOLLIN;
				evt.data.fd = cfd;
				
				res = epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &evt);                           //添加事件
				if(res == -1) { perr_exit("epoll_ctl_add error\n"); }
			}
			else                                                               //读事件
			{
				cfd = evts[i].data.fd;            //获取文件描述符
				
				memset(buf, 0, sizeof(buf));
				n = read(cfd, buf, MAX_LINE);     //读文件。 若用 readn(),阻塞和不阻塞由 cfd 的 flag 设置,若该处
				
				
				if(n < 0) { perr_exit("read error\n"); }
				else if(n == 0)                  //客户端断开
				{
					//break;
					printf("disconnected to clie	nt: %s, %d\n", inet_ntop(AF_INET, &clie_addr.sin_addr.s_addr, clie_ipbuf, (socklen_t)sizeof(clie_ipbuf)), ntohs(clie_addr.sin_port));

					res = epoll_ctl(epfd, EPOLL_CTL_DEL, cfd, NULL);         //事件删除
					if(res == -1) { perr_exit("epoll_ctl_del error\n"); }
					close(cfd);                                              //文件关闭
				}


				//处理
				printf("%s\n", buf);
				/*
				for(i=0; i<n; i++)	
				{
					buf[i] = toupper(buf[i]);
				}
				//写
				write(cfd, buf, n);
				*/
			}
		}
	}

	close(lfd);
	
	printf("server close...\n");

	return 0;
}

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值