网络基础+Socket编程+高并发服务器

网络编程

P1 复习 Linux 网络编程

一、线程同步概念
1、协同步调,按预定先后次序之行与时间有关系的错误
2、数据混乱(资源共享,调度随机,缺乏必要的同步机制)
3、多个控制流访问同一共享资源,必须同步

二、互斥量(互斥锁)
1、mutex
建议锁;
锁,不会限制资源访问;
线程不按规则访问数据依然成功,会出现数据混乱)

2、函数
pthread_mutex_t类型 本质:结构体
pthread_mutex_init 初始化一个互斥锁 1
pthread_mutex_destroy 销毁一个互斥锁
pthread_mutex_lock 加锁 -1
pthread_mutex_unlock 解锁 +1
pthread_mutex_trylock 非阻塞加锁

#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
 
pthread_mutex_t mutex;      //定义锁
 
void *tfn(void *arg)
{
 
    while (1) {
        pthread_mutex_lock(&mutex);  //加锁
 
        printf("hello ");
        sleep(1);	/*模拟长时间操作共享资源,导致cpu易主,产生与时间有关的错误*/
        printf("world\n");
        pthread_mutex_unlock(&mutex); //解锁
 
        sleep(1);//睡眠,释放cpu
    }
 
    return NULL;
}
 
int main(void)
{
   
    pthread_t tid;
    
  
    pthread_mutex_init(&mutex, NULL);  //初始化锁 mutex==1
    pthread_create(&tid, NULL, tfn, NULL);
    while (1) 
	{
 
        pthread_mutex_lock(&mutex); //加锁
 
        printf("HELLO ");
        sleep(1);
        printf("WORLD\n");
        pthread_mutex_unlock(&mutex); //解锁
 
        sleep(1);
 
    }
   
    pthread_mutex_destroy(&mutex);  //销毁锁
 
    return 0;
}

三、死锁
产生原因:
1、对同一个互斥量,重复加锁
2、持有锁A的线程1请求锁B,持有锁B的线程2请求锁A
避免方法:
1、保证资源的获取顺序,要求每个线程获取资源的顺序一致
2、当得不到所有所需资源时,放弃已经获得的资源,等待。

#include <iostream>
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>

using namespace std;

int var = 1;
int num = 5;

pthread_mutex_t m_var;
pthread_mutex_t m_num;

void *tfn(void* arg)
{
	int i = (int)arg;
	if(i == 1)
	{
		pthread_mutex_lock(&m_var);
		var = 11;
		sleep(1);
	
		pthread_mutex_lock(&m_num);
		num = 111;

		pthread_mutex_unlock(&m_var);
		pthread_mutex_unlock(&m_num);
		
		cout << "-----thread " << i << "finish" << endl;
		pthread_exit(NULL);
	}
	else if(i == 2)
	{
		pthread_mutex_lock(&m_num);
		var = 22;
		sleep(1);

		pthread_mutex_lock(&m_var);
                num = 222;

                pthread_mutex_unlock(&m_var);
                pthread_mutex_unlock(&m_num);

                cout << "-----thread " << i << "finish" << endl;
                pthread_exit(NULL);

	}
	return NULL;
}

int a = 100;

int main()
{
#if 0
	pthread_t tid1, tid2;
	int ret1, ret2;
	

	pthread_mutex_init(&m_var, NULL);
	pthread_mutex_init(&m_num, NULL);

	pthread_create(&tid1, NULL, tfn, (void*)1);
	pthread_create(&tid2, NULL, tfn, (void*)2);

	sleep(3);
	printf("var = %d, num = %d\n", var, num);

	ret1 = pthread_mutex_destroy(&m_var);
	ret2 = pthread_mutex_destroy(&m_num);

	if(ret1 == 0 && ret2 == 0)
	{
		printf("---------- join thread finish\n");
	}

	return 0;
#else 
	pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

	pthread_mutex_lock(&mutex);
	a = 777;
	//pthread_mutex_lock(&mutex);

	pthread_mutex_unlock(&mutex);

	printf("--------a = %d\n", a);
	pthread_mutex_destroy(&mutex);	
	return 0;
#endif
}

四、读写锁
1、使用要领:读共享,写独占;写锁优先级高
2、状态:
读模式下加锁状态(读锁)
写模式下加锁状态(写锁)
不加锁状态

3、特性
写模式加锁:解锁前,所有对该锁加锁的线程都会被阻塞

读模式加锁:
如果线程以读模式对其加锁会成功
如果线程以写模式加锁会阻塞;
既有试图以写模式加锁的线程,也有试图以读模式加锁的线程

4、使用场景:适合与对数据结构读的次数远大于写

5、函数
pthread_rwlock_t 类型
pthread_rwlock_init
pthread_rwlock_destory
pthread_rwlock_rdlock
pthread_rwlock_wrlock
pthread_rwlock_tryrdlock
pthread_rwlock_trywrlock
pthread_rwlock_unlock

五、条件变量
1、特性:
不是锁
可以造成线程阻塞
与mutex配合使用

2、函数
pthread_cond_t 类型
pthread_cond_init
pthread_cond_destory
pthread_cond_wait
1)阻塞等待一个条件变量
2)释放已经掌握的互斥锁mutex
3)被唤醒时重新申请获取互斥锁
1)2)步为原子操作

pthread_cond_timewait 绝对时间
pthread_cond_signal 唤醒一个阻塞在条件变量上的线程
pthread_cond_broadcast 唤醒全部阻塞在条件变量上的线程

3、生产者消费者模型
4、优点:减少不必要的竞争

/*借助条件变量模拟 生产者-消费者 问题*/
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <stdio.h>

/*链表作为共享数据,需被互斥量保护*/
struct msg
{
	struct msg *next;
	int num;
};

struct msg *head;

/*静态初始化 一个条件变量 和 一个互斥量*/
pthread_cond_t has_product = PTHREAD_COND_INITIALIZER;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;

void *consumer(void *p)
{
	struct msg *mp;
	
	while(1)
	{
		pthread_mutex_lock(&lock);
		while(head == NULL)//头指针为空,说明没有节点
		{
			pthread_cond_wait(&has_product, &lock);
		}
		
		mp = head;
		head = mp->next;//模拟消费掉一个产品
		pthread_mutex_unlock(&lock);
		
		printf("-Consume %lu -- %d\n", pthread_self(), mp->num);
		free(mp);
		sleep(rand() % 5);
	}
}

void *producer(void *p)
{
	struct msg *mp;
	
	while(1)
	{
		mp = (struct msg*)malloc(sizeof(struct msg));
		mp->num = rand() % 1000 + 1;//模拟生产一个产品
		printf("-Produce --------------%d\n", mp->num);
		
		
		pthread_mutex_lock(&lock);
		mp->next = head;
		head = mp;
		pthread_mutex_unlock(&lock);
		
		pthread_cond_signal(&has_product);//将等待在该条件变量上的一个线程唤醒
		sleep(rand() % 5);
	}
}

int main(int argc, char *argv[])
{
	pthread_t pid, cid;
	srand(time(NULL));
	
	pthread_create(&pid, NULL, producer, NULL);
	pthread_create(&cid, NULL, consumer, NULL);
	
	pthread_join(pid, NULL);
	pthread_join(cid, NULL);
	
	return 0;
}

P2 信号量生产者复习

一、信号量
1、sem
进化版互斥锁(1 – N)
保证同步的同时,提高并发

2、函数
sem_t 类型 N代表线程数量 <semaptore.h>
sem_init 是否进程间共享(非0进程间 PTHREAD_PROCESS_SHARED 0线程间 PTHREAD_PROCESS_PRIVATE)
sem_destroy
sem_wait 给信号量加锁 sem-- lock
sem_trywait
sem_timedwait

sem_post
1)给信号量解锁 sem++ unlock
2)唤醒阻塞在信号量上的线程

3、生产者消费者模型

/*信号量实现 生产者 消费者问题*/
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <stdio.h>
#include <semaphore.h>

#define NUM 5

int queue[NUM];	//全局数组实现环形队列
sem_t blank_number;
sem_t product_number;

void* producer(void* arg)
{
	int i = 0;
	
	while(1)
	{
		sem_wait(&blank_number);//生产者将格子数减一,为0则阻塞等待
		queue[i] = rand() % 1000 + 1;//生成一个产品
		printf("---produce---%d\n", queue[i]);
		sem_post(&product_number);//将产品数加一
		
		i = (i + 1) % NUM;//借助下标实现环形
		sleep(rand() % 1);
	}
}

void* consumer(void* arg)
{
	int i = 0;
	 while(1)
	 {
		sem_wait(&product_number);//消费者将产品数减一,为0则阻塞等待
		printf("---consume---%d\n", queue[i]);
		queue[i] = 0;//消费一个产品
		sem_post(&blank_number);//消费掉以后,将空格数加一
		
		i = (i + 1) % NUM;
		sleep(rand() % 3);
	 }
}

int main(int argc, char* argv[])
{
	pthread_t pid, cid;
	
	sem_init(&blank_number, 0, NUM);
	sem_init(&product_number, 0, 0);
	
	pthread_create(&pid, NULL, producer, NULL);
	pthread_create(&cid, NULL, consumer, NULL);
	
	pthread_join(pid, NULL);
	pthread_join(cid, NULL);
	
	return 0;
}

P3 协议

什么是协议:一组规则。
TCP 协议注重数据的传输,http协议着重于数据的解释。

传输层 TCP,UDP
应用层 HTTP:80,FTP:20,21,TFTP:69,SSH:22,Telnet:23
网络层 IP,ICMP,IGMP
网络接口层 ARP(通过IP找MAC),RARP(通过MAC找IP)

P4 七层模型和四层模型及代表协议

分层模型结构:
OSI七层模型:物、数、网、传、会、表、应
在这里插入图片描述

P5 网络传输数据封装流程

数据没有封装之前,是不能在网络中传递的。
需要我们做的只有应用层,其他部分(传输层、网络层、链路层)内核已经帮我们做了
以太网首部 + IP首部 + TCP 首部 + 应用数据 + 以太网尾部
14 20 20 4
|—————46 ~ 1500——|

P6 以太网帧和ARP请求

以太网的帧格式如下:
目的地址 + 源地址 + 类型 + 数据 + CRC
6 6 2 46~1500 4

类型
0800 数据
0806 ARP
8035 RARP

ARP协议,根据IP 地址获取mac 地址
以太网协议:根据mac地址,完成数据包传输
ff:ff:ff:ff:ff:ff:ff + 00:50:56:3c:9e:ed + 0806 + 8 + 00:50:56:3c:9e:ed + 192.168.0.110 + ff:ff:ff:ff:ff:ff:ff + 123.46.76.22

P7 IP 协议

IP段格式:
在这里插入图片描述
版本:IPV4、IPV6 --4位
TTL: time to live(设置下一跳次数,每经过一个路由节点 -1,直到为0丢弃)
源IP:32 位
目的IP:32 位

P8 TCP协议

IP地址,可以在网络环境中,唯一标识一台主机。
端口号,可以网络的一台主机上,唯一标识一个进程。
IP地址+端口号:可以在网络环境中,唯一标识一个进程。
在这里插入图片描述
UDP协议:
16位:源地址口号 2^16 = 65536
16位:目的端口号

TCP协议:
16位:源地址口号 2^16 = 65536
16位:目的端口号
32位序号
32位确认序号
6个标志位
16位窗口大小

P9 BS 和 CS 模型比对

b/s 模型:
browser-server
c/s 模型:
client-server
在这里插入图片描述

P10 套接字

在这里插入图片描述
网络套接字: socket
一个文件描述符指向一个套接字(该套接字内部由内核借助两个缓冲区实现。)
在通信过程中,套接字成对出现的

P11 回顾

P12 网络字节序

小端(pc本地存储):高位存高地址,地位存低地址
大端(网络):高位存低地址,地位存高地址

#include<arpa/inet.h>

uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);

P13 IP地址转换函数

#include <arpa/inet.h>
//将点分十进制的ip地址转化为用于网络传输的数值格式
//返回值:若成功则为1,若输入不是有效的表达式则为0,若出错则为-1
int inet_pton(int af, const char* src, void* dst);

//将数值格式转化为点分十进制的ip地址格式
//返回值:若成功则为指向结构的指针,若出错则为NULL
const char* inet_ntop(int af, const void *src, char* dst, socklen_t size);

P14 socketaddr 地址结构

在这里插入图片描述
sockaddr 地址结构 :

	struct sockaddr_in addr;
	addr.sin_family = AF_INET;
	addr.sin_port = htons(9527);

	int dst;
	inet_pton(AF_INET, "192.168.9.11", (void*)&dst)
	addr.sin_addr.s_addr = dst;//htonl(INADDR_ANY) 取出系统中有效的任意IP地址
	bind(fd, (struct sockaddr*)&addr, size);

man 7 ip =》sockaddr_in

P15 socket 模型创建流程分析

P16 socket 和 bind

#include <sys/socket.h>
int socket(int domain, int type, int protocol);//创建套接字
  domain:AF_INET、AF_INET6
  type:SOCK_STREAM(tcp)、SOCK_DGRAM(udp)
  protocol:0
返回值:成功:文件描述符
失败: -1

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);//绑定IP + port

P17 listen 和 accept

int listen(int sockfd, int backlog);//同时设置监听上线,最大值128

//阻塞等待客户端建立连接,成功的话,返回一个与客户端成功连接的socket文件描述符
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
addr:传出参数。成功与服务器建立连接的那个客户端的地址结构(IP + port)
addrlen: 传入传出。入:addr 的大小,出:客户端addr实际大小

P18 connect

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
addr:传入参数。服务器的地址结构(IP + port)
addrlen: 服务器的地址结构的大小

如果不使用bind绑定客户端地址结构,采用“隐式绑定”

P19 CS模型的TCP 通信分析

在这里插入图片描述

P20 server的实现

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

void sys_err(const char * str)
{
        perror(str);
        exit(1);
}

int main(int argc, char* argv[])
{
        int fd = socket(AF_INET, SOCK_STREAM, 0);
        if(fd == -1)
        {
                sys_err("socket error");
        }

        struct sockaddr_in serv_addr;
        serv_addr.sin_family = AF_INET;
        serv_addr.sin_port = htons(9517);
        serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);

        bind(fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));

        listen(fd, 128);

        struct sockaddr_in client_addr;
        socklen_t client_len = sizeof(client_addr);
        int cfd = accept(fd, (struct sockaddr *)&client_addr, &client_len);
        if(cfd == -1)
        {
                sys_err("socket error");
        }

        char buf[1024] = {0};
        while(1)
        {
                memset(buf, '\0', sizeof(buf));
                int ret = read(cfd, buf, sizeof(buf));

                printf("----%s----%d----\n", buf, ret);

                for(int i = 0; i < ret; ++i)
                {
                        buf[i] = toupper(buf[i]);
                }

                write(cfd, buf, ret);
        }
        close(fd);
        close(cfd);

        return 0;
}    

nc(内测 net connet) 命令
nc 127.0.0.1 9517

P21 获取客户端地址结构

char client_ip[1024];
inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, client_ip, sizeof(client_ip));
printf("client ip = %s, port = %d", client_ip, client_addr.sin_port);

P22 client 的实现

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

void sys_err(const char * str)
{
	perror(str);
	exit(1);
}

int main(int argc, char* argv[])
{
	int fd = socket(AF_INET, SOCK_STREAM, 0);
	if(fd == -1)
	{
		sys_err("socket error");
	}

	struct sockaddr_in serv_addr;
	serv_addr.sin_family = AF_INET;
	serv_addr.sin_port = htons(9517);
	inet_pton(AF_INET, "192.168.0.109", &serv_addr.sin_addr.s_addr);

	int ret = connect(fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
	if(ret != 0)
	{
		sys_err("connect err");
	}

	int nCount = 5;
	char buf[BUFSIZ] = {0};
	while(nCount--)
	{
		write(fd, "hello\n", 6);
		int n = read(fd, buf, sizeof(buf));
		printf("----%s----%d----\n", buf, n);
	}
	close(fd);
	return 0;
}

P23 总结

P24 复习

P25 三次握手建立连接

在这里插入图片描述
客户端发起请求:SYN TCP标志位,1000 包号,(0)包所携带数据的大小 <mess 1460> => ip首部 + tcp 首部 + 数据
服务器应答:SYN TCP标志位,8000 包号,应答ACK 标志位,1001 包号
客户端确认:ACK 标志位 8001包号

P26 数据通信

在这里插入图片描述

P27 四次握手关闭连接

在这里插入图片描述

P28 半关闭补充说明

半关闭:一端发,一端收数据(没有发送功能,只有接收功能)

P29 滑动窗口和TCP 数据格式

win 4096 , 滑动窗口大小
如果发送端发送的速度较快,接收端接收到数据后处理的速度较慢,而接收缓冲区的大小是固定的,就会丢数据。
TCP协议通过“滑动窗口” 机制解决这一问题
在这里插入图片描述

P30 通信时序与代码对应关系

在这里插入图片描述

P31 TCP通信时序总结

在这里插入图片描述

P32 错误处理函数的封装思路

在外面再封装一层,例如:

int Accept(int fd, struct sockaddr* sa, socklen_t* salenptr)
{
	int n;
again:
	if((n = accept(fd, sa, salenptr)) < 0)
	{
		if((errno == ECONNABORTED) || (errno == EINTR))
			goto again;
		else
			perr_exit("accept error");
	}
	return n;
}

P33 错误处理函数封装

wrap.c
自定义函数

wrap.h
自定义函数原型

P34 封装思想和readn, readline 封装思想说明

readn N个字节
readline 读一行

P35 中午复习

P36 多进程并发服务器思路分析

多进程并发服务器:
在这里插入图片描述
在这里插入图片描述

P37 多进程并发服务器分析

在这里插入图片描述

P38 多进程并发服务器实现

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


void catch_child(int signum)
{
	while((waitpid(0, NULL, WNOHANG)) > 0);
	return;
}

int main()
{
	int lfd = socket(AF_INET, SOCK_STREAM, 0);

	struct sockaddr_in srv_addr;
	memset(&srv_addr, 0, sizeof(srv_addr));	
	srv_addr.sin_family = AF_INET;
	srv_addr.sin_port = htons(9517);
	srv_addr.sin_addr.s_addr = htonl(INADDR_ANY);	
	
	bind(lfd, (struct sockaddr*)&srv_addr, sizeof(srv_addr));
	
	listen(lfd, 128);

	int cfd;
	struct sockaddr_in clt_addr;
	socklen_t clt_addr_len = sizeof(clt_addr);

	pid_t pid;
	int ret;
	char buf[BUFSIZ] = {0};
	while(1)
	{
		cfd = accept(lfd, (struct sockaddr*)&clt_addr, &clt_addr_len);
		pid = fork();
		if(pid < 0)
		{
			perror("fork error");
		}
		else if(pid == 0)
		{
			close(lfd);
			while(1)
			{
				memset(buf, 0, sizeof(buf));	
				ret = read(cfd, buf, sizeof(buf));
				if(ret == 0)
				{
					close(cfd);
					exit(1);
				}
	
				for(int i = 0; i < ret;i++)
				{
					buf[i] = toupper(buf[i]);
				}
				write(cfd, buf, ret);
				//printf("---%s---\n", buf);

			}
		}
		else
		{
			struct sigaction act;

			act.sa_handler = catch_child;
			sigemptyset(&act.sa_mask);
			act.sa_flags = 0;

			ret = sigaction(SIGCHLD, &act, NULL);
			if(ret != 0)
			{
				perror("signal error");
			}
			close(cfd);
		}
	}

	return 0;
}

P39 多进程服务器测试ip地址调整

P40 服务器程序上传外网服务器,并访问

P41 多线程服务器代码review

#include <stdio.h>
#include <arpa/inet.h>
#include <string.h>
#include <pthread.h>
#include <ctype.h>
#include <unistd.h>
#include <errno.h>
#include <ctype.h>

struct s_info
{
	struct sockaddr_in clt_addr;
	int connfd;
};

#define MAXLINE 8192

void*do_work(void* arg)
{
	int n;
	struct s_info *ts = (struct s_info*)arg;
	char buf[MAXLINE] = {0};
	char str[8000] = {0};
	while(1)
	{
		n = read(ts->connfd, buf, MAXLINE);
		if(n == 0)
		{
			printf("the client %d close...\n", ts->connfd);
			break;
		}
		printf("received from %s at port %d\n",
			inet_ntop(AF_INET, &(*ts).clt_addr.sin_addr, str, sizeof(str)), ntohs((*ts).clt_addr.sin_port));
			
		for(int i = 0; i < n; i++)
		{
			buf[i] = toupper(buf[i]);
		}
		
		write(ts->connfd, buf, n);
	}
	close(ts->connfd);
	return (void*)0;
}

int main(int argc, char* argv[])
{
	struct sockaddr_in servaddr;
	int listenfd;
	
	struct s_info ts[256];
	
	listenfd = socket(AF_INET, SOCK_STREAM, 0);
	
	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_port = htons(9517);
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);

	bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr));
	
	listen(listenfd, 128);
	
	printf("accepting client connect...\n");
	
	struct sockaddr_in cliaddr;
	socklen_t cliaddr_len;
	pthread_t tid;
	int connfd;
	int i = 0;
	while(1)
	{
		cliaddr_len = sizeof(cliaddr);
		connfd = accept(listenfd, (struct sockaddr*)&cliaddr, &cliaddr_len);
		ts[i].clt_addr = cliaddr;
		ts[i].connfd = connfd;
		
		pthread_create(&tid, NULL, do_work, (void*)&ts[i]);
		pthread_detach(tid);
		i++;
	}
	return 0;
}

P42 read返回值和总结

在这里插入图片描述

P43 复习

P44 TCP状态-主动发起连接

在这里插入图片描述

P45 TCP状态-主动关闭连接

在这里插入图片描述

P46 TCP状态-被动接收连接

在这里插入图片描述

P47 TCP状态-被动关闭连接

在这里插入图片描述

P48 2MSL时长

一定出现在【主动关闭连接请求端】
保证最后一个ACK能成功被对端接收。(等待期间,对端没收到我发的ACK,对端会再次发送FIN请求)

P49 TCP状态-其他状态

P50 端口复用函数

int opt = 1;//设置端口复用
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (void*)&opt, sizeof(opt));

P51 半关闭及shutdown函数

通信双方中,只有一端关闭通信。
close(cfd);

shutdown(int fd, int how);
how: SHUT_RD 关闭读端
SHUT_WR 关闭写端
SHUT_RDWR 关闭读写

shutdown 不考虑描述符的引用计数,直接关闭描述符
1、如果有多个进程共享一个套接字,close每被调用一次,计数减1,直到计数为0时,也就是所用进程都调用了close,套接字将被释放。
2、在多进程中如果一个进程调用了shutdown(sfd, SHUT_RDWR)后,其他的进程将无法进行通信。但,如果一个进程close(sfd)将不会影响到其他进程。

P52 多路IO转接服务器设计思路

P53 select函数参数简介

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
nfds : 监控的文件描述符集里最大文件描述符加1
readfds:监控有读数据到达文件描述符集合,传入传出参数
writefds:监控写数据到达文件描述符集合,传入传出参数
exceptfds:监控异常发送达到文件描述符集合,传入传出参数
timeout:定时阻塞监控时间,3种情况
1、NULL,永远等下去
2、设置timeval,等待固定时间
3、设置timeval里时间均为0,检查描述字后立即返回,轮询
struct timeval
{
long tv_sec;
long tv_usec;
};

P54 中午复习

P55 select 函数原型分析

原理:借助内核,select 来监听,客户端连接、数据通信事件。
在这里插入图片描述

P56 select相关函数参数分析

void FD_CLR(int fd, fd_set *set);//将指定的文件描述符从集合中清空
int FD_ISSET(int fd, fd_set *set);//判断这个文件描述符是不是在集合里面
void FD_SET(int fd, fd_set *set);//把某个文件描述符添加到集合里面
void FD_ZERO(fd_set *set);//将集合内所有元素置零

P57 select实现多路IO转接设计思路

//伪代码
lfd = socket();
bind();
listen();
fd_set rset, allset;
FD_ZERO(&allset);
FD_SET(lfd, &allset);

while(1)
{
	rset = allset;
	ret = select(lfd + 1, &rset, NULL, NULL, NULL);//监听文件描述符集合对应事件。
	if(ret > 0)
	{
		if(FD_ISSET(lfd, &rset))//true 存在
		{
			cfd = accept();
			FD_SET(cfd, &allset);
		}
		for(int i = lfd + 1; i < 1024; i++)
		{
			FD_ISSET(i, &rset)
			read();--write();
		}
	}
}

P58 select实现多路IO转接-代码review

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

int main(int argc, char* argv[])
{
	int listenfd;
	listenfd = socket(AF_INET, SOCK_STREAM, 0);
	
	int opt = 1;
	struct sockaddr_in serv_addr;
	setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
	bzero(&serv_addr, sizeof(serv_addr));
	serv_addr.sin_family = AF_INET;
	serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
	serv_addr.sin_port = htons(9517);
	
	bind(listenfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
	
	listen(listenfd, 128);

	fd_set rset, allset;

	int maxfd = 0;
	maxfd = listenfd;

	FD_ZERO(&allset);
	FD_SET(listenfd, &allset);

	
	int n, nready;
	int connfd;
	char buf[BUFSIZ] = {0};
	struct sockaddr_in clie_addr;
	socklen_t clie_addr_len;
	while(1)
	{
		rset = allset;
		nready = select(maxfd + 1, &rset, NULL, NULL, NULL);
		if(nready < 0)
		{
			printf("select error\n");		
		}
		if(FD_ISSET(listenfd, &rset))
		{
			clie_addr_len = sizeof(clie_addr);
			connfd = accept(listenfd, (struct sockaddr *)&clie_addr, &clie_addr_len);
			FD_SET(connfd, &allset);
			
			if(maxfd < connfd)
			{
				maxfd = connfd;
			}
			
			if(0 == --nready)
			{
				continue;			
			}	
		}

		printf("--listenfd:%d, maxfd:%d--\n",listenfd, maxfd);
			
		for(int i = listenfd + 1; i <= maxfd; ++i)
		{
			if(FD_ISSET(i, &rset))
			{
				if((n = read(i, buf, sizeof(buf))) == 0)
				{
					close(i);
					FD_CLR(i, &allset);
				}
				else if(n > 0)
				{
					for(int j = 0; j < n; ++j)
					{
						buf[j] = toupper(buf[j]);
					}
					write(i, buf, n);
					printf("--%s--\n", buf);
				}
			}
		}
	}	
	
	close(listenfd);

	return 0;
}

P59 select实现多路IO转接-代码实现

P60 select实现多路IO转接-添加注释

P61 select优缺点

缺点:监听上限受文件描述符限制,最大1024
检测满足条件的fd,自己添加业务逻辑提高小。提高了编码难度
优点:跨平台。win、linux、macOS

P62 添加一个自己定义数组提高效率

P63 总结

P64 复习

P65 poll函数原型分析

int poll(struct pollfd fds, nfds_t nfds, int timeout);
fds:监听的文件描述符【数组】
nfds:监听数组的,实际有效监听个数
timeout:超时时长。毫秒
在这里插入图片描述
struct pollfd {
int fd; /
file descriptor待监听的文件描述符 /
short events; /
requested events待监听的文件描述符对应的监听事件 POLLIN、POLLOUT、POLLERR*/
short revents; /* returned events 传入时,给0。如果满足对应事件的话,返回非0–》POLLIN、POLLOUT、POLLERR*/
};

P66 poll函数使用注意事项示例

在这里插入图片描述

P67 poll函数实现服务器

在这里插入图片描述

P68 poll总结

优点:
自带数组结构。可以将 监听事件集合 和 返回事件集合分离
拓展 监听上限。超出1024限制。
缺点:
不能跨平台。Linux
无法直接定位满足监听事件的文件描述符,编码难度较大。

P69 epoll函数实现的多路IO转接

在这里插入图片描述

P70 突破1024文件描述符设置

cat /proc/sys/fs/file-max -->当前计算机所能打开的最大文件个数
sudo vi /etc/security/limits.conf
软件限制:soft nofile 10000
硬件限制:hard nofile 100000

P71 epoll_create 和 epoll_ctl

int epoll_create(int size);
epoll_create :创建epoll模型,efd指向红黑树根节点
size:创建的红黑树的监听节点数量
返回值:指向新创建的红黑树的根节点的fd。
失败:-1 error

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epoll_ctl:将lfd及对应的结构体设置到树上,efd可找到该树
epfd:epoll_create 函数的返回值
op:对该监听红黑树所做的操作
EPOLL_CTL_ADD 添加fd到 监听红黑树
EPOLL_CTL_MOD 修改fd在 监听红黑树上的监听事件
EPOLL_CTL_DEL 将一个fd从监听红黑树上摘下(取消监听)
fd:
待监听的fd
event:
本质 struct epoll_event{
uint32_t events;//EPOLLIN/ EPOLLOUT/ EPOLLERR
epoll_data_t data;
};

	typedef union epoll_data{
		void *ptr;
		int fd; //对应监听事件的fd
		void *ptr;
		uint32_t u32;
		uint64_t u64;
	}epoll_data_t;

返回值:成功 0; 失败:-1 errno

P72 epoll_wait函数

int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);//阻塞监听
epfd:epoll_create 函数的返回值
events: 【数组】传出参数
maxevents:数组元素的总个数 1024
timeout:
-1 阻塞
0 不阻塞
>0 超时事件(毫秒)
返回值:
>0 满足监听的总个数

P73 中午复习

P74 ET 和 LT模式

Edget Triggered(ET) 边缘触发只有数据到来才触发,不管缓冲区中是否还有数据。
Level Triggered(LT) 水平触发只要有数据都会触发。

event.events = EPOLLIN | EPOLLET;//ET 边沿触发
//event.events = EPOLLIN;//LT水平触发(默认)
P75 网络中ET和LT模式
P76 epoll的ET非阻塞模式
P77 epoll优缺点总结
P78 补充对比ET和LT
P79 epoll反应堆模型总述
P80 epoll反应堆main逻辑
P81 epoll反应堆-给lfd和cfd指定回调函数
P82 epoll反应堆-init listen socket
P83 epoll反应堆-wait被触发后read和write回调及监听
P84 epoll反应堆-超时时间
P85 总结
P86 复习
P87 补充说明epoll的man手册
P88 epoll反应堆再说明
P89 ctags使用

P90 线程池模型原理分析

在这里插入图片描述

  • 12
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

C葭葭

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值