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

2023-11-14

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检验和

请添加图片描述

TCP UDP
Transmission Control Protocol 传输控制协议 User Datagram Protocol 用户数据报文协议
面向连接(三次握手四次分手) 无连接
可靠、安全、保证数据有序 不可靠、不安全、数据可能丢失、顺序不确定
延时重传、丢失重传、应答、检验、滑动窗口 没有重传、没有检验、没有应答
复杂、传输效率稍低 简单、高效、传输速度快
适合场合:安全性高、数据量少 适合场景:视频传输、数据量大的情况、对数据安全性要求不高
SOCK_STREAM SOCK_DGRAM
socket/bind/listen/accept/recv/send/connect/close socket/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
optname optval类型 描述
SO_ACCEPTCONN int 返回信息指示该套接字是否能被监听(仅getsockopt) 是否能调用listen
SO_BROADCAST int 如果*optval非0,广播数据报
SO_DEBUG int 如果*optval非0,启用网络驱动调试功能
SO_DONTROUTE int 如果*optval非0,绕过通常路由
SO_ERROR int 返回挂起的套接字错误并清除(仅getsockopt)
SO_KEEPALIVE int 如果*optval非0,启用周期性keep-alive报文
SO_LINGER struct linger 当还未发报文而套接字已关闭时,延迟时间
SO_OOBINLINE int 如果*optval非0,将带外数据放在普通数据中
SO_RCVBUF int 接收缓冲区的字节长度
SO_RCVLOWAT int 接收调用中返回的最小数据字节数
SO_RCVTIMEO struct timeval 套接字接收调用的超时值
SO_REUSEADDR int 如果*optval非0,重用bind中的地址
SO_SNDBUF int 发送缓冲区字节长度
SO_SNDLOWAT int 发送调用中传送的最小数据字节数
SO_SNDTIMEO struct timeval 套接字发送调用的超时值
SO_TYPE int 标识套接字类型(仅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/write OK OK
readv/writev OK OK
recv/send OK OK OK
recvfrom/sendto OK OK OK OK
recvmsg/sendmsg OK OK OK OK OK

多路复用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);
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

TCP UDP协议的应用以及高级IO的介绍 的相关文章

  • 播放 UDP 数据包中收到的原始 PCM 音频

    远程设备正在 UDP 数据包中发送实时原始 PCM 音频 不包含标头 我需要在 java 中实现一个程序来接收这些数据包并在 PC 上实时播放它们 据我所知 原始 PCM 的属性是 16 位 单声道 采样率 24KHz 因此我尝试向该原始
  • UDP 数据报中发送的消息未经过净化?

    我的代码如下 一切都按照我想要的方式进行 但是当我的消息收到时 它们的末尾有很多框 有点像这样 消息 你好 如何才能让接收和打印的内容仅为 Message hello 我非常感谢任何帮助 import java io import java
  • 使用 Winsock 通过单个 UDP 服务器处理 10 个客户端

    我已经使用 UDP 套接字建立了一个服务器 客户端应用程序 但我的服务器无法一次处理多个客户端 现在我想修改我的应用程序 让 10 个客户端分别运行在不同的计算机上 而我的服务器运行在单独的计算机上 我希望我的服务器能够与 10 个不同机器
  • GCDAsyncUDPSocket源地址返回null

    谷歌代码问题镜像 https groups google com forum topic cocoaasyncsocket grhjZSMLr3U https groups google com forum topic cocoaasync
  • Android udp 多播与以太网

    大家好 我正在开发一个使用 udp 多播的项目 我有一台服务器通过以太网电缆发送多播 udp 数据包 我花了几周的时间阅读有关 android 上多播的所有帖子 但我仍然无法在我的 Asus Transformer Tablet 4 1 上
  • 如何设置Winsock UDP套接字?

    我想创建一个仅向客户端发送数据的 Winsock UDP 套接字 我希望内核为我选择一个可用的端口 另一方面 我想指出要使用哪个本地 IP 因为我正在运行一些网卡 我尝试过梳理迷宫般的套接字选项 以及将套接字地址中的端口绑定设置为 0 但均
  • Java:使用多个 DatagramSocket 接收 UDP 数据报包

    我正在尝试实现一种将 UDP 数据包发送到多个接收者的方法 我认为这应该是可行的设置setReuseAddress true 在接收 DatagramSocket 实例上 我的问题是 在某些情况下 我需要限制与本地计算机的通信 因此限制本地
  • udp数据包被tcpdump捕获,但没有被套接字接收[关闭]

    Closed 这个问题是无关 help closed questions 目前不接受答案 我编写了一个 rawudp 程序 通过原始套接字发送 udp 数据包 按照网页http www tenouk com Module43a html h
  • C++ 反序列化通过 UDP 从 C# 应用程序发送的对象

    我有一个申请c 它连接到另一个应用程序 编写为c 通过UDP 我更喜欢高性能的解决方案 因为我希望测量事件客户端 某些处理服务器端和客户端处理完成的响应之间的时间 本质上是 往返延迟 我有一个 C 对象 例如 public class Pa
  • 更改Windows下的默认套接字缓冲区大小[关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 我无法更改的应用程序正在丢弃一些传入的 UDP 数据包 我怀疑接收缓冲区溢出 是否有注册表设置可以使默认缓冲区大于 8KB From th
  • UDP sendto 上的 ECONNREFUSED 错误

    我在使用正在写入的应用程序时遇到一些无法解释的行为 使用 sendto 向多个端口发送 UDP 数据 所有端口均使用套接字 PF INET SOCK DGRAM 0 为了一组客户端读取进程的利益 这些 sendto 偶尔会不可预测地触发经济
  • iOS 14 在进行本地网络广播时给出“操作系统错误:错误的文件描述符,errno = 9”

    做一点Jeopardy 风格问答 https stackoverflow blog 2011 07 01 its ok to ask and answer your own questions here 我正在 Flutter 中开发一个应
  • TCP 兼容性:为什么 TCP 不兼容数据包广播和组播操作?

    http en wikipedia org wiki User Datagram Protocol http en wikipedia org wiki User Datagram Protocol 与 TCP 不同 UDP 与数据包广播
  • 互联网上的 UDP 多播?

    我不确定如何最好地解决我的问题 我有一个在远程计算机上运行的服务 用于接收和处理 UDP 数据包 我希望该服务能够将这些数据包重新发送给任何需要它们的人 可能是任何人 通常是一台机器 但也可能更多 我认为 UDP 多播将是理想的 该服务可以
  • Windows 操作系统中无法访问的 IP 套接字关闭时间

    这些代码通过用户数据报协议提供发送数据 下面有两个代码 当我使用第一个代码来处理无法访问的 IP 地址时 我得到了三秒的延迟 请查看新结果标题 只需打开新的 C 控制台应用程序并将这些代码粘贴到其中 第一个代码 using System u
  • 自 2012 年以来,WinSock 注册 IO 性能是否有所下降?

    我最近使用 MS 为该 API 提供的稍微可接受的文档编写了基于 WinSock Registered IO RIO 的 UDP 接收 最终的性能非常令人失望 单套接字性能有些稳定 约为每秒 180k 数据包 使用多个 RSS 队列 即多个
  • 数据包丢失和数据包重复

    我试图找出数据包丢失和数据包重复问题之间的区别 有谁知道 数据包重复 是什么意思 和TCP检测到丢失时重传数据包一样吗 No In TCP 数据包 的传递是可靠的 我认为在这种情况下术语数据应该更好 因为它是面向流的协议 数据包丢失和重复是
  • F1 2019 UDP解码

    我目前正在为 F1 方向盘开发自己的显示器 F1 2019 由codemasters提供 通过UDP发送数据 该数据存储在字节数组中 我在解码返回的数组时遇到一些问题 问题是我得到了很多信息 但我不知道如何处理它们 我将向您介绍我所尝试过的
  • 我应该害怕使用 UDP 进行客户端/服务器广播通话吗?

    我在过去的两天里阅读了每一篇StackOverflow问题和答案 以及googling当然 关于印地TCP and UDP协议 以便决定在我的用户应用程序和 Windows 服务之间的通信方法中应该使用哪一种 从我目前所看到的来看 UDP是
  • 为什么 UDP 服务器中只有一个套接字?

    我正在准备考试 发现了这个问题 典型的 UDP 服务器可以使用单个套接字来实现 解释一下为什么 对于 TCP 驱动的服务器 我发现创建了两个套接字 一个用于所有客户端访问服务器 另一个用于每个客户端的特定 套接字 用于服务器和客户端之间的进

随机推荐

  • telnet端口不通怎么解决(单边不通的方法建议)

    telnet端口不通是大家在检测端口的时候可能会遇到的问题之一 遇到这种状况一般要如何解决呢 这里为各位带来分享 看一下telnet端口不通的解决方式 看一下如何处理吧 telnet端口不通怎么解决 1 开放供应商服务器端口 总是出现由于连
  • The engine “node“ is incompatible with this module. Expected version

    前言 vue项目用了yarn yarn install后报错如下 开始 执行 yarn config set ignore engines true 然后yarn install后成功 结束 在此记录问题 如有需要修改的地方 还请不吝赐教
  • Kubernetes—K8S运维管理

    Kubernetes K8S运维管理 更新中 一 Node管理 1 1 Node的隔离与恢复 1 2 Node 的扩容 二 更新资源对象的Label 三 Namespace 集群环境共享与隔离 3 1 创建Namespace 3 2 定义C
  • [病虫害识别|博士论文]面向农作物叶片病害鲁棒性识别的深度卷积神经网络研究

    文章目录 创新点 文章中的方法 国内外现状 手工设计特征 基于深度特征学习的农作物病害识别研究 基于高阶残差的卷积神经网络的农作物病害识别 结构图 对比方法 基于高阶残差和参数共享反馈的卷积神经网络农作物病害识别方法 结构图 对比方法 基于
  • CSS选择除第一个和最后两个以外的所有子元素 + 结构伪类选择器深度解析

    最近在练习网易严选首页的布局时 发现它的顶部导航栏需求很特殊 第一项和最后两项是没有下拉选择框的 那么问题来了 在写css的时候该怎么使用选择器去达到这样的需求呢 首先先贴一下我最后的解决方案 nav first gt li nth chi
  • 数据库技术之mysql50题

    目录 数据表介绍 数据SQL 练习题 数据表介绍 1 学 表 Student SId Sname Sage Ssex SId 学 编号 Sname 学 姓名 Sage 出 年 Ssex 学 性别 2 课程表 Course CId Cname
  • 18-Go语言之单元测试

    go test工具 Go语言中的测试依赖go test命令 编写测试代码和编写普通的Go代码过程是类似的 并不需要学习新的语法或工具 go test命令是一个按照一定约定和组织的测试代码的驱动程序 在包目录内 所有以 test go为后缀的
  • 就业DAY7_web服务器_http协议

    import socket def servece client new socket 为这个客户端返回数据 1 接收浏览器发送过来的请求 即http请求 GET HTTP 1 1 request new socket recv 1024
  • 【Unity3D】如何快速做出点击按钮切换场景

    1 首先建立第一个场景 在Canvas创建一个Button 快捷键为Ctrl N 再按Ctrl S保存该场景到文件 如图所示 图中的 开始 为按钮 2 创建第二个场景 作为点击按钮后切换的场景 点击左上角 File Build Settin
  • 精心挑选了三种热门的Python技术书籍送给大家!!

    本周三狗哥给大家挑选了三种热门的Python书籍 送给大家 每种书送两本 共6本 文末查看送书规则 Python大数据分析 公众号回复 送书 Python最优化算法实战 扫码回复 送书 Python数据分析 扫码回复 送书 公众号回复 送书
  • js 把带有对象的数组里的某个属性组成新的数组

    如果想将数组对象中的某个属性组成一个新的数组 可以使用Array map 方法 这个方法会遍历原始数组的每个元素 并返回一个新的数组 其中包含指定属性的值 以下是一个示例 假设有一个包含对象的数组 每个对象都有一个name属性 你想要将所有
  • html5新特性

    目录 使用语义化标签的目的 1 html5新增的语义化标签 2 html新增的多媒体标签 1 视频 video 2 音频 audio 属性 object fit 3 html5新增的input表单元素属性 1 新增的input标签type属
  • 准备加入第二个项目(第5960小时加入)

    今天 老师过来办事 看了我做的东西后 邀请我加入他的项目 让我受宠若惊 2012年10月 我加入老师的项目后 2天内落荒而逃 因为一句代码都没有写出来 再然后 老师以我没有项目经验为由 拒绝了我后来想加入项目的要求 2年后 老师邀请我去做项
  • 安装Anaconda科学计算包

    Anaconda介绍 最近在看 Python语言及其应用 这本书 作为一本介绍Python语言和应用的书非常不错 在这本书的最后 介绍了一些Python常用的第三方类库 像科学计算库 金融计算库 图形图像库等等 其中也介绍了Anaconda
  • 移动端H5页面生成图片解决方案

    现在有很多微信公众号运营活动 都有生成图片的需求 生成图片后可以发送给好友和发到朋友圈扩散 利于产品的宣传 1 生成图片可以用canvas 但是由于已经有了html2canvas这个开源库 所以为了节省时间就没有自己写了 github地址
  • 为什么文件删除了但磁盘空间没有释放?

    1 案例现象 这天 监控系统发来一条告警消息 内容说某台服务器根目录磁盘占用空间达到阈值 超过百分之八十了 登上服务器 df Th 看一下 发现磁盘空间确实不够用了 root localhost df Th 文件系统 类型 容量 已用 可用
  • java怎么从一个类传值到另一个类,关于JAVA的引用类型传值.

    方法参数传递都按值传递 对于基本类型 传递原始值 对于对象类型 传递其指向的对象的地址值 多个同类型不同的变量可以指向同一个对象 但是其中任何一个变量被重新赋值 也就是指向一个新的对象时 不影响其它变量的指向 方法定义的形参 在调用的发生的
  • Socket编程之聊天程序 - 模拟Fins/ModBus协议通信过程

    设备控制软件编程涉及到的基本通信方式主要有TCP IP与串口 用到的数据通信协议有Fins与ModBus 更高级别的通信如 net中的Remoting与WCF在进行C S架构软件开发时会采用 本篇文章结合Fins ModBus协议的指令帧结
  • 关于前端获取后端传输的参数并在js中应用该参数

    在进行dynamicTree名称获取时 如果是涉及到不同数据库需要使用不同的dynamicTree的xml文件且该名称在前端写死状态 可以采用setAttribute方法将值赋给前端 前端用 接收该值 并在js处使用document get
  • TCP UDP协议的应用以及高级IO的介绍

    TCP UDP协议的应用以及高级IO的介绍 网络通信协议 模型 TCP和UDP两个协议都是一对多的网络通信模型 TCP编程模型 UDP编程模型 实例 TCP模型 聊天室的服务器 有私密消息功能以及列出聊天者的功能 include