统一声明:
博客转载 声 明 : 本博客部分内容来源于网络、书籍、及各类手册。
        内容宗旨为方便查询、总结备份、开源分享。
        部分转载内容均有注明出处,如有侵权请联系博客告知并删除,谢谢!

百度云盘提取码:统一提取码: ziyu

个人网站入口:http://www.baiziqing.cn/

一、TCP

1.1、网络基础理论知识

1、早期的ARPAnet使用ncp控制协议

2、TCP / IP协议
		TCP:用来检测网络传输中的差错问题。
		IP :用于互联不同网络的主机。
		TCP/IP协议是iniernet网络中的“世界语”

3、网络体系结构:网络层次结构和协议的集合。
			  网络体系结构分为osi模型和tcp/ip模型。	***
			  
			  osi模型7层结构:应用层、表示层、会话层、传输层、网络层、数据链路层、物理层。
			  tcp/ip体系结构:应用层、				  传输层、网络层、网络接口和物理层。
			  
			  应用层:http
			  传输层:tcp, udp
			  网络层: ip, icmp
			  网络接口层(数据链路与物理层): 以太网
			  
4、TCP/IP是协议是传输控制协议,以称为网络通信协议。
		tcp/IP协议协议族:
			TCP:传输控制协议。
			IP:网间协议
			UDP:数据报协议
			
			SMTP:邮件传输协议
			HTTP:超文本传输协议

5、UDP和TCP 	***
	共同点:同为传输层协议
	不同点:
		TCP:面向有连接,可靠(保证数据可靠<数据无失序、无重复、无丢失到达>)
		UDP:面向无连接,不保证数据可靠
		
	TCP:是一种面向连接的传输协议,提供高可靠通信(数据无丢失,数据无误,数据无失序)
		使用场景:对传输质量要求较高,传输大量数据的通信
		
	UDP:用户数据报协议,是不可靠的无连接协议。
		使用场景:  1.传输小尺寸数据
					2.应答困难的网络中(无线网络)
					3.QQ点对点的文件传输和音视频传输
					4.网络电话、流媒体
					5.广播、组播

1.2、编程实现网络通信(搭建服务器与客户端)(tcp)

1、socket:是一个编程接口,也是一种(网卡设备文件的)文件面试符。
	Linux下的网络编程就是套接字编程。

2、socket类型:
	1.流式套接字(SOCK_STREAM):主要用于:TCP传输控制协议
	2.数据报套接字(SOCK_DGRAM):主要用于:UDP传输控制协议
	3.原始套接字(SOCK_RAW):较低层协议,如IP,ICMP直接访问

3、IP地址:(表示主机)
	(1)、网络中标识主机。分为32位(IPV4)和128位(IPV6)
	(2)、以点分形式体现。“192.168.2.6”
	(3)、分类:以二进制的前8位进行区分
		A类:0 ~ 127.255.255.255
		B类:128 ~ 191.255.255.255
		C类:192 ~ 223.255.255.255
		D类:224 ~ 239.255.255.255
		E类:240 ~ 255.255.255.255
			
		网络号	1字节	主机号	3字节	A类
		网络号	2字节	主机号	2字节	B类
		网络号	3字节	主机号	1字节	C类
		
	(4)、将字符串1转换成网络字节序二进制
		intet_aton:将点分形式的字符串转换成 网络字节序二进制。
		intet_addr:将点分形式的字符串转换成 网络字节序二进制,
				   并得到转换后的地址。
		intet_ntoa:将网络字节序二进制转换成 点分形式的字符串。

4、端口号:(区分进程)
	(1)、处理网络数据的进程号
	(2)、端口号:2字节(0~65532)
	
5、字节序:
	主机字节序和网络字节序 
	字节存储方式分为两类:	***
		小端序:(小端存储)低序字节存储在低地址。(pc机通用小端序)
		大端序:(大端存储)高序字节存储在低地址。(网络传输大端序)网络字节序属于大端。
			eg:
				0x112233  :高序字节为11,低序字节为33
											
		(1)、字节序转换:
			小端序转大端序(主机字节序转网络字节序)
				htons、htonl
			大端序转小端序(网络字节序转主机字节序)
				ntohs、ntohl

6.、服务器:也称为下位机软件,目的是为客户端提供1数据交互等服务。
	 客户端:也称为上位机软件,目的是与用户进行直接交互。

7、基于tcp的服务器和客户端编程。		***
	
	查看本地ip地址:ifconfig
	
	(1)、服务器基本框架流程:
		1.创建套接字、<socket>
			(打开网卡设备文件,设置传输控制协议,得到文件描述符,也就是套接字)
			
		2.绑定套接字、<bind>
			(将本机ip地址、端口与套接字进行绑定,告诉接收数据发起者的ip和处理数据的进程号)
			
		3.启动监听、<listen>  
			(启动监听,设置监听(检测是否有客户端连接),设置服务器同一时刻能接收的最大连接请求数)
			
		4.等待接收客户端连接、<accept>
			(阻塞程序,当有客户端发起连接请求,就从队列中按顺序接收客户端连接)
			
		5.数据交互、<read/write、recv/send>
		
		6.关闭套接字、<close>
		
	(2)、客户端流程框架
		1.创建套接字、<socket>
			(打开网卡设备文件,设置传输控制协议,得到文件描述符,也就是套接字)
			
		2.绑定套接字(可选)
			(//填写1 要连接服务器的 ip地址和端口号)
			
		3.建立连接(连接到服务器)、<connect> **
			(发起连接请求,这里需要三次握手)
			
		4.数据交互、<read/write、recv/send>
		
		5.关闭套接字、<close>

8、tcp服务器和客服端实现:

/*搭建tcp服务器端*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <strings.h>

#include <sys/types.h>				
#include <sys/socket.h>
#include <arpa/inet.h>

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <stdlib.h>


int main(int argc, const char *argv[])
{
	int sockfd = 0;	//监听套接字
	int connfd = 0;	//连接套接字
	int ret = 0;

	/*1.创建套接字(tcp)*/
	sockfd = socket(AF_INET, SOCK_STREAM, 0);	//创建;买手机
	if(sockfd == -1)	//监听套接字
	{
		perror("socket");
		return -1;
	}

	/*2.绑定套接字*/
	struct sockaddr_in seraddr;
	seraddr.sin_family = AF_INET;		//协议族
	seraddr.sin_port  = htons(6666);	//端口消息
	seraddr.sin_addr.s_addr = inet_addr("192.168.7.146");

	/*绑定地址消息(关联socket + 地址消息)*/
	ret = bind(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));	//绑定;插卡
	if(ret == -1)
	{
		perror("bind");
		exit(-1);
	}
	else
		printf("bind success\n");

	/*3.启动监听、也就是启动服务器*/
	listen(sockfd, 32);	//监听;开机

	while(1)
	{
		printf("wait for the connection: ");

		/*4.等待接收客户端连接*/
		connfd = accept(sockfd, NULL, NULL);	//等待;接听
		if(connfd == -1)	//连接套接字
		{
			perror("accept");
			exit(-1);
		}
		printf("connect a client\n");

		char buf[512];

		/*5.数据交互*/
		while(1)
		{
			memset(buf, 0, sizeof(buf));	//清空buf
			ret = recv(connfd, buf, sizeof(buf), 0);	//交互;接收
			//buf[ret] = '\0';	//清空buf
			printf("recv: %d bytes, buf: %s\n", ret, buf);
			if(ret <= 0)
			{
				printf("client quit\n");
				break;
			}

		}
		/*6.关闭套接字*/
		close(connfd);//关闭监听套接字;挂断
	}
	close(socket);//关闭连接套接字;挂断

	return 0;
}
/*搭建tcp客户端*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <strings.h>

#include <sys/types.h>				
#include <sys/socket.h>
#include <arpa/inet.h>

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <stdlib.h>

int main(int argc, const char **argv[])
{
	int sockfd = 0;	//监听套接字
	int connfd = 0;	//连接套接字
	int ret = 0;

	/*1.创建套接字(tcp)*/
	sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if(sockfd == -1)	//监听套接字
	{
		perror("socket");
		return -1;
	}

	/*2.绑定套接字*/
	struct sockaddr_in seraddr;
	seraddr.sin_family = AF_INET;		//协议族
	seraddr.sin_port  = htons(6666);	//端口消息
	seraddr.sin_addr.s_addr = inet_addr("192.168.7.146");

	/*3.建立连接*/
	ret = connect(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));	//连接
	if(ret == -1)
	{
		perror("connect");
		exit(-1);
	}
	printf("connect to service\n");

	/*数据交换*/
	char buf[512];
	while(1)
	{
		gets(buf);
		if(*buf == 'q')
		{
			printf("quit\n");
			break;
		}

		send(sockfd, buf, sizeof(buf), 0);//交换
	}

	/*关闭套接字*/
	close(sockfd);

	return 0;
}

/输出功能:客户端连接服务器,客服端输出什么,服务器端返回什么,输入字符q退出/

9、测试工具:
	(1)、TcpUdpDebug
	
	(2)、telnet 命令
		telnet 192.168.7.199 7777
		注意:输入 ctrl + ] + q 退出 

练习:
1.实现一个时间服务器,客户端发送time,服务器返回当前时间。

//搭建TCP服务器				
#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
//搭建TCP服务器
int main(void)
{    
	int sockfd; //监听套接字
	int connfd; //连接套接字
	int ret;
	sockfd = socket(AF_INET,SOCK_STREAM,0);   //创建套接字 (TCP) 

	struct sockaddr_in seraddr;  
	seraddr.sin_family = AF_INET;    //协议族
	seraddr.sin_port   = htons(2222);//端口  
	seraddr.sin_addr.s_addr = inet_addr("192.168.7.199");

	ret = bind(sockfd,(struct sockaddr *)&seraddr,sizeof(seraddr));    //绑定地址信息(关联sockfd + 地址信息)
	if(ret==-1){
		perror("bind");
		exit(-1);
	}
	printf("bind success\n");

	listen(sockfd,32);  //监听
   
	while(1)
	{
		printf("wait for client...\n");
		connfd = accept(sockfd,NULL,NULL);  //建立连接
		if(connfd==-1){
			perror("accept");
			exit(-1);
		}
		printf("connect a client\n");
		char buf[512];
		while(1){

			memset(buf,0,sizeof(buf));
			ret = recv(connfd,buf,sizeof(buf),0); //接收
			if(ret<=0){
				printf("client quit\n");
				break;
			}

			printf("recv:%d bytes,buf:%s\n",ret,buf);
	
			//解析客户端请求  根据请求给响应数据
			
			if(0==strcmp(buf,"get 1.txt")){
			
#if 0
				char data[512];
				FILE *fp =  fopen("1.txt","r");
				ret = fread(data,1,sizeof(data),fp);

				send(connfd,data,ret,0); 
#endif 

#if 1
				time_t val = time(NULL) ;
				char *ptr = ctime(&val);
				char data[512];
				strcpy(data,ptr);
				send(connfd,data,sizeof(data),0); 
#endif 
			}
		}
		close(connfd);    //关闭connfd
	}
	close(sockfd);    //关闭sockfd
	return 0;
}
//搭建TCP客户端				
#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
//搭建TCP服务器
int main(int argc,char **argv)
{    
	int sockfd; //监听套接字
	int connfd; //连接套接字
	int ret;
	sockfd = socket(AF_INET,SOCK_STREAM,0);   //创建套接字 (TCP) 

	struct sockaddr_in seraddr;  
	seraddr.sin_family = AF_INET;    //协议族
	seraddr.sin_port   = htons(2222);//端口  
	seraddr.sin_addr.s_addr = inet_addr("192.168.7.199");

	ret =  connect(sockfd,(struct sockaddr *)&seraddr,sizeof(seraddr));  //连接
	if(ret == -1){
		perror("connect");
		exit(-1);
	}
	printf("connect to service\n");

	char buf[512];
	char data[512];
	while(1){
		
		gets(buf);
		if(*buf=='Q'){
			printf("quit\n");
			break;
		}
		send(sockfd,buf,sizeof(buf),0);

		if(0==strcmp(buf,"time")){
			memset(data,0,sizeof(data));
			ret = recv(sockfd,data,sizeof(data),0);
			if(ret<=0){
				break;
			}
			printf("client_recv:%s\n",data);
		}	
	}
	close(sockfd);
	return 0;
}
10、扩展:	0表示本机地址:
	seraddr.sin_addr.s_addr = inet_addr("0.0.0.0");
	seraddr.sin_addr.s_addr = inet_addr("0");

	设置地址重用:setsokopt
	绑定之前设置:int on = 1;
				setsokopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));

	记录对方地址信息:
					struct sockaddr cliaddr;

					int len = sizeof(cliaddr);
					connfd = accept(sockfd, (struct sockaddr *)&cliaddr, &len);	//等待;接听
					printf("connect a client ip:%s,port:%u \n",inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port) );

11、tcp客户和服务器端架构:

//搭建TCP服务器
				
#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
//搭建TCP服务器
int main(void)
{    
	int sockfd; //监听套接字
	int connfd; //连接套接字
	int ret;
	sockfd = socket(AF_INET,SOCK_STREAM,0);   //创建套接字 (TCP) 

	struct sockaddr_in seraddr;  //服务器地址信息  
	struct sockaddr_in cliaddr;  //记录客户端地址
	seraddr.sin_family = AF_INET;    //协议族
	seraddr.sin_port   = htons(2222);//端口  
	seraddr.sin_addr.s_addr = inet_addr("0");    //0地址代表本机

	int on = 1;
	setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));
	/* 设置地址重用 */
	
	ret = bind(sockfd,(struct sockaddr *)&seraddr,sizeof(seraddr));    //绑定地址信息(关联sockfd + 地址信息)
	if(ret==-1){
		perror("bind");
		exit(-1);
	}
	printf("bind success\n");

	listen(sockfd,32);  //监听

	int len = sizeof(cliaddr);
   
	while(1)
	{
		printf("wait for client...\n");
		connfd = accept(sockfd,(struct sockaddr *)&cliaddr,&len);  //建立连接
		if(connfd==-1){
			perror("accept");
			exit(-1);
		}
		printf("connect a client ip:%s,port:%u \n",inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port) );
		//解析出 客户端的 ip与端口
		char buf[512];
		while(1){
		
			memset(buf,0,sizeof(buf));
			ret = recv(connfd,buf,sizeof(buf),0); //接收
			if(ret<=0){
				printf("client quit\n");
				break;
			}

			printf("recv:%d bytes,buf:%s\n",ret,buf);
			//业务逻辑

			//解析客户端请求 根据请求给响应数据
		}

		close(connfd);    //关闭connfd
	}
	close(sockfd);    //关闭sockfd
	
	return 0;
}
//搭建TCP客户端				
#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
//搭建TCP服务器
int main(int argc,char **argv)
{    
	int sockfd; //监听套接字
	int connfd; //连接套接字
	int ret;
	sockfd = socket(AF_INET,SOCK_STREAM,0);   //创建套接字 (TCP) 

	struct sockaddr_in seraddr;  
	seraddr.sin_family = AF_INET;    //协议族
	seraddr.sin_port   = htons(5555);//端口  
	seraddr.sin_addr.s_addr = inet_addr("192.168.7.172");

	ret =  connect(sockfd,(struct sockaddr *)&seraddr,sizeof(seraddr));  //连接
	if(ret == -1){
		perror("connect");
		exit(-1);
	}
	printf("connect to service\n");

	char buf[512];
	while(1){
		
		gets(buf);
		if(*buf=='Q'){
			printf("quit\n");
			break;
		}
		send(sockfd,buf,sizeof(buf),0);
	}

	close(sockfd);

	return 0;
}

二、UDP

2.1、UDP编程

1、 搭建udp服务器
		socket-->bind-->recvfrom/sendto-->close
	
2、搭建udp客户端
	socket-->bind(可选)-->recvfrom/sendto-->close

3、相关工具编译:
	nc -u 192.168.7.199 7777  // 测试udp服务器
	nc -u 244.10.10.10 7777   // 发送组播消息
//搭建:udp服务器
#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>

int main(void)
{
	int sockfd;
	int ret;
	sockfd = socket(AF_INET,SOCK_DGRAM,0); //创建套接字(udp)

	struct sockaddr_in seraddr,cliaddr;
	seraddr.sin_family = AF_INET;
	seraddr.sin_port = htons(7777);
	seraddr.sin_addr.s_addr = inet_addr("0"); //0地址代表本机
	
	int on=1;
	setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on)); //设置地址重用

	ret = bind(sockfd,(struct sockaddr *)&seraddr,sizeof(seraddr));  //绑定地址信息( sockfd + addr )
	if(ret==-1){
		perror("bind");
		exit(-1);
	}
	char buf[512];
	int len = sizeof(cliaddr);
	while(1){
		
		memset(buf,0,sizeof(buf));
		ret = recvfrom(sockfd,buf,sizeof(buf),0,(struct sockaddr *)&cliaddr,&len);  //接收数据
		if(ret==-1){
			break;
		}
		printf("recv:%d bytes,buf:%s\n",ret,buf);
		
		if(0==strcmp(buf,"time")){
			
			time_t val = time(NULL);
			char *ptr = ctime(&val);
			char data[512];
			strcpy(data,ptr);

			sendto(sockfd,data,sizeof(data),0,(struct sockaddr *)&cliaddr,len);          

		}

		//sendto();
	}

	close(sockfd);  //关闭
	
	return 0;
}
//搭建:udp客户端			
#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
//搭建:udp客户端
int main(int argc,char **argv)
{
	if(argc!=2){
		printf("usage:%s + ip\n",argv[0]);
		exit(-1);
	}
	int sockfd;
	int ret;
	sockfd = socket(AF_INET,SOCK_DGRAM,0); //创建套接字(udp)

	struct sockaddr_in seraddr;
	seraddr.sin_family = AF_INET;
	seraddr.sin_port = htons(7777);
	seraddr.sin_addr.s_addr = inet_addr(argv[1]);
	
	char buf[512];

	while(1){
		gets(buf);
		if(*buf == 'Q'){
			printf("quit\n");
			break;
		}
		sendto(sockfd,buf,sizeof(buf),0,(struct sockaddr *)&seraddr,sizeof(seraddr));
	}
	
	close(sockfd);

	
	return 0;
}

2.2、广播与组播

1 、广播
	发送广播消息:
		1.向广播地址发送 //192.168.7.255
		2.开广播权限

	接收广播消息:
		1.绑定0地址 或 广播地址
		2.端口一致
//接收广播消息			
#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
//接收广播消息
//1.绑定0地址 或 广播地址
//2.端口一致
int main(void)
{
	int sockfd;
	int ret;
	sockfd = socket(AF_INET,SOCK_DGRAM,0); //创建套接字(udp)

	struct sockaddr_in seraddr,cliaddr;
	seraddr.sin_family = AF_INET;
	seraddr.sin_port = htons(7777);
	seraddr.sin_addr.s_addr = inet_addr("0"); //0地址代表本机
	
	int on=1;
	setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on)); //设置地址重用

	ret = bind(sockfd,(struct sockaddr *)&seraddr,sizeof(seraddr));  //绑定地址信息( sockfd + addr )
	if(ret==-1){
		perror("bind");
		exit(-1);
	}
	char buf[512];
	int len = sizeof(cliaddr);
	while(1){
		
		memset(buf,0,sizeof(buf));
		ret = recvfrom(sockfd,buf,sizeof(buf),0,(struct sockaddr *)&cliaddr,&len);  //接收数据
		if(ret==-1){
			break;
		}
		printf("recv:%d bytes,buf:%s\n",ret,buf);
		
		if(0==strcmp(buf,"time")){
			
			time_t val = time(NULL);
			char *ptr = ctime(&val);
			char data[512];
			strcpy(data,ptr);

			sendto(sockfd,data,sizeof(data),0,(struct sockaddr *)&cliaddr,len);          

		}

		//sendto();
	}

	close(sockfd);  //关闭
	
	return 0;
}
//发送广播消息			
#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
//发送广播消息
//1.向广播地址发送 
//2.开广播权限
int main(int argc,char **argv)
{
	if(argc!=2){
		printf("usage:%s + ip\n",argv[0]);
		exit(-1);
	}
	int sockfd;
	int ret;
	sockfd = socket(AF_INET,SOCK_DGRAM,0); //创建套接字(udp)

	struct sockaddr_in seraddr;
	seraddr.sin_family = AF_INET;
	seraddr.sin_port = htons(7777);
	seraddr.sin_addr.s_addr = inet_addr(argv[1]);
	
	/* 打开广播权限 */
	int on = 1;
	setsockopt(sockfd,SOL_SOCKET,SO_BROADCAST,&on,sizeof(on));

	char buf[512];

	while(1){
		gets(buf);
		if(*buf == 'Q'){
			printf("quit\n");
			break;
		}
		ret = sendto(sockfd,buf,sizeof(buf),0,(struct sockaddr *)&seraddr,sizeof(seraddr));
		if(ret==-1){
			perror("sendto");
			exit(-1);
		}
	}
	
	close(sockfd);

	return 0;
}
2、组播
	组播的地址范围224.0.0.0 到 239.255.255.255
	发送组播消息:
		1.向组播地址发送 //224.10.10.10

	接收组播消息:
		1.绑定0地址 或 组播地址
		2.加入多播组
		3.端口一致		
//接收组播消息			
#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
//接收组播消息
//1.绑定0地址 或 组播地址
//2.加入多播组
//3.端口一致
int main(void)
{
	int sockfd;
	int ret;
	sockfd = socket(AF_INET,SOCK_DGRAM,0); //创建套接字(udp)

	struct sockaddr_in seraddr,cliaddr;
	seraddr.sin_family = AF_INET;
	seraddr.sin_port = htons(7777);
	seraddr.sin_addr.s_addr = inet_addr("0"); //0地址代表本机
	
	//加入多播组 224.10.10.10

#if 0
	struct ip_mreqn {
		struct in_addr imr_multiaddr; /* IP multicast group
		address */
		struct in_addr imr_address;   /* IP address of local
		interface */
		int            imr_ifindex;   /* interface index */
	};
#endif
	
	struct ip_mreqn mreq;
	memset(&mreq,0,sizeof(mreq));
	mreq.imr_multiaddr.s_addr = inet_addr("224.10.10.10");   //组播地址
	mreq.imr_address.s_addr   = inet_addr("0");              //本机地址

	setsockopt(sockfd,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mreq,sizeof(mreq));

	int on=1;
	setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on)); //设置地址重用

	ret = bind(sockfd,(struct sockaddr *)&seraddr,sizeof(seraddr));  //绑定地址信息( sockfd + addr )
	if(ret==-1){
		perror("bind");
		exit(-1);
	}
	char buf[512];
	int len = sizeof(cliaddr);
	while(1){
		
		memset(buf,0,sizeof(buf));
		ret = recvfrom(sockfd,buf,sizeof(buf),0,(struct sockaddr *)&cliaddr,&len);  //接收数据
		if(ret==-1){
			break;
		}
		printf("recv:%d bytes,buf:%s\n",ret,buf);
		
		if(0==strcmp(buf,"time")){
			
			time_t val = time(NULL);
			char *ptr = ctime(&val);
			char data[512];
			strcpy(data,ptr);

			sendto(sockfd,data,sizeof(data),0,(struct sockaddr *)&cliaddr,len);          
		}

		//sendto();
	}

	close(sockfd);  //关闭
	
	return 0;
}
//发送组播消息			
#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
//发送组播消息
//1.向组播地址发送  //224.10.10.10

int main(int argc,char **argv)
{
	if(argc!=2){
		printf("usage:%s + ip\n",argv[0]);
		exit(-1);
	}
	int sockfd;
	int ret;
	sockfd = socket(AF_INET,SOCK_DGRAM,0); //创建套接字(udp)

	struct sockaddr_in seraddr;
	seraddr.sin_family = AF_INET;
	seraddr.sin_port = htons(7777);
	seraddr.sin_addr.s_addr = inet_addr(argv[1]);

	char buf[512];

	while(1){
		gets(buf);
		if(*buf == 'Q'){
			printf("quit\n");
			break;
		}
		ret = sendto(sockfd,buf,sizeof(buf),0,(struct sockaddr *)&seraddr,sizeof(seraddr));
		if(ret==-1){
			perror("sendto");
			exit(-1);
		}
	}
	
	close(sockfd);

	
	return 0;
}

练习:
1.完成网络实验二
2.熟悉udp编程,组播,广播
3.复习进程,线程

2.3、setsockopt 的基本用法

/*********************** setsockopt 的基本用法****************/
	/* 设置地址重用 防止异常退出 */
	int on = 1;
	setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));

	/* 打开广播权限 创建广播就下面两句 */
	int on = 1;
	setsockopt(sockfd,SOL_SOCKET,SO_BROADCAST,&on,sizeof(on));
	
	/* 加入多播组 创建多播就下面五句 */
	struct ip_mreqn mreq;
	memset(&mreq,0,sizeof(mreq));
	mreq.imr_multiaddr.s_addr = inet_addr("224.10.10.10");   //组播地址
	mreq.imr_address.s_addr   = inet_addr("0");              //本机地址                       

	setsockopt(sockfd,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mreq,sizeof(mreq));	//退出组播
	
/*********************** setsockopt 的基本用法****************/

2.4、广播与组播

1、广播与组播只能用udp协议.
	tcp与udp
	
		tcp:面向连接(三次握手),保证数据可靠,完整性
		udp:无连接(没有三次握手)		

2、广播		
	广播地址:在局域网内部,都是主机号全为1的形式
	例如: 192.168.7.122.   对应的广播地址: 192.168.7.255
	广播是一对多的服务,从广播服务器发送的数据,
	所有该网段的主机都能收到.
	
		发送端:
		{
			创建套接字. ---socket() //SOCK_DGRAM
			
			绑定地址信息. bind().  struct sockaddr_in;
			
			设置广播地址信息.  struct sockaddr_in.
			
			开启广播服务. setsockopt()
			
			发送广播数据sendto().				
		}
		
		接收端:
		{
			创建套接字. ---socket() //SOCK_DGRAM
			
			绑定地址信息. bind().  struct sockaddr_in; 
			(绑定的端口要和发送端指定的发送端口一致才能接收解析数据)
			(绑定的目的是为了显示自己的端口以及ip信息)
			
			接受广播数据.				
		}
		
3、组播(多播)

	由于广播默认发给所有局域网内的主机,因此容易造成网络风暴.
	解决该问题可以用组播: 只给加入到组播组内的成员发送消息.
	
	D类ip地址通常被用于组播地址:
	224.0.0.1 ~ 239.255.255.255
	但是有的是被预留了,有的是用于互联网,所以这些不能直接使用.
	
	用户可用: 224.0.2.0 ~ 238.255.255.255.(临时使用,全网有效)
			   239.0.0.0 ~ 239.255.255.255.(本地常用,局域网)
	
		组播发送端
		{
			创建套接字. ---socket() //SOCK_DGRAM
			
			绑定地址信息. bind().  struct sockaddr_in;
			
			设置组播地址信息.  struct sockaddr_in.
			
			开启组播服务. setsockopt()   //允许开启组播服务:IP_MULTICAST_IF
			
			发送广播数据sendto()
			
		}
		
		组播接收端
		{
			创建套接字. ---socket() //SOCK_DGRAM
			
			绑定地址信息. bind().  struct sockaddr_in;
			
			加入组播组当中.  setsockopt().  //IP_ADD_MEMBERSHIP 
			
			接收数据.
		}	
            SOL_SOCKET 
	-----------------------------------------------------
	 参数optname            宏的作用         对应参数optaval的类型
	 
	SO_BROADCAST     允许发送广播数据         int
	SO_DEBUG        允许调试            int 
	SO_DONTROUTE     不查找路由            int 
	SO_ERROR        获得套接字错误         int
	SO_KEEPALIVE      保持连接             int 
	SO_LINGER         延迟关闭连接          struct linger 
	SO_OOBINLINE        带外数据放入正常数据流  int 
	SO_RCVBUF       接收缓冲区大小       int
	SO_SNDBUF        发送缓冲区大小      int
	SO_RCVLOWAT      接收缓冲区下限       int
	SO_SNDLOWAT      发送缓冲区下限       int 
	SO_RCVTIMEO      接收超时             struct timeval
	SO_SNDTIMEO       发送超时              struct timeval 
	SO_REUSEADDR    允许重用本地地址和端口     int 
	SO_TYPE          获得套接字类型       int
	SO_BSDCOMPAT     与BSD系统兼容         int
	======================================================
						IPPROTO_IP
	------------------------------------------------------
	IP_ADD_MEMBERSHIP    加入到组播组中               struct ip_mreq 
	IP_MULTICAST_IF      允许开启组播报文的接口       struct ip_mreq
	
	1. select(): 针对所有文件描述符表中的io操作,
			 检测一定时间内是否有任何相关的io操作
	2.setsockopt(listenfd): 检测监听套接字,在一定时间内是否有客户端连接
	3.setsockopt(connfd): 检测通信套接字,在一定时间内是否有客户端发送消息

三、并发服务器

3.1、循环服务器

1、循环服务器:同一时间,只能处理一个客户,通过轮询来处理多个客户的请求
	
2、并发服务器:同一时间,能够处理多个客户

3.2、并发服务器

1、多进程并发服务器
	基本原理: 每连接一个客户端,创建一个子进程,子进程负责处理connfd(客户请求)
	父进程处理sockfd(连接请求)。
	
	//注意:子进程结束时,需要进行资源回收
/*多进程并发服务器*/
#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>

//多进程并发服务器

int tcpser_init(const char *ip,int port);
int do_client(int connfd);
void signal_handler(int sig);

int main(void)
{   
	int connfd;   //连接套接字
	int sockfd;   //监听套接字
	struct sockaddr_in cliaddr;      //记录客户端地址
	int len = sizeof(cliaddr);

	sockfd =  tcpser_init("0",6666); //调用初始化函数 
	signal(SIGCHLD,signal_handler);  //1.注册SIGCHIL信号,设定好信号处理函数
   
	while(1)
	{
		printf("wait for client...\n");
		connfd = accept(sockfd,(struct sockaddr *)&cliaddr,&len);  //建立连接
		if(connfd==-1){
			
			if(errno == EINTR){	//错误码判断,如果是接继续执行不退出
				continue;
			}
			else{
				perror("accept");
				exit(-1);
			}
		}
		printf("connect a client ip:%s,port:%u \n",inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port) );
		
		//每连接一个客户,创建一个子进程
		
		pid_t pid = fork();
		if(pid==0){
			close(sockfd);    	//关闭监听  
			do_client(connfd);  //子进程去处(connfd)理客户请求
			exit(0);           
		}
		else{
			close(connfd);	//关闭连接 
			continue;           //父进程去处(sockfd)理客户连接   
		}
	}
	return 0;
}

/*tcp初始化函数*/
int tcpser_init(const char *ip,int port)
{
	int sockfd; //监听套接字
	int connfd; //连接套接字
	int ret;
	sockfd = socket(AF_INET,SOCK_STREAM,0);   //创建套接字 (TCP) 

	struct sockaddr_in seraddr;  //服务器地址信息  
	seraddr.sin_family = AF_INET;    //协议族
	seraddr.sin_port   = htons(port);//端口  
	seraddr.sin_addr.s_addr = inet_addr(ip);    //0地址代表本机

	int on = 1;
	setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));
	/* 设置地址重用 */
	
	ret = bind(sockfd,(struct sockaddr *)&seraddr,sizeof(seraddr));    //绑定地址信息(关联sockfd + 地址信息)
	if(ret==-1){
		perror("bind");
		exit(-1);
	}
	printf("bind success\n");

	listen(sockfd,32);  //监听

	return sockfd;
}

/*用户处理函数*/
int do_client(int connfd)
{
	//处理客户的 请求与响应
	char buf[512];
	int ret;
	while(1){

		memset(buf,0,sizeof(buf));
		ret = recv(connfd,buf,sizeof(buf),0); //接收
		if(ret<=0){
			printf("client quit\n");
			break;
		}
		printf("recv:%d bytes,buf:%s\n",ret,buf);
		//业务逻辑
	}
}

/*信号处理函数*/
void signal_handler(int sig)
{
	while(waitpid(-1,NULL,WNOHANG)>0);	//当产生晓得僵尸进程时,需要while清理
}
2、多线性并发服务器
	基本原理: 每连接一个客户端,创建一个子线程,子线程负责处理connfd(客户请求)
	主线程处理sockfd(连接请求)。

	//注意:子线程结束时,需要进行资源回收		
/*多线程并发服务器*/					
#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
#include <pthread.h>

//多线程并发服务器
int tcpser_init(const char *ip,int port);
void * do_client(void *arg);

int main(void)
{   
	int connfd;   //连接套接字
	int sockfd;   //监听套接字
	struct sockaddr_in cliaddr;      //记录客户端地址
	int len = sizeof(cliaddr);

	sockfd =  tcpser_init("0",6666); 
	while(1)
	{
		printf("wait for client...\n");
		connfd = accept(sockfd,(struct sockaddr *)&cliaddr,&len);  //建立连接
		if(connfd==-1){
			perror("accept");
			exit(-1);
		}
		printf("connect id:%d a client ip:%s,port:%u \n",connfd,inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port) );
		
		//每连接一个客户,创建一个子线程

		pthread_t tid;
		pthread_create(&tid, NULL,  do_client, (void *)connfd);

	}

	return 0;
}
int tcpser_init(const char *ip,int port)
{
	int sockfd; //监听套接字
	int connfd; //连接套接字
	int ret;
	sockfd = socket(AF_INET,SOCK_STREAM,0);   //创建套接字 (TCP) 

	struct sockaddr_in seraddr;  //服务器地址信息  
	seraddr.sin_family = AF_INET;    //协议族
	seraddr.sin_port   = htons(port);//端口  
	seraddr.sin_addr.s_addr = inet_addr(ip);    //0地址代表本机

	int on = 1;
	setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));
	/* 设置地址重用 */
	

	ret = bind(sockfd,(struct sockaddr *)&seraddr,sizeof(seraddr));    //绑定地址信息(关联sockfd + 地址信息)
	if(ret==-1){
		perror("bind");
		exit(-1);
	}
	printf("bind success\n");

	listen(sockfd,32);  //监听

	return sockfd;
}

void * do_client(void *arg)
{
	pthread_detach( pthread_self() ); //子线程 资源回收
	
	int connfd = (int)arg;
	//处理客户的 请求与响应
	char buf[512];
	int ret;
	while(1){

		memset(buf,0,sizeof(buf));
		ret = recv(connfd,buf,sizeof(buf),0); //接收
		if(ret<=0){
			printf("client quit id:%d\n",connfd);
			close(connfd);
			
			pthread_exit(NULL);
		}

		printf("recv:%d bytes,buf:%s\n",ret,buf);
		//业务逻辑
	}
}

3.3、IO多路复用机制(select、poll、epoll)

基本原理:
先构造一张有关描述符的表,然后调用一个函数。
当这些文件描述符中的一个或多个已准备好进行I/O时函数才返回。
函数返回时告诉进程那个描述符已就绪,可以进行I/O操作。

//相关博客
//https://www.cnblogs.com/shengguorui/p/11949282.html
//https://www.jianshu.com/p/397449cadc9a

select函数
	int select(int nfds, fd_set *readfds, fd_set *writefds,
					  fd_set *exceptfds, struct timeval *timeout);
	/*********************************************************************************  
	* Description:  select机制
	* Input:         nfds: 最大文件描述符值+1
					 readfds: 读事件
					 writefds: 写事件
					 exceptfds:异常事件
					 timeout:超时值
					 
	* Return:        成功返回 准备好的文件描述符个数
					 0:超时返回
	* Others:        -1:出错
	**********************************************************************************/
	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);            //清空
/*通过io多路复用: 实现了 "同时" 读3根管道*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

//通过io多路复用: 实现了 "同时" 读3根管道 
int main(void)
{
	char buf[128];

	int fd1 =  open("f1",O_RDWR);
	int fd2 =  open("f2",O_RDWR);
	int fd3 =  open("f3",O_RDWR);
	
	//1.准备一张表(读事件)
	fd_set readfds;
	fd_set tmpfds;

	//2.把文件描述符添加到 表中
	FD_ZERO(&readfds);     //清空表
	FD_SET(fd1,&readfds);  //添加fd1
	FD_SET(fd2,&readfds);  //添加fd2 
	FD_SET(fd3,&readfds);  //添加fd3
	
	tmpfds = readfds;

	int maxfd = fd3;
	int ret;
	int i;
	while(1){
		
		readfds = tmpfds ; //修正readfds

		ret = select(maxfd+1,&readfds,NULL,NULL,NULL);  //阻塞 轮询检测
		
		//循环 找到准备好的管道
		
		for(i=fd1; i<maxfd+1;i++){
			
			if(FD_ISSET(i,&readfds)){
	   
				//读管道数据
				memset(buf,0,sizeof(buf));
				read(i,buf,sizeof(buf));
				printf("%s\n",buf);

			}
		}
	}

	return 0;
}

练习:
1.熟练掌握并发服务器
2.熟悉select函数的基本使用

四、IO多路复用

IO多路复用
	优点:节约资源
	缺点:代码麻烦,不是并发,而是轮询

4.1、Linux下主要有4种i/o模型

1.1、阻塞I/O:最常用、最简单、效率低			
	
1.2、非阻塞I/O:可防止非阻塞在i/o操作上,需要轮询			

1.3、I/O多路复用:允许同时对多个I/O进行控制		

1.4、信号驱动I/O:一种同步IO,一般通过注册,回调实现

1.5.异步IO:真正的异步IO

//https://www.baidu.com/link?url=EnsmiY8o3v_xZXyFR_APQOqxRHCZrqGY9Zkm4W0uJ_ItTFFy_GuagkPxgBlaBKQXLP79QumrEVGYe-rD2g5I7qK-iOYgGLelWKyM2n5Lot_&wd=&eqid=ec1bce8b000017230000000660efae81

4.2、IO多路复用机制(select、poll、epoll)

基本原理:
	先构造一张有关描述符的表,然后调用一个函数。
	当这些文件描述符中的一个或多个已准备好进行I/O时函数才返回。
	函数返回时告诉进程那个描述符已就绪,可以进行I/O操作。
	
	//https://www.cnblogs.com/Anker/p/3265058.html

1、采用select实现 IO多路复用服务器

/*采用select实现 IO多路复用服务器*/	
#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

//采用select实现 IO多路复用服务器

int tcpser_init(const char *ip,int port);
int do_client(int connfd);

int main(void)
{   
	int connfd;   //连接套接字
	int sockfd;   //监听套接字
	char buf[256];
	struct sockaddr_in cliaddr;      //记录客户端地址
	int len = sizeof(cliaddr);
	int ret;
	sockfd =  tcpser_init("0",6666); 
   
	/*准备一张表*/
	fd_set readfds,tmpfds;

	/*向表中 添加文件描述符 */
	FD_ZERO(&readfds);
	FD_SET(sockfd,&readfds);
	
	tmpfds = readfds;
	
	int maxfd = sockfd;
	int i;

	//select 轮询检测
	while(1){

		readfds = tmpfds;  //修正readfds
		ret =  select(maxfd+1,&readfds,NULL,NULL,NULL);
		if(ret==-1){
			perror("select");
			exit(-1);
		}
		
		for(i=sockfd;i<maxfd+1;i++){
			
			if(FD_ISSET(i,&readfds)){
				
				if(i==sockfd){
					/* 建立连接 (sockfd准备好了) */
					connfd = accept(sockfd,NULL,NULL);  
					printf("connect a client :%d\n",connfd);
					
					/* 把connfd 添加到表中 修正maxfd */
					FD_SET(connfd,&tmpfds);

					if(maxfd<connfd){
						maxfd = connfd; //修正maxfd 
					}

				}
				else{
					printf("connfd:%d 准备好了\n",i);
					/* 处理客户请求 (connfd准备好了)*/
					memset(buf,0,sizeof(buf));
					ret = recv(i,buf,sizeof(buf),0);
					if(ret<=0){
						printf("client quit %d\n",i);
						close(i);
						FD_CLR(i,&tmpfds);
					}
					else{
						printf("recv:%s\n",buf);
					}
					
				}


			}

		}

	}
	return 0;
}
#if 0
	while(1)
	{
		printf("wait for client...\n");
		connfd = accept(sockfd,(struct sockaddr *)&cliaddr,&len);  //建立连接
		if(connfd==-1){

			perror("accept");
			exit(-1);

		}
		printf("connect a client id:%d ip:%s,port:%u \n",connfd,inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port) );			
#endif 
int tcpser_init(const char *ip,int port)
{
	int sockfd; //监听套接字
	int connfd; //连接套接字
	int ret;
	sockfd = socket(AF_INET,SOCK_STREAM,0);   //创建套接字 (TCP) 

	struct sockaddr_in seraddr;  //服务器地址信息  
	seraddr.sin_family = AF_INET;    //协议族
	seraddr.sin_port   = htons(port);//端口  
	seraddr.sin_addr.s_addr = inet_addr(ip);    //0地址代表本机

	int on = 1;
	setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));
	/* 设置地址重用 */
	
	ret = bind(sockfd,(struct sockaddr *)&seraddr,sizeof(seraddr));    //绑定地址信息(关联sockfd + 地址信息)
	if(ret==-1){
		perror("bind");
		exit(-1);
	}
	printf("bind success\n");

	listen(sockfd,32);  //监听

	return sockfd;
	}
}

2、通过io多路复用: 实现了 “同时” 读3根管道 采用poll机制

/*通过io多路复用: 实现了 "同时" 读3根管道  采用poll机制*/		
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <poll.h>

//通过io多路复用: 实现了 "同时" 读3根管道  采用poll机制

int main(void)
{
	char buf[128];

	int fd1 =  open("f1",O_RDWR);
	int fd2 =  open("f2",O_RDWR);
	int fd3 =  open("f3",O_RDWR);
	
	//1.准备一张表 (结构体)
	struct pollfd events[3];   


	//2.把文件描述符添加到 表中
	memset(events,0,sizeof(events));  //清空表
	
	events[0].fd = fd1;         //添加fd1
	events[0].events = POLLIN;   //读事件

	events[1].fd = fd2;         //添加fd2
	events[1].events = POLLIN;   //读事件

	events[2].fd = fd3;         //添加fd3
	events[2].events = POLLIN;   //读事件

	int ret;
	int i;
	while(1){
 
		ret = poll(events,3,-1);
	
		for(i=0;i<3;i++){
			
			if(events[i].revents & POLLIN ){               
				int fd = events[i].fd;
				printf("%d 准备好了\n",fd);

				memset(buf,0,sizeof(buf));
				read(fd,buf,sizeof(buf));
				printf("%s\n",buf);
			}
		}
	}
	return 0;
}

4.3、超时检测。

方法1:设置套接字属性、设置超时值。
/*方法1:设置套接字属性*/			
#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
//超时检测
//方法1:设置套接字属性

int main(void)
{    
	int sockfd; //监听套接字
	int connfd; //连接套接字
	int ret;
	sockfd = socket(AF_INET,SOCK_STREAM,0);   //创建套接字 (TCP) 

	struct sockaddr_in seraddr;  //服务器地址信息  
	struct sockaddr_in cliaddr;  //记录客户端地址
	seraddr.sin_family = AF_INET;    //协议族
	seraddr.sin_port   = htons(6666);//端口  
	seraddr.sin_addr.s_addr = inet_addr("0");    //0地址代表本机

	int on = 1;
	setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));
	/* 设置地址重用 */
	

	ret = bind(sockfd,(struct sockaddr *)&seraddr,sizeof(seraddr));    //绑定地址信息(关联sockfd + 地址信息)
	if(ret==-1){
		perror("bind");
		exit(-1);
	}
	printf("bind success\n");

	listen(sockfd,50);  //监听

	int len = sizeof(cliaddr);
   
	while(1)
	{
		printf("wait for client...\n");
		connfd = accept(sockfd,(struct sockaddr *)&cliaddr,&len);  //建立连接
		if(connfd==-1){
			perror("accept");
			exit(-1);
		}
		printf("connect a client ip:%s,port:%u \n",inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port) );
		//解析出 客户端的 ip与端口

		struct timeval tv={5,0};  //5s								//****
		setsockopt(connfd,SOL_SOCKET,SO_RCVTIMEO,&tv,sizeof(tv));	//****

		//设置接收超时

		char buf[512];
		while(1){

			memset(buf,0,sizeof(buf));
			ret = recv(connfd,buf,sizeof(buf),0); //接收
			if(ret<=0){
				
				if((errno== EAGAIN )|| (errno == EWOULDBLOCK)){		//****
					printf("超时返回\n");							//****
					//continue;										//****
					break;											//****
				}


				printf( "client quit\n");
				break;
			}

			printf("recv:%d bytes,buf:%s\n",ret,buf);
			//业务逻辑
		}

		close(connfd);    //关闭connfd
	}
	close(sockfd);    //关闭sockfd

	return 0;
}
方法2:设置闹钟
/*方法2:设置闹钟*/			
#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
//超时检测
//方法2:设置闹钟

void alarm_handler(int sig)				//****
{
	printf("time out\n");
	alarm(5);
}
int main(void)
{    
	int sockfd; //监听套接字
	int connfd; //连接套接字
	int ret;

	signal(SIGALRM,alarm_handler);		//****

	sockfd = socket(AF_INET,SOCK_STREAM,0);   //创建套接字 (TCP) 

	struct sockaddr_in seraddr;  //服务器地址信息  
	struct sockaddr_in cliaddr;  //记录客户端地址
	seraddr.sin_family = AF_INET;    //协议族
	seraddr.sin_port   = htons(6666);//端口  
	seraddr.sin_addr.s_addr = inet_addr("0");    //0地址代表本机

	int on = 1;
	setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));
	/* 设置地址重用 */
	

	ret = bind(sockfd,(struct sockaddr *)&seraddr,sizeof(seraddr));    //绑定地址信息(关联sockfd + 地址信息)
	if(ret==-1){
		perror("bind");
		exit(-1);
	}
	printf("bind success\n");

	listen(sockfd,50);  //监听

	int len = sizeof(cliaddr);
   
	while(1)
	{
		printf("wait for client...\n");
		connfd = accept(sockfd,(struct sockaddr *)&cliaddr,&len);  //建立连接
		if(connfd==-1){
			perror("accept");
			exit(-1);
		}
		printf("connect a client ip:%s,port:%u \n",inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port) );
		//解析出 客户端的 ip与端口


		char buf[512];
		while(1){

			alarm(5);		//****
			memset(buf,0,sizeof(buf));
			ret = recv(connfd,buf,sizeof(buf),0); //接收
			if(ret<=0){
				printf( "client quit\n");
				break;
			}
			printf("recv:%d bytes,buf:%s\n",ret,buf);
			//业务逻辑   

		}

		close(connfd);    //关闭connfd
	}
	close(sockfd);    //关闭sockfd


	return 0;
}
方法3:在select函数 或poll函数中设置超时值。
/*方法3:在select函数中 设置超时值	*/		
#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

//超时检测
//方法3:在select函数中 设置超时值
int tcpser_init(const char *ip,int port);
int main(void)
{   
	int connfd;   //连接套接字
	int sockfd;   //监听套接字
	char buf[256];
	struct sockaddr_in cliaddr;      //记录客户端地址
	int len = sizeof(cliaddr);
	int ret;
	sockfd =  tcpser_init("0",6666); 
   
	/*准备一张表*/
	fd_set readfds,tmpfds;

	/*向表中 添加文件描述符 */
	FD_ZERO(&readfds);
	FD_SET(sockfd,&readfds);
	
	tmpfds = readfds;
	
	int maxfd = sockfd;
	int i;

	//select 轮询检测
	while(1){
	
		struct timeval tv = {5,0};

		readfds = tmpfds;  //修正readfds
		ret =  select(maxfd+1,&readfds,NULL,NULL,&tv);		//****
		if(ret==0){
			printf("time out\n");
			continue;
		}
		else if(ret==-1){
			perror("select");
			exit(-1);
		}
	
		for(i=sockfd;i<maxfd+1;i++){
			
			if(FD_ISSET(i,&readfds)){                
				if(i==sockfd){
					/* 建立连接 (sockfd准备好了) */
					connfd = accept(sockfd,NULL,NULL);   // 从监听队列中取出一个连接
					printf("connect a client :%d\n",connfd);
					
					/* 把connfd 添加到表中 修正maxfd */
					FD_SET(connfd,&tmpfds);

					if(maxfd<connfd){
						maxfd = connfd; //修正maxfd 
					 }
				}
				else{
					printf("connfd:%d 准备好了\n",i);
					/* 处理客户请求 (connfd准备好了)*/
					memset(buf,0,sizeof(buf));
					ret = recv(i,buf,sizeof(buf),0);
					if(ret<=0){
						printf("client quit %d\n",i);
						close(i);
						FD_CLR(i,&tmpfds);
					}
					else{
						printf("recv:%s\n",buf);
					}      

				} 
			}
		}
	}
	return 0;
}
int tcpser_init(const char *ip,int port)
{
	int sockfd; //监听套接字
	int connfd; //连接套接字
	int ret;
	sockfd = socket(AF_INET,SOCK_STREAM,0);   //创建套接字 (TCP) 

	struct sockaddr_in seraddr;  //服务器地址信息  
	seraddr.sin_family = AF_INET;    //协议族
	seraddr.sin_port   = htons(port);//端口  
	seraddr.sin_addr.s_addr = inet_addr(ip);    //0地址代表本机

	int on = 1;
	setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));
	/* 设置地址重用 */
	

	ret = bind(sockfd,(struct sockaddr *)&seraddr,sizeof(seraddr));    //绑定地址信息(关联sockfd + 地址信息)
	if(ret==-1){
		perror("bind");
		exit(-1);
	}
	printf("bind success\n");

	listen(sockfd,32);  //监听

	return sockfd;

}
方法2和3只是模拟方法1效果,三者不能等同

练习:
熟悉网络超时检测与io多路复用机制

五、数据库操作

7/16 11:00	
结构体3个变量、成员、密码、请求响应
文件拷贝

gcc sqlite3 -lsqlite3

7/16 15:16
创建插入数据库表

7/17 9:20

查看本地ip地址:ifconfig

5.1、数据库操作

1、 数据简介
	SQLite的源代码是C,其源代码完全开放。SQLite第一个Alpha版本诞生于2000年5月。
	他是一个轻量级的嵌入式数据库。

2、 sqlite3基本命令 
	//命令以.开头 
	sqlite3 my.db  		//打开my.db 不存在则创建
	.help
	.database 
	.tables				
	.schema tablename  	//查看表的结构
	.quit				//退出
	.exit 				//退出

3 、sqlite3基本语句(SQL语句)
	//sql语句以;结束
	//注意常见的数据类型:int, float,text(char)

	create table usr (name text, passwd text);  			//创建表
	create table if not exists stuinfo(id int,name text);   //不存在则创建表
	create table usr (name text primary key, passwd text);  //创建表 以name作为主键
	insert into usr values ("jack","ps666");    			//向表中插入数据
	select * from usr;                          			//查询表中数据 
	select * from usr where name="rose" and passwd="ps777"; //按规则查找
	delete from usr where name="rose";                      //删除指定记录项
	update usr set passwd="abc123xyz" where name="lilei";   //更新表
	drop table usr;   										//删除表

4 、sqlite3接口函数
	//注意编译时 指定库文件 -lsqlite3

	sqlite3_open();			//打开	
	sqlite3_close();		//关闭
	sqlite3_exec();			//执行sql语句	
	sqlite3_get_table();	//查询	
/* 1.sqlite3 */	
#include <stdio.h>
#include <sqlite3.h>
#include <stdlib.h>
int main(void)
{
	sqlite3 *db;

	int ret;
	ret = sqlite3_open("my.db",&db);  //打开数据库
	if(ret!=SQLITE_OK){
		printf("open my.db failed\n");
		exit(-1);
	}
	printf("open my.db success\n");

#if 1
	//在数据库中  创建表
	char sql[256]="create table if not exists stuinfo(id int,name text);";

	char *errmsg;
	ret = sqlite3_exec(db,sql,NULL,NULL,&errmsg);
	if(ret!=SQLITE_OK){
		printf("exec failed:%s\n",errmsg);
		exit(-1);
	}
#endif 

#if 0
	//向数据库 插入一条记录

	printf("输入id与名字\n");
	int id;
	char name[32];
	scanf("%d %s",&id,name);
	
	//拼凑sql语句
	char sql[256];
	snprintf(sql,sizeof(sql),"insert into stuinfo values('%d','%s');",id,name);


	char *errmsg;
	ret = sqlite3_exec(db,sql,NULL,NULL,&errmsg);
	if(ret!=SQLITE_OK){
		printf("exec failed:%s\n",errmsg);
		exit(-1);
	}
#endif 

	sqlite3_close(db);   //关闭数据

	return 0;
}
/* 2.sqlite3 */			
#include <stdio.h>
#include <sqlite3.h>
#include <stdlib.h>

//通过回调实现查询

int call_back(void *para, int f_num, char **f_value, char **f_name)
{
	//f_num   : 字段数目 
	//f_value : 结果
	//f_name  : 字段名
	int i;
	for(i=0;i<f_num;i++){
		printf("%s ",f_value[i]);
	}
	printf("\n");

	return 0;
}

int main(void)
{
	sqlite3 *db;

	int ret;
	ret = sqlite3_open("my.db",&db);  //打开数据库
	if(ret!=SQLITE_OK){
		printf("open my.db failed\n");
		exit(-1);
	}
	printf("open my.db success\n");

#if 1
	//在数据库中  创建表
	char sql[256]="select * from stuinfo;";

	char *errmsg;
	ret = sqlite3_exec(db,sql,call_back,NULL,&errmsg);
	if(ret!=SQLITE_OK){
		printf("exec failed:%s\n",errmsg);
		exit(-1);
	}
#endif 

	sqlite3_close(db);   //关闭数据

	return 0;
}
/* 3.sqlite3 */			
#include <stdio.h>
#include <sqlite3.h>
#include <stdlib.h>

//通过get_table实现查询

int main(void)
{
	sqlite3 *db;

	int ret;
	ret = sqlite3_open("my.db",&db);  //打开数据库
	if(ret!=SQLITE_OK){
		printf("open my.db failed\n");
		exit(-1);
	}
	printf("open my.db success\n");

#if 1
	//在数据库中  创建表
	//char sql[256]="select * from stuinfo where id=2 and name='lily';";
	char sql[256]="select * from stuinfo;";
	char *errmsg;
	char **resultp;//结果 
	int nrow;      //行数 
	int ncolumn;   //列数

	ret = sqlite3_get_table(db, sql,&resultp, &nrow,  &ncolumn, &errmsg);
	if(ret!=SQLITE_OK){
		printf("sqlite3_get_table failed:%s\n",errmsg);
		exit(-1);
	}

	printf("nrow:%d,ncolumn:%d\n",nrow,ncolumn);
	
	int i;
	int index=2;
	
	//循环遍历 查看结果
	for(i=0;i<nrow;i++){
		printf("%s : %s \n",resultp[index],resultp[index+1]);
		index = index+2;
	}

#endif 

	sqlite3_close(db);   //关闭数据

	return 0;
}

练习:
1.掌握常用的sql语句
2.熟悉sqlite3编程接口

六、 unix域套接字编程

区分流式与数据报式

6.1、unix域套接字编程(本地进程间通信)

1、UNIX域(流式)套接字服务器端:
	socket-->bind-->listen-->accept-->recv/send-->close;
	
	UNIX域(流式)套接字客户端:
	socket-->bind(可选)-->connect-->send/recv-->close;

2、UNIX域(数据报)套接字服务器端:
	socket-->bind-->recvfrom/sendto-->close
	
	UNIX域(数据报)套接字客户端:
	socket-->bind(可选)-->recvfrom/sendto-->close
	
注意地址信息:
struct sockaddr_un        		//  <sys/un.h>
{
	sa_family_t  sun_family;    //协议族 
	char  sun_path[108];        //套接字文件的路径
};
/*搭建 unix域 流式 服务器*/			
#include <stdio.h>
#include <sys/types.h>      /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/un.h>
//搭建 unix域 流式 服务器
int main(void)
{    
	int sockfd; //监听套接字
	int connfd; //连接套接字
	int ret;
	sockfd = socket(AF_UNIX,SOCK_STREAM,0);   //创建套接字 (本地) 

	struct sockaddr_un seraddr; //服务器地址信息  

	remove("socket");
	seraddr.sun_family = AF_UNIX;         //协议族  本地通信
	strcpy(seraddr.sun_path,"./socket");  //地址    本地文件

	
	ret = bind(sockfd,(struct sockaddr *)&seraddr,sizeof(seraddr));    //绑定地址信息(关联sockfd + 地址信息)
	if(ret==-1){
		perror("bind");
		exit(-1);
	}
	printf("bind success\n");

	listen(sockfd,50);  //监听

	while(1)
	{
		printf("wait for client...\n");
		connfd = accept(sockfd,NULL,NULL);  //建立连接
		if(connfd==-1){
			perror("accept");
			exit(-1);
		}
		printf("connect a client\n");

		char buf[512];
		while(1){

			memset(buf,0,sizeof(buf));
			ret = recv(connfd,buf,sizeof(buf),0); //接收
			if(ret<=0){
				printf("client quit\n");
				break;
			}

			printf("recv:%d bytes,buf:%s\n",ret,buf);


		}

		close(connfd);    //关闭connfd
	}
	close(sockfd);    //关闭sockfd


	return 0;
}
/*搭建 unix 流式套接字 客户端*/			
#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/un.h>
//搭建 unix 流式套接字 客户端
int main(int argc,char **argv)
{    
	int sockfd; //监听套接字
	int connfd; //连接套接字
	int ret;
	sockfd = socket(AF_UNIX,SOCK_STREAM,0);   //创建套接字 (本地) 

	struct sockaddr_un seraddr;
	seraddr.sun_family = AF_UNIX;             //协议族  本地 
	strcpy(seraddr.sun_path,"./socket");      //地址    本地文件


	ret =  connect(sockfd,(struct sockaddr *)&seraddr,sizeof(seraddr));  //连接
	if(ret == -1){
		perror("connect");
		exit(-1);
	}
	printf("connect to service\n");

	char buf[512];
	while(1){
		
		gets(buf);
		if(*buf=='Q'){
			printf("quit\n");
			break;
		}
		send(sockfd,buf,sizeof(buf),0);
	}

	close(sockfd);
	return 0;
}

6.2、抓包分析

抓包分析链接

七、 项目实训

1、项目题目:聊天软件

项目要求:
1.登录注册功能
2.获取在线用户
3.私聊
4.群聊
5.发送离线消息
6.记录聊天数据

涉及知识:
1.Tcp编程或UDP编程
2.Udp广播与组播
3.多进程多线程
4.数据库
5.数据结构
6.进程间通信

2、项目题目:网络词典

项目要求:
1.登录注册功能
2.单词查询
3.历史记录
4.支持多客户端连接

涉及知识:
1.Tcp编程
2.多进程多线程
3.数据库
4.文件操作

3、项目题目:文件服务器(云盘系统)

项目要求:
1.登录注册功能
2.获取服务器上文件列表
3.上传文件与下载文件
4.支持多用户同时登录
5.记录用户上传下载信息
6.支持多客户端连接

涉及知识:
1.TCP编程
2.文件操作
3.多进程多线程
4.数据库

4、项目题目:在线点餐系统

项目要求:
1.用户端实现菜品浏览、点餐、买单、查询等功能
2.管理端实现查看点餐信息、修改、添加新菜品等功能

涉及知识:
1.TCP或UDP编程
2.数据库
3.多进程多线程		

网络编程-调试工具及LinuxC函数手册!

网络编程-调试工具及LinuxC函数手册!
链接:https://pan.baidu.com/s/1jXWeGyPuFlV3WW5p3wezcg
提取码:t9m4

跳转:上一篇,IO进线程编程!

跳转:上一篇,IO进线程编程!

跳转:下一篇,C++编程总结!

跳转:下一篇,C++ 编程!

跳转:开头

GitHub 加速计划 / li / linux-dash
10.39 K
1.2 K
下载
A beautiful web dashboard for Linux
最近提交(Master分支:1 个月前 )
186a802e added ecosystem file for PM2 4 年前
5def40a3 Add host customization support for the NodeJS version 4 年前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐