首页 > 其他分享 >31_网络

31_网络

时间:2024-04-30 15:47:04浏览次数:20  
标签:addr epoll int 31 网络 fd include struct

网络

网络基础

​ 怎么解决通信设备间的通信问题?解决这个问题,各设备就必须要使用同一套通信协议,才能互相理解对方“说的话”,目前在互联网中这个一直被我们使用的协议叫TCP/IP协议簇,简称TCP/IP。其中TCP是Transmission Control Protocol的简称,它是一种面向连接的、可靠的、基于字节流的传输层通信协议,IP是Internet Protocol的简称,它的任务仅仅是根据源主机和目的主机的地址来传送数据。

TCP/IP协议分为4层

image-20240408120612104

IP地址网段和分类

image-20240408120631305

4层架构在RFC 1122中描述的不同层数据的封装

image-20240408120716997

socket网络编程

​ linux中的网络编程通过socket接口实现。Socket既是一种特殊的IO,它也是一种文件描述符。一个完整的Socket 都有一个相关描述{协议,本地地址,本地端口,远程地址,远程端口};每一个Socket 有一个本地的唯一Socket 号,由操作系统分配。
​ TCP把连接作为最基本的对象,每一条TCP连接都有两个端点,这种断点我们叫作套接字(socket),它的定义为端口号拼接到IP地址即构成了套接字,例如,若IP地址为192.168.1.100 而端口号为80,那么得到的套接字为192.168.1.100:80。

套接字的三种类型:

  • 流式套接字(SOCK_STREAM)
    可以提供可靠的、面向连接的通讯流。它使用了TCP协议。TCP 保证了数据传输的正确性和顺序
    性。

  • 数据报文套接字(SOCK_DGRAM)

    定义了一种无连接的服务,数据通过相互独立的报文进行传输,是无序的,并且不保证可靠,无差错。使用数据报协议UDP协议。

  • 原始套接字
    原始套接字允许对低层协议如IP或ICMP直接访问,主要用于新的网络协议实现的测试等。

基于数据流的socket编程流程

image-20240408120927462

基于数据报文的编程流程

image-20240408121017925

socket系列函数使用说明

struct sockaddr 和 struct sockaddr_in

这两个结构体用来处理网络通信的地址。

sockaddr在头文件#include <sys/socket.h>中定义,sockaddr的缺陷是:sa_data把目标地址和端口信息混在一起了,如下:

/* POSIX.1g specifies this type name for the `sa_family' member.  */
typedef unsigned short int sa_family_t;

/* This macro is used to declare the initial common members
of the data types used for socket addresses, `struct sockaddr',
`struct sockaddr_in', `struct sockaddr_un', etc.  */

#define	__SOCKADDR_COMMON(sa_prefix) \
sa_family_t sa_prefix##family


/* Structure describing a generic socket address.  */
struct sockaddr
{
 __SOCKADDR_COMMON (sa_);	/* Common data: address family and length.  */
 char sa_data[14];		/* Address data.  */
};
struct sockaddr {  
  sa_family_t sin_family;//地址族
    char sa_data[14]; //14字节,包含套接字中的目标地址和端口信息               
}; 

*sockaddr_in*在头文件#include<netinet/in.h>或#include <arpa/inet.h>中定义,该结构体解决了sockaddr的缺陷,把port和addr 分开储存在两个变量中,如下:

这里写图片描述

/* Structure describing an Internet socket address.  */
struct sockaddr_in
{
 __SOCKADDR_COMMON (sin_);
 in_port_t sin_port;			/* Port number.  */
 struct in_addr sin_addr;		/* Internet address.  */

 /* Pad to size of `struct sockaddr'.  */
 unsigned char sin_zero[sizeof (struct sockaddr)
			   - __SOCKADDR_COMMON_SIZE
			   - sizeof (in_port_t)
			   - sizeof (struct in_addr)];
  };

举例:

img

  1. 将整个结构体清零;
  2. 设置地址类型为AF_INET;
  3. 网络地址为INADDR_ANY, 这个宏表示本地的任意IP地址,因为服务器可能有多个网卡,每个网卡也可能绑定多个IP 地址, 这样设置可以在所有的IP地址上监听,直到与某个客户端建立了连接时才确定下来到底用哪个IP 地址;
  4. 端口号为SERV_PORT, 我们定义为8080;

socket

功能: 创建socket套接字

image-20240409174749949

bind

img

listen

image-20240408123615089

accept

image-20240408123634232

connect

image-20240408123648866

htons/htonl

#include <arpa/inet.h> 

uint16_t htons(uint16_t hostshort);
uint32_t htonl(uint32_t hostlong);

这两个函数是为了解决大小端问题而生的。

功能:将一个uint16_t或者uint32_t类型数值转换为网络字节序的数值,即大端模式(big-endian)(在主机本身就使用大端字节序时,函数通常被定义为空宏)。

可以通过其英文记忆两个函数:

  • h(=host “主机”字节序的意思)
  • to:转换
  • n(=network "网络"字节序的意思)
  • l/s(l=long 32位,s=short 16位)

扩展: 大端模式(TCP / IP网络字节顺序)、小端模式是什么?

举个例子:假定你的port是 0x1234,

在x86电脑上,0x1234放到内存中实际是:addr addr+1  0x34 0x12 (我们常用的 x86 CPU (intel, AMD) 电脑是小端模式( little-endian),也就是整数的低位字节放在内存的低字节处。)

而在网络字节序里 这个port放到内存中就应该显示成  addr addr+1 0x12 0x34(而TCP / IP网络字节顺序是大端模式( big-endian),也就是整数的高位字节存放在内存的低地址处。)

htons 的用处就是把实际内存中的整数存放方式调整成“网络字节序”的方式。

inet_pton

#include <arpa/inet.h> 

int inet_pton(int af, const char *src, void *dst);

是为了解决人可读和机器执行的冲突而生的。

点分十进制表示的字符串形式(人容易读)转换成二进制 Ipv4 或Ipv6 地址(机器容易理解)

我是这样记忆这个函数的:

  • p = people read 意思是人类可读的。
  • to 转变
  • n = network read 意思是网络识别的

函数原型:

int inet_pton(int af, const char *src, void *dst);

参数:

​ af: 地址族 (Address Family)。AF_INET 或AF_INET6,AF_INET 表示待转换的Ipv4地址,AF_INET6 表示待转换的是Ipv6 地址;

​ src: 指向字符串形式的 IP 地址的指针。点分十进制表示的字符串

​ dst: 一个指向存储转换后的二进制地址的缓冲区的指针。对于 IPv4,这应该是一个指向 struct in_addr 的指针;对于 IPv6,这应该是一个指向 struct in6_addr 的指针。

返回值:

  • 成功时返回1。

  • 如果输入地址不是有效的表现形式,返回0。

  • 出错时返回-1,并设置 errno 为具体的错误代码。

示例:

#include <stdio.h>
#include <arpa/inet.h>

int main() {
    const char *ip_str = "192.168.1.1";
    struct in_addr ip_addr;  // for IPv4

    if (inet_pton(AF_INET, ip_str, &ip_addr) <= 0) {
        perror("inet_pton");
        return 1;
    }

    printf("Binary representation of IP: %u\n", ip_addr.s_addr);
    return 0;
}

inet_ntop

#include <arpa/inet.h> 

const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);

ntop()参数:基本与pton()相同,src 和 dst 相反而已。

ntop()返回值:

inet_ntop()在成功时会返回dst 指针。如果size 的值太小了,那么将会返回NULL 并将errno 设置为ENOSPC。

inet_ntoa

#include <arpa/inet.h>
char *inet_ntoa(struct in_addr addr );

功能:是将一个32位大端网络字节序整数转换为点分十进制的ipv4的IP地址。
参数:
addr: 属于sockaddr_in结构体的结构体in_addr地址
返回值:存放转化结果的首地址,char*指针,要提前分配空间。失败时返回-1。

setsockopt

功能介绍:

setsockopt是用来为网络套接字设置选项值,比如:允许重用地址、网络超时等;在Linux下和Windows下均有该函数,但是使用略有不同;很多语言也支持或者封装了该接口

函数原型:

#include <sys/types.h>          /* See NOTES */
#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);

参数介绍:

  • sockfd:网络套接字

  • level:协议层,整个网络协议中存在很多层,指定由哪一层解析;通常是SOL_SOCKET,也有IPPROTO_IP/IPPROTO_TCP

  • optname:需要操作的选项,比如SO_RCVTIMEO接受超时

  • optval:设置的选项值,类型不定;比如SO_REUSERADDR设置地址重用,那么只需要传入一个int指针即可

  • optlen:选项值的长度

选项介绍:

  • SO_REUSERADDR:允许本地地址和端口重用,参数为int类型;在Linux下主要作用是:关闭套接字监听后其地址很快能被另一个套接字使用;如果不设置的话,同一个程序绑定同一个端口,关闭后立马再次运行可能会遭遇绑定失败

  • SO_RCVTIMEO:为接受数据的系统调用设置超时,包括:recvrecvfromaccept;若超时, 返回-1,并设置errno为EAGAIN或EWOULDBLOCK;网络套接字在运行中是会出现阻塞的情况,设置该值可能提升程序健壮性。

简单示例——client端:

#include <stdio.h>
#include <string.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
int main()
{
    char szBuf[1024];
    struct sockaddr_in addr;
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if (-1 == fd) {
        perror("socket");
        return -1;
	} 
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(9999);
    inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr);
    if (connect(fd, (struct sockaddr *)&addr, sizeof(addr))) {
        perror("connect");
        close(fd);
        return -1;
	}
    while(1) {
        scanf("%s", szBuf);
        write(fd, szBuf, strlen(szBuf)+1);
        memset(szBuf, 0, sizeof(szBuf));
        read(fd, szBuf, sizeof(szBuf));
        printf("recv from server:%s\n", szBuf);
    } 
    return 0;
}

简单示例——server端:

#include <stdio.h>
#include <string.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
int main()
{
    char szBuf[1024];
    struct sockaddr_in addr;
    struct sockaddr_in client_addr;
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if (-1 == fd) {
        perror("socket");
        return -1;
    } 
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(9999);
    inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr);
    bind(fd, (struct sockaddr *)&addr, sizeof(addr));
    listen(fd, 20);
    socklen_t nSockLen;
    while (1) {
        int nConnFd = accept(fd, (struct sockaddr *)&client_addr, &nSockLen);
        while(1) {
            int nRet = read(nConnFd, szBuf, sizeof(szBuf));
            if (nRet < 0) {
                perror("read");
                close(fd);
                break;
            } 
            write(nConnFd, szBuf, nRet);
            printf("recv from client:%s\n", szBuf);
        }
    } 
    close(nConnFd);
    close(fd);
    return 0;
}

聊天室示例代码——服务端

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

#define CHAT_DEBUG 1
#if CHAT_DEBUG
#define DBG(x...) printf(x)
#else
#define DBG(x...)
#endif

void *pthread_client(void *);

/* 连接上的客户端的套接字描述符 */
struct user
{
	int confd;
	char name[20];
};

struct user user_info[50];
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;

int main()
{
	/* 获取套接字 */
	int sockfd, i, connfd;
	pthread_t tid;
	struct sockaddr_in server;
	struct sockaddr_in client;
	socklen_t len = sizeof(struct sockaddr);
	int optval;

	signal(SIGPIPE, SIG_IGN);

	sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if (sockfd < 0)
	{
		perror("socket");
		return -1;
	}

	/* 设置端口号支持重复绑定 */
	if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) < 0)
	{
		perror("setsockopt");
		return -1;
	}

	/* 绑定自己的IP和port */
	server.sin_family = AF_INET;
	server.sin_port = htons(8192);
	server.sin_addr.s_addr = htonl(INADDR_ANY);
	// inet_pton(AF_INET, "192.168.6.100", (void *)&server.sin_addr);
	if (bind(sockfd, (struct sockaddr *)&server, sizeof(server)) < 0)
	{
		perror("bind");
		close(sockfd);
		return -1;
	}

	listen(sockfd, 10);
	printf("wait for...\n");

	while (1)
	{
		if ((connfd = accept(sockfd, (struct sockaddr *)&client, &len)) < 0)
		{
			perror("accept");
			continue;
		}
		pthread_mutex_lock(&lock);
		for (i = 0; i < 50; i++)
		{
			if (user_info[i].confd == 0)
			{
				user_info[i].confd = connfd;
				break;
			}
		}
		pthread_mutex_unlock(&lock);
		if (i >= 50)
			continue;
		printf("recives from %s, new connfd=%d\n", inet_ntoa(client.sin_addr), connfd);
		pthread_create(&tid, NULL, pthread_client, (void *)&connfd);
	}
	close(sockfd);

	return 0;
}

void *pthread_client(void *arg)
{
	int i, connfd;
	char buf[256];
	char str[256];

	connfd = *((int *)arg);

	while (1)
	{
		/* 读取客户端内容 */
		if (read(connfd, buf, 256) <= 0)
		{
			close(connfd);
			pthread_mutex_lock(&lock);
			for (i = 0; i < 50; i++)
			{
				if (connfd == user_info[i].confd)
				{
					user_info[i].confd = 0;
					strcpy(buf, user_info[i].name);
					strcat(buf, "已经下线了!");
					break;
				}
			}
			for (i = 0; i < 50; i++)
			{
				if (user_info[i].confd != 0)
				{
					write(user_info[i].confd, buf, strlen(buf) + 1);
				}
			}
			pthread_mutex_unlock(&lock);
			pthread_exit(NULL);
		}
		else
		{
			if (buf[0] == 'g')
			{
				pthread_mutex_lock(&lock);
				for (i = 0; i < 50; i++)
				{
					if (user_info[i].confd == connfd)
					{
						sprintf(str, "%s说:%s", user_info[i].name, &buf[2]);
						break;
					}
				}
				for (i = 0; i < 50; i++)
				{
					if (user_info[i].confd != 0)
						write(user_info[i].confd, str, strlen(str) + 1);
				}
				pthread_mutex_unlock(&lock);
			}
			else if (buf[0] == 'u')
			{
				pthread_mutex_lock(&lock);
				for (i = 0; i < 50; i++)
				{
					if (user_info[i].confd == connfd)
					{
						strcpy(user_info[i].name, &buf[2]);
						break;
					}
				}
				strcat(&buf[2], "已经上线了!");
				for (i = 0; i < 50; i++)
				{
					if ((user_info[i].confd != 0) && (user_info[i].confd != connfd))
					{
						write(user_info[i].confd, &buf[2], strlen(buf) - 1);
					}
				}
				pthread_mutex_unlock(&lock);
			}
			else if (buf[0] == '@')
			{
				char *p1;
				char *p2;
				p1 = &buf[1];
				p2 = strchr(buf, ':');
				*p2 = '\0';
				p2++;
				pthread_mutex_lock(&lock);
				for (i = 0; i < 50; i++)
				{
					if (user_info[i].confd == connfd)
					{
						sprintf(str, "\033[0;34m%s悄悄对你说:%s\033[0m", user_info[i].name, p2);
						break;
					}
				}
				for (i = 0; i < 50; i++)
				{
					if (!strcmp(user_info[i].name, p1))
					{
						write(user_info[i].confd, str, strlen(str) + 1);
						break;
					}
				}
				pthread_mutex_unlock(&lock);
			}
		}
	}

	pthread_exit(NULL);
}

聊天室示例代码——客户端

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

#define CHAT_DEBUG	0
#if CHAT_DEBUG
#define DBG(x...)	printf(x)
#else
#define DBG(x...)
#endif

void* pthread_read(void *);

int main(int argc, char *argv[])
{
	/* 获取套接字 */
	int sockfd, ret;
	char buf[256];
	char str[256];
	pthread_t tid;
	struct sockaddr_in server;
	
	if (2 != argc) {
		printf("Usage:%s <ip>\n", argv[0]);
		exit(EXIT_FAILURE);
	}

	sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if(sockfd < 0) {
		perror("socket");
		return -1;
	}
	
	/* 连接远程服务器 */
	server.sin_family = AF_INET;
	server.sin_port = htons(8192);
	inet_pton(AF_INET, argv[1], (void *)&server.sin_addr);
	ret = connect(sockfd, (struct sockaddr *)&server, sizeof(server));
	if(-1 == ret) {
		perror("connect");
		return -1;
	}

	pthread_create(&tid, NULL, pthread_read, (void *)&sockfd);
	
	printf("请输入登录名称:");
	scanf("%s", buf);
	sprintf(str, "u:%s", buf);
	write(sockfd, str, strlen(str)+1);
	getchar();
	
	while(1)
	{
		/* 获取终端输入 */
		printf("请输入:\n");
		fgets(buf, 256, stdin);
		buf[strlen(buf)-1] = '\0';
		DBG("b:%s#\n", buf);
		/* 发送给服务器处理 */
		if(buf[0] != '@')
			sprintf(str, "g:%s", buf);
		else {
			strcpy(str, buf);
		}
		DBG("a:%s#\n", str);
		write(sockfd, str, strlen(str)+1);
	}
	close(sockfd);
	return 0;
}

void*	pthread_read(void *arg)
{
	int sockfd = *((int *)arg);
	char str[256];
	while(1)
	{
		/* 接收服务器的数据 */
		if(read(sockfd, str, 256) <= 0)
			exit(0);
		else
			printf("%s\n:", str);
	}
}

实现高性能业务服务器

select/epoll简介

​ 通过上一个阶段的学习,我们了解和掌握了多线程、网络编程的概念和编码,也实现了一个网络聊天室的基本群聊和私聊的功能。但是有一个问题是,如果连接到服务端的客户数量过多时,由于每连接一个客户端,都需要为这个客户端单独建立一个线程来完成通信,当客户端过多时,线程就会很多。线程过多的坏处就凸显了,如下:

  • Linux每创建一个线程,系统会增加8MByte的空间用于维护线程栈、数据结构等,造成资源浪费,想象一下5000个客户端将占用40G的内存空间是多么的奢侈;

  • 线程是被系统分时调用的,过多的线程会导致过多的任务调度,浪费CPU真实执行有效代码的时
    间;

  • 过多的线程可能会有资源共享导致的互斥访问问题,导致代码互斥锁导致的效率和维护难的问题;

  • 过多的线程调试也不是一件简单的事情;

​ 由于以上原因,我们在开发一款高效的网络服务器时,应该尽量减少对线程的依赖,最好使用一个线程来完成网络的数据收发工作即可,目前我们有两个选择:select/epoll两种多路I/O复用技术。

​ 什么是I/O多路复用技术?按字面理解,是"I/O"表示输入输出,多路表示多个输入输出,复用代表重复使用,即多个输入输出可以重复在一个地方去监听使用。这是什么意思呢?我们想下和客户端通信时,read/recv读取客户端的数据是阻塞的,导致没办法单线程完成。假设现在有一个"代理",可以帮助我们监听所有的read/recv客户端是否有数据到来,那就只有这个"代理"一个阻塞就可以了,当任意客户端有数据可读时,由代理统一通知我们,这样就解决了之前必须多线程才能做到的事情,这就是I/O多路复用技术的优势,可以完成在使用很少的资源的情况下,完成很高的网络并发和吞吐能力,著名的nodejs、nginx就是依赖这种技术完成的高并发。

image-20240410174715411

​ select/epoll是两种不同实现的多路I/O复用技术。

​ select是一种轮询的机制来完成I/O端口的复用的,即select函数会把复用的所有文件描述符都不定期的去"询问"对应的描述符是否准备就绪,当文件描述符过多时,显然会影响执行的效率,并且select在内核中的实现是通过把读到的数据拷贝到用户空间的,效率上也会比较低,并且最大的问题是select的最大支持文件描述符个数是1024个,也导致了不能做为高性能服务器的主要技术。

​ epoll不同于select的实现。我们知道,最高效的方式应该是当客户端有数据过来时,程序再执行,没有数据过来时,程序应该睡眠。epoll就是按照这种思路实现的,并且当读到数据后,是直接把数据映射到用户空间,也少了一次数据的拷贝,非常高效。另外epoll由于不是轮询的机制实现,在支持的文件描述符上也不会有1024的限制。而且还有一点更加高效的是epoll支持边沿触发,即当在文件描述符上发生了事件时,只会通知一次,这就要求我们在代码的编写时,要非常小心的做一些额外的错误处理,否则会丢失要读写的数据。

​ 接下来就让我们一起来看一下,如何通过边沿触发的方式来实现一个高效的服务端的核心代码部分。以下分为两个部分,一个是函数说明,一个是代码示例。

epoll相关函数说明

epoll_create

/**
* @brief 创建一个epoll多路IO复用对象
*
* @param[int] size 是非负数,表示能够监听的最大文件描述符个数
*
* @return 成功返回非负文件描述符,错误返回-1,错误码存放在errno中
*
*/
int epoll_create(int size);

epoll_ctl

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 */
};
/**
* @brief 把fd的event事件的操作op放到epoll中
*
* @param[in] epfd 表示epoll的文件描述符
* @param[in] op 操作方式,有如下三种
* 三大功能:
* 	EPOLL_CTL_ADD:增加一个文件描述符fd的事件event到epfd中
* 	EPOLL_CTL_MOD:修改一个文件描述符fd所关心的事件event到epfd中
* 	EPOLL_CTL_DEL:删除一个文件描述符fd的事件event
*
* @param[in] fd 被操作的文件描述符
* @param[in] event 被操作的文件描述符的事件,如上所示的结果体,其中的events包含以下几类:
* 	EPOLLIN 表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
* 	EPOLLOUT 表示对应的文件描述符可以写;
* 	EPOLLPRI 表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
* 	EPOLLERR 表示对应的文件描述符发生错误;
* 	EPOLLHUP 表示对应的文件描述符被挂断;
* 	EPOLLET 将EPOLL设为边缘触发(Edge Triggered)模式,默认的是水平触发(LevelTriggered)。
* 	EPOLLONESHOT 只监听一次事件,监听完之后,如果还需要继续监听,需要再次把事件加入到EPOLL队列里。
* @return 成功返回0,调用失败返回错误码
*/
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

epoll_wait

/**
* @brief 等待一个I/O事件的发生
*
* @param[in] epfd 用于等待事件发生的epoll文件描述符
* @param[in] events 发生事件的文件描述符集
* @param[in] maxevents 一次最大返回的事件个数
* @param[in] timeout 等待的超时事件,以毫秒为单位。0会立即返回,-1会一直阻塞等待事件发生,大于0为超时时间
* @return 成功返回发生的文件描述符时间的个数,超时返回0,错误返回-1,错误码存放于errno中
*
*/
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

fcntl

函数原型:

#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );
// arg表示可变参数,由cmd决定

fcntl()对打开的文件描述符fd执行下面描述的操作之一。操作由cmd决定。

fcntl()的第三个参数是可选。是否需要此参数由cmd决定。所需的参数类型在每个cmd名称后面的括号中指示(在大多数情况下,所需的类型是int,我们使用名称arg来标识参数),如果不需要参数,则指定void。

以下某些操作仅在特定的Linux内核版本之后才受支持。检查主机内核是否支持特定操作的首选方法是使用所需的cmd值调用fcntl(),然后使用EINVAL测试调用是否失败,这表明内核无法识别该值。

主要介绍下面4个功能:

​ 1、复制文件描述符(F_DUPFD、F_DUPFD_CLOEXEC);
​ 2、获取/设置文件描述符标志(F_GETFD、F_SETFD);
​ 3、获取/设置文件状态标志(F_GETFL、F_SETFL);
​ 4、获取/设置记录锁(F_GETLK、F_SETLK、F_SETLKW);

epoll高效服务器示例代码

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#define MAX_BUFF        4096

static int set_nonblock(int fd)
{
    int opts;
    opts = fcntl(fd, F_GETFL);
    if (opts < 0) {
        perror("fcntl F_GETFL");
        return -1;
    }
    opts = opts | O_NONBLOCK;
    if (fcntl(fd, F_SETFL, opts) < 0) {
        perror("fcntl F_SETFL O_NONBLOCK");
        return -1;
    }
    return 0;
}

static int set_recvbuff(int fd, int nBuffLen)
{
    return setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &nBuffLen, sizeof(nBuffLen));
}

static int get_recvbuff(int fd)
{
    int nValue = -1;
    socklen_t nValueLen = sizeof(int);
    if (getsockopt(fd, SOL_SOCKET, SO_RCVBUF, &nValue, &nValueLen)) {
        perror("getsockopt");
    }
    return nValue;
}

static int socket_init(short nPort)
{
    struct sockaddr_in stServerAddr;
    /* 创建一个socket对象 */
    int nListenFd = socket(AF_INET, SOCK_STREAM, 0);
    if (-1 == nListenFd) {
        perror("socket");
        return -1;
    }

    /* 使得绑定端口号可以复用,解决重新运行时的address is already in use的错误 */
    socklen_t nReuseAddr = 1;
    if (setsockopt(nListenFd, SOL_SOCKET, SO_REUSEADDR, (const void *)&nReuseAddr, sizeof(socklen_t))) {
        perror("set reuse addr");
        close(nListenFd);
        return -1;
    }
    if (set_recvbuff(nListenFd, MAX_BUFF)) {
        perror("set_recvbuff");
        close(nListenFd);
        return -1;
    }
    bzero(&stServerAddr, sizeof(stServerAddr));
    stServerAddr.sin_family = AF_INET;
    stServerAddr.sin_port = htons(nPort);
    stServerAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    /* stServerAddr.sin_addr.s_addr = inet_addr("127.0.0.1"); */
    if (bind(nListenFd, (struct sockaddr *)&stServerAddr, sizeof(stServerAddr))) {
        perror("bind");
        close(nListenFd);
        return -1;
    }
    listen(nListenFd, 20);
    return nListenFd;
}

static int epoll_init(int fd)
{
    struct epoll_event ev;
    /* 把socket设置为非阻塞方式 */
    set_nonblock(fd);
    /* 创建epoll多路复用接口 */
    int nEpollFd = epoll_create(1024);
    if (-1 == nEpollFd) {
        perror("epoll_create");
        return -1;
    }
    /* 设置要处理的事件类型为边沿触发读事件,并注册到epoll中 */
    ev.data.fd = fd;
    ev.events = EPOLLIN | EPOLLET;
    if (epoll_ctl(nEpollFd, EPOLL_CTL_ADD, fd, &ev)) {
        perror("epoll_ctl");
        close(nEpollFd);
        return -1;
    }
    return nEpollFd;
}

static int epoll_run(int nListenFd, int nEpollFd)
{
    int fd;
    ssize_t n;
    socklen_t nClientAddrLen;
    char sRecvBuffer[MAX_BUFF];
    struct epoll_event ev, events[20];
    struct sockaddr_in stClientAddr;
    int nLoop = 1;
    while (nLoop)
    {
        /* 等待epoll事件的发生 */
        int nfds = epoll_wait(nEpollFd, events, 20, -1);
        /* 处理所发生的所有事件 */
        for (int i = 0; i < nfds; ++i) {
            /* 如果新监测到一个SOCKET用户连接到了绑定的SOCKET端口,建立新的连接 */
            if (events[i].data.fd == nListenFd) {
                int nConnFd = accept(nListenFd, (struct sockaddr *)&stClientAddr, &nClientAddrLen);
                if (nConnFd < 0) {
                    perror("accept");
                    break;
                }
                printf("accapt a connection from:%s, port:%d\n", inet_ntoa(stClientAddr.sin_addr), ntohs(stClientAddr.sin_port));
                /* 设置客户端文件描述符为非阻塞方式 */
                if (set_nonblock(nConnFd)) {
                    close(nConnFd);
                    perror("set_nonblock");
                    continue;
                }
                /* 注册nConnFd到epoll中,边沿方式监听是否有数据可读 */
                ev.data.fd = nConnFd;
                ev.events = EPOLLIN | EPOLLET;
                epoll_ctl(nEpollFd, EPOLL_CTL_ADD, nConnFd, &ev);
            }
            /* 判断是否有数据可读 */
            else if (events[i].events & EPOLLIN)
            {
                int nCount = 0;
                if ((fd = events[i].data.fd) < 0) continue;
                while(n = read(fd, sRecvBuffer+nCount, MAX_BUFF-1-nCount)) {
                    if (n <= 0) {
                        if (EAGAIN != n) {
                            sRecvBuffer[nCount] = '\0';
                            printf("read %s\n", sRecvBuffer);
                        }
                        else {
                            ev.data.fd = fd;
                            epoll_ctl(nEpollFd, EPOLL_CTL_DEL, fd, NULL);
                            close(fd);
                            printf("fd:%d is closed\n", fd);
                        }
                        break;
                    }
                    nCount += n;
                }
                /* 修改为边沿方式监听是否有数据可写 */
                ev.data.fd = fd;
                ev.events = EPOLLOUT | EPOLLET;
                epoll_ctl(nEpollFd, EPOLL_CTL_MOD, fd, &ev);
            }
            /* 判断是否有数据可写 */
            else if (events[i].events & EPOLLOUT) {
                fd = events[i].data.fd;
                write(fd, sRecvBuffer, n);
                /* 修改为边沿方式监听是否有数据可读 */
                ev.data.fd = fd;
                ev.events = EPOLLIN | EPOLLET;
                epoll_ctl(nEpollFd, EPOLL_CTL_MOD, fd, &ev);
            }
        }
    }
}

int main(int argc, char *argv[])
{
    int nListenFd = -1, nEpollFd = -1;
    if (2 != argc) {
        fprintf(stderr, "Usage:%s port\n", argv[0]);
        exit(EXIT_FAILURE);
    }
    do {
        nListenFd = socket_init(atoi(argv[1]));
        if (-1 == nListenFd) break;
        nEpollFd = epoll_init(nListenFd);
        if (-1 == nEpollFd) break;

        epoll_run(nListenFd, nEpollFd);
    } while(0);
    
    if (nEpollFd > 0) close(nEpollFd);
    if (nListenFd > 0) close(nListenFd);
    return 0;
}

标签:addr,epoll,int,31,网络,fd,include,struct
From: https://www.cnblogs.com/mzx233/p/18168117

相关文章

  • 网络复习题
    HTTP缓存HTTP缓存是一种存储和重用HTTP响应的机制,以减少服务器的延迟和网络负载。HTTP缓存可以存在于多个位置,包括客户端(如浏览器缓存)、代理服务器(如公司或ISP的缓存服务器)以及原始服务器的附近(如反向代理缓存,例如Varnish或CDN节点)。缓存的实现通常遵循以下步骤:缓存存储:当......
  • 【Modelsim问题】# ** Error: (vsim-3170) Could not find 'lab1_tb'.
     #**Error:(vsim-3170)Couldnotfind'lab1_tb'. testbench文件名与其中module 后紧跟的名称不匹配......
  • 预习一 计算机网络发展史
    计算机网络的发展史可以大致划分为以下几个阶段:起源:1946年,世界上第一台计算机ENIAC在美国诞生。随后的二十多年里,计算机技术一直在寻找与通信技术相结合的发展。20世纪50年代初,美国建立了一个半自动地面防控系统,即SAGE(赛其)系统,这可以看作是网络的雏形。ARPANET阶段:1969年,美国国......
  • KVM网络管理
    一:创建nat网络模式 主要就是nat模式的话,网桥没有连接宿主机的物理网卡,就是创建出来一个网桥,会自动的虚拟出来2个接口(2个网卡的配置文件),一个是网桥的配置文件,另外一个就是nic的配置文件,主要作用就是虚拟机与宿主机进行通信用的1:命令行的操作思路主要就是,通过配置文件来进行定义......
  • 云原生周刊:K8s 中的服务和网络 | 2024.4.29
    开源项目推荐k8s-image-swapperk8s-image-swapper是Kubernetes的一个变更Webhook,它将镜像下载到自己的镜像仓库,并将镜像指向该新位置。它是dockerpull-throughproxy的一个替代方案。KubeIPv2KubeIPv2是DoiTKubeIPv1-main开源项目的全面改进版本,最初由AvivLau......
  • 双栈网络使用什么技术实现总部网络能访问分部网络?
    本文分享自天翼云开发者社区《双栈网络使用什么技术实现总部网络能访问分部网络?》,作者:SD万为了实现总部和分部之间的IPv4和IPv6互访,可以使用以下隧道技术:1、IPv4隧道技术:可以使用GRE(GenericRoutingEncapsulation)隧道技术,在IPv4网络中隧道传输IPv6数据包。在总部和分部之间分别......
  • 共常州市委网信办网络安全公开课讲师名单公示
    开个贴记录一下,以防自己记不得这个转行之前从常州市委网信办网络安全公开课资源池获得的小荣誉,感谢czdx的领导和同事们支持。明天新单位来老单位zs,意味着又到了一个人生转折点,即将踏上一个从来没有想过的职业生涯,这一走算转行也不算转行,有可能要重回搞计算机了。原文地址:https:/......
  • 社会网络分析及其Python实现
    社会网络分析(SocialNetworkAnalysis,SNA)在人类学、心理学、社会学、数学以及统计学等领域中发展起来,是综合运用图论、数学模型来研究社会行动者之间的关系或通过这些关系流动的各种有形或无形的东西,如信息、资源等,近年来逐渐成为一种热门的社会科学研究方法。社会网络分析旨在......
  • m基于Yolov2深度学习网络的智能零售柜商品识别系统matlab仿真,带GUI界面
    1.算法仿真效果matlab2022a仿真结果如下:  2.算法涉及理论知识概要       YOLO(YouOnlyLookOnce)是一种实时的目标检测算法,YOLOv2则是其改进版本,由JosephRedmon和AliFarhadi于2016年提出。YOLOv2采用了端到端的方式直接从整幅图像预测边界框和类别概率,极大......
  • Flow(网络流 written on 4.29)
    前言Classtakenon4.2Writtenon4.29Flow解决问题类网络流是用有向图每条边来模拟流动,有流量限制的情况下,求解最大流量(有时以及最小费用)的问题。同时也是将各类问题(尤其匹配问题)通过建模为网络流来用网络流算法求解的一个方法。解决问题的一般特点:数据范围不大不小,\(......