2024/03/19(网络编程·day5)

一、思维导图

二、selec函数实现TCP并发服务器

#include<myhead.h>

#define SER_PORT 8888 	//服务器端口号
#define SER_IP "192.168.117.116" 	//服务器IP
int main(int argc, const char *argv[])
{
	//1、创建一个套接字
	int sfd = -1;
	sfd = socket(AF_INET,SOCK_STREAM,0);
	//参数1:表示创建的是网络通信的套接字
	//参数2:表示使用的是TCP通信协议
	//参数3:参数2指定了协议,参数3填0即可
	if(sfd == -1)
	{
		perror("socket error");
		return -1;
	}
	printf("%d success sfd = %d\n",__LINE__,sfd);  //3

	//设置端口号快速重用
	int reuse = 1;
	if(setsockopt(sfd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse)) == -1)
	{
		perror("setsockopt error");
		return -1;
	}
	printf("端口号快速重用成功\n");

	//2、绑定IP和端口号
	//2.1填充地址信息结构体
	struct sockaddr_in sin;
	sin.sin_family = AF_INET; 	//地址族
	sin.sin_port = htons(SER_PORT); 	//端口号
	sin.sin_addr.s_addr = inet_addr(SER_IP); //IP地址

	//2.2绑定
	if(bind(sfd,(struct sockaddr *)&sin,sizeof(sin))==-1)
	{
		perror("bind error");
		return -1;
	}
	printf("%d bind success\n",__LINE__);

	//3、将套接字设置成被监听状态
	if(listen(sfd,128) == -1)
	{
		perror("listen error");
		return -1;
	}

	//4、阻塞等待客户端的连接请求
	int newfd = -1;
	//定义结构体变量接受客户端地址信息结构体
	struct sockaddr_in cin;//接收客户端信息结构体
	socklen_t addrlen = sizeof(cin);//接收客户端结构体大小
	char sbuf[128] = ""; //服务器输入数据内容


	//11、定义一个文件描述符集合
	fd_set readfds,tempfds;

	//22、将集合清空
	FD_ZERO(&readfds);

	//33、将要被检测的文件描述符放入集合
	FD_SET(0,&readfds);
	FD_SET(sfd,&readfds);

	int maxfd = sfd; //记录当前容器中的最大文件描述符

	struct sockaddr_in cin_arr[1024]; //存储客户端信息结构体


	while(1)
	{
		//将readfds备份
		tempfds = readfds;

		int res = select(maxfd+1,&tempfds,NULL,NULL,NULL); //阻塞等待集合中的事件产生
		if(res == -1)
		{
			perror("select error");
			return -1;
		}
		else if(res == 0)
		{
			printf("time out\n");
			return -1;
		}

		//当程序执行到此处说明集合中有事件产生,此时集合中只剩下本次触发事件的描述符
		for(int i=0;i<=maxfd;i++)
		{
			//如果不是触发事件的文件描述符,直接跳过
			if(!FD_ISSET(i,&tempfds))
			{
				continue;
			}

			//程序执行至此,表示当前i文件描述符触发了事件


		//判断sfd是否触发事件
		if(i == sfd)
		{
			if((newfd = accept(sfd,(struct sockaddr*)&cin,&addrlen)) == -1)
			{
				perror("accept error");
				return -1;
			}
			printf("[%s %d]:发来连接请求\n",inet_ntoa(cin.sin_addr),ntohs(cin.sin_port));

			//将客户端地址信息结构体放入容器中
			cin_arr[newfd] = cin;

			//将newfd放入readfds容器中参与检测
			FD_SET(newfd,&readfds);

			//可能要更新的maxfd
			if(newfd > maxfd)
			{
				maxfd = newfd;
			}
		}

		//判断0号文件描述符是否触发事件
		else if(0 == i)
		{
			fgets(sbuf,sizeof(sbuf),stdin); //终端输入
			sbuf[strlen(sbuf)-1] = 0;
			printf("触发了键盘输入事件:%s\n",sbuf);

			//将消息发送给所有客户端
			for(int i = 4;i<= maxfd;i++)
			{
				send(i,sbuf,sizeof(sbuf),0);
			}
			printf("发送成功\n");

		}

		else
		{
			//说明某个客户端发来消息了,遍历所有客户端,判断是哪个发来的消息

			//5、收发数据
			char rbuf[128] = "";//接收客户端发送的信息

			//将容器清空
			bzero(rbuf,sizeof(rbuf));//memset(rbuf,0,sizeof(rbuf));

			//从套接字中读取数据
			int res = recv(i,rbuf,sizeof(rbuf)-1,0);
			if(res == 0)
			{			
				printf("客户端已下线\n");
				//关闭与客户端通信的套接字
				close(i);

				//将当前文件描述符移除容器
				FD_CLR(i,&readfds);

				//可能要更新maxfd
				for(int k=maxfd;k>=sfd;k--)
				{
					if(FD_ISSET(k,&readfds))
					{
						maxfd = k;
						break;
					}
				}

				continue;
			}
			printf("[%s  %d]:%s\n",inet_ntoa(cin_arr[i].sin_addr),ntohs(cin_arr[i].sin_port),rbuf);

			//加个收到再回回去
			strcat(rbuf," <Got it>!");

			send(i,rbuf,strlen(rbuf),0);
			printf("回复成功\n");
		}
		}
	}
	//6、关闭服务器
	close(sfd);

	return 0;
}

三、poll函数实现TCP客户端

#include<myhead.h>

#define SER_PORT 8888 	//服务器端口号
#define SER_IP "192.168.117.116" 	//服务器IP
#define CLI_PORT 9999 	//客户端端口号
#define CLI_IP "192.168.117.116" 	//客户端IP


int main(int argc, const char *argv[])
{
	//1、创建一个套接字
	int cfd = -1;
	cfd = socket(AF_INET,SOCK_STREAM,0);
	//参数1:表示创建的是网络通信的套接字
	//参数2:表示使用的是TCP通信协议
	//参数3:参数2指定了协议,参数3填0即可
	if(cfd == -1)
	{
		perror("socket error");
		return -1;
	}
	printf("%d success cfd = %d\n",__LINE__,cfd);  //3

	//设置端口号快速重用
	int reuse = 1;
	if(setsockopt(cfd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse)) == -1)
	{
		perror("setsockopt error");
		return -1;
	}
	printf("端口号快速重用成功\n");

	//2、绑定IP和端口号
	//2.1填充客户端信息结构体
	struct sockaddr_in cin;
	cin.sin_family = AF_INET; 	//地址族
	cin.sin_port = htons(CLI_PORT); 	//端口号
	cin.sin_addr.s_addr = inet_addr(CLI_IP); //IP地址

	//2.2绑定
	if(bind(cfd,(struct sockaddr *)&cin,sizeof(cin))==-1)
	{
		perror("bind error");
		return -1;
	}
	printf("bind success\n");

	//3、连接服务器
	//3.1、填充要连接服务器的地址信息结构体
	struct sockaddr_in sin;
	sin.sin_family = AF_INET; //地质族
	sin.sin_port = htons(SER_PORT); //服务器端口号
	sin.sin_addr.s_addr = inet_addr(SER_IP); //服务器的IP地址
	
	//3.2、连接服务器
	if(connect(cfd,(struct sockaddr*)&sin,sizeof(sin)) ==-1)
	{
		perror("connect error");
		return -1;
	}
	printf("连接成功!\n");

	//使用poll完成0号文件描述符和cfd文件描述符的多路复用
	//11、准备文件描述符容器
	struct pollfd pfds[2];
	pfds[0].fd = 0; //文件描述符
	pfds[0].events = POLLIN; //检测读事件

	pfds[1].fd = cfd; //文件描述符
	pfds[1].events = POLLIN; //检测读事件

	
	//4、收发数据
	char wbuf[128] = "";
	while(1)
	{
		int res = poll(pfds,2,-1); //阻塞检测集合中是否有事件发生
		if(res == -1)
		{
			perror("poll error");
			return -1;
		}
		else if(res == 0)
		{
			printf("time out\n");
			return -1;
		}

		//程序执行至此,说明检测的文件描述符集合中有事件发生
		
		//判断是否为0号文件描述符产生的事件
		if(pfds[0].revents == POLLIN)
		{
			fgets(wbuf,sizeof(wbuf),stdin); //终端读取
			wbuf[strlen(wbuf)-1] = '\0'; //将换行换成'\0'

			//判断输入的字符串
			if(strcmp(wbuf,"quit") == 0)
			{
				break;
			}

			//将数据发送给服务器
			send(cfd,wbuf,strlen(wbuf),0);

		}

		//判断是否为cfd文件描述符中产生了事件
		if(pfds[1].revents == POLLIN)
		{
			//将字符数组清空
			bzero(wbuf,sizeof(wbuf));
			recv(cfd,wbuf,sizeof(wbuf)-1,0);
			printf("收到的服务器消息为:%s\n",wbuf);

		}
	}

	//5、关闭套接字
	close(cfd);

	return 0;
}

四、select函数实现的TCP并发服务器连接poll函数实现的TCP客户端

五、模拟面试

TCP通信中的三次握手和四次挥手过

三次握手:
1、客户端向服务器发送同步序列编号(SYN)的数据包,表示请求建立连接,并选择一个初始序列号(ISN)。

2、服务器收到客户端的SYN请求后,会发送一个带有SYN和ACK标志的数据包作为应答,确认客户端的SYN,并选择自己的初始序列号,并对客户端的初始序列号进行确认。

3、客户端收到服务器的确认后,会发送一个带有ACK标志的数据包作为确认,表示连接建立成功。

四次挥手:

1、客户端已经发送完所有数据,想要关闭连接,于是发送一个带有FIN标志的数据包给服务器,表示不再发送数据。

2、服务器收到客户端的FIN后,会发送一个带有ACK标志的数据包作为确认,表示已收到关闭请求,但仍可以发送数据。

3、服务器发送完所有数据后,也想要关闭连接,于是发送一个带有FIN标志的数据包给客户端,表示不再发送数据。

4、客户端收到服务器的FIN后,会发送一个带有ACK标志的数据包作为确认,表示已收到关闭请求,连接终止。

并发和并行的区别

并发是指多个任务交替地快速执行,而并行是指多个任务同时执行


并发指的是系统具有同时处理多个任务的能力。在单处理器系统中,通过快速地在不同任务之间进行切换,使得似乎多个任务在几乎同时执行。在多处理器系统中,不同的处理器可以同时执行不同的任务,这也是一种并发。并发一般是通过时间片轮转或者事件驱动的方式实现的。


并行指的是系统真正同时执行多个任务。在单处理器系统中是不可能真正实现并行的,只能通过并发来模拟。但在多处理器系统中,多个处理器可以同时执行多个任务,这就是真正的并行。

阻寒IO和非阻塞IO的区别

阻塞 I/O 会导致程序在进行 I/O 操作时被挂起,而非阻塞 I/O 允许程序在等待 I/O 操作的同时继续执行其他任务

在阻塞 I/O 中,当程序发起一个 I/O 操作时,程序会被阻塞,直到操作完成才能继续执行后续代码。

非阻塞 I/O 允许程序在发起一个 I/O 操作后立即返回,而不会等待操作完成。程序可以继续执行后续代码,然后再通过轮询或者事件通知等方式来检查 I/O 操作是否完成,如果完成则处理结果,否则继续进行其他任务。

同步和异步的区别

同步是顺序执行的,需要等待上一个操作完成才能执行下一个操作;而异步则是并发执行的,可以同时进行多个操作,不需要等待每个操作的完成

在同步操作中,当一个操作被发起后,必须等待这个操作完成后才能继续执行后续的操作。

在异步操作中,当一个操作被发起后,不需要等待这个操作完成,可以继续执行后续的操作。

详细描述IO多路复用的原理

①将多个阻塞任务的文件描述符统一放入一个检测容器中。

②用一个阻塞函数进行管理。

③如果检测容器中有一个或者多个文件描述符对应的事件产生,就去解除阻塞,执行相应函数。

广播的相关内容

在IPv4网络中,广播地址通常是特定的IP地址,网络号+主机号全为1,表示向本地网络中的所有设备发送广播数据包。对发送端套接字设置为允许广播,发送端类似于UDP客户端,接收端类似于UDP服务器端。UDP广播的优点是简单直接,可以方便地向局域网中的所有设备发送消息。

组播的相关内容

组播地址是D类网络地址,需要对接收端设置加入多播组属性。

发送端类似于UDP的客户端,接收端类似于UDP的服务器端,实现的一对多的通信,不同于广播的是范围不同,只有加入多播组的主机才能通信了。

在使用套接字通信时,客户端就一定不需要绑
定操作吗
不一定,对于报式域套接字而言,如果客户端不绑定套接字文件,系统不会自动绑定,可以发送数据,但是服务器端不能向客户端发送消息。
目前学习的进程间通信方式有哪些套接字,共享内存,有名管道,无名管道,信号,信号灯集,消息队列
线程的同步互斥机制

线程的同步互斥机制是指在多线程环境中控制线程之间执行顺序和共享资源访问,以确保线程之间的协调和正确性

同步互斥机制:

互斥锁(Mutex)

互斥锁是最常见的同步机制之一,用于保护临界区(一次只允许一个线程访问的资源或代码段),确保同一时间只有一个线程可以访问被锁定的资源。当一个线程获得了互斥锁后,其他线程需要等待直到该线程释放锁

  • 18
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值