TCP UDP协议的应用以及高级IO的介绍

TCP UDP协议的应用以及高级IO的介绍+

网络通信协议

模型:

  • TCP和UDP两个协议都是一对多的网络通信模型
  • TCP编程模型

请添加图片描述

  • UDP编程模型

请添加图片描述

实例:

TCP模型

聊天室的服务器:

有私密消息功能以及列出聊天者的功能

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <signal.h>
#include <pthread.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <netinet/in.h>

#define MAX_CNT 100
#define MSG_LEN 1024
#define NAME_LEN 48

struct Client{
	char name[NAME_LEN];//网名
	int fd;//套接字文件描述符
	struct sockaddr_in addr;//ip port
	pthread_t id;//线程ID
};

//所有客户端数组
struct Client clts[MAX_CNT] = {};
size_t cnt = 0;//目前有cnt个客户端成员
pthread_mutex_t lock;
int sockfd; //服务器的socket套接字 用于接收客户端的连接请求

#define LOG_ERROR(fmt, args...)\
	fprintf(stderr, "[ERROR [%s:%d]\n"fmt,__func__,__LINE__,##args);

void handleExit(int sig){
	close(sockfd);
	int i;
	for(i = 0; i < cnt; i++){
		pthread_cancel(clts[i].id);
	}
	for(i = 0; i < cnt; i++){
		pthread_join(clts[i].id, NULL);
	}
}

int init_server(const char *ip, unsigned short port){
	sockfd = socket(AF_INET, SOCK_STREAM, 0);//第一步,建立socket套接字
	if(sockfd == -1){
		LOG_ERROR("socket:%s\n", strerror(errno));
		return -1;
	}
	struct sockaddr_in addr;
	memset(&addr, 0, sizeof(addr));
	addr.sin_family = AF_INET;
	addr.sin_port = htons(port);
	addr.sin_addr.s_addr = inet_addr(ip);

	if(bind(sockfd, (struct sockaddr*)&addr, sizeof(addr)) == -1){//第二步,绑定端口
		LOG_ERROR("bind:%s\n", strerror(errno));
		return -1;
	}

	if(listen(sockfd, MAX_CNT) == -1){//第三步,监听使套接字变为被动模式(监听模式),用于接收客户端的连接请求
		LOG_ERROR("listen:%s\n", strerror(errno));
		return -1;
	}
	return 0;
}

void broadcast(int fd, const char *msg){
	for(int i = 0; i < cnt; i++){
		if(fd == clts[i].fd){
			continue;}
		send(clts[i].fd, msg, strlen(msg)+1,0);
	}
}

void delClt(int fd){
	pthread_mutex_lock(&lock);
	for(int i = 0; i < cnt; i++){
		if(clts[i].fd == fd){
			clts[i] = clts[cnt-1];
			--cnt;
			break;
		}
	}
	pthread_mutex_unlock(&lock);
}

void handlePrivateMsg(char *buf, const char *name){
	int fd = 0;
	sscanf(buf, "%d", &fd);
	char msg[MSG_LEN] = {};
	while(*buf != ' ' && *buf != '\0') ++buf;
	if(*buf == '\0'){
		strcpy(msg,name);
		strcat(msg, "拍了拍我");
	}
	else{
		strcpy(msg, name);
		strcat(msg, "  ");
		strcat(msg, "四米消息:");
		strcat(msg, buf);
	}
	send(fd, msg, strlen(msg)+1, 0);
}

void handldList(int fd){
	char msg[MSG_LEN] = {};
	sprintf(msg, "id:name\r\n");
	int i = 0;
	for(i = 0; i < cnt; ++i){
		char buf[128] = {};
		if(clts[i].fd == fd){
			sprintf(buf, "%d:%s[自己]\r\n", clts[i].fd, clts[i].name);
		}
		else{
			sprintf(buf, "%d:%s\r\n", clts[i].fd, clts[i].name);
		}
		strcat(msg, buf);
	}

	if(i == 0){
		strcpy(msg, "it's empty!\r\n");
	}
	send(fd, msg, strlen(msg)+1, 0);
}

void *handleClient(void *arg)
{
	struct Client clt = *(struct Client *)arg;
	ssize_t rb = recv(clt.fd, clt.name, NAME_LEN, 0);
	if(rb <0){
		LOG_ERROR("recv:%s\n",strerror(errno));
		return NULL;
	}
	char buf[NAME_LEN] = {};
	strcpy(buf, clt.name);
	strcat(buf, "  ");
	strcat(buf, "进入聊天室,真是太帅辣!");
	broadcast(clt.fd, buf);
	pthread_mutex_lock(&lock);
	clts[cnt++] = clt;
	pthread_mutex_unlock(&lock);
	int len = strlen(clt.name);
	for(;;){
		strcpy(buf,clt.name);
		strcat(buf,":");
		rb = recv(clt.fd, buf+len+1, MSG_LEN-len-1, 0);
		if(rb < 0){
			LOG_ERROR("recv:%s\n",strerror(errno));
			delClt(clt.fd);
			break;
		}
		if(rb == 0){
			strcpy(buf, clt.name);
			strcat(buf, "   ");
			strcat(buf, "退出了聊天室");
			delClt(clt.fd);
			broadcast(clt.fd, buf);
			break;
		}

		if(strncmp(buf+len+1, "@", 1) == 0){
			handlePrivateMsg(buf+len+2, clt.name);
		}
		else if(strncmp(buf+len+1, "!list", 5) == 0){
			handldList(clt.fd);
		}
		else{
			broadcast(clt.fd, buf);
		}
	}
	return NULL;


}


void server_run(const char *ip, unsigned short port){
	//注册一个信号函数 让服务器正常停止
	if(signal(SIGINT, handleExit) == SIG_ERR)
	{
		LOG_ERROR("signal:%s\n",strerror(errno));
		return;
	}
	if(init_server(ip, port) == -1){
		return;
	}

	struct Client clt = {};
	socklen_t addrlen = sizeof(clt.addr);
	for(;;){
		clt.fd = accept(sockfd, (struct sockaddr*)&clt.addr, &addrlen);//第四步,从监听套接字的未完成连接请求队列中取出第一个连接请求,创建一个新的套接字和该连接请求建立连接并返回
		if(clt.fd == -1)
		{
			LOG_ERROR("accept:%s\n",strerror(errno));
			exit(-1);
		}
		errno = pthread_create(&clt.id, NULL, handleClient, &clt);//建立线程处理信号
		if(errno != 0){
			LOG_ERROR("pthread_create:%s\n", strerror(errno));
		}
		else{
			LOG_ERROR("%s[%hu] client connected!\n",inet_ntoa(clt.addr.sin_addr),ntohs(clt.addr.sin_port));
		}
	}

}

int main(int argc,const char* argv[])
{
	if(argc < 3){
		printf("用法 :%s <ip> <port>\n", argv[0]);
		return -1;
	}
	server_run(argv[1], atoi(argv[2]));
	return 0;
}

聊天室客户端

#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <pthread.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>

int sockfd;

int connect_server(const char* ip, unsigned short port){
	sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if(sockfd == -1){
		perror("socket");
		return -1;
	}
	struct sockaddr_in addr;
	memset(&addr, 0, sizeof(addr));
	addr.sin_family = AF_INET;
	addr.sin_port = htons(port);
	addr.sin_addr.s_addr = inet_addr(ip);
	int cnt = 0;
	while(connect(sockfd, (struct sockaddr*)&addr, sizeof(addr)) == -1){
		perror("connect");
		if(errno == ECONNREFUSED || errno == EAGAIN){
			++cnt;
			if(cnt == 3)
				return -1;
		}
	}
	return 0;
}

void *recvData(void *arg){
	char buf[1024] = {};
	for(;;){	
		ssize_t rb = recv(sockfd, buf, sizeof(buf), 0);
		if(rb <= 0){
			break;
		}
		printf("\r%s\n>",buf);
		fflush(stdout);
	}
	return NULL;

}


void *sendData(void *arg){
	char buf[1024] = {};
	scanf("%*[^\n]");
	scanf("%*c");
	for(;;){
		printf(">");
		fgets(buf, sizeof(buf), stdin);
		int len = strlen(buf);
		if(buf[len-1] == '\n'){
			len--;
			buf[len] = '\0';
		}
		if(len > 0){
			ssize_t wb = send(sockfd, buf, len+1, 0);
			if(wb <= 0){
				perror("send");
				break;
			}
		}
	}
	return NULL;
}

void handleExit(){
	close(sockfd);
	exit(0);
}

void client_run(const char *ip, unsigned short port){
	if(signal(SIGINT, handleExit) == SIG_ERR){
		perror("signal");
		return;
	}
	if(connect_server(ip, port) == -1){
		return;
	}
	char name[48] = {};
	printf("input your name\n");
	scanf("%s", name);
	ssize_t wb = send(sockfd, name, strlen(name)+1, 0);
	
	pthread_t id;
	int err = pthread_create(&id, NULL, recvData, NULL);
	
	sendData(NULL);
}

int main(int argc,const char* argv[])
{
	if(argc < 3){
		printf("用法:%s <ip> <port>\n", argv[0]);
		return -1;
	}
	
	client_run(argv[1], atoi(argv[2]));
	return 0;
}
UDP模型

服务器

#include <time.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>

int main(int argc,const char* argv[])
{
	if(argc < 3){
		printf("用法; %s <ip> <port>\n", argv[0]);
		return -1;
	}
	
	printf("1.udp服务器:创建socket套接字...\n");
	int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
	if(sockfd == -1){
		perror("socket");
		return -1;
	}

	printf("2.upd服务器:绑定到明确的ip和port通信地址上...\n");
	struct sockaddr_in addr;
	addr.sin_family = AF_INET;
	addr.sin_port = htons(atoi(argv[2]));
	addr.sin_addr.s_addr = inet_addr(argv[1]);

	int ret = bind(sockfd, (struct sockaddr*)&addr, sizeof(addr));
	if(ret == -1){
		perror("bind");
		return -1;
	}

	printf("3.udp服务器:循环接受用户信息...\n");
	for(;;){
		struct sockaddr_in caddr;
		socklen_t addrlen = sizeof(caddr);
		char buf[1024] = {};
		ret = recvfrom(sockfd, buf, sizeof(buf), 0, (struct sockaddr*)&caddr, &addrlen);
		printf("recv:%s(ip:%s port:%hu)\n",buf, inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port));
		time_t t = time(NULL);
		char *pt = ctime(&t);
		ret = sendto(sockfd, pt, strlen(pt)+1, 0, (struct sockaddr*)&caddr, addrlen);
		if(ret < 0){
			perror("sendto");
			return -1;
		}

	}
	close(sockfd);
	return 0;
}

客户端

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
int main(int argc,const char* argv[])
{
	if(argc < 3){
		printf("%s <ip> <port>", argv[0]);
		return -1;
	}

	printf("1.udp客户端:创建socket套接字...\n");
	int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
	if(sockfd == -1){
		perror("socket");
		return -1;
	}

	printf("2.udp客户端:准备服务器的通信地址...\n");
	struct sockaddr_in saddr;
	memset(&saddr, 0, sizeof(saddr));
	saddr.sin_family = AF_INET;
	saddr.sin_port = htons(atoi(argv[2]));
	saddr.sin_addr.s_addr = inet_addr(argv[1]);
	socklen_t addrlen = sizeof(saddr);

	printf("3.udp客户端:循环发送数据给服务器...\n");
	for(;;){
		char buf[1024] = {};
		fgets(buf, sizeof(buf), stdin);
		if(strncmp(buf, "!quit", 5) == 0){
			break;
		}

		int ret = sendto(sockfd, buf, strlen(buf)+1, 0, (struct sockaddr*)&saddr, addrlen);
		if(ret <= 0){
			perror("sendto");
			break;
		}

		ret = recvfrom(sockfd, buf, sizeof(buf), 0, NULL, NULL);
		printf("recv%s form server\n", buf);
	}
	close(sockfd);
	return 0;
}

区别:

TCP
  • Transmission Control Protocol 传输控制协议

  • 面向连接 (客户端需要调用connect进行连接,三次握手)

  • 可靠 数据传输保证数据的完整性和有序性 数据检验、超时自动重传、丢失重传、滑动窗口机制(保证数据收发一致)应答机制

    A B C D E —> ABCDE

  • 传输效率比较慢

  • 安全性要高

UDP
  • User Datagram Protocol 用户数据报文协议

  • 不连接 (客户端和服务器不会建立连接)

  • 不可靠 数据传输可能导致数据丢失 接收到的数据顺序和发送数据的顺序可能不一致

    A B C —> C A

  • 传输效率比较高

  • 安全性比较低

TCP UDP协议报头

  • TCP头部
  • 请添加图片描述
    • 16位源端口 16为目的地址端口
    • 点到点一台主机上的进程发送到另一台主机上
    • 32位的序列号,TCP数据报的编号, 没法送一个编号自动加一, 32为的确认序列号,每收到一个+1

请添加图片描述

- 4位首部长度     TCP首部最少20字节,最多60字节             以4字节为单位(TCP首部长度一定是4的整数倍)         TCP首部长度 =  首部长度数值X4  字节
- URG、ACK、PSH、RST、SYN、FIN是六个控制位
    * URG:紧急标志位(The urgent pointer),说明紧急指针有效。
    * ACK:确认标志位(Acknowledgement Number),大多数情况下该标志位是置位的,说明确认序列号有效。该标志在TCP连接的大部分时候都有效。
    * PSH:推(PUSH)标志位,该标志置位时,接收端在收到数据后应立即请求将数据递交给应用程序,而不是将它缓冲起来直到缓冲区接收满为止。在处理telnet或rlogin等交互模式的连接时,该标志总是置位的。
    * RST:复位标志,用于重置一个已经混乱(可能由于主机崩溃或其他的原因)的连接。该位也可以被用来拒绝一个无效的数据段,或者拒绝一个连接请求。
    * SYN:同步标志,说明序列号有效。该标志仅在三次握手建立TCP连接时有效。它提示TCP连接的服务端检查序列号,该序列编号为TCP连接初始端(一般是客户端)的初始序列编号。
    * FIN:结束标志,带有该标志置位的数据包用来结束一个TCP会话,但对应端口仍处于开放状态,准备接收后续数据。在TCP四次断开时会使用这个标志位。
- 16位窗口大小      滑动窗口机制    为了限制传输速度
- 16位检验和  
- 16位紧急指针   URGUDP报文

- 16位原端口 16位目标地址端口
  • UDP报头

    • 16位源端口 16位目的地端口 点到点
    • 16位UDP长度
    • 16位UDP检验和

请添加图片描述

TCPUDP
Transmission Control Protocol 传输控制协议User Datagram Protocol 用户数据报文协议
面向连接(三次握手四次分手)无连接
可靠、安全、保证数据有序不可靠、不安全、数据可能丢失、顺序不确定
延时重传、丢失重传、应答、检验、滑动窗口没有重传、没有检验、没有应答
复杂、传输效率稍低简单、高效、传输速度快
适合场合:安全性高、数据量少适合场景:视频传输、数据量大的情况、对数据安全性要求不高
SOCK_STREAMSOCK_DGRAM
socket/bind/listen/accept/recv/send/connect/closesocket/bind/recvfrom/sendto/close
  • UDP可以实现可靠的数据传输吗?
    • 可以
    • 怎么实现:在使用udp时,在应用层实现检验、应答、重传等机制

套接字选项

#include <sys/types.h>          
#include <sys/socket.h>
//获取套接字选项
int getsockopt(int sockfd, int level, int optname,void *optval, socklen_t *optlen);
//设置套接字选项
int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen);

//optlen参数用来说明 optval所指向内存中数据的字节大小  "sizeof(*optval)"

  • 可以设置/获取的选项
    • 通用选项,工作在套接字类型上
    • 在套接字层次管理的选项,但是依赖于下层协议的支持
    • 特定用于某协议的选项,某个协议独有
  • 参数level标识了应用的协议
    • 如果是通用的套接字层次选项 SOL_SOCKET
    • 否则level设置成控制这个选项的协议编号
      • TCP选项 IPPROTO_TCP
      • IP选项 IPPROTO_IP
optnameoptval类型描述
SO_ACCEPTCONNint返回信息指示该套接字是否能被监听(仅getsockopt) 是否能调用listen
SO_BROADCASTint如果*optval非0,广播数据报
SO_DEBUGint如果*optval非0,启用网络驱动调试功能
SO_DONTROUTEint如果*optval非0,绕过通常路由
SO_ERRORint返回挂起的套接字错误并清除(仅getsockopt)
SO_KEEPALIVEint如果*optval非0,启用周期性keep-alive报文
SO_LINGERstruct linger当还未发报文而套接字已关闭时,延迟时间
SO_OOBINLINEint如果*optval非0,将带外数据放在普通数据中
SO_RCVBUFint接收缓冲区的字节长度
SO_RCVLOWATint接收调用中返回的最小数据字节数
SO_RCVTIMEOstruct timeval套接字接收调用的超时值
SO_REUSEADDRint如果*optval非0,重用bind中的地址
SO_SNDBUFint发送缓冲区字节长度
SO_SNDLOWATint发送调用中传送的最小数据字节数
SO_SNDTIMEOstruct timeval套接字发送调用的超时值
SO_TYPEint标识套接字类型(仅getsockopt)
  • 做一个测试,首先启动server,然后启动client,用Ctrl-C终止server,马上再运行server,运行结果:
# ./server
bind error: Address already in use 
  • erver终止时,socket描述符会自动关闭并发FIN段给client,client收到FIN后处于CLOSE_WAIT状态,但是client并没有终止,也没有关闭socket描述符,因此不会发FIN给server,因此server的TCP连接处于FIN_WAIT2状态

  • client终止时自动关闭socket描述符,server的TCP连接收到client发的FIN段后处于TIME_WAIT状态。TCP协议规定,主动关闭连接的一方要处于TIME_WAIT状态,等待两个MSL(maximum segment lifetime)的时间后才能回到CLOSED状态,因为我们先Ctrl-C终止了server,所以server是主动关闭连接的一方,在TIME_WAIT期间仍然不能再次监听同样的server端口。

    MSL在RFC 1122中规定为两分钟,但是各操作系统的实现不同,在Linux上一般经过半分钟后就可以再次启动server了

  • 端口复用

    int opt = 1;
    setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
    
  • 心跳检测机制

    • 方式一:SO_KEEPALIVE 用来检测非正常断开
    • 方式二:写一个守护进程,定时发送Heart-Beat包,用于检测对方是否在线

带外数据

  • 区别于普通数据,可以优先处理紧急数据

  • 当有紧急数据时,内部会为该进程递送一个SIGURG信号,如果需要处理带外数据, 需要实现注册SIGURG信号的处理函数, 直接或者简介去接受带外数据

  •   signal(SIGURG,handleurg);
    
  • 建立socket套接字的所有权,以确保信号可以被地送到合适的进程-

  •   fcntl(sockfd, F_SETOWN, getpid());
    
  • recv/send在发送和接收带外数据时, 可以指定flag为MSG_OOB

  • TCP头v不URG的标识,以及一个16位 的紧急指针

    • 紧急数据只有一个字节, 只会把tcp首部的紧急指针的前一个字节当作紧急数据
    • 如果多次接受到多次紧急数据,会把前面的紧急数据丢弃
  • 如果接收端请求读取带外数据(recv指定MSG_OOB),但是没有带外数据,则recv将出错并设置errno位EINVAL

  • 在一个接受进程中, 被告知有带外数据的前提下,但是读取带外数据时,带外数据还没有到达,如果使用非阻塞的读取则直接返回-1并设置errno位EOWULDBLOOK

  • 如果接收进程已经设置了套接字选项SO_OOBINLINE,则将带外数据作为普通数据读取,此时如果试图用MSG_OOB标识标志读取带外数据,则返回-1,且设置errno位EINVAL

  • 带外数据不会受到流量控制,会确保能够正确的发送,在接受时带外数据拥有独立缓冲区,即使接收缓冲区已满,带外缓冲区仍然可以也能正常读取

带外标记
#include <sys/socket.h>
int sockatmark(int sockfd);
//返回1标识带外标记
//返回0 标识不是
//返回-1为出错
  • 每当收到一个带外数据时,就有一个与之关联的带外标记

  • 在从套接字读入期间,接收进程可以通过sockatmark函数确认是否处于带外标记

  • 可以通过SO_OOBINLINE这样的方式读取带外数据,

高级IO

阻塞IO/非阻塞IO
同步IO/异步IO
散布读/聚集写
//相当于sendmsg和recvmsg简化版本
ssize_t readv(int fd, const struct iovec *iov, int iovcnt);
ssize_t writev(int fd, const struct iovec *iov, int iovcnt);

ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
size_t recvmsg(int sockfd, struct msghdr *msg, int flags);
函数任何描述符仅套接字描述符单个缓冲区分散/集中读写是否可选标志可选对端地址可选控制 信息
read/writeOKOK
readv/writevOKOK
recv/sendOKOKOK
recvfrom/sendtoOKOKOKOK
recvmsg/sendmsgOKOKOKOKOK
多路复用IO
  • 在使用线程模型开发服务器时需考虑以下问题:

    • 1.调整进程内最大文件描述符上限 头文件中定义的一个宏
    • 2.线程如有共享数据,考虑线程同步
    • 3.服务于客户端线程退出时,退出处理。(退出值,分离态)
    • 4.系统负载,随着链接客户端增加,导致其它线程不能及时得到CPU
  • 使用多进程和多线程实现服务器会有瓶颈

    • 消耗资源型
    • 高并发得不到及时响应
  • 多路复用IO

    • 开多线程的目的是为了一个线程监视一个客户端(去读取和响应客户端的请求)
    • 多路复用IO可以让内核监视所有的客户端(文件描述符),并且通过一定的方式告诉用户,有哪些客户端发来了请求和数据
select/pselect
#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 *exceptfds, struct timeval *timeout);
/*
参数:
	nfds 	-  最大文件描述符fd + 1           得到最大文件描述(循环找)
	fd_set	-  文件描述符集合                 文件描述符的集合
		readfds	  -  要监听是否可读的文件描述符集合
		writefds  -  要监听是否可写的文件描述符集合
		exceptfds -  要监听是否异常的文件描述符集合
	struct timeval
		timeout	- 定时阻塞监控时间
			1.NULL	一直阻塞,直到监听的文件描述符集合中有对应的事件产生
			2.设置timeval为{0,0}阻塞时间为0,调用select时会检查监听的文件描述符集合之后立即返回
			3.设置timeval为固定时间,等待固定时间返回
返回值:
	返回监听的文件描述符集合中有事件产生的文件描述符的个数
	注意:  readfds/writefds/exceptfds 既作为输入参数也作为输出参数
		readfds 作为参数输入时,是用户关心的文件描述符集合(把要监听可读事件的文件描述符全部添加到这个集合中),在调用select函数时,内核会把没有可读事件的文件描述符从该集合中删除
		readfds/writefds/exceptfds作为输出参数,表示的是这些个文件描述符集合中有事件产生的文件描述符
*/
struct timeval {
       long    tv_sec;         /* seconds */
       long    tv_usec;        /* microseconds */
};
struct timespec {
       long    tv_sec;         /* seconds */
       long    tv_nsec;        /* nanoseconds */
};

//操作文件描述符集合的
//把fd文件描述符从set集合中删除
void FD_CLR(int fd, fd_set *set);
//判断fd文件描述是否在文件描述符集合set中  如果在返回非0 
int  FD_ISSET(int fd, fd_set *set);
//把文件描述符fd添加到文件描述符集合set中
void FD_SET(int fd, fd_set *set);
//清空文件描述符集合set   初始化
void FD_ZERO(fd_set *set);

#include <sys/select.h>
int pselect(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, const struct timespec *timeout,const sigset_t *sigmask);//在调用pselect函数时,可以屏蔽sigmask信号集中的信号

  • select的编程模型

    • 轮询的机制 一直在调用select函数
      • 第一步:把关心的文件描述符添加到对应的文件描述符集合中 (循环) 找出最大的文件描述符
      • 第二步: 调用select函数
        • 需要提前保存文件描述符集合
        • select函数在内核运行中,去遍历文件描述符符合,测试每一个文件描述符是否可读、可写、巩异常 (循环0-maxfd) 把没有可读、可写、异常文件描述符从对应的文件描述符集合中删除
      • 第三步:遍历测试FD_ISSET关心的文件描述符是否还在可读、可写、异常的文件描述符集合中 (循环)
  • select的缺点

    • 随着文件描述符(客户端)的增加,效率急剧下降
    • 解决1024以下客户端时使用select是很合适的,但如果链接客户端过多,select采用的是轮询模型,会大大降低服务器响应效率
    • select能监听的文件描述符个数受限于FD_SETSIZE,一般为1024,单纯改变进程打开的文件描述符个数并不能改变select监听文件个数
poll/ppoll
#include <poll.h>

int poll(struct pollfd *fds, nfds_t nfds, int timeout);
/*
参数: 
	fds    - 数组首地址	
	nfds   - 数组长度
	timeout - 超时等待
		-1  :阻塞等待,直到监听的文件描述符的事件有满足条件的
		0   :立即返回,会对监听的文件描述符的事件进行一次测试
		>0  :阻塞等待timeout毫秒数
返回值:
	返回满足监视事件的文件描述符的个数,超时返回0(代表没有满足的监听事件)  -1失败
*/

struct pollfd {
    int   fd;         //文件描述符
    short events;     //监听fd文件描述符的事件(可以有多个事件)  内核并不会修改这个值
    short revents;    //作为返回用的 所监听fd文件描述符发生发生的事件有哪些   内核只会修改这个值
};
/*
事件取值:(如果对于一个文件描述符想监听多个事件,则按位或)
	POLLIN			普通或带外优先数据可读,即POLLRDNORM | POLLRDBAND
	POLLRDNORM		数据可读
	POLLRDBAND		优先级带外数据可读
	POLLPRI 		高优先级可读数据
	POLLOUT		    普通或带外数据可写
	POLLWRNORM		数据可写
	POLLWRBAND		优先级带数据可写
	POLLERR 		发生错误
	POLLHUP 		发生挂起
	POLLNVAL 		描述字不是一个打开的文件
*/

#define _GNU_SOURCE        
#include <signal.h>
#include <poll.h>

int ppoll(struct pollfd *fds, nfds_t nfds,const struct timespec *tmo_p, const sigset_t *sigmask);

  • 编程模型

    • 把初始的文件描述符和监听的事件放到struct pollfd数组中
    • 轮询的机制
      • 调用poll函数 (需要遍历struct pollfd数组)
      • 处理文件描述符的事件 循环遍历struct pollfd数组中所有成员,判断其返回事件中revents是否有关心的事件发生,如果则去处理 (有客户端连接 struct pollfd数组中添加成员,如果有退出删除其值)
    • 如果不再监控某个文件描述符时,可以把pollfd中,fd设置为-1,poll不再监控此pollfd,下次返回时,把revents设置为0
  • poll相对于select而言,效率提高了

    • 复杂的文件描述符集合的操作
    • 不需要每次都像select一样把文件文件符重新组装
    • poll返回事件和监听事件分开
epoll
  • linux下独有的 efficient高效的poll机制
  • 非常适用于文件描述符数量巨大且只有少量处于活跃状态的场景
  • 高并发首选epoll
#include <sys/epoll.h>

//创建epoll句柄     epoll_create本身创建一个文件描述符
int epoll_create(int size);  
/*
	在之前 size 表示 能够监听文件描述符的最大个数
	现代的linux内核忽略size参数,只要给大于0的数值即可
	
	创建epoll句柄,后续的epoll_ctl和epoll_wait都依赖于这个返回值
	本身就是一个文件描述符
*/

//操作事件
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
/*
op参数:
	EPOLL_CTL_ADD   注册事件              只需要注册一次,只要不删除修改,永久有效
		注册的事件用红黑树来组织管理的
    EPOLL_CTL_MOD   修改注册的事件
    EPOLL_CTL_DEL   删除注册的事件
*/
typedef union epoll_data {
    void        *ptr;
    int          fd;
    uint32_t     u32;
    uint64_t     u64;
} epoll_data_t;

struct epoll_event {
    uint32_t     events;      /* Epoll events */
    epoll_data_t data;        /* User data variable */
};

//等待接收注册事件中满足条件的事件
int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);
/*
	events: 数组首地址   只作为输出参数
	maxevents: events最大长度
返回值:
	返回往events数组中写入记录的数量 
	在处理结果时,只需要遍历0-epoll_wait返回值的区间
*/
int epoll_pwait(int epfd, struct epoll_event *events,int maxevents, int timeout,const sigset_t *sigmask);
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值