精华内容
下载资源
问答
  • 解决:error LNK1169: 找到一个或多个多重定义的符号
    2021-05-25 04:06:48

    MemCache超详细解读

    MemCache是什么 MemCache是一个自由.源码开放.高性能.分布式的分布式内存对象缓存系统,用于动态Web应用以减轻数据库的负载.它通过在内存中缓存数据和对象来减少读取数据库的次数,从而提高 ...

    Python在centos下的安装

    1.wget http://www.python.org/ftp/python/2.7.9/Python-2.7.9.tgz默认下载到主目录下 2.tar xzf Python-2.6.6.tgz 3 ...

    LINQ to XML学习笔记

    一.XML基础知识 1.XML:可扩展标记语言 Extensible Markup Language ,提供了一种保存数据的格式,数据可以通过这种格式很容易地在不同的应用程序之间实现共享. 2.使用X ...

    基本DOS命令之 netstat 命令详解

    netstat 命令(查看端口) netstat 命令用于显示与 IP .TCP .UDP 和 ICMP 协议相关的统计数据,一般用于检验本机各端口的网络连接情况,可以使用 netstat 命令查看 ...

    python csv例子

    import csv fieldnames = ['Column1', 'Column2', 'Column3', 'Column4'] rows = [{'Column1': '0', 'Colum ...

    mysql优化-》查询缓存

    使用MySql查询缓存(query_cache_size) 在MySql中查询缓存的原理: 其实是MySql创建了一个临时的空间叫Qcache(这个空间生成在MySql的编译器内存中),这个空间的大小 ...

    C语言通讯录管理系统

    本文转载自:http://blog.csdn.net/hackbuteer1/article/details/6573488 实现了通讯录的录入信息.保存信息.插入.删除.排序.查找.单个显示等功能. ...

    Java的家庭记账本程序(A)

    日期:2019.2.1 博客期:028 星期五 其实我早就开始开发“家庭记账本”的软件了,只不过写博客写的有点晚,我是打算先做web的!因为Android Studio的教程,还是要对应版本,好多问题 ...

    Spring WebSocket初探2 (Spring WebSocket入门教程)<转>

    See more: Spring WebSocket reference整个例子属于WiseMenuFrameWork的一部分,可以将整个项目Clone下来,如果朋友们有需求,我可以整理一个独立的de ...

    apk 反编译 - 最新版图文教程

    apk 反编译 - 最新版图文教程 结合网上众多教程,整理一篇自己操作的,工具都是目前最新版 apk 反编译也就是将打包后的 apk 反编译为资源文件(图片).layout.样式.相关的实现代码等.( ...

    更多相关内容
  • 今天和大家分享一下win7系统连接网络变成多重网络无法上网问题的解决方法,在使用win7系统的过程中经常不知道如何去解决win7系统连接网络变成多重网络无法上网的问题,有什么好的办法去解决win7系统连接网络变成多重...

    今天和大家分享一下win7系统连接网络变成多重网络无法上网问题的解决方法,在使用win7系统的过程中经常不知道如何去解决win7系统连接网络变成多重网络无法上网的问题,有什么好的办法去解决win7系统连接网络变成多重网络无法上网呢?小编教你只需要1、在任务栏上,点击“网络和共享中心”项,然后在网络和Internet面板上,点击“更改适配器设置”; 2、在弹出的“网络连接”对话框上,右键单击“本地连接”项,选择“属性”按钮。接着在网络选项卡下“此连接使用下列选项”处的勾取消掉,仅保留“QOS数据包计划程序”项。然后再点击确定按钮;就可以了。下面小编就给小伙伴们分享一下win7系统连接网络变成多重网络无法上网的详细步骤:

    1、在任务栏上,点击“网络和共享中心”项,然后在网络和Internet面板上,点击“更改适配器设置”;

    e296b565badf523d37b308ac4287fcc6.png

    2、在弹出的“网络连接”对话框上,右键单击“本地连接”项,选择“属性”按钮。接着在网络选项卡下“此连接使用下列选项”处的勾取消掉,仅保留“QOS数据包计划程序”项。然后再点击确定按钮;

    cad26ab21acb64b4288a7d3cc91a1368.png

    3、最后一步再重新连接“宽带连接”,可看到Win7系统上的多重网络已经消失。

    关于Win7系统连接网络后变成多重网络的解决方法就介绍到这,如果你有遇到同样的问题。

    展开全文
  • 如今网络应用随处可见,web、http、email 等这些都是网络应用程序,他们都有着基于相同的基本编程模型,有着相似的整体逻辑结构,并且还有着相同的编程接口。我们需要了解基本的客户端-服务器编程模型。 1.1 客户端...

    一、简介

    如今网络应用随处可见,web、http、email 等这些都是网络应用程序,他们都有着基于相同的基本编程模型,有着相似的整体逻辑结构,并且还有着相同的编程接口。我们需要了解基本的客户端-服务器编程模型。

    1.1 客户端-服务器编程模型
    每个应用程序都是基于客户端-服务器编程模型的,他们由一个服务器进程和多个客户端进程组成,服务器管理某种资源,通过操作这种资源来为客户端提供某种服务。例如ftp服务器管理磁盘文件,为客户端存储和检索。
    客户端-服务器编程模型中的基本操作是事务,一个客户端-服务器事务由以下四步组成

    1. 当一个客户端需要服务时,它向服务器发送一个请求,发起一个事务
    2. 服务器收到请求后,解释它,并以适当的方式操作它的资源。
    3. 服务器给客户端一个响应,并等待下一个请求。
    4. 客户端收到响应并处理它。。
      客户端和服务器是两个进程而不是机器或者主机,这是一个非常重要的,因为一台主机可以有多个不同的客户端和服务器。

    1.2 网络
    通常,客户端和服务器是在不同主机上,他们通过计算机网络硬件和软件资源来通信。网络是一个很复杂的系统(过段时间我会做一个计算机网络的专栏来讲解,这里不大体说一下)。对于操作系统来说,网络只是一个
    I/O设备,是数据源和数据接收方,一个插到I/O总线扩展槽的适配器提供了网络的物理接口,从网络上接收到的数据从适配器经过I/O和内存总线复制到内存,通常是通过DMA传送,当然数据也可以从内存到网络。
    如图:
    在这里插入图片描述

    1.3 两个重要的模型 OSI 和TCP IP 模型
    网络将不同功能分为不同模块,以分层的形式组合在一起,每层实现不同的功能,其内部实现方法对外部来说是透明的,每层向上提供服务,同时使用下层服务。

    	OSI开放系统互联模型
    	应用层		应用程序:FTP、E-mail、Telnet
    	表示层		数据格式定义、数据转换/加密
    	会话层		建立通信进程的逻辑名字与物理名字之间的联系 
    	传输层		差错处理/恢复,流量控制,提供可靠的数据传输
    	网络层		数据分组、路由选择
    	数据链路层	数据组成可发送、接收的帧
    	物理层		传输物理信号、接口、信号形式、速率
    

    TCP/IP协议族:传输控制/网际协议(Transfer Control Protocol/Internet Protocol) 又称作网络通讯协议
    TCP/IP协议是Internet事实上的工业标准,

    	应用层	TFTP,HTTP,SNMP,FTP,SMTP,DNS,Telnet 
    	传输层	TCP,UDP 
    	网络层	IP,ICMP,RIP,OSPF,BGP,IGMP 
    	网络接口与物理层 SLIP,CSLIP,PPP,ARP,RARP,MTU ISO2110,IEEE802.1,EEE802.2 
    

    通信协议模型如下图
    在这里插入图片描述

    数据封装与解封过程如下图
    在这里插入图片描述

    	TCP(Transport Control Protocol)传输控制协议
    	IP(Internetworking Protocol)网间协议
    	UDP(User Datagram Protocol)用户数据报协议
    	SMTP(Simple Mail Transfer Protocol)简单邮件传输协议
    	HTTP(Hypertext Transfer Protocol) 超文本传输协议
    	FTP(File Transfer Protocol)文件传输协议
    	ARP(Address Resolution Protocol)地址解析协议
    

    1.4 IP地址
    IP地址就是一个32位无符号的整数(IPV4)或者128位无符号的整数(IPV6),是因特网中主机的唯一标识。
    ipv4表示形式:点分十进制,192.168.3.178

    	IP地址分类(依据ipv4前八位来区分)
    	A类	  0000 0000 - 0111 1111 0.0.0.1 - 126.255.255.255
    	B类	  1000 0000 - 1011 1111 128.0.0.1 - 191.255.255.255
    	C类	  1100 0000 - 1101 1111 192.0.0.1 - 223.255.255.255
    	D类	  1110 0000 - 1110 1111 224.0.0.1 - 239.255.255.255 表示组播地址(多投点数据传送)
    	E类	  1111 0000 - 1111 1111 240.0.0.1 - 255.x.x.x 属于保留测试
    

    127ip地址是保留回环地址,使用保留地址的网络只能在内部进行通信,而不能与其他网络互连。

    1.5 域名
    因特网客户端和服务器相互通信时使用的是IP地址,后来因特网定义了一组人性化的机制,将域名映射到IP地址,域名是一串用局点分割的单纯(字母、数组、破折号)。这个映射通过一个分布
    世界范围内的数据库(DNS,域名系统)来管理,其每条定义了一组域名和一组IP地址之间的映射。每台主机都有本地定义的域名俗称 localhost ,这个域名总是映射回送地址127.0.0.1。
    一个域名可以和一个ip映射。
    多个域名可以映射同一个IP
    多个域名可以映射同一组的多个IP

    1.6 端口
    客户端和服务器通过在连接上发送和接收字节流来通信,从连接一对进程来说是点对点的,从数据同时可以双向流动来说是全双工的,并且发出去的字符和接收到的字符来说是可靠的。
    一个套接字连接的是一个端点,每个套接字都有相应的套接字地址,是由一个因特网地址和一个16位整数端口组成的,用 “端口” 来表示。
    当客户端发起一个请求时,客服端的端口是由内核自动分配的,称为临时端口,而服务器的端口通常是已分配好的,例如web端口是80,email是25,我们可以在/etc/services来查到。
    通俗点讲为了区分一台主机接收到的数据包应该转交给哪个进程来进行处理,使用端口号来区别。

    1.7 字节序
    字节序是一个处理器架构的特性,用于指示像整数这样的大数据内部的字节如何排序,同一台计算机内进程通信不需要关心这个问题。字节序分为大端序和小端序。
    大端序:最大字节地址出现在最低有效字节上
    小端序:最小字节地址出现在最低有效字节上
    例如一个值:0x04030201,如果用一个指针 p 指向这个值,那么小端 p[0]存储1,p[3]存储4,大端相反。

    1.8 套接字
    套接字是通信端点的抽象,如同文件描述符一样。用来创建网络应用,
    流式套接字(SOCK_STREAM) TCP
    提供了一个面向连接、可靠的数据传输服务,数据无差错、无重复的发送且按发送顺序接收。内设置流量控制,避免数据流淹没慢的接收方。
    数据被看作是字节流,无长度限制。

    	数据报套接字(SOCK_DGRAM) UDP
    		提供无连接服务。数据包以独立数据包的形式被发送,不提供无差错保证,数据可能丢失或重复,顺序发送,可能乱序接收。
    	
    	原始套接字(SOCK_RAW)
    		可以对较低层次协议如IP、ICMP直接访问。使用这个套接字需要程序字节构造协议头部,同时需要超级用户权限。
    

    1.9 UDP和TCP
    相同点:
    同为传输层协议

    不同点:
    tcp:有连接,保证可靠
    udp:无连接,不保证可靠

    TCP(即传输控制协议)
    是一种面向连接的传输层协议,它能提供高可靠性通信(即数据无误、
    数据无丢失、数据无失序、数据无重复到达的通信)

    适用情况:
    适合于对传输质量要求较高,以及传输大量数据的通信。
    在需要可靠数据传输的场合,通常使用TCP协议
    MSN/QQ等即时通讯软件的用户登录账户管理相关的功能通常采用TCP协议

    应用: SMTP 电子邮件
    TELNET 远程终端接入
    HTTP 万维网
    FTP 文件传输

    UDP(User Datagram Protocol)用户数据报协议
    是不可靠的无连接的协议。在数据发送前,因为不需要进行连接,所以
    可以进行高效率的数据传输。

    适用情况:
    发送小尺寸数据(如对DNS服务器进行IP地址查询时)
    在接收到数据,给出应答较困难的网络中使用UDP。(如:无线网络)
    适合于广播/组播式通信中。
    MSN/QQ/Skype等即时通讯软件的点对点文本通讯以及音视频通讯通常采用UDP协议
    流媒体、VOD、VoIP、IPTV等网络多媒体服务中通常采用UDP方式进行实时数据传输

    应用: DNS 域名转换
    TFTP 文件传输
    SNMP 网络管理
    NFS 远程文件服务器

    1.10 网络编程流程
    TCP:
    服务器:
    创建套接字 socket( )
    填充服务器网络信息结构体 sockaddr_in
    将套接字与服务器网络信息结构体绑定 bind( ) 固定自己的信息
    将套接字设置为被动监听状态 listen( )
    阻塞等待客户端的连接请求 accept( )
    进行通信 接收数据recv( )/发送数据 send( )
    关闭套接字close()

    客户端:
    创建套接字 socket( )
    填充服务器网络信息结构体 sockaddr_in
    发送客户端连接请求 connect( )
    进行通信 发送数据 send( )/接收数据recv( )
    关闭套接字close()

    UDP:
    服务器

    创建套接字 socket( )
    填充服务器网络信息结构体 sockaddr_in
    将套接字与服务器网络信息结构体绑定 bind( )
    进行通信 recvfrom( )/sendto( )

    客户端
    创建套接字 socket( )
    填充服务器网络信息结构体 sockaddr_in
    进行通信 sendto( )/recvfrom( )

    二、网络编程函数

    2.1 创建套接字

    	    #include <sys/types.h>          /* See NOTES */
    		#include <sys/socket.h>
    
    		int socket(int domain, int type, int protocol);
    		功能:创建一个套接字
    		参数:
    			domain:通信域,协议族
    					AF_UNIX:本地通信
    					AF_INET:ipv4网络协议
    					AF_INET6:ipv6网络协议
    					AF_PACKET:底层通信协议
    			type:SOCK_STREAM:流式套接字 tcp(面向连接的套接字)
    				  SOCK_DGRAM:数据报套接字 UDP (无连接的套接字)
    				  SOCK_RAW:原始套接字
    				  SOCK_SEQPACKET:固定长度的、有序的、可靠的、面向连接的报文传递。
    			protocol:协议,一般为0
    							如果需要其他协议:
    							IPPROTO_IP:ipv4网际协议
    							IPPROTO_IPV6:ipv6网际协议
    							IPPROTO_ICMP:因特网控制报文协议
    							IPPROTO_RAW:原始IP数据包协议
    							IPPROTO_TCP:传输控制协议
    							IPPROTO_UDP:用户数据报协议			
    		返回值:
    			成功:文件描述符
    			失败:-1
    

    注释:对于数据报(SOCK_DGRAM),两个对等进程之间通信时不需要建立逻辑连接,只需要向对等进程的套接字发送报文。
    字节流(SOCK_STREAM),在交换数据前需要建立连接。
    socket套接字与open相似,使用完之后直接用close关闭即可。

    套机制通信是双向的,我们可以使用shutdown函数来禁止一个套接字的I/O。

    			  #include <sys/socket.h>
    
    			  int shutdown(int sockfd, int how);
    			  参数:sockfd:socket函数返回的fd
    					how:SHUT_RD:关闭读端:无法从套接字读数据
    						 SHUT_WR:关闭写端:无法向套接字写数据
    						 SHUT_RDWR:关闭读写端:既无法读又无法写。
    

    设置允许重用本地地址和端口(最好加上)

    		int on = 1;
    		if (0 > setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)))
    		{
    			perror("setsockopt");
    			return -1;
    		} 
    

    对于无连接的套接字(UDP),数据包到达时可能已经没有了次序,因此如果不能将所有数据放在一个数据包中,则应用程序必须关系数据包的次序,另外无连接的套接字数据包可能会丢失,所以
    如果不可以容忍丢失数据就使用面向连接的套接字(tcp)

    2.2 字节序转换
    一般Linux系统采用小端序,TCP/IP协议采用大端序。使用下面四个函数来处理处理器字节序和网络字节序之间的转换。
    #include <arpa/inet.h>

    	    uint32_t htonl(uint32_t hostlong);
    		返回值:以网络字节序表示的32位整数
    		
    	    uint16_t htons(uint16_t hostshort);
    		返回值:以网络字节序表示的16位整数
    		
    	    uint32_t ntohl(uint32_t netlong);
    		返回值:以主机字节序表示的32位整数
    		
    	    uint16_t ntohs(uint16_t netshort);
    		返回值:以主机字节序表示的16位整数
    		区分:h:主机
    			  n:网络
    			  l:长(4字节)整数
    			  s:短(2字节)整数
    			  
    		#include <sys/socket.h>
    	    #include <netinet/in.h>
    	    #include <arpa/inet.h>
    
    	    int inet_aton(const char *cp, struct in_addr *inp);
    		功能;j将cp字符串转换成32位网络字节序二进制值存放在inp
    		返回值:失败 0  成功 非0 
    		
    	    in_addr_t inet_addr(const char *cp);
    		功能:同上,返回转换后的地址
    
    	    in_addr_t inet_network(const char *cp);
    
    	    char *inet_ntoa(struct in_addr in);
    		功能:将32位网络字节序二进制地址转换为字符串
    
    	    struct in_addr inet_makeaddr(int net, int host);
    
    	    in_addr_t inet_lnaof(struct in_addr in);
    
    	    in_addr_t inet_netof(struct in_addr in);
    

    2.3 关联套接字与地址
    对于服务端需要给一个接收客户端请求的服务器套接字关联一个地址,客户端应有一种方法来发现连接服务器所需的地址,最简单办法就是服务器保留一个地址并注册在/etc/services中。

    		#include <sys/types.h>          /* See NOTES */
    		#include <sys/socket.h>
    
    		int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    		功能:将套接字et与网络信息结构体绑定
    		参数:
    			sockfd:文件描述符,socket的返回值
    			addr:网络信息结构体
    				通用的(一般不用)
    					struct sockaddr {
    						sa_family_t sa_family; 2 bytes
    						char        sa_data[14]; 14 bytes
    					}
    					
    				网络信息结构体:sockaddr_in
    					#include <netinet/in.h>
    					
    					struct sockaddr_in {           
    						u_short sin_family;      // 地址族, AF_INET,2 bytes
    						u_short sin_port;      	 // 端口,2 bytes	htons(9999);
    						struct in_addr sin_addr; 
    							==>
    								struct in_addr
    								{
    									in_addr_t  s_addr; // IPV4地址,4 bytes inet_addr("172.16.6.123");
    								};
    
    						char sin_zero[8];        // 8 bytes unused,作为填充
    					}; 
    			addrlen:addr的长度
    		返回值:
    			成功:0
    			失败:-1	  
    

    用法:

    			struct sockaddr_in serveraddr;
    			
    			serveraddr.sin_family = AF_INET;
    			serveraddr.sin_port = htons(8888);
    			serveraddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    			
    			bind(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));	  
    

    使用限制:

    1. 在进程正在运行的计算机上,指定的地址必须有效,不能指定一个其他机器的地址

    2. 地址必须和创建的套接字时的地址族所支持的格式相匹配

    3. 地址中端口号不得小于1024,

    4. 一个套接字端点只能绑定到一个地址

       	查询绑定到套接字上的地址
       	#include <sys/socket.h>
      
       	int getsockname(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
       	
       	如果已经建立连接使用下面函数查询对方地址
       	int getpeername(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
      

    2.4 建立连接
    如果要处理一个面向连接(tcp)的网络服务,那么在开始数据交换前,需要在请求服务的套接字(客户端)和提供服务的进程套接字(服务端)之间建立连接。

    		#include <sys/types.h>          /* See NOTES */
    	    #include <sys/socket.h>
    
    	    int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    		功能:发送客户端的连接请求
    		参数:
    			sockfd:文件描述符,socket的返回值
    			addr:自己填充的服务器的网络信息结构体,必须与服务器保持一致
    			addrlen:addr的长度
    		返回值:
    			成功:0
    			失败:-1
    

    当尝试连接服务器时,可能会因为一些原因导致连接失败,如果需要链接成功,要保证连接的计算机必须是开启并且运行的,服务器必须绑定到一个与之向连接的地址上,并且服务器等待队列有空间。
    connect还用于无连接网络服务(UDP)

    2.5 建立监听

    		#include <sys/types.h>          /* See NOTES */
    	    #include <sys/socket.h>
    
    	    int listen(int sockfd, int backlog);
    		功能:将套接字设置为被动监听的状态
    		参数:
    			sockfd:文件描述符,socket的返回值
    			backlog:允许同时响应客户端连接请求的个数,最大128,一旦满了就拒绝多余请求,
    		返回值:
    			成功:0
    			失败:-1
    

    2.6 建立连接

    		#include <sys/types.h>          /* See NOTES */
    	    #include <sys/socket.h>
    
    	    int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
    		功能:阻塞等待客户端的连接请求
    		参数:
    			sockfd:文件描述符,socket的返回值
    			addr:获取到的客户端的网络信息结构体
    			addrlen:接收到客户端的addr的长度
    			当不需要知道客户端地址时, addr 和addrlen 可设置为NULL
    		返回值:
    			成功:新的文件描述符(用于通信)
    			失败:-1
    		例子:
    			int acceptfd;
    			struct sockaddr_in clientaddr;
    			
    			acceptfd = accept(sockfd, (struct sockaddr *)&clientaddr, &addrlen);	  
    

    注释:函数返回的文件描述符是套接字描述符,该描述符连接到调用connect的客户端,与原始套接字描述符具有相同的类型和地址族,原始的套接字将继续保持可用状态并接收其他连接请求。
    如果没有连接请求accept将会一直阻塞直到请求来。当然服务器可以使用select和poll来等待请求,这样效率更高,我们下面会说。

    2.7 数据传输
    2.7.1 发送数据
    当发送函数成功返回时并不意味着另一端接收到了数据,只是保证数据已经发送到了网络驱动程序。

    			#include <sys/types.h>
    		    #include <sys/socket.h>
    
    		    ssize_t send(int sockfd, const void *buf, size_t len, int flags);
    			功能:发送数据
    			参数:
    				sockfd:文件描述符
    					服务器:accept的返回值acceptfd
    					客户端:socket的返回值sockfd
    				buf:发送的数据
    				len:buf的长度
    				flags: 一般为0
    						MSG_CONFIRM:提供链路层反馈以保持地址映射有效。
    						MSG_DONTROUTE:勿将数据包路由出本地网络
    						MSG_DONTWAIT:允许非阻塞操作
    						MSG_EOR:发送数据后关闭套接字的发送端
    						MSG_MORE:延迟发送数据包允许写更多数据
    						MSG_NOSIGNAL:在写无连接的套接字时不产生SIGPIPE信号
    						MSG_OOB:如果协议支持,发送外带数据,
    			返回值:
    				成功:发送的数据的长度
    				失败:-1
    
    		    ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
    						  const struct sockaddr *dest_addr, socklen_t addrlen);
    			功能:与send函数一样,但是该函数可以在无连接套接字上指定一个目标地址,常用语udp
    			参数:
    				socket:文件描述符
    				message:发送的数据
    				length:数据的长度
    				flags:标志位,一般为0
    				dest_addr:目的地址(需要知道给谁发送)
    				dest_len:addr的长度
    			返回值:
    				成功:发送的数据的长度
    				失败:-1
    
    		    ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
    			功能:通过带有msghdr结构体里指定多重缓冲区传输数据
    			struct msghdr {
    			   void         *msg_name;       /* optional address */
    			   socklen_t     msg_namelen;    /* size of address */
    			   struct iovec *msg_iov;        /* scatter/gather array */
    			   size_t        msg_iovlen;     /* # elements in msg_iov */
    			   void         *msg_control;    /* ancillary data, see below */
    			   size_t        msg_controllen; /* ancillary data buffer len */
    			   int           msg_flags;      /* flags on received message */
    		    };
    

    2.7.2 接收数据

    		    #include <sys/types.h>
    		    #include <sys/socket.h>
    
    		    ssize_t recv(int sockfd, void *buf, size_t len, int flags);
    		   	功能:接收数据
    			参数:
    				sockfd:文件描述符
    					服务器:accept的返回值acceptfd
    					客户端:socket的返回值sockfd
    				buf:接收的数据
    				len:buf的长度
    				flags: 一般为0阻塞   
    						MSG_CMSG_CLOEXEC:套接字上接收的文件描述符设置执行时关闭的标准
    						MSG_DONTWAIT:非阻塞
    						MSG_ERRQUEUE:接收错误信息作为辅助数据
    						MSG_OOB:获取外带数据
    						MSG_PEEK:返回数据包内容而不真正取走数据包
    						MSG_TRUNC:即使数据包被截断,也返回数据包实际长度
    						MSG_WAITALL:等待直到所有数据可用
    			返回值:
    				成功:接收的数据的长度
    				失败:-1
    				0:发送端关闭文件描述符
    			
    		    ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
    							struct sockaddr *src_addr, socklen_t *addrlen);
    				功能:接收数据
    				参数:
    					sockfd:文件描述符
    					buf:接收的数据
    					len:buf 的长度
    					flags:标志位,一般为0
    					src_addr:源的地址(接收者的信息,自动填充)
    					addrlen:addr的长度
    				返回值:
    					成功:接收的数据的字节数
    					失败:-1
    			
    		    ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
    			功能:将接收到的数据放入多个缓冲区
    

    2.8 带外数据
    带外数据是一些通信协议所支持的可选功能,与普通数据相比它允许更高优先级的数据传输,tcp支持带外数据,udp不支持。tcp将带外数据成为紧急数据,而且仅支持一个字节的紧急数据。

    2.9 TCP demo
    服务端

    			#include<stdio.h>
    			#include<stdlib.h>
    			#include<sys/types.h>
    			#include<sys/socket.h>
    			#include<netinet/in.h>
    			#include<unistd.h>
    			#include<string.h>
    			#include<pthread.h>
    			#include<semaphore.h>
    
    			#define N 512
    			sem_t sem_r;
    			sem_t sem_w;
    			char buf[N];
    			int accept_fd;
    			int ret;
    
    			void *handler_A(void *arg)
    			{
    				sem_wait(&sem_r);
    				while(1) {
    					memset(&buf,0,sizeof(buf));
    					
    					ret = recv(accept_fd,buf,sizeof(buf),0);
    					if(ret < 0) {
    						perror("fail to read");
    						break;
    					}
    					else if(ret == 0){
    						printf("write close\n");
    						break;
    					}
    					else {
    						fputs(buf,stdout);
    					}
    				}
    
    				sem_post(&sem_r);
    
    				pthread_exit((void *)0);
    
    			}
    
    			void *handler_B(void *arg)
    			{
    				sem_wait(&sem_w);
    				
    				while(1) {
    					memset(&buf,0,sizeof(buf));
    
    					fgets(buf,sizeof(buf),stdin);
    
    					if((send(accept_fd,&buf,sizeof(buf),0)) < 0) {
    						perror("fail to write");
    						break;
    					}
    				}
    
    				sem_post(&sem_r);
    
    				pthread_exit((void *)0);
    			}
    
    
    			int main()
    			{
    				int socket_fd;
    				socklen_t addrlen;
    				struct sockaddr_in socket_addr;
    				struct sockaddr_in clientaddr;
    
    				pthread_t thread_A,thread_B;
    
    				addrlen = sizeof(clientaddr);
    
    				socket_fd = socket(AF_INET,SOCK_STREAM,0);
    				if(socket_fd == -1) {
    					perror("fail to socket");
    					exit(1);
    				}
    				printf("socket............\n");
    				memset(&socket_addr,0,sizeof(socket_addr));
    				socket_addr.sin_family = AF_INET;
    				socket_addr.sin_port = htons(8878);
    				socket_addr.sin_addr.s_addr = inet_addr("192.168.1.22");
    
    
    				if((bind(socket_fd,(struct sockaddr *)&socket_addr,sizeof(socket_addr))) == -1) {
    					perror("fail to bind");
    					exit(1);
    				}
    				printf("bind............\n");
    
    				if((listen(socket_fd,5)) == -1) {
    					perror("fail to listen");
    					exit(1);
    				}
    
    				printf("listen..........\n");
    				accept_fd = accept(socket_fd,(struct sockaddr*)&clientaddr,&addrlen);
    				printf("accept:1ip %s port = %hu\n",inet_ntoa(clientaddr.sin_addr),ntohs(clientaddr.sin_port));
    
    				if(sem_init(&sem_r,0,1) < 0) {
    					perror("fail to sem_init");
    					exit(1);
    				}
    				if(sem_init(&sem_w,0,1) < 0) {
    					perror("fail to sem_init");
    					exit(1);
    				}
    
    				if(pthread_create(&thread_A,NULL,handler_A,NULL) != 0) {
    					perror("fail to pthread_create");
    					exit(1);
    				}
    				if(pthread_create(&thread_B,NULL,handler_B,NULL) != 0) {
    					perror("fail to pthread_create");
    					exit(1);
    				}
    
    				pthread_join(thread_A,NULL);
    				pthread_join(thread_B,NULL);
    				sem_destroy(&sem_r);
    				sem_destroy(&sem_w);
    
    				close(socket_fd);
    				close(accept_fd);
    
    			}
    

    客户端

    			#include<stdio.h>
    			#include<stdlib.h>
    			#include<sys/types.h>
    			#include<sys/socket.h>
    			#include<netinet/in.h>
    			#include<unistd.h>
    			#include<string.h>
    			#include<pthread.h>
    			#include<semaphore.h>
    
    			#define N 512
    			char buf[N];
    			sem_t sem_r;
    			sem_t sem_w;
    			int socket_fd;
    			int ret;
    
    			void *handler_A(void *arg)
    			{
    				sem_wait(&sem_r);
    				while(1) {
    					memset(&buf,0,sizeof(buf));
    					
    					ret = recv(socket_fd,buf,sizeof(buf),0);
    					if(ret < 0) {
    						perror("fail to read");
    						break;
    					}
    					else if(ret == 0){
    						printf("write close\n");
    						break;
    					}
    					else {
    						fputs(buf,stdout);
    					}
    				}
    
    				sem_post(&sem_r);
    
    				pthread_exit((void *)0);
    
    			}
    
    			void *handler_B(void *arg)
    			{
    				sem_wait(&sem_w);
    				
    				while(1) {
    					memset(&buf,0,sizeof(buf));
    
    					fgets(buf,sizeof(buf),stdin);
    				//	value = p->data;
    
    					if((send(socket_fd,buf,sizeof(buf),0)) < 0) {
    						perror("fail to write");
    						break;
    					}
    				}
    
    				sem_post(&sem_r);
    
    				pthread_exit((void *)0);
    			}
    
    			int main()
    			{
    				struct sockaddr_in connect_fd;
    				pthread_t thread_A,thread_B;
    				socket_fd = socket(AF_INET,SOCK_STREAM,0);
    
    				if(socket_fd == -1) {
    					perror("fail to socket");
    					exit(1);
    				}
    				printf("socket.............\n");
    				memset(&connect_fd,0,sizeof(connect_fd));
    				connect_fd.sin_family = AF_INET;
    				connect_fd.sin_port = htons(8878);
    				connect_fd.sin_addr.s_addr = inet_addr("192.168.1.22");
    
    				if((connect(socket_fd,(struct sockaddr *)&connect_fd,sizeof(connect_fd))) == -1) {
    					perror("fail to connect");
    					exit(1);
    				}
    				printf("connect............\n");
    
    				if(sem_init(&sem_r,0,1) < 0) {
    					perror("fail to sem_init");
    					exit(1);
    				}
    				if(sem_init(&sem_w,0,1) < 0) {
    					perror("fail to sem_init");
    					exit(1);
    				}
    
    				if(pthread_create(&thread_A,NULL,handler_A,NULL) != 0) {
    					perror("fail to pthread_create");
    					exit(1);
    				}
    				if(pthread_create(&thread_B,NULL,handler_B,NULL) != 0) {
    					perror("fail to pthread_create");
    					exit(1);
    				}
    				
    				pthread_join(thread_A,NULL);
    				pthread_join(thread_B,NULL);
    				sem_destroy(&sem_r);
    				sem_destroy(&sem_w);
    				close(socket_fd);
    			}
    

    2.10 UDP demo
    服务端

    			#include <stdio.h>
    			#include <string.h>
    			#include <stdlib.h>
    			#include <strings.h>
    
    			#include <sys/types.h>
    			#include <sys/socket.h>
    			#include <netinet/in.h>
    			#include <arpa/inet.h>
    
    			int main()
    			{
    				/*1. 创建套接字*/
    				int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    				if (sockfd < 0)
    				{
    					perror("socket");
    					return -1;
    				}
    				printf("socket..............\n");
    
    				/*2. 绑定本机地址和端口*/
    				struct sockaddr_in addr;
    				memset(&addr, 0, sizeof(addr));
    				addr.sin_family 		= AF_INET;
    				addr.sin_port 			= htons(8888);
    				addr.sin_addr.s_addr 	= htonl(INADDR_ANY);
    				if (0 > bind(sockfd, (struct sockaddr*)&addr, \
    							sizeof(addr)))
    				{
    					perror("bind");
    					return -1;
    				}
    				printf("bind..............\n");
    
    				/*3. 数据接收*/
    				struct sockaddr_in cliaddr;
    				socklen_t addrlen = sizeof(cliaddr);
    				char buf[1024];
    				int ret;
    				while (1)
    				{
    					ret = recvfrom(sockfd, buf, sizeof(buf), \
    							0, (struct sockaddr*)&cliaddr, &addrlen);
    					if (0 > ret)
    					{
    						perror("recvfrom");
    						break;
    					}
    					printf("recv: ");
    					fputs(buf, stdout);
    
    					if (0 > sendto(sockfd, buf, sizeof(buf), 0, \
    								(struct sockaddr*)&cliaddr, addrlen))
    					{
    						perror("sendto");
    						break;
    					}
    				}
    
    				/*4. 关闭套接字*/
    				close(sockfd);
    
    				return 0;
    			}
    

    客服端

    			#include <stdio.h>
    			#include <string.h>
    			#include <stdlib.h>
    			#include <strings.h>
    
    			#include <sys/types.h>
    			#include <sys/socket.h>
    			#include <netinet/in.h>
    			#include <arpa/inet.h>
    
    			int main(int argc, const char *argv[])
    			{
    				if (argc < 2)
    				{
    					fprintf(stderr, "Usage: %s <srv_ip>\n", argv[0]);
    					return -1;
    				}
    
    				/*1. 创建套接字*/
    				int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    				if (sockfd < 0)
    				{
    					perror("socket");
    					return -1;
    				}
    				printf("socket..............\n");
    
    				struct sockaddr_in addr;
    				memset(&addr, 0, sizeof(addr));
    				addr.sin_family 		= AF_INET;
    				addr.sin_port 			= htons(8888);
    				addr.sin_addr.s_addr 	= inet_addr(argv[1]);
    
    				/*3. 数据接收*/
    				char buf[1024];
    				int ret;
    				while (1)
    				{
    					printf("send: ");
    					fgets(buf, sizeof(buf), stdin);
    					ret = sendto(sockfd, buf, sizeof(buf), 0, \
    							(struct sockaddr*)&addr, sizeof(addr));
    					if (0 > ret)
    					{
    						perror("recvfrom");
    						break;
    					}
    					
    					ret = recvfrom(sockfd, buf, sizeof(buf), \
    							0, NULL, NULL);
    					if (0 > ret)
    					{
    						perror("recvfrom");
    						break;
    					}
    					printf("recv: ");
    					fputs(buf, stdout);
    				}
    
    				/*4. 关闭套接字*/
    				close(sockfd);
    
    				return 0;
    			}
    

    网络高级编程

    三、I/O模型

    3.1 I/O模型

    1. 阻塞IO:常用、简单、效率低
    2. 非阻塞IO:可防止进程阻塞在IO操作上,需要轮询,极其消费CPU
    3. IO多路复用:允许同时多个IO进行控制(重点)
    4. 信号驱动IO:一种异步通信方式

    3.2 IO多路复用

    1. 为什么使用I/O多路复用?
      如果应用程序处理多路输入输出流,采用阻塞模式的话效率将极其的低,如果采用非阻塞,那么将一直轮询,CPU占用率又会非常高,所以使用I/O多路

    2. IO多路复用基本思想
      先构造一张有关描述符的表,然后调用一个函数,当这些文件描述符中的一个或多个已准备好进行IO时函数才返回,函数返回时告诉进程已经有描述符就绪,可以进行IO操作。

    3. 实现函数select
      select函数可以使我们执行I/O多路转接,通过传给select函数的参数可以告诉内核:
      a.我们所关心的描述符
      b.对于每个描述符我们所关心的条件,是否想从一个给定描述符读/写,是否关心描述符异常
      c.愿意等待多长时间
      也可以通过返回值得到以下信息
      a.已经准备好的文件描述符
      b. 对于读、写、异常者三个条件中每一个,哪些已经准备好
      然后我们就可以使用read和write函数读写。

       		#include<sys/time.h>
       		#include<sys/types.h>
       		#include<unistd.h>
      
       		int select(int nfds,fd_set *read_fds,fd_set *write_fds,fd_set *except_fds,struct timeval *timeout);
       		参数:  nfds 所有监控文件描述符最大的那一个 +1.(因为文件描述符编号从0开始,所以要加1)
       				read_fds 所有可读的文件描述符集合。		没有则为NULL
       				write_fds 所有可写的文件描述符集合。		没有则为NULL
       				except_fds 处于异常条件的文件描述符		没有则为NULL
       				timeval: 超时设置。 NULL:一直阻塞,直到有文件描述符就绪或出错
       									0   :仅仅监测文件描述符集的状态,然后立即返回
       									非0 :在指定时间内,如果没有事件发生,则超时返回
       		返回值:当timeval设置为NULL:返回值 -1 表示出错
       											>0 表示集合中有多少个描述符准备好
       				当设置timeval非0时: 返回值 -1:表示出错
       											>0: 表示集合中有多少描述符准备好
       											=0: 表示时间到了还没有描述符准备好
       											
       		对于fd_set数据类型有以下四种处理方式	 fd:文件描述符、 fdset文件描述符集合
       			void FD_SET(int fd,fd_set *fdset):   将fd加入到fdest        
       			void FD_CLR(int fd,fd_set *fdest):  将fd从fdest里面清除
       			void FD_ZERO(fd_set *fdest):	     从fdest中清除所有文件描述符
       			void FD_ISSET(int fd,fd_set *fdest):判断fd是否在fdest集合中
       		这些接口实现为宏或者函数,调用  FD_ZERO 将fd_set变量的所有位置设置为0,如果要开启描述符集合的某一位,可以调用 FD_SET ,调用FD_CLR 可以清除某一位,FD_ISSET用来检测某一位是否打开。
       		在申明了一个描述符集合之后,必须使用FD_ZERO将其清零,下面是使用操作:
       			fd_set reset;
       			int fd;
       			FD_ZERO(&reset);
       			FD_SET(fd, &reset);
       			FD_ZERO(STDIN_FILENO, &reset);
       			if (FD_ISSET(fd, &reset)) {}
      

      对于“准备好” 这个词这里说明一下,什么才是准备好,什么是没有准备好,如果对读集(read_fds/write_fds) 中的一个描述符进行read/write操作没有阻塞则认为是准备好,或者对except_fds有一个未决异常条件,则认为准备好。
      一个描述符的阻塞并不影响整个select的阻塞。当文件描述符读到文件结尾时候,read返回0.

    4. 实现函数poll
    poll函数与select函数相似,不同的是,poll不是为每个条件(读、写、异常)构造一个文件描述符,而是构造一个pollfd结构数组,每个数组元素指定一个描述符编号,poll函数可以用于任何类型的文件描述符。

    		#include <poll.h>
    
    		int poll(struct pollfd *fds, nfds_t nfds, int timeout);
    		参数:fds:pollfd结构数组
    				struct pollfd {
    				   int   fd;         /* 文件描述符 */
    				   short events;     /* 请求事件 */
    				   short revents;    /* 返回事件 */
    				};
    				events:需要将events设置为以下一个或者多个值,这些值会告诉内核哪些是我们关系的文件描述符
    						POLLIN		不阻塞地读高优先级数据意外的数据
    						POLLRDNORM	不阻塞地读普通数据
    						POLLRDBAND	不阻塞地读优先级数据
    						POLLPRI		不阻塞地读高优先级数据
    						POLLOUT		普不阻塞地读写普通数据
    						POLLWRNORM	同POLLOUT
    						POLLWRBAND	不阻塞地写低优先级数据
    						POLLERR		发生错误
    						POLLHUP		发生挂起(当挂起后就不可以再写该描述符,但是可以读)
    						POLLNVAL	描述字不是一个打开的文件
    				revents:返回的文件描述符,用于说明描述符发生了哪些事件。
    			  nfds:数组中元素数
    			  timeout:等待时间
    						= -1:永远等待,直到有一个描述符准备好,或者捕捉到一个信号,如果捕捉到信号返回-1。
    						= 0 :不等待,立即返回。这是轮询的方法。
    						> 0: 等待的毫秒数,有文件描述符准备好或者timeout超时立即返回。超时返回值为0.
    

    5. 应用demo

    		select 程序
    		#include <stdio.h>
    		#include <string.h>
    		#include <stdlib.h>
    		#include <strings.h>
    
    		#include <sys/types.h>
    		#include <sys/stat.h>
    		#include <fcntl.h>
    		#include <sys/time.h>
    		#include <unistd.h>
    		#include <sys/select.h>
    
    		int main()
    		{
    			int fd = open("/dev/input/mouse1", O_RDONLY);
    			if (fd < 0)
    			{
    				perror("open");
    				return -1;
    			}
    
    			fd_set fds, rfds;
    			FD_ZERO(&fds);    //清空集合
    			FD_SET(0, &fds);  //把键盘加入集合中
    			FD_SET(fd, &fds); //把鼠标加入集合中
    
    			int retval;
    			while (1)
    			{
    				rfds = fds;
    				struct timeval tv = {1, 0};
    				//retval = select(fd+1, &rfds, NULL, NULL, NULL);
    				retval = select(fd+1, &rfds, NULL, NULL, &tv);
    				if (retval < 0)
    				{
    					perror("select");
    					break;
    				}
    				else if (0 == retval)
    				{
    					printf("timeout..........\n");
    					continue;
    				}
    
    				char buf[1024];
    				if (FD_ISSET(0, &rfds))   //判断是否是键盘产生事件
    				{
    					read(0, buf, sizeof(buf));
    					printf("Data from Keyboard!\n");
    				}
    				if (FD_ISSET(fd, &rfds))
    				{
    					read(fd, buf, sizeof(buf));
    					printf("Data from Mouse!\n");
    				}
    			}
    
    			close(fd);
    		}
    			  
    		poll 程序	
    		#include <stdio.h>
    		#include <string.h>
    		#include <stdlib.h>
    		#include <strings.h>
    
    		#include <sys/types.h>
    		#include <sys/stat.h>
    		#include <fcntl.h>
    		#include <sys/time.h>
    		#include <unistd.h>
    		#include <sys/select.h>
    		#include <poll.h>
    
    		int main()
    		{
    			int fd = open("/dev/input/mouse1", O_RDONLY);
    			if (fd < 0)
    			{
    				perror("open");
    				return -1;
    			}
    
    		#if 0
    			fd_set fds, rfds;
    			FD_ZERO(&fds);    //清空集合
    			FD_SET(0, &fds);  //把键盘加入集合中
    			FD_SET(fd, &fds); //把鼠标加入集合中
    		#else
    			struct pollfd fds[2];
    			fds[0].fd = 0;    //键盘
    			fds[0].events = POLLIN; //请求读
    			fds[1].fd = fd;    //鼠标
    			fds[1].events = POLLIN; //请求读
    		#endif
    
    			int retval;
    			while (1)
    			{
    				retval = poll(fds, 2, 1000);
    				if (retval < 0)
    				{
    					perror("poll");
    					break;
    				}
    				else if (0 == retval)
    				{
    					printf("timeout..........\n");
    					continue;
    				}
    
    				char buf[1024];
    				if (fds[0].revents == POLLIN)   //判断是否是键盘产生事件
    				{
    					read(0, buf, sizeof(buf));
    					printf("Data from Keyboard!\n");
    				}
    				if (fds[1].revents == POLLIN)
    				{
    					read(fd, buf, sizeof(buf));
    					printf("Data from Mouse!\n");
    				}
    			}
    
    			close(fd);
    		}
    

    四、服务器模型
    在网络中,通常都是一个服务器多个客户端,为了处理每个客户端不同的请求,服务器程序必须有不同的处理程序,所以就有了服务器模型,常用的服务器模型有两种:
    循环服务器:同一时刻只可以处理一个客户请求
    并发服务器:同一时刻可以处理多个客户请求

    4.1. 循环服务器模型
    服务器运行后等待客户端连接,当接收到一个客户连接后就开始处理请求,处理完之后断开连接。一次只可以处理一个客户端请求,只有处理完当前客户端请求才可以处理下一个客户端请求,如果
    一个客户端一直占有则其他客户端将无法连接,所以一般很少用。

    		socket(...); 
    		bind(...); 
    		listen(...); 
    		while(1) 
    		{    
    			accept(...); 
    			while(1) 
    			{          
    				recv(...); 
    				process(...); 
    				send(...); 
    			}     
    			close(...); 
    		}		  
    

    4.2. 并发服务器
    并发服务器的设计思想服务器接收客户端连接请求后创建子进程来处理客户端服务,但是过多的客户端连接就会有多个子进程,这也会影响服务器效率。
    流程如下:

    			void handler(int sigo)
    			{
    				while (waitpid(-1, NULL, WNOHANG) > 0);	 //一个SIGCHLD可能对应多个僵尸进程,循环收尸
    			}
    			int sockfd = socket(...); 
    			bind(...); 
    			listen(...);
    			signal(SIGCHLD, handler);  //注册SIGCHLD信号,当产生SIGCHLD信号时调用handler函数
    			while(1) { 
    				int connfd = accept(...); 
    				if (fork() = = 0) { 
    					close(sockfd);
    					while(1) 
    					{ 
    						recv(...); 
    						process(...); 
    						send(...); 
    					}
    					close(connfd);           
    					exit(...); 
    				} 
    				close(connfd); 
    			}
    

    五、套接字属性设置与获取
    套接字机制提供了两个套接字接口来控制套接字行为,一个用来获取一个用来查询,可以设置/获取以下三种选项

    1. 通用选项,工作在所有套接字类型上

    2. 在套接字层次管理的选项,但是依赖下层协议支持

    3. 特定于某种协议的选项,每个协议独有的。

       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>
      
       int getsockopt(int sockfd, int level, int optname,
                     void *optval, socklen_t *optlen);
       功能:查看当前选项值
       参数:同setsockopt参数
       返回值:成功0,失败-1	
       			  
       int setsockopt(int sockfd, int level, int optname,
                     const void *optval, socklen_t optlen);
       功能:设置套接字选项
       参数:	sockfd:套接字
       		level指定控制套接字的层次.可以取三种值: 
       			1)SOL_SOCKET:通用套接字选项. 
       			2)IPPROTO_IP:IP选项. 
       			3)IPPROTO_TCP:TCP选项. 
       		optname:以下值
       			 SO_DEBUG			如果*optval非0,则启动网络驱动调试功能
       			 SO_REUSEADDR		如果*optval非0,则重用bind中地址
       			 SO_TYPE			标识套接字类型(仅getsockopt)
       			 SO_ERROR			返回挂起套接字错误并清除
       			 SO_DONTROUTE		如果*optval非0,绕过通常路由
       			 SO_BROADCAST		如果*optval非0,广播数据报
       			 SO_SNDBUF			发送缓冲区的字节长度
       			 SO_SNDTIMEO		套接字发送调用超时值
       			 SO_RCVBUF			接收缓冲区的字节长度
       			 SO_RCVTIMEO		套接字接收调用超时值
       			 SO_KEEPALIVE		如果*optval非0,启用周期性keep-alive报文
       			 SO_OOBINLINE		如果*optval非0,将带外数据放在普通数据中				
       			 SO_LINGER			当有未发送报文而套接字关闭时,延迟时间		
       		optval:0:禁止选项
       				1:开启选项
       		optlen:指向optval参数的大小
       返回值:成功0,失败-1	
       
       设置超时监测:1.struct timeval tv = {1, 0};
       				if (0 > setsockopt(connfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)))
       				{
       					perror("setsockopt");
       					return -1;
       				}		
       			  2. struct timeval tv = {5, 0};
       				 select(fd+1, &rfds, NULL, NULL, &tv);
      

    六、广播和组播
    上面所说的都属于单播,如果发给局域网中所有主机则为广播,只有UDP协议才可以使用广播。
    因为广播可以发送给所有主机,过多广播会占用大量网络宽带,所以组播是将一些主机加入到一个多播组,只有该组的主机可以接收。

    6.1 广播发送流程

    1. 创建用户数据报套接字
    2. 缺省创建套接字不允许广播数据包,需要aetsockopt设置(SO_BROADCAST)属性
    3. 接收方地址定位广播地址
    4. 指定端口信息
    5. 发送数据包

    6.2 广播接收

    1. 创建用户数据报套接字
    2. 绑定IP地址(广播IP或者0.0.0.0)和端口号
    3. 等待接收数据

    6.3 组播发送流程

    1. 创建用户数据报套接字
    2. 接收方地址定位组播地址
    3. 指定端口信息
    4. 发送数据包

    6.4 组播接收流程

    1. 创建用户数据报套接字
    2. 加入多播
    3. 绑定IP地址(广播IP或者0.0.0.0)和端口号
    4. 等待接收数据

    5.5 广播demo
    发送数据

    		#include <stdio.h>
    		#include <string.h>
    		#include <stdlib.h>
    		#include <strings.h>
    
    		#include <sys/types.h>
    		#include <sys/socket.h>
    		#include <netinet/in.h>
    		#include <arpa/inet.h>
    
    		int main(int argc, const char *argv[])
    		{
    			/*1. 创建数据报套接字*/
    			int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    			if (sockfd < 0)
    			{
    				perror("socket");
    				return -1;
    			}
    			printf("socket..............\n");
    
    			/*2. 设置允许发送广播包*/
    			int on = 1;
    			if (0 > setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on)))
    			{
    				perror("setsockopt");
    				return -1;
    			}
    
    			/*3. 指定接收方的地址为广播地址,以及端口号*/
    			struct sockaddr_in addr;
    			memset(&addr, 0, sizeof(addr));
    			addr.sin_family 		= AF_INET;
    			addr.sin_port 			= htons(8888);
    			addr.sin_addr.s_addr 	= inet_addr("192.168.3.255");
    
    			/*4. 发送数据*/
    			char buf[1024];
    			int ret;
    			while (1)
    			{
    				printf("send: ");
    				fgets(buf, sizeof(buf), stdin);
    				ret = sendto(sockfd, buf, sizeof(buf), 0, \
    						(struct sockaddr*)&addr, sizeof(addr));
    				if (0 > ret)
    				{
    					perror("recvfrom");
    					break;
    				}
    			}
    
    			/*4. 关闭套接字*/
    			close(sockfd);
    
    			return 0;
    		}
    

    接收数据

    		#include <stdio.h>
    		#include <string.h>
    		#include <stdlib.h>
    		#include <strings.h>
    
    		#include <sys/types.h>
    		#include <sys/socket.h>
    		#include <netinet/in.h>
    		#include <arpa/inet.h>
    
    		int main()
    		{
    			/*1. 创建套接字*/
    			int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    			if (sockfd < 0)
    			{
    				perror("socket");
    				return -1;
    			}
    			printf("socket..............\n");
    
    			int on = 1;
    			setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
    
    			/*2. 绑定广播地址和端口*/
    			struct sockaddr_in addr;
    			memset(&addr, 0, sizeof(addr));
    			addr.sin_family 		= AF_INET;
    			addr.sin_port 			= htons(8888);
    			addr.sin_addr.s_addr 	= inet_addr("192.168.3.255");
    			if (0 > bind(sockfd, (struct sockaddr*)&addr, \
    						sizeof(addr)))
    			{
    				perror("bind");
    				return -1;
    			}
    			printf("bind..............\n");
    
    			/*3. 数据接收*/
    			struct sockaddr_in cliaddr;
    			socklen_t addrlen = sizeof(cliaddr);
    			char buf[1024];
    			int ret;
    			while (1)
    			{
    				ret = recvfrom(sockfd, buf, sizeof(buf), \
    						0, (struct sockaddr*)&cliaddr, &addrlen);
    				if (0 > ret)
    				{
    					perror("recvfrom");
    					break;
    				}
    				printf("recv: ");
    				fputs(buf, stdout);
    			}
    
    			/*4. 关闭套接字*/
    			close(sockfd);
    
    			return 0;
    		}
    

    六、扩展函数
    6.1 获取/设置主机名称

    	#include <unistd.h>
    
    	int gethostname(char *name, size_t len);
    	int sethostname(const char *name, size_t len);
    

    6.2 获取与套接字相连的远程协议地址

    	#include <sys/socket.h>
    
        int getpeername(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
    

    6.3 获取本地套接字接口协议地址

    	#include <sys/socket.h>
    
        int getsockname(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
    

    6.4 根据主机名获取主机信息

    	#include <netdb.h>
    
        struct hostent *gethostbyname(const char *name);
    

    6.5 根据主机地址获取主机地址

    	#include <sys/socket.h>       /* for AF_INET */
    	
        struct hostent *gethostbyaddr(const void *addr, socklen_t len, int type);
    

    6.6 根据主机协议名获取主机协议信息

    	#include <netdb.h>
    	
    	struct protoent *getprotobyname(const char *name);
    

    6.7 根据协议号获取主机协议信息

    	#include <netdb.h>
    	
        struct protoent *getprotobynumber(int proto);
    
    展开全文
  • 华为C语言编程规范(精华总结)

    万次阅读 多人点赞 2020-03-24 09:48:55
    所有头文件都应当使用#define 防止头文件被多重包含,命名格式为FILENAME_H,为了保证唯一性,更好的命名是PROJECTNAME_PATH_FILENAME_H。 注:没有在宏最前面加上单下划线"_",是因为一般以单下划线"_"和双下划线"_...

    目录

    1、代码总体原则

    2、头文件

    2、函数

    3、标识符命名与定义

    4、变量

    5、宏、常量

    6、表达式

    7、注释

    8、排版与格式

    9、代码编辑编译 


    “编写程序应该以人为本,计算机第二。”                                                               

                                                                                                                                           ——Steve McConnell

    “无缘进华为,但可以用华为的标准要求自己。”                                                               

                                                                                                                                           ——不脱发的程序猿


    1、代码总体原则

    1、清晰第一 

    清晰性是易于维护、易于重构的程序必需具备的特征。代码首先是给人读的,好的代码应当可以像文章一样发声朗诵出来。

    目前软件维护期成本占整个生命周期成本的40%~90%。根据业界经验,维护期变更代码的成本,小型系统是开发期的5倍,大型系统(100万行代码以上)可以达到100倍。业界的调查指出,开发组平均大约一半的人力用于弥补过去的错误,而不是添加新的功能来帮助公司提高竞争力。

    一般情况下,代码的可阅读性高于性能,只有确定性能是瓶颈时,才应该主动优化。

    2、简洁为美

    简洁就是易于理解并且易于实现。代码越长越难以看懂,也就越容易在修改时引入错误。写的代码越多,意味着出错的地方越多,也就意味着代码的可靠性越低。因此,我们提倡大家通过编写简洁明了的代码来提升代码可靠性。

    废弃的代码(没有被调用的函数和全局变量)要及时清除,重复代码应该尽可能提炼成函数。

    3、选择合适的风格,与代码原有风格保持一致

    产品所有人共同分享同一种风格所带来的好处,远远超出为了统一而付出的代价。在公司已有编码规范的指导下,审慎地编排代码以使代码尽可能清晰,是一项非常重要的技能。 如果重构/ / 修改其他风格的代码时,比较明智的做法是根据 现有 代码 的 现有风格继续编写代码,或者使用格式转换工具进行转换成公司内部风格。


    2、头文件

    对于C语言来说,头文件的设计体现了大部分的系统设计。 不合理的头文件布局是编译时间过长的根因,不合理的头文件实际上反映了不合理的设计。

    1、头文件中适合放置接口的声明,不适合放置实现

    头文件是模块(Module)或单元(Unit)的对外接口。头文件中应放置对外部的声明,如对外提供的函数声明、宏定义、类型定义等。

    要求:

    • 内部使用的函数(相当于类的私有方法)声明不应放在头文件中。
    • 内部使用的宏、枚举、结构定义不应放入头文件中。
    • 变量定义不应放在头文件中,应放在.c文件中。
    • 变量的声明尽量不要放在头文件中,亦即尽量不要使用全局变量作为接口。变量是模块或单元的内部实现细节,不应通过在头文件中声明的方式直接暴露给外部,应通过函数接口的方式进行对外暴露。 即使必须使用全局变量,也只应当在.c中定义全局变量,在.h中仅声明变量为全局的。

    2、头文件应当职责单一,切忌依赖复杂

    头文件过于复杂,依赖过于复杂是导致编译时间过长的主要原因。很多现有代码中头文件过大,职责过多,再加上循环依赖的问题,可能导致为了在.c中使用一个宏,而包含十几个头文件。

    错误示例:某平台定义WORD类型的头文件:

    #include <VXWORKS.H>
    #include <KERNELLIB.H>
    #include <SEMLIB.H>
    #include <INTLIB.H>
    #include <TASKLIB.H>
    #include <MSGQLIB.H>
    #include <STDARG.H>
    #include <FIOLIB.H>
    #include <STDIO.H>
    #include <STDLIB.H>
    #include <CTYPE.H>
    #include <STRING.H>
    #include <ERRNOLIB.H>
    #include <TIMERS.H>
    #include <MEMLIB.H>
    #include <TIME.H>
    #include <WDLIB.H>
    #include <SYSLIB.H>
    #include <TASKHOOKLIB.H>
    #include <REBOOTLIB.H>
    …
    typedef unsigned short WORD;
    …

    这个头文件不但定义了基本数据类型WORD,还包含了stdio.h syslib.h等等不常用的头文件。如果工程中有10000个源文件,而其中100个源文件使用了stdio.h的printf,由于上述头文件的职责过于庞大,而WORD又是每一个文件必须包含的,从而导致stdio.h/syslib.h等可能被不必要的展开了9900次,大大增加了工程的编译时间。

    3、头文件应向稳定的方向包含 

    头文件的包含关系是一种依赖,一般来说,应当让不稳定的模块依赖稳定的模块,从而当不稳定的模块发生变化时,不会影响(编译)稳定的模块。

    就我们的产品来说,依赖的方向应该是: 产品依赖于平台,平台依赖于标准库。某产品线平台的代码中已经包含了产品的头文件,导致平台无法单独编译、发布和测试,是一个非常糟糕的反例。除了不稳定的模块依赖于稳定的模块外,更好的方式是两个模块共同依赖于接口,这样任何一个模块的内部实现更改都不需要重新编译另外一个模块。在这里,我们假设接口本身是最稳定的。

    4、每一个 .c 文件应有一个同名 .h 文件,用于声明需要对外公开的接口

    如果一个.c文件不需要对外公布任何接口,则其就不应当存在,除非它是程序的入口,如main函数所在的文件。

    现有某些产品中,习惯一个.c文件对应两个头文件,一个用于存放对外公开的接口,一个用于存放内部需要用到的定义、声明等,以控制.c文件的代码行数。编者不提倡这种风格。这种风格的根源在于源文件过大,应首先考虑拆分.c文件,使之不至于太大。另外,一旦把私有定义、声明放到独立的头文件中,就无法从技术上避免别人include之,难以保证这些定义最后真的只是私有的。

    5、禁止头文件循环依赖

    头文件循环依赖,指a.h包含b.h,b.h包含c.h,c.h包含a.h之类导致任何一个头文件修改,都导致所有包含了a.h/b.h/c.h的代码全部重新编译一遍。而如果是单向依赖,如a.h包含b.h,b.h包含c.h,而c.h不包含任何头文件,则修改a.h不会导致包含了b.h/c.h的源代码重新编译。

    6、  .c/.h文件禁止包含用不到的头文件

    很多系统中头文件包含关系复杂,开发人员为了省事起见,可能不会去一一钻研,直接包含一切想到的头文件,甚至有些产品干脆发布了一个god.h,其中包含了所有头文件,然后发布给各个项目组使用,这种只图一时省事的做法,导致整个系统的编译时间进一步恶化,并对后来人的维护造成了巨大的麻烦。

    7、  头文件应当自包含 

    简单的说,自包含就是任意一个头文件均可独立编译。如果一个文件包含某个头文件,还要包含另外一个头文件才能工作的话,就会增加交流障碍,给这个头文件的用户增添不必要的负担。

    示例:如果a.h不是自包含的,需要包含b.h才能编译,会带来的危害:每个使用a.h头文件的.c文件,为了让引入的a.h的内容编译通过,都要包含额外的头文件b.h。额外的头文件b.h必须在a.h之前进行包含,这在包含顺序上产生了依赖。

    注意:该规则需要与“.c/.h文件禁止包含用不到的头文件”规则一起使用,不能为了让a.h自包含,而在a.h中包含不必要的头文件。a.h要刚刚可以自包含,不能在a.h中多包含任何满足自包含之外的其他头文件。

    8、总是编写内部 #include 保护符( #define  保护)

    多次包含一个头文件可以通过认真的设计来避免。如果不能做到这一点,就需要采取阻止头文件内容被包含多于一次的机制。通常的手段是为每个文件配置一个宏,当头文件第一次被包含时就定义这个宏,并在头文件被再次包含时使用它以排除文件内容。所有头文件都应当使用#define 防止头文件被多重包含,命名格式为FILENAME_H,为了保证唯一性,更好的命名是PROJECTNAME_PATH_FILENAME_H。

    注:没有在宏最前面加上单下划线"_",是因为一般以单下划线"_"和双下划线"__"开头的标识符为ANSIC等使用,在有些静态检查工具中,若全局可见的标识符以"_"开头会给出告警。

    定义包含保护符时,应该遵守如下规则:

    • 保护符使用唯一名称;

    • 不要在受保护部分的前后放置代码或者注释。

    正确示例:假定VOS工程的timer模块的timer.h,其目录为VOS/include/timer/timer.h,应按如下方式保护:

    #ifndef VOS_INCLUDE_TIMER_TIMER_H
    #define VOS_INCLUDE_TIMER_TIMER_H
    ...
    #endif
    
    也可以使用如下简单方式保护:
    
    #ifndef TIMER_H
    #define TIMER_H
    ...
    #endif

    例外情况:头文件的版权声明部分以及头文件的整体注释部分(如阐述此头文件的开发背景、使用注意事项等)可以放在保护符(#ifndef XX_H)前面。

    9、禁止在头文件中定义变量

    在头文件中定义变量,将会由于头文件被其他.c文件包含而导致变量重复定义。

    10、只能通过包含头文件的方式使用其他 .c 提供的接口,禁止在.c 中通过 extern 的方式使用外部函数接口、变量

    若a.c使用了b.c定义的foo()函数,则应当在b.h中声明extern int foo(int input);并在a.c中通过#include <b.h>来使用foo。禁止通过在a.c中直接写extern int foo(int input);来使用foo,后面这种写法容易在foo改变时可能导致声明和定义不一致。

    11、禁止在 extern "C" 中包含头文件

    在extern "C"中包含头文件,会导致extern "C"嵌套,Visual Studio对extern "C"嵌套层次有限制,嵌套层次太多会编译错误。
    在extern "C"中包含头文件,可能会导致被包含头文件的原有意图遭到破坏。

    错误示例:

    extern “C”
    {
    #include “xxx.h”
    ...
    }
    

    正确示例:

    #include “xxx.h”
    extern “C”
    {
    ...
    } 

    12、一个模块通常包含多个 .c 文件,建议放在同一个目录下,目录名即为模块名。为方便外部使用者,建议每一个模块提供一个 .h ,文件名为目录名

    需要注意的是,这个.h并不是简单的包含所有内部的.h,它是为了模块使用者的方便,对外整体提供的模块接口。以Google test(简称GTest)为例,GTest作为一个整体对外提供C++单元测试框架,其1.5版本的gtest工程下有6个源文件和12个头文件。但是它对外只提供一个gtest.h,只要包含gtest.h即可使用GTest提供的所有对外提供的功能,使用者不必关系GTest内部各个文件的关系,即使以后GTest的内部实现改变了,比如把一个源文件c拆成两个源文件,使用者也不必关心,甚至如果对外功能不变,连重新编译都不需要。对于有些模块,其内部功能相对松散,可能并不一定需要提供这个.h,而是直接提供各个子模块或者.c的头文件。

    比如产品普遍使用的VOS,作为一个大模块,其内部有很多子模块,他们之间的关系相对比较松散,就不适合提供一个vos.h。而VOS的子模块,如Memory(仅作举例说明,与实际情况可能有所出入),其内部实现高度内聚,虽然其内部实现可能有多个.c和.h,但是对外只需要提供一个Memory.h声明接口。

    13、如果一个模块包含多个子模块,则建议每一个子模块提供一个对外的 .h,文件名为子模块名

    降低接口使用者的编写难度

    14、头文件不要使用非习惯用法的扩展名,如 .inc 

    目前很多产品中使用了.inc作为头文件扩展名,这不符合c语言的习惯用法。在使用.inc作为头文件扩展名的产品,习惯上用于标识此头文件为私有头文件。但是从产品的实际代码来看,这一条并没有被遵守,一个.inc文件被多个.c包含比比皆是。

    除此之外,使用.inc还导致source insight、Visual stduio等IDE工具无法识别其为头文件,导致很多功能不可用,如“跳转到变量定义处”。虽然可以通过配置,强迫IDE识别.inc为头文件,但是有些软件无法配置,如Visual Assist只能识别.h而无法通过配置识别.inc。

    15、同一产品统一包含头文件排列方式

    常见的包含头文件排列方式:功能块排序、文件名升序、稳定度排序。

    正确示例1:以升序方式排列头文件可以避免头文件被重复包含:

    #include <a.h>
    #include <b.h>
    #include <c/d.h>
    #include <c/e.h>
    #include <f.h>

    正确示例2:以稳定度排序,建议将不稳定的头文件放在前面,如把产品的头文件放在平台的头文件前面:

    #include <product.h>
    #include <platform.h>

    相对来说,product.h修改的较为频繁,如果有错误,不必编译platform.h就可以发现product.h的错误,可以部分减少编译时间。


    2、函数

    函数设计的精髓:编写整洁函数,同时把代码有效组织起来。

    整洁函数要求:代码简单直接、不隐藏设计者的意图、用干净利落的抽象和直截了当的控制语句将函数有机组织起来。

    代码的有效组织包括:逻辑层组织和物理层组织两个方面。逻辑层,主要是把不同功能的函数通过某种联系组织起来,主要关注模块间的接口,也就是模块的架构。物理层,无论使用什么样的目录或者名字空间等,需要把函数用一种标准的方法组织起来。例如:设计良好的目录结构、函数名字、文件组织等,这样可以方便查找。

    1、一个函数仅完成一件功能

    一个函数实现多个功能给开发、使用、维护都带来很大的困难。

    将没有关联或者关联很弱的语句放到同一函数中,会导致函数职责不明确,难以理解,难以测试和改动。

    2、重复代码应该尽可能提炼成函数

    重复代码提炼成函数可以带来维护成本的降低。

    重复代码是我司不良代码最典型的特征之一。在“代码能用就不改”的指导原则之下,大量的烟囱式设计及其实现充斥着各产品代码之中。新需求增加带来的代码拷贝和修改,随着时间的迁移,产品中堆砌着许多类似或者重复的代码。

    项目组应当使用代码重复度检查工具,在持续集成环境中持续检查代码重复度指标变化趋势,并对新增重复代码及时重构。当一段代码重复两次时,即应考虑消除重复,当代码重复超过三次时,应当立刻着手消除重复。

    3、避免函数过长,新增函数不超过 50 行 (非空非注释行) 

    过长的函数往往意味着函数功能不单一,过于复杂。

    函数的有效代码行数,即NBNC(非空非注释行)应当在[1,50]区间。

    例外:某些实现算法的函数,由于算法的聚合性与功能的全面性,可能会超过50行。

    延伸阅读材料: 业界普遍认为一个函数的代码行不要超过一个屏幕,避免来回翻页影响阅读;一般的代码度量工具建议都对此进行检查,例如Logiscope的函数度量:"Number of Statement" (函数中的可执行语句数)建议不超过20行,QA C建议一个函数中的所有行数(包括注释和空白行)不超过50行。

    4、避免函数的代码块嵌套过深,新增函数的代码块嵌套不超过4层

    函数的代码块嵌套深度指的是函数中的代码控制块(例如:if、for、while、switch等)之间互相包含的深度。每级嵌套都会增加阅读代码时的脑力消耗,因为需要在脑子里维护一个“栈”(比如,进入条件语句、进入循环„„)。应该做进一步的功能分解,从而避免使代码的阅读者一次记住太多的上下文。优秀代码参考值:[1, 4]。

    错误示例:代码嵌套深度为5层:

    void serial (void)
    {
        if (!Received)
        {
            TmoCount = 0;
             switch (Buff)
            {
                case AISGFLG:
                    if ((TiBuff.Count > 3)&& ((TiBuff.Buff[0] == 0xff) || (TiBuf.Buff[0] == CurPa.ADDR)))
                    {
                        Flg7E = false;
                        Received = true;
                    }
                    else
                    {
                        TiBuff.Count = 0;
                        Flg7D = false;
                        Flg7E = true;
                    }
                    break;
                default:
                    break;
            }
        }
    }

    5、 可重入函数应避免使用共享变量;若需要使用,则应通过互斥手段(关中断、信号量)对其加以保护

    可重入函数是指可能被多个任务并发调用的函数。在多任务操作系统中,函数具有可重入性是多个任务可以共用此函数的必要条件。共享变量指的全局变量和static变量。编写C语言的可重入函数时,不应使用static局部变量,否则必须经过特殊处理,才能使函数具有可重入性。

    示例:函数square_exam返回g_exam平方值。那么如下函数不具有可重入性。

    int g_exam;
    unsigned int example( int para )
    {
        unsigned int temp;
        g_exam = para; // (**)
        temp = square_exam ( );
        return temp;
    }

    此函数若被多个线程调用的话,其结果可能是未知的,因为当(**)语句刚执行完后,另外一个使用本函数的线程可能正好被激活,那么当新激活的线程执行到此函数时,将使g_exam赋于另一个不同的para值,所以当控制重新回到“temp =square_exam ( )”后,计算出的temp很可能不是预想中的结果。此函数应如下改进。 

    int g_exam;
    unsigned int example( int para )
    {
        unsigned int temp;
        [申请信号量操作] // 若申请不到“信号量”,说明另外的进程正处于
        g_exam = para; //给g_exam赋值并计算其平方过程中(即正在使用此
        temp = square_exam( ); // 信号),本进程必须等待其释放信号后,才可继
        [释放信号量操作] // 续执行。其它线程必须等待本线程释放信号量后
        // 才能再使用本信号。
        return temp;
    }

    6、对参数的合法性检查,由调用者负责还是由接口函数负责,应在项目组/模块内应统一规定。缺省由调用者负责。

    对于模块间接口函数的参数的合法性检查这一问题,往往有两个极端现象,即:要么是调用者和被调用者对参数均不作合法性检查,结果就遗漏了合法性检查这一必要的处理过程,造成问题隐患;要么就是调用者和被调用者均对参数进行合法性检查,这种情况虽不会造成问题,但产生了冗余代码,降低了效率。

    7、对函数的错误返回码要全面处理

    一个函数(标准库中的函数/第三方库函数/用户定义的函数)能够提供一些指示错误发生的方法。这可以通过使用错误标记、特殊的返回数据或者其他手段,不管什么时候函数提供了这样的机制,调用程序应该在函数返回时立刻检查错误指示。

    8、设计高扇入,合理扇出(小于7)的函数

    扇出是指一个函数直接调用(控制)其它函数的数目,而扇入是指有多少上级函数调用它。

    扇出过大,表明函数过分复杂,需要控制和协调过多的下级函数;而扇出过小,例如:总是1,表明函数的调用层次可能过多,这样不利于程序阅读和函数结构的分析,并且程序运行时会对系统资源如堆栈空间等造成压力。通常函数比较合理的扇出(调度函数除外)通常是3~5。 

    扇出太大,一般是由于缺乏中间层次,可适当增加中间层次的函数。扇出太小,可把下级函数进一步分解多个函数,或合并到上级函数中。当然分解或合并函数时,不能改变要实现的功能,也不能违背函数间的独立性。扇入越大,表明使用此函数的上级函数越多,这样的函数使用效率高,但不能违背函数间的独立性而单纯地追求高扇入。公共模块中的函数及底层函数应该有较高的扇入。

    较良好的软件结构通常是顶层函数的扇出较高,中层函数的扇出较少,而底层函数则扇入到公共模块中。

    9、废弃代码(没有被调用的函数和变量) ) 要及时清除

    程序中的废弃代码不仅占用额外的空间,而且还常常影响程序的功能与性能,很可能给程序的测试、维护等造成不必要的麻烦。

    10、函数不变参数使用const 

    不变的值更易于理解/跟踪和分析,把const作为默认选项,在编译时会对其进行检查,使代码更牢固/更安全。

    正确示例:C99标准 7.21.4.4 中strncmp 的例子,不变参数声明为const。

    int strncmp(const char *s1, const char *s2, register size_t n)
    {
        register unsigned char u1, u2;
        while (n-- > 0)
        {
            u1 = (unsigned char) *s1++;
            u2 = (unsigned char) *s2++;
            if (u1 != u2)
            {
                return u1 - u2;
            }
            if (u1 == '\0')
            {
                return 0;
            }
        }
        return 0;
    }

    11、函数应避免使用全局变量、静态局部变量和 I/O 操作,不可避免的地方应集中使用

    带有内部“存储器”的函数的功能可能是不可预测的,因为它的输出可能取决于内部存储器(如某标记)的状态。这样的函数既不易于理解又不利于测试和维护。在C语言中,函数的static局部变量是函数的内部存储器,有可能使函数的功能不可预测。

    错误示例:如下函数,其返回值(即功能)是不可预测的。

    unsigned int integer_sum( unsigned int base )
    {
        unsigned int index;
        static unsigned int sum = 0;// 注意,是static类型的。
        // 若改为auto类型,则函数即变为可预测。
        for (index = 1; index <= base; index++)
        {
            sum += index;
        }
        return sum;
    }

    12、检查函数所有非参数输入的有效性,如数据文件、公共变量等

    函数的输入主要有两种:一种是参数输入;另一种是全局变量、数据文件的输入,即非参数输入。函数在使用输入参数之前,应进行有效性检查。

    13、 函数的参数个数不超过5个

    函数的参数过多,会使得该函数易于受外部(其他部分的代码)变化的影响,从而影响维护工作。函数的参数过多同时也会增大测试的工作量。

    函数的参数个数不要超过5个,如果超过了建议拆分为不同函数。

    14、除打印类函数外,不要使用可变长参函数。

    可变长参函数的处理过程比较复杂容易引入错误,而且性能也比较低,使用过多的可变长参函数将导致函数的维护难度大大增加。

    15、在源文件范围内声明和定义的所有函数,除非外部可见,否则应该增加static关键字

    如果一个函数只是在同一文件中的其他地方调用,那么就用static声明。使用static确保只是在声明它的文件中是可见的,并且避免了和其他文件或库中的相同标识符发生混淆的可能性。

    正确示例:建议定义一个STATIC宏,在调试阶段,将STATIC定义为static,版本发布时,改为空,以便于后续的打热补丁等操作。

    #ifdef _DEBUG
    #define STATIC static
    #else
    #define STATIC
    #endif

    3、标识符命名与定义

    标识符的命名规则历来是一个敏感话题,典型的命名风格如unix风格、windows风格等,从来无法达成共识。实际上,各种风格都有其优势也有其劣势,而且往往和个人的审美观有关。我们对标识符定义主要是为了让团队的代码看起来尽可能统一,有利于代码的后续阅读和修改,产品可以根据自己的实际需要指定命名风格,规范中不再做统一的规定。

    1、标识符的命名要清晰、明了,有明确含义,同时使用完整的单词或大家基本可以理解的缩写,避免使人产生误解

    尽可能给出描述性名称,不要节约空间,让别人很快理解你的代码更重要。

    正确示例:

    int error_number;
    int number_of_completed_connection;

    错误示例:

    int n;
    int nerr;
    int n_comp_conns;

    2、除了常见的通用缩写以外,不使用单词缩写,不得使用汉语拼音

    较短的单词可通过去掉“元音”形成缩写,较长的单词可取单词的头几个字母形成缩写,一些单词有大家公认的缩写,常用单词的缩写必须统一。协议中的单词的缩写与协议保持一致。对于某个系统使用的专用缩写应该在注视或者某处做统一说明。

    正确示例:一些常见可以缩写的例子:

    argument 可缩写为 arg
    buffer 可缩写为 buff
    clock 可缩写为 clk
    command 可缩写为 cmd
    compare 可缩写为 cmp
    configuration 可缩写为 cfg
    device 可缩写为 dev
    error 可缩写为 err
    hexadecimal 可缩写为 hex
    increment 可缩写为 inc
    initialize 可缩写为 init
    maximum 可缩写为 max
    message 可缩写为 msg
    minimum 可缩写为 min
    parameter 可缩写为 para
    previous 可缩写为 prev
    register 可缩写为 reg
    semaphore 可缩写为 sem
    statistic 可缩写为 stat
    synchronize 可缩写为 sync
    temp 可缩写为 tmp

    3、产品/项目组内部应保持统一的命名风格

    Unix like和windows like风格均有其拥趸,产品应根据自己的部署平台,选择其中一种,并在产品内部保持一致。

    4、用正确的反义词组命名具有互斥意义的变量或相反动作的函数等

    正确示例:

    add/remove begin/end create/destroy
    insert/delete first/last get/release
    increment/decrement put/get add/delete
    lock/unlock open/close min/max
    old/new start/stop  next/previous
    source/target show/hide  send/receive
    source/destination copy/paste up/down

    5、尽量避免名字中出现数字编号,除非逻辑上的确需要编号

    错误示例:如下命名,使人产生疑惑。

    #define EXAMPLE_0_TEST_
    #define EXAMPLE_1_TEST_

    正确示例:应改为有意义的单词命名。

    #define EXAMPLE_UNIT_TEST_
    #define EXAMPLE_ASSERT_TEST_

    6、标识符前不应添加模块、项目、产品、部门的名称作为前缀

    很多已有代码中已经习惯在文件名中增加模块名,这种写法类似匈牙利命名法,导致文件名不可读,并且带来带来如下问题:

    • 第一眼看到的是模块名,而不是真正的文件功能,阻碍阅读;
    • 文件名太长;
    • 文件名和模块绑定,不利于维护和移植。若foo.c进行重构后,从a模块挪到b模块,若foo.c
    • 中有模块名,则需要将文件名从a_module_foo.c改为b_module_foo.c。

    7、平台/ / 驱动等适配代码的标识符命名风格保持和平台

    涉及到外购芯片以及配套的驱动,这部分的代码变动(包括为产品做适配的新增代码),应该保持原有的风格。

    8、重构/修改部分代码时,应保持和原有代码的命名风格一致

    根据源代码现有的风格继续编写代码,有利于保持总体一致。

    9、文件命名统一采用小写字符

    因为不同系统对文件名大小写处理会不同(如MS的DOS、Windows系统不区分大小写,但是Linux系统则区分),所以代码文件命名建议统一采用全小写字母命名。

    10、全局变量应增加“g_” 前缀,静态变量应增加“s_”

    首先,全局变量十分危险,通过前缀使得全局变量更加醒目,促使开发人员对这些变量的使用更加小心。

    其次,从根本上说,应当尽量不使用全局变量,增加g_和s_前缀,会使得全局变量的名字显得很丑陋,从而促使开发人员尽量少使用全局变量。

    11、禁止使用单字节命名变量,但 允许 定义i 、j、k作为局部循环变量

    12、 不建议使用匈牙利命名法

    匈牙利命名法是一种编程时的命名规范。基本原则是:变量名=属性+类型+对象描述。匈牙利命名法源于微软,然而却被很多人以讹传讹的使用。而现在即使是微软也不再推荐使用匈牙利命名法。历来对匈牙利命名法的一大诟病,就是导致了变量名难以阅读,这和本规范的指导思想也有冲突,所以本规范特意强调,变量命名不应采用匈牙利命名法,而应该想法使变量名为一个有意义的词或词组,方便代码的阅读。

    变量命名需要说明的是变量的含义,而不是变量的类型。在变量命名前增加类型说明,反而降低了变量的可读性;更麻烦的问题是,如果修改了变量的类型定义,那么所有使用该变量的地方都需要修改。

    13、使用名词或者形容词+名词方式命名变量 

    14、函数命名应以函数要执行的动作命名,一般采用动词或者动词+名词的结构

    正确示例:找到当前进程的当前目录:

    DWORD GetCurrentDirectory( DWORD BufferLength, LPTSTR Buffer );

    15、函数指针除了前缀,其他按照函数的命名规则命名

    16、对于数值或者字符串等等常量的定义,建议采用全大写字母,单词之间加下划线“_”的方式命名(枚举同样建议使用此方式定义)

    正确示例:

    #define PI_ROUNDED 3.14

    17、除了头文件或编译开关等特殊标识定义,宏定义不能使用下划线“_”开头和结尾

    一般来说,‟_‟开头、结尾的宏都是一些内部的定义,ISO/IEC 9899(俗称C99)中有如下的描述(6.10.8 Predefined macro names):

    None of these macro names (这里上面是一些内部定义的宏的描述),nor the identifier defined,shall be the subject of a #define or a #undef preprocessing directive.Any other predefined macro names shall begin with a leading underscore fol lowedby an uppercase letter ora second underscore.


    4、变量

    1、一个变量只有一个功能,不能把一个变量用作多种用途

    一个变量只用来表示一个特定功能,不能把一个变量作多种用途,即同一变量取值不同时,其代表的意义也不同。

    错误示例:具有两种功能的反例

    WORD DelRelTimeQue( void )
    {
        WORD Locate;
        Locate = 3; 
        Locate = DeleteFromQue(Locate); /* Locate具有两种功能:位置和函数DeleteFromQue的返回值 */
        return Locate;
    }

    正确做法:使用两个变量

    WORD DelRelTimeQue( void )
    {
        WORD Ret;
        WORD Locate;
        Locate = 3;
        Ret  = DeleteFromQue(Locate);
        return Ret;
    }

    2、结构功能单一,不要设计面面俱到的数据结构 

    相关的一组信息才是构成一个结构体的基础,结构的定义应该可以明确的描述一个对象,而不是一组相关性不强的数据的集合。设计结构时应力争使结构代表一种现实事务的抽象,而不是同时代表多种。结构中的各元素应代表同一事务的不同侧面,而不应把描述没有关系或关系很弱的不同事务的元素放到同一结构中。

    错误示例:如下结构不太清晰、合理。

    typedef struct STUDENT_STRU
    {
        unsigned char name[32]; /* student's name */
        unsigned char age; /* student's age */
        unsigned char sex; /* student's sex, as follows */
        /* 0 - FEMALE; 1 - MALE */
        unsigned char teacher_name[32]; /* the student teacher's name */
        unsigned char teacher_sex; /* his teacher sex */
    } STUDENT;

    正确示例:若改为如下,会更合理些。

    typedef struct TEACHER_STRU
    {
        unsigned char name[32]; /* teacher name */
        unsigned char sex; /* teacher sex, as follows */
        /* 0 - FEMALE; 1 - MALE */
        unsigned int teacher_ind; /* teacher index */
    } TEACHER;
    
    typedef struct STUDENT_STRU
    {
        unsigned char name[32]; /* student's name */
        unsigned char age; /* student's age */
        unsigned char sex; /* student's sex, as follows */
        /* 0 - FEMALE; 1 - MALE */
        unsigned int teacher_ind; /* his teacher index */
    } STUDENT; 

    3、不用或者少用全局变量

    单个文件内部可以使用static的全局变量,可以将其理解为类的私有成员变量。

    全局变量应该是模块的私有数据,不能作用对外的接口使用,使用static类型定义,可以有效防止外部文件的非正常访问,建议定义一个STATIC宏,在调试阶段,将STATIC定义为static,版本发布时,改为空,以便于后续的打补丁等操作。

    4、防止局部变量与全局变量同名

    尽管局部变量和全局变量的作用域不同而不会发生语法错误,但容易使人误解。

    5、通讯过程中使用的结构,必须注意字节序

    通讯报文中,字节序是一个重要的问题,我司设备使用的CPU类型复杂多样,大小端、32位/64位的处理器也都有,如果结构会在报文交互过程中使用,必须考虑字节序问题。由于位域在不同字节序下,表现看起来差别更大,所以更需要注意对于这种跨平台的交互,数据成员发送前,都应该进行主机序到网络序的转换;接收时,也必须进行网络序到主机序的转换。

    6、严禁使用未经初始化的变量作为右值 

    在首次使用前初始化变量,初始化的地方离使用的地方越近越好。

    7、构造仅有一个模块或函数可以修改、创建,而其余有关模块或函数只访问的全局变量,防止多个不同模块或函数都可以修改、创建同一全局变量的现象

    降低全局变量耦合度。

    8、使用面向接口编程思想,通过 API 访问数据:如果本模块的数据需要对外部模块开放 ,应提供接口函数来设置、获取,同时注意全局数据的访问互斥

    避免直接暴露内部数据给外部模型使用,是防止模块间耦合最简单有效的方法。定义的接口应该有比较明确的意义,比如一个风扇管理功能模块,有自动和手动工作模式,那么设置、查询工作模块就可以定义接口为SetFanWorkMode,GetFanWorkMode;查询转速就可以定义为GetFanSpeed;风扇支持节能功能开关,可以定义EnabletFanSavePower等。

    9、明确全局变量的初始化顺序,避免跨模块的初始化依赖 

    系统启动阶段,使用全局变量前,要考虑到该全局变量在什么时候初始化,使用全局变量和初始化全局变量,两者之间的时序关系,谁先谁后,一定要分析清楚,不然后果往往是低级而又灾难性的。

    10、尽量减少没有必要的数据类型默认转换与强制转换

    当进行数据类型强制转换时,其数据的意义、转换后的取值等都有可能发生变化,而这些细节若考虑不周,就很有可能留下隐患。

    错误示例:如下赋值,多数编译器不产生告警,但值的含义还是稍有变化。

    char ch;
    unsigned short int exam;
    ch = -1;
    exam = ch; // 编译器不产生告警,此时exam为0xFFFF。

    5、宏、常量

    1、用宏定义表达式时,要使用完备的括号 

    因为宏只是简单的代码替换,不会像函数一样先将参数计算后,再传递。

    错误示例:如下定义的宏都存在一定的风险

    #define RECTANGLE_AREA(a, b) a * b
    #define RECTANGLE_AREA(a, b) (a * b)
    #define RECTANGLE_AREA(a, b) (a) * (b)

    正确示例:

    #define RECTANGLE_AREA(a, b) ((a) * (b))

    这是因为:如果定义 #define RECTANGLE_AREA(a, b) a * b  或 #define RECTANGLE_AREA(a, b) (a * b)则 c/RECTANGLE_AREA(a, b)  将扩展成 c/a * b , c  与 b 本应该是除法运算,结果变成了乘法运算,造成错误。

    如果定义 #define RECTANGLE_AREA(a, b) (a * b)则 RECTANGLE_AREA(c + d, e + f) 将扩展成: (c + d * e + f), d 与 e 先运算,造成错误。 

    2、将宏所定义的多条表达式放在大括号中

    3、使用宏时,不允许参数发生变化

    错误示例:

    #define SQUARE(a) ((a) * (a))
    int a = 5;
    int b;
    b = SQUARE(a++); // 结果:a = 7,即执行了两次增。

    正确示例:

    b = SQUARE(a);
    a++; // 结果:a = 6,即只执行了一次增。

    同时也建议即使函数调用,也不要在参数中做变量变化操作,因为可能引用的接口函数,在某个版本升级后,变成了一个兼容老版本所做的一个宏,结果可能不可预知。

    4、不允许直接使用魔鬼数字 

    使用魔鬼数字的弊端:代码难以理解;如果一个有含义的数字多处使用,一旦需要修改这个数值,代价惨重。

    使用明确的物理状态或物理意义的名称能增加信息,并能提供单一的维护点。

    解决途径:对于局部使用的唯一含义的魔鬼数字,可以在代码周围增加说明注释,也可以定义局部const变量,变量命名自注释。对于广泛使用的数字,必须定义const全局变量/宏;同样变量/宏命名应是自注释的。0作为一个特殊的数字,作为一般默认值使用没有歧义时,不用特别定义。

    5、除非必要,应尽可能使用函数代替宏

    宏对比函数,有一些明显的缺点:

    • 宏缺乏类型检查,不如函数调用检查严格;
    • 宏展开可能会产生意想不到的副作用,如#define SQUARE(a) (a) * (a)这样的定义,如果是SQUARE(i++),就会导致i被加两次;如果是函数调用double square(double a) {return a * a;}则不会有此副作用;
    • 以宏形式写的代码难以调试难以打断点,不利于定位问题;
    • 宏如果调用的很多,会造成代码空间的浪费,不如函数空间效率高。

    错误示例:下面的代码无法得到想要的结果:

    #define MAX_MACRO(a, b) ((a) > (b) ? (a) : (b))
    
    int MAX_FUNC(int a, int b) {
        return ((a) > (b) ? (a) : (b));
    }
    
    int testFunc()
    {
        unsigned int a = 1;
        int b = -1;
        printf("MACRO: max of a and b is: %d\n", MAX_MACRO(++a, b));
        printf("FUNC : max of a and b is: %d\n", MAX_FUNC(a, b));
        return 0;
    }

    上面宏代码调用中,由于宏缺乏类型检查,a和b的比较变成无符号数的比较,结果是a < b,所以a只加了一次,所以最终的输出结果是:

    MACRO: max of a and b is: -1
    FUNC : max of a and b is: 2

    6、常量建议使用 const 定义代替宏

    “尽量用编译器而不用预处理”,因为#define经常被认为好象不是语言本身的一部分。看下面的语句:

    #define ASPECT_RATIO 1.653

    编译器会永远也看不到ASPECT_RATIO这个符号名,因为在源码进入编译器之前,它会被预处理程序去掉,于是ASPECT_RATIO不会加入到符号列表中。如果涉及到这个常量的代码在编译时报错,就会很令人费解,因为报错信息指的是1.653,而不是ASPECT_RATIO。如果ASPECT_RATIO不是在你自己写的头文件中定义的,你就会奇怪1.653是从哪里来的,甚至会花时间跟踪下去。这个问题也会出现在符号调试器中,因为同样地,你所写的符号名不会出现在符号列表中。
    解决这个问题的方案很简单:不用预处理宏,定义一个常量:

    const double ASPECT_RATIO = 1.653;

    这种方法很有效,但有两个特殊情况要注意。首先,定义指针常量时会有点不同。因为常量定义一般是放在头文件中(许多源文件会包含它),除了指针所指的类型要定义成const外,重要的是指针也经常要定义成const。例如,要在头文件中定义一个基于char*的字符串常量,你要写两次const:

    const char * const authorName = "Scott Meyers";

    延伸阅读材料:关于const和指针的使用,这里摘录两段ISO/IEC 9899(俗称C99)的描述: 

    7、宏定义中尽量不使用 return 、 goto 、 continue 、 break等改变程序流程的语句

    如果在宏定义中使用这些改变流程的语句,很容易引起资源泄漏问题,使用者很难自己察觉。

    错误示例:在某头文件中定义宏CHECK_AND_RETURN:

    #define CHECK_AND_RETURN(cond, ret) {if (cond == NULL_PTR) {return ret;}}
    //然后在某函数中使用(只说明问题,代码并不完整):
    pMem1 = VOS_MemAlloc(...);
    CHECK_AND_RETURN(pMem1 , ERR_CODE_XXX)
    pMem2 = VOS_MemAlloc(...);
    CHECK_AND_RETURN(pMem2 , ERR_CODE_XXX) /*此时如果pMem2==NULL_PTR,则pMem1未释放函数就返回了,造成内存泄漏。*/

    所以说,类似于CHECK_AND_RETURN这些宏,虽然能使代码简洁,但是隐患很大,使用须谨慎。 


    6、表达式

    1、表达式的值在标准所允许的任何运算次序下都应该是相同的

    2、函数调用不要作为另一个函数的参数使用,否则对于代码的调试、阅读都不利

    错误示例:如下代码不合理,仅用于说明当函数作为参数时,由于参数压栈次数不是代码可以控制的,可能造成未知的输出:

    int g_var;
    
    int fun1()
    {
        g_var += 10;
        return g_var;
    }
    
    int fun2()
    {
        g_var += 100;
        return g_var;
    }
    
    int main(int argc, char *argv[], char *envp[])
    {
        g_var = 1;
        printf("func1: %d, func2: %d\n", fun1(), fun2());
        g_var = 1;
        printf("func2: %d, func1: %d\n", fun2(), fun1());
    }

    上面的代码,使用断点调试起来也比较麻烦,阅读起来也不舒服,所以不要为了节约代码行,而写这种代码。

    3、赋值语句不要写在 if 等语句中,或者作为函数的参数使用

    因为if语句中,会根据条件依次判断,如果前一个条件已经可以判定整个条件,则后续条件语句不会再运行,所以可能导致期望的部分赋值没有得到运行。

    错误示例:

    int main(int argc, char *argv[], char *envp[])
    {
        int a = 0;
        int b;
        if ((a == 0) || ((b = fun1()) > 10))
        {
            printf("a: %d\n", a);
        }
        printf("b: %d\n", b);
    }

    作用函数参数来使用,参数的压栈顺序不同可能导致结果未知。

    4、用括号明确表达式的操作顺序,避免过分依赖默认优先级

    使用括号强调所使用的操作符,防止因默认的优先级与设计思想不符而导致程序出错;同时使得代码更为清晰可读,然而过多的括号会分散代码使其降低了可读性。

    5、赋值操作符不能使用在产生布尔值的表达式上 

    示例:

    x = y;
    if (x != 0)
    {
        foo ();
    }

    不能写成:

    if (( x = y ) != 0)
    {
        foo ();
    }

    或者更坏的:

    if (x = y)
    {
        foo ();
    }

    7、注释

     1、优秀的代码可 以自我解释,不通过注释即可轻易读懂

    优秀的代码不写注释也可轻易读懂,注释无法把糟糕的代码变好,需要很多注释来解释的代码往往存在坏味道,需要重构。

    错误示例:注释不能消除代码的坏味道:

    /* 判断m是否为素数*/
    /* 返回值:: 是素数,: 不是素数*/
    int p(int m)
    {
        int k = sqrt(m);
        for (int i = 2; i <= k; i++)
            if (m % i == 0)
                break; /* 发现整除,表示m不为素数,结束遍历*/
        /* 遍历中没有发现整除的情况,返回*/
        if (i > k)
            return 1;
        /* 遍历中没有发现整除的情况,返回*/
        else
            return 0;
    }

    重构代码后,不需要注释:

    int IsPrimeNumber(int num)
    {
        int sqrt_of_num = sqrt (num);
        for (int i = 2; i <= sqrt_of_num; i++)
        {
            if (num % i == 0)
            {
                return FALSE;
            }
        }
        return TRUE;
    }

    2、注释的内容要清楚、明了,含义准确,防止注释二义性

    有歧义的注释反而会导致维护者更难看懂代码,正如带两块表反而不知道准确时间。

    3、在代码的功能、意图层次上进行注释,即注释解释 代码难以直接表达的意图 , 而不是重复描述代码

    注释的目的是解释代码的目的、功能和采用的方法,提供代码以外的信息,帮助读者理解代码,防止没必要的重复注释信息。对于实现代码中巧妙的、晦涩的、有趣的、重要的地方加以注释。注释不是为了名词解释(what),而是说明用途(why)。

    4、修改代码时,维护代码周边的所有注释,以保证注释与代码的一致性,不再有用的注释要删除

    不要将无用的代码留在注释中,随时可以从源代码配置库中找回代码;即使只是想暂时排除代码,也要留个标注,不然可能会忘记处理它。

    5、文件头部应进行注释,注释必须列出:版权说明、版本号、生成日期、作者姓名、工号、内容、功能说明、与其它文件的关系、修改日志等,头文件的注释中还应有函数功能简要说明

    正确示例:下面这段头文件的头注释比较标准,当然,并不局限于此格式,但上述信息建议要包含在内。

    6、函数声明处注释描述函数功能、性能及用法,包括输入和输出参数、函数返回值、可重入的要求等;定义处详细描述函数功能和实现要点,如实现的简要步骤、实现的理由、 设计约束等

    重要的、复杂的函数,提供外部使用的接口函数应编写详细的注释。

    7、全局变量要有较详细的注释,包括对其功能、取值范围以及存取时注意事项等的说明

    正确示例:

    /* The ErrorCode when SCCP translate */
    /* Global Title failure, as follows */ /* 变量作用、含义*/
    /* 0 -SUCCESS 1 -GT Table error */
    /* 2 -GT error Others -no use */ /* 变量取值范围*/
    /* only function SCCPTranslate() in */
    /* this modual can modify it, and other */
    /* module can visit it through call */
    /* the function GetGTTransErrorCode() */ /* 使用方法*/
    BYTE g_GTTranErrorCode;

    8、注释应放在其代码上方相邻位置或右方,不可放在下面,如放于上方则需与其上面的代码用空行隔开,且与下方代码缩进相同

    正确示例:

    /* active statistic task number */
    #define MAX_ACT_TASK_NUMBER 1000
    #define MAX_ACT_TASK_NUMBER 1000 /* active statistic task number */
    可按如下形式说明枚举/数据/联合结构。
    /* sccp interface with sccp user primitive message name */
    enum SCCP_USER_PRIMITIVE
    {
        N_UNITDATA_IND, /* sccp notify sccp user unit data come */
        N_NOTICE_IND, /* sccp notify user the No.7 network can not transmission this message */
        N_UNITDATA_REQ, /* sccp user's unit data transmission request*/
    };

    9、对于 switch语句下的case语句,如果因为特殊情况需要处理完一个case后进入下一个case处理,必须在该case语句处理完、下一个case语句前加上明确的注释

    这样比较清楚程序编写者的意图,有效防止无故遗漏break语句。

    case CMD_FWD:
        ProcessFwd();
        /* now jump into c ase CMD_A */
    case CMD_A:
        ProcessA();
        break;
    //对于中间无处理的连续case,已能较清晰说明意图,不强制注释。
    switch (cmd_flag)
        {
            case CMD_A:
            case CMD_B:
        {
            ProcessCMD();
            break;
        }
        ……
    }

    10、避免在注释中使用缩写,除非是业界通用或子系统内标准化的缩写

    11、同一产品或项目组统一注释风格

    12、避免在一行代码或表达式的中间插入注释

    除非必要,不应在代码或表达中间插入注释,否则容易使代码可理解性变差

    13、注释应考虑程序易读及外观排版的因素,使用的语言若是中、英兼有的,建议多使用中文,除非能用非常流利准确的英文表达,对于有外籍员工的,由产品确定注释语言

    注释语言不统一,影响程序易读性和外观排版,出于对维护人员的考虑,建议使用中文。

    14、文件头、函数头、全局常量变量、类型定义的注释格式采用工具可识别的格式

    采用工具可识别的注释格式,例如doxygen格式,方便工具导出注释形成帮助文档。以doxygen格式为例,文件头,函数和全部变量的注释的示例如下:


    8、排版与格式

    1、程序块采用缩进风格编写, 每级缩进为4个空格

    2、相对独立的程序块之间、变量说明之后必须加空行 

    错误示例:如下例子不符合规范。

    if (!valid_ni(ni))
    {
        // program code
        ...
    }
    repssn_ind = ssn_data[index].repssn_index;
    repssn_ni = ssn_data[index].ni;

    正确示例:

    if (!valid_ni(ni))
    {
        // program code
        ...
    }
    
    repssn_ind = ssn_data[index].repssn_index;
    repssn_ni = ssn_data[index].ni;

    3、一条语句不能过长,如不能拆分需要分行写。一行到底多少字符换行比较合适,产品可以自行确定

    对于目前大多数的PC来说,132比较合适(80/132是VTY常见的行宽值);对于新PC宽屏显示器较多的产品来说,可以设置更大的值。换行时有如下建议:

    • 换行时要增加一级缩进,使代码可读性更好;
    • 低优先级操作符处划分新行;换行时操作符应该也放下来,放在新行首;
    • 换行时建议一个完整的语句放在一行,不要根据字符数断行。

    正确示例:

    if ((temp_flag_var == TEST_FLAG)
    &&(((temp_counter_var - TEST_COUNT_BEGIN) % TEST_COUNT_MODULE) >= TEST_COUNT_THRESHOLD))
    {
        // process code
    }

    4、多个短语句(包括赋值语句)不允许写在同一行内 ,即一行只写一条语句

    错误示例:

    int a = 5; int b= 10; //不好的排版

    正确示例:

    int a = 5;
    int b= 10;

    5、if 、 for 、 do 、 while 、 case 、 switch 、 default 等语句独占一行

    执行语句必须用缩进风格写,属于if、for、do、while、case、switch、default等下一个缩进级别;

    一般写if、for、do、while等语句都会有成对出现的„{}‟,对此有如下建议可以参考:if、for、do、while等语句后的执行语句建议增加成对的“{}”;如果if/else配套语句中有一个分支有“{}”,那么另一个分支即使一行代码也建议增加“{}”;添加“{”的位置可以在if等语句后,也可以独立占下一行;独立占下一行时,可以和if在一个缩进级别,也可以在下一个缩进级别;但是如果if语句很长,或者已经有换行,建议“{”使用独占一行的写法。

    6、在两个以上的关键字、变量、常量进行对等操作时,它们之间的操作符之前、之后或者前后要加空格 ; 进行非对等操作时,如果是关系密切的立即操作符(如-> > ),后不应加空格

    采用这种松散方式编写代码的目的是使代码更加清晰。

    在已经非常清晰的语句中没有必要再留空格,如括号内侧(即左括号后面和右括号前面)不需要加空格,多重括号间不必加空格,因为在C语言中括号已经是最清晰的标志了。在长语句中,如果需要加的空格非常多,那么应该保持整体清晰,而在局部不加空格。给操作符留空格时不要连续留两个以上空格。

    正确示例:

    1、逗号、分号只在后面加空格。

    int a, b, c;

    2、比较操作符, 赋值操作符"="、 "+=",算术操作符"+"、"%",逻辑操作符"&&"、"&",位域操作符"<<"、"^"等双目操作符的前后加空格。 

    if (current_time >= MAX_TIME_VALUE)
    a = b + c;
    a *= 2;
    a = b ^ 2;

    3、"!"、"~"、"++"、"--"、"&"(地址操作符)等单目操作符前后不加空格。

    *p = 'a'; // 内容操作"*"与内容之间
    flag = !is_empty; // 非操作"!"与内容之间
    p = &mem; // 地址操作"&" 与内容之间
    i++; 

     4、"->"、"."前后不加空格。

    p->id = pid; // "->"指针前后不加空格

    5、if、for、while、switch等与后面的括号间应加空格,使if等关键字更为突出、明显。

    if (a >= b && c > d)

    7、注释符(包括/**/、//)与注释内容之间要用一个空格进行分隔

    8、源程序中关系较为紧密的代码应尽可能相邻


    9、代码编辑编译 

    1、使用编译器的最高告警级别,理解所有的告警,通过修改代码而不是降低告警级别来消除所有告警

    编译器是你的朋友,如果它发出某个告警,这经常说明你的代码中存在潜在的问题。

    2、在产品软件(项目组)中,要统一编译开关、静态检查选项以及相应告警清除策略

    如果必须禁用某个告警,应尽可能单独局部禁用,并且编写一个清晰的注释,说明为什么屏蔽。某些语句经编译/静态检查产生告警,但如果你认为它是正确的,那么应通过某种手段去掉告警信息。

    4、本地构建工具(如 PC-Lint)的配置应该和持续集成的一致

    两者一致,避免经过本地构建的代码在持续集成上构建失败

    5、 使用版本控制(配置管理)系统,及时签入通过本地构建的代码,确保签入的代码不会影响构建成功

    及时签入代码降低集成难度。

    6、要小心地使用编辑器提供的块拷贝功能编程

    以上为自我总结,感兴趣的同志,推荐阅读全文,也就60余页。

    展开全文
  • 邻接多重表存储无向图 空间复杂度:O(|V|+|E|) 删除边、删除节点等操作很方便 注意:邻接多重表只适用于存储无向图 二、图的基本操作 Adjacent(G,x,y):判断图G是否存在边或(x, y)。 Neighbors(G,x):列出图G中与...
  • C语言概述C语言的执行速度快,执行效率高,功能强大,编程自由。 但缺点也是有的,代码实现的周期较长,可移植性较差, 由于过于自由,模块的操作单一(历史性原因)。 因为C语言是底层代码,所以需要了解计算机的基本...
  • C语言中一道多层for循环程序题來源:互聯網2009-08-03 23:08:38評論分類: 電腦/網絡 >> 程序設計 >> 其他編程語言問題描述:要求是:打印如下图形*************************我想的有点乱了,主要是思路不...
  • 一、C语言概述 1、什么是C语言 语言是用来交流沟通的,有一方说,有一方听。必须要两方参与。(自言自语的除外) 语言是人与人交流,而C语言是人与机器交流 语言有独特的语法规则和定义,双方必须遵循这些规则和定义...
  • C语言专插本知识点

    千次阅读 2021-11-09 16:09:27
    c语言编写的程序称为源程序,又称为编译单位 只有一个main函数,是程序运行的起点 4.标识符 关键词不能作为标识符号 预定义标识符:define scanf printf include 可以做为用户标识符 5.进制转换 十进制转换...
  • C语言定义共享全局变量

    千次阅读 2021-05-20 04:25:51
    好久没写C语言了,突然忘记怎么定义全局共享变量了,由于老项目的Code Base都是C的风格,其中又大量用了全局变量,只能跟着糊一坨shit上去了。没办法。再共享全局变量的global_shared_var.h文件中写入:extern int ...
  • 如果是C语言风格的注释出现了嵌套,则/*总是与离它最近的*/匹配,第二个/*并不会被认为是注释符号,因此第一个/*与第一个*/匹配,代码会剩下一个*/: 注释的一些注意事项 注释应当准确、易懂,反之有二义性。...
  • 张桥珍摘 要:C语言是高职院校计算机相关专业的专业基础课程,一般在大一上学期开设。学习C语言的目的是培养计算机专业学生的计算机程序设计思维,理解计算机解题的思路,掌握C语言程序设计的方法,最后能够具备运用...
  • C语言程序设计

    2021-05-18 17:15:36
    软件学院《C语言程序设计》课程教学大纲课程名称C语言程序设计英文名称C Programming Language适用专业软件工程课程编码20H15281开课学期1学分/周学时4/ 5-2课程性质1课程类别2先修课程无教材或参考书与学习资源1....
  • goto语句怎么用(c语言goto用法)

    千次阅读 2021-05-19 02:26:43
    goto语句怎么用(c语言goto用法)2020-07-28 02:03:29共10个回答1、C语言中goto又叫无条件转移语句,可以让程序直接跳转到任意标记的位置.用法就是“gotolabel……label:”.下面用一个示例来演示具体的用法,这里先新建...
  • 五邑大学2015专插本C语言程序设计... 所在单位 姓名 准考证号 报考学校 密 封 线 内 不 要 答 题 五邑大学2015年通信工程(计算机通信网络)专业 本科插班生招生考试《C语言程序设计》试题 题号 一 二 三 四 五 六 七...
  • 安徽省年月二级C语言考试.doc安徽省08年6月二级 C语言考试一、单项选择题每题1分,共40分1. 计算机能够自动工作,主要是因为采用了 。 A 二进制数制 B 大规模集成电路C 程序设计语言 D 存储程序控制原理2. 下列数值...
  • 计算机导论与C语言

    2021-06-29 11:11:14
    书名计算机导论与C语言作者耿国华、董卫军、邢为民ISBN9787121116643定价36元出版社出版时间2010年9月1日开本16开计算机导论与C语言内容简介编辑语音《计算机导论与C语言(第2版)》根据教育部计算机基础教育教学指导...
  • C语言转换大小写

    2021-05-19 12:57:04
    } 1991: C语言实验——大小写转换 1991: C语言实验——大小写转换 Time Limit: 1 Sec Memory Limit: 64 MBSubmit: 183 Solved: 109[Submit][Status][We ... JS中应用正则表达式转换大小写 JS中应用正则表达式转换...
  • 高职C语言课程教学论文时间:2014-04-23栏目:高职C语言课程教学论文提高高职C语言课程教学效果的研究与实践文/杨晓明 刘淑婷摘 要:C语言是高职院校学生接触的第一门计算机语言课,语法灵活、知识点多、程序设计...
  • 要有效使用多重处理,必须采用多道程序设计技术,而多道程序设计原则上不一定要求多重处理系统的支持。多道程序多道程序是作业之间自动调度执行、共享系统资源,并不是真正地同时执行多个作业。多道程序设计技术是在...
  • C语言说课稿..ppt

    2021-05-21 08:35:50
    C语言说课稿.LOGO “ Add your company slogan ” C语言程序设计 涧踏踪欠阉勺怕遏湃苏难殊爸魁伍葡啮模候榨追亭彦滩帖霖铰侯溃叭冠搂C语言说课稿.C语言说课稿. 目录 课程性质和地位 1 教 学 内 容 2 教 学 目 标 学...
  • 在一一个表达式中可能有多个不同的运算符结合起来,由于运算符的优先级别不...确认应答实现可靠传输 应答码:ACK TCP的滑动窗口机制 TCP这个协议是网络中使用的比较广泛,他是一个面向连接的可靠的传输协议.既然是一 ...
  • 目 录《C语言程序设计》教学大纲1《实用操作系统》教学大纲5《网页设计》教学大纲8《计算机硬件基础》教学大纲12《计算机网络技术》教学大纲15《数据库技术》教学大纲19《专业英语》教学大纲23《Java程序设计》教学...
  • c语言不仅是一门基础课程,更是一门专业技术课程,中职学生学好C语言对后续课程的学习以及毕业后从事计算机相关工作具有重要意义。关键词:核心素养 c语言 信息技术 教学设计课题:本文系贵州师范大学2018年省级教学...
  • 比如在C语言用于检查一个字符是否为阿拉伯数字的isdigit函数就只会返回是(1)或否(0)#include #include intmain(int argc, char *argv[]){char c = 'a';printf("isdigit('%c') is %d\n", c, isdigit(c));return 0;...
  • 6)多重图:图中某两个结点之间的边数多余一条,又允许顶点通过同一条边和自己关联。7)无向完全图:在无向图中,任意两个顶点之间都存在边。含有n个顶点的无向完全图有n(n-1)/2条边。8)有向完...
  • 在图中任何两个顶点之间都可能存在联系,所以图的存储结构应该需要根据具体问题的要求来进行设计。从图的逻辑结构定义来看,图中...常用的存储结构有邻接矩阵、邻接表(逆邻接表)、十字链表、邻接多重表、 边集数组。
  • 数据结构c语言

    2021-05-19 11:09:37
    数据结构c语言版是一款非常使用的数据结构课程的编写和教材;它也可将软件作为学习数据结构、算法C程序设计的参数教材,只需要用户学习该软件之后,就算遇到在繁杂的代码也可以清晰的写出来,本书的钱半部分主要介绍...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 10,128
精华内容 4,051
关键字:

多重网络c语言