5网络编程全集

********************基本概念***************DAY 1

【1】计算机与网络发展的7个阶段
1. 批处理(20世纪50年代)
是指实现将用户个数据装入卡带或者磁带。并有计算机按照一定的顺序读取,是用户索要执行的这些程序和数据能够一并批量得到处理的方式。
2. 分时系统(20世纪60年代)
是指多个终端(包含鼠标、键盘、显示器等输入输出设备组成,最初还包括打印机)与一台计算机连接,允许多个用户同时使用一台计算机的系统。
特性:多路性、独占性、交互性和及时性。
3.计算机之间的通信(20世纪70年代)
4. 计算机网络的产生(20世纪80年代) 
5. 互联网的普及(20世纪90年代)
6. 以互联网技术为中心的时代(2000年)
7. 从“单纯建立连接”到“安全建立连接”(2010年)
【2】网络体系结构即指网络的层次结构和每层所使用协议的集合
【3】OSI开放系统互联模型
应用层:应用程序:FTP、E-mail、Telnet
表示层:数据格式定义、数据转换/加密
会话层:建立通信进程的逻辑名字与物理名字之间的联系 
传输层:差错处理/恢复,流量控制,提供可靠的数据传输
网络层:数据分组、路由选择
数据链路层:数据组成可发送、接收的帧
物理层:传输物理信号、接口、信号形式、速率

1. ISO(国际标准化组织)制定了一个国际标准OSI(开放式通信系统互联参考模型),对通信系统进行了标准化。
2. OSI模型将通信协议中必要的功能分成了7层,每个分层都接收有它下一层所提供的特定服务,并且负责为自己的上一层提供特定的服务。上下层之间进行交互时所遵循的约定叫做 “接口”。同一层之间的交互所遵循的约定叫做“协议”。
3. 7层通信
(1)应用层:指定特定应用的协议(比如发送和接受文件的软件按钮,发送者输入“早上好”并附上收件人,
按下发送按钮,接受者收到信息会将其存储在硬盘或者非易失存储器(数据不会因为断电而丢失的一种存储
设备)上,这些都是在应用层上的)
(2)表示层:设备固有数据格式和网络标准数据格式的转换(接受者和发送者如果使用的邮件客户端不一
样,那么就会出现问题,如何实现用户之间的通信,那么就需要在表示层来起作用,使得在不同的客户端上
拥有相同的网络格式)
(3)会话层:通信管理,负责建立或者断开通信连接(发送者一次性发送5份邮件,那么接受者如何接受,
是一次性接受所有的文件然后断开连接还是没接受一次就断开,然后在此进行,发送者同理)
(4)传输层:管理两个节点(互联的网络中断)之间的数据传输。负责可靠传输(确保数据被可靠地传送
到目标地址)(确保发送者和接受者之间的通信,会话层负责决定建立连接和断开连接的时机,而传输层进
行实际的建立和断开处理)
(5)网络层:地址管理与路由选择,作用:在网络相互连接的环境中,将数据从发送端主机发送到接受端
主机
(6)数据链路层:互连设备之间传送和识别数据帧
(7)物理层:以“0”、“1”代表的电压的高低、灯光的闪灭。界定连接器和网络的规格。
【5】
1.协议
一组控制数据通信的规则。
三要素:语法(包括数据格式、编码及信号电平等)、语义(包括用于协议和差错处理的控制信息)、
时序(包括速度匹配和排序)。
2. 标准
一致同意的规则。
分类:
事实上的标准:实际情况或者习惯
合法标准:法律或者规章制度
3.标准化组织
缓慢发展:
ISO:国际标准化组织
ITU-T:国际电联-电信标准部
ANSI:美国国家标准化局
IEEE:电气电子工程师协会(主要是以太网、局域网方面的)
EIA:电子工业协会(物理传输标准、光钎传输)
快速发展:
论坛:帧中继论坛、ATM论坛
管理机构:FCC 联邦通信委员会


【4】TCP/IP协议族
应用层: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)用户数据报协议

ICMP(Internet Control Message Protocol)互联网控制信息协议

SMTP(Simple Mail Transfer Protocol)简单邮件传输协议

SNMP(Simple Network manage Protocol)简单网络管理协议

HTTP(Hypertext Transfer Protocol) 超文本传输协议

FTP(File Transfer Protocol)文件传输协议

ARP(Address Resolution Protocol)地址解析协议

【5】UDP和TCP
共同点:同为传输层协议
不同点:
TCP:有连接,可靠
UDP:无连接,不保证可靠


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


适用情况:
适合于对传输质量要求较高,以及传输大量数据的通信。
在需要可靠数据传输的场合,通常使用TCP协议
MSN/QQ等即时通讯软件的用户登录账户管理相关的功能通常采用TCP协议
udp
UDP(User Datagram Protocol)用户数据报协议,是不可靠的无连接的协议。在数据发送前,
因为不需要进行连接,所以可以进行高效率的数据传输。


适用情况:
发送小尺寸数据(如对DNS服务器进行IP地址查询时)

在接收到数据,给出应答较困难的网络中使用UDP。(如:无线网络)

适合于广播/组播式通信中。

MSN/QQ/Skype等即时通讯软件的点对点文本通讯以及音视频通讯通常采用UDP协议

流媒体、VOD、VoIP、IPTV等网络多媒体服务中通常采用UDP方式进行实时数据传输
【6】Socket
是一个编程接口

是一种特殊的文件描述符 (everything in Unix is a file)

并不仅限于TCP/IP协议

面向连接 (Transmission Control Protocol - TCP/IP)

无连接 (User Datagram Protocol -UDP 和 Inter-network Packet Exchange - IPX)

分类:
流式套接字(SOCK_STREAM)
提供了一个面向连接、可靠的数据传输服务,数据无差错、无重复的发送且按发送顺序接收。
内设置流量控制,避免数据流淹没慢的接收方。数据被看作是字节流,无长度限制。


数据报套接字(SOCK_DGRAM)
提供无连接服务。数据包以独立数据包的形式被发送,不提供无差错保证,数据可能丢失或重
复,顺序发送,可能乱序接收。


原始套接字(SOCK_RAW)
可以对较低层次协议如IP、ICMP直接访问。


【7】IP地址
IP地址是Internet中主机的唯一标识
IP地址为32位(IPv4)或者128位(IPv6)
每个数据包都必须携带目的IP地址和源IP地址,路由器依靠此信息为数据包选择路由

IPV4表示形式:点分十进制 192.168.5.222

IP地址分类:(根据第一部分分类)
A类  0000 0000 - 0111 1111  0.0.0.0 - 127.255.255.255
B类  1000 0000 - 1011 1111  128.0.0.0 - 191.255.255.255 
C类  1100 0000 - 1101 1111  192.0.0.0 - 223.255.255.255
D类  1110 0000 - 1110 1111  224.0.0.0 - 239.255.255.255  表示组播地址
E类  1111 0000 - 1111 1111  240.0.0.0 - 255.255.255.255  保留测试

127.x.x.x 都代表当前主机地址
192.168.x.x 代表局域网

192.168.5.x IP地址
192.168.5.1 表示网段
192.168.5.255 代表广播地址
子网掩码:表示当前网段连接主机的最大个数
A类:255.0.0.0
B类:255.255.0.0
C类:255.255.255.0

#include <arpa/inet.h>


将点分十进制IP地址转化为网络能够识别的整型数据
    in_addr_t inet_addr(const char *cp);

将网络能够识别的整型数据转化为点分十进制IP地址
    char *inet_ntoa(struct in_addr in);


例子:
inet_addr("192.168.5.232");

【8】端口号(使用 vi /etc/service可以查看已经被占用的端口号)
为了区分一台主机接收到的数据包应该转交给哪个进程来进行处理,使用端口号来区别
端口号一般由IANA (Internet Assigned Numbers Authority) 管理
众所周知端口:1~1023(1~255之间为众所周知端口,256~1023端口通常由UNIX系统占用)
已登记端口:1024~49151
动态或私有端口:49152~65535


一般使用:6666 7777 8888 9999 10000 10001

【9】字节序
不同类型CPU的主机中,内存存储多字节整数序列有两种方法,称为主机字节序(HBO):
小端序(little-endian) - 低序字节存储在低地址
将低字节存储在起始地址,称为“Little-Endian”字节序,Intel、AMD等采用的是这种方式;
大端序(big-endian)- 高序字节存储在低地址
将高字节存储在起始地址,称为“Big-Endian”字节序,由ARM、Motorola等所采用

网络中传输的数据必须按网络字节序,即大端字节序

如何测试字节序:
方法一:使用指针
方法二:使用file命令,file a.out 其中LSB中的L代表小端存储‘
方法三:使用共用体
 
#include <arpa/inet.h>


将主机字节序转化为网络字节序
    uint32_t htonl(uint32_t hostlong);
    uint16_t htons(uint16_t hostshort);

将网络字节序转化为主机字节序
    uint32_t ntohl(uint32_t netlong);
    uint16_t ntohs(uint16_t netshort);

例子:
htons(8888);


*************************TCP网络编程*********************
【1】流程
举个例子:
买个手机,买张卡
手机和卡需要对应
将手机和卡绑定
设置为非飞行模式
进行通信

服务器端:server
创建套接字   socket( )
填充服务器网络信息结构体 sockaddr_in
将套接字与网络信息结构体绑定  bind( )
将套接字设置为监听模式 listen( )
阻塞等待客户端的连接请求 accept( )
进行通信 recv( )/send( )

客户端:client
创建套接字  socket( )
填充服务器网络信息结构体 sockaddr_in
发送客户端的连接请求 connect( )
进行通信 send( )/recv( )
【2】ctags的创建
第一步:在/usr/include 文件夹下执行
sudo ctags -R,会生成一个tags的文件,vim -t sockaddr_in
第二步:设置为全局
在家目录下的.vimrc文件里面添加一条命令
set tags+=/usr/include/tags 
【3】socket
#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_PACKET 底层通信
type:类型
SOCK_STREAM 流式套接字 TCP
SOCK_DGRAM 数据报套接字 UDP
SOCK_RAW 底层通信
protocol:一般为0
返回值:
成功:文件描述符
失败:-1
例子:
int sockfd;
if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
perror("fail to socket");
exit(1);
}
【4】bind
#include <sys/types.h>          /* See NOTES */
    #include <sys/socket.h>


    int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
功能:将套接字与网络信息结构体绑定
参数:
sockfd:文件描述符,socket的返回值
addr:网络信息结构体
通用的:sockaddr(一般不使用)
struct sockaddr {
sa_family_t sa_family;  2个字节
char        sa_data[14]; 14个字节
}

一般使用:sockaddr_in
#include <netinet/in.h>
struct sockaddr_in
{
__SOCKADDR_COMMON (sin_);
===>
#define __SOCKADDR_COMMON(sa_prefix) \                                                                           
sa_family_t sa_prefix##family
===>
在函数宏里面,##代表字符串的拼接
sa_family_t sin_family;    协议族  2个字节


in_port_t sin_port;     端口号 2个字节
struct in_addr sin_addr;       
===>
struct in_addr
{
in_addr_t s_addr;   //IP地址  4个字节
};   

没有用,只是为了与sockaddr保持一致
unsigned char sin_zero[sizeof (struct sockaddr) -                                                            
__SOCKADDR_COMMON_SIZE -
sizeof (in_port_t) -
sizeof (struct in_addr)];
};




addrlen:addr的长度
返回值:
成功:0
失败:-1

例子:
struct sockaddr_in serveraddr;
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(9999);
serveraddr.sin_addr.s_addr = inet_addr("192.168.5.222");

if(bind(sockfd, (struct sockaddr *)&serveraddr, sizeof(struct sockaddr_in)) < 0)
{
perror("fail to bind");
exit(1);
}
【5】listen
#include <sys/types.h>          /* See NOTES */
    #include <sys/socket.h>


    int listen(int sockfd, int backlog);
功能:将套接字设置为监听状态
参数:
sockfd:文件描述符,socket的返回值
backlog:允许同时连接的最大个数
一般设置为5,10
返回值:
成功:0
失败:-1
例子:
if(listen(sockfd, 5) < 0)
{
perror("fail to listen");
exit(1);
}
【6】accept
#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的长度
返回值:
成功:新的文件描述符,用于通信
失败:-1
例子:
int acceptfd;
struct sockaddr_in clientaddr;
socklen_t addrlen = sizeof(clientaddr);
if((acceptfd = accept(sockfd, (struct sockaddr *)&clientaddr, &addrlen)) < 0)
{
perror("fail to accept");
exit(1);
}
【7】connect
#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

【8】send
#include <sys/types.h>
    #include <sys/socket.h>


    ssize_t send(int sockfd, const void *buf, size_t len, int flags);
功能:发送数据
参数:
sockfd:文件描述符
服务器端:accept的返回值
客户端:socket的返回值
buf:发送的数据
len:buf的长度
flags:标志位
阻塞:0
非阻塞:MSG_DONTWAIT
返回值:
成功:发送的数据的个数
失败:-1

【9】recv
#include <sys/types.h>
    #include <sys/socket.h>


    ssize_t recv(int sockfd, void *buf, size_t len, int flags);
功能:接收数据
参数:
sockfd:文件描述符
服务器端:accept的返回值
客户端:socket的返回值
buf:接收的数据
len:buf的长度
flags:标志位
阻塞:0
非阻塞:MSG_DONTWAIT
返回值:
成功:接收的数据的个数
0:代表发送端突然断开
失败:-1

作业:基于TCP的文件服务器
功能:
客户端可以查看服务器所在目录的文件
客户端可以下载服务器所在目录的文件

客户端可以上传文件给服务器


DAY 2

【1】UDP网络编程
流程:
服务器:
创建套接字 socket( )
填充服务器网络信息结构体 sockaddr_in
将套接字与网络信息结构体绑定 bind( )
进行通信 recvfrom( )/sendto( )
客户端:
创建套接字 socket( )
填充服务器网络信息结构体 sockaddr_in
进行通信 sendto( )/recvfrom( )
【2】recvfrom
#include <sys/socket.h>


    ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                        struct sockaddr *src_addr, socklen_t *addrlen);
功能:接收数据
参数:
sockfd:文件描述符,socket的返回值
buf:接收的数据
len:buf的长度
flags:标志位
一般为0,MSG_DONTWAIT表示非阻塞
src_addr:源的地址(接收谁的数据)
addrlen:addr的大小
返回值:
成功:接收到的数据的个数
失败:-1

【3】sendto
#include <sys/socket.h>


    ssize_t sendto(int socket, const void *message, size_t length,
            int flags, const struct sockaddr *dest_addr,socklen_t dest_len);
功能:发送数据
参数:
socket:文件描述符
message:发送的数据
length:数据的长度
flags:标志位,一般为0
dest_addr:目的地址(发送给谁)
dest_len:addr 的长度
返回值:
成功:发送的字节数
失败:-1

书籍:
tcp/ip详解 卷一 卷二 卷三
UNIX环境高级编程
linux网络编程 卷一 卷二

作业:基于UDP的网络聊天室
功能:
客户端登录,在线的其他人可以收到这个人的登录信息
客户端发广播,在线的其他人可以收到这个人发的群聊信息,但是自己不接收
客户端退出,在线的其他人可以收到这个人退出的信息
服务器可以发送系统消息





DAY 3

****************IO模型****************
【1】分类
在UNIX/Linux下主要有4种I/O 模型:
阻塞I/O:
最常用、最简单、效率最低
非阻塞I/O:
可防止进程阻塞在I/O操作上,需要轮询
I/O 多路复用:
允许同时对多个I/O进行控制
信号驱动I/O:
一种异步通信模型
【2】阻塞I/O 模式
阻塞I/O 模式是最普遍使用的I/O 模式,大部分程序使用的都是阻塞模式的I/O 。


缺省情况下,套接字建立后所处于的模式就是阻塞I/O 模式。


前面学习的很多读写函数在调用过程中会发生阻塞。
读操作中的read、recv、recvfrom
写操作中的write、send
其他操作:accept、connect
【3】非阻塞I/O
当我们将一个套接字设置为非阻塞模式,我们相当于告诉了系统内核:“当我请求的I/O 操作不能够马上完成,你想让我的进程进行休眠等待的时候,不要这么做,请马上返回一个错误给我。”


当一个应用程序使用了非阻塞模式的套接字,它需要使用一个循环来不停地测试是否一个文件描述符有数据可读(称做polling)。


应用程序不停的polling 内核来检查是否I/O操作已经就绪。这将是一个极浪费CPU 资源的操作。


这种模式使用中不普遍。

使用fcntl函数实现非阻塞IO
#include <unistd.h>
    #include <fcntl.h>


    int fcntl(int fd, int cmd, ... /* arg */ );
功能:操作文件描述符
参数:
fd:文件描述符
cmd:操作选项
F_GETFL 获取文件状态标志位
F_SETFL 设置文件状态标志位
O_NONBLOCK 非阻塞
arg:可变参,具体由cmd决定(cmd后面括号里面如果为void,则忽略,如果为long,则使用)
返回值:
成功:
F_GETFL 返回文件状态标志位
F_SETFL 0
失败:-1

注意:对寄存器或者位的操作,一般执行读、改、写三步
例子:
第一步:获取文件状态标志位
int flags;

if((flags = fcntl(0, F_GETFL)) < 0)
{
perror("fail to fcntl");
exit(1);
}

第二步:修改标志位
flags = flags | O_NONBLOCK;  //00000123  | 00004000 = 00004123


第三步:将修改后的标志位写进去
if(fcntl(0, F_SETFL, flags) < 0)
{
perror("fail to fcntl");
exit(1);
}

【4】多路复用I/O
应用程序中同时处理多路输入输出流,
若采用阻塞模式,将得不到预期的目的;
若采用非阻塞模式,对多个输入进行轮询,但又太浪费CPU时间;
若设置多个进程,分别处理一条数据通路,将新产生进程间的同步与通信问题,使程序变得更加复杂;
比较好的方法是使用I/O多路复用。其基本思想是:
先构造一张有关描述符的表,然后调用一个函数。当这些文件描述符中的一个或多个已准备好进行I/O时函数才返回。
函数返回时告诉进程那个描述符已就绪,可以进行I/O操作。


使用select函数实现IO多路复用
#include <sys/select.h>
    #include <sys/time.h>
    #include <sys/types.h>
    #include <unistd.h>


    int select(int nfds, fd_set *readfds, fd_set *writefds,
               fd_set *exceptfds, struct timeval *timeout);
功能:同步IO操作,允许一个程序操作多个文件描述符,阻塞等待附件描述符准备就绪,
 如果有文件描述符准备就绪,函数立即返回,并执行相应的IO操作
参数:
nfds:最大的文件描述符加1
readfds:读文件描述符集合
writefds:写文件描述符集合
exceptfds:其他或者异常的文件描述符集合
timeout:超时
返回值:
成功:准备就绪的文件描述符的个数
失败:-1

void FD_ZERO(fd_set *set);
清空集合set

void FD_SET(int fd, fd_set *set);
将文件描述符fd添加到集合set里面

    void FD_CLR(int fd, fd_set *set);
将文件描述符fd从集合set里面移除

    int  FD_ISSET(int fd, fd_set *set);
判断文件描述符fd是否在集合set里面
    
*******************服务器模型*********************  
【1】
在网络程序里面,通常都是一个服务器处理多个客户机。


为了处理多个客户机的请求, 服务器端的程序有不同的处理方式。


目前最常用的服务器模型. 
循环服务器:
循环服务器在同一个时刻只能响应一个客户端的请求 
tcp循环服务器
udp循环服务器
并发服务器:
并发服务器在同一个时刻可以响应多个客户端的请求
tcp并发服务器
udp并发服务器
【2】如何实现tcp并发服务器
方法1:使用父子进程实现tcp并发服务器
socket( )
sockaddr_in
bind( )
listen( )
//处理僵尸进程
...
while(1)
{
accept( )
pid = fork( )
if(pid > 0)  //父进程负责连接
{
close(acceptfd);
}
else if(pid == 0)  //子进程负责通信
{
close(sockfd);
recv( )/send( )
}
}

方法2:使用select函数实现tcp并发服务器
//使用select函数实现tcp并发服务器

//第一步:清空集合
FD_ZERO(&readfds);

//第二步:将需要的文件描述符添加到集合里面
FD_SET(sockfd, &readfds);

maxfd = sockfd;
while(1)
{
tempfds = readfds;

//第三步:执行select函数,阻塞等待文件描述符准备就绪
if((ret = select(maxfd + 1, &tempfds, NULL, NULL, NULL)) < 0)
{
errlog("fail to select");
}

//printf("ret = %d\n", ret);

//第四步:判断准备就绪的文件描述符是否在集合里面,如果在,立即执行相应的IO操作
for(i = 0; i <= maxfd; i++)
{
if(FD_ISSET(i, &tempfds))
{
if(i == sockfd)
{
acceptfd = accept();

//需要将acceptfd添加到集合里面
FD_SET(acceptfd, &readfds);

//确定最大的文件描述符
maxfd = acceptfd > maxfd ? acceptfd : maxfd;
}
else
{
recv( )
send( )
}
}
}
}

作业:使用poll实现IO多路复用  fgets accept
#include <poll.h>


    int poll(struct pollfd *fds, nfds_t nfds, int timeout);
功能:同select
参数:
fds:结构体数组
struct pollfd {
int   fd;         文件描述符

short events;     请求的事件
POLLIN 普通或优先级带数据可读
POLLRDNORM 普通数据可读
POLLRDBAND 优先级带数据可读
POLLPRI 高优先级数据可读
POLLOUT 普通数据可写
POLLWRNORM 普通数据可写
POLLWRBAND 优先级带数据可写
POLLERR 发生错误
POLLHUP 发生挂起
POLLNVAL 描述字不是一个打开的文件

short revents;    返回的事件
};
nfds:文件描述符的个数
timeout:超时
<0 永远等待
0 立即返回,不阻塞进程
>0 等待指定数目的毫秒数
返回值:
成功:准备就绪的文件描述符的个数
失败:-1








DAY 4



******************网络信息检索函数*****************
【1】getsockopt() 获取一个套接口选项 
#include <sys/socket.h>


    int getsockopt(int socket, int level, int option_name,
              void *restrict option_value, socklen_t *restrict option_len);


功能:获取一个套接字的选项
参数:
socket:文件描述符
level:协议层次
SOL_SOCKET socket层次
IPPROTO_IP IP层次
IPPROTO_TCP tcp层次
option_name:选项的名称
SO_BROADCAST 设置是否允许发送广播
SO_REUSEADDR 是否允许重复使用本地地址
SO_SNDBUF 获取发送缓冲区大小
SO_RCVBUF 获取接收缓冲区大小
SO_RCVTIMEO 设置接收超时时间
SO_SNDTIMEO 设置发送超时时间
option_value:选项的具体值(就是获取的信息)
option_len:option_value的大小
返回值:
成功:0
失败:-1
***********************网络超时检测******************
【1】超时检测的必要性
在网络通信中,很多操作会使得进程阻塞


TCP套接字中的recv/accept/connect


UDP套接字中的recvfrom


超时检测的必要性
避免进程在没有数据时无限制地阻塞
当设定的时间到时,进程从原操作返回继续运行


原理:对于阻塞函数,使用超时检测。在规定时间内阻塞等待数据的读取,
 如果时间到达还没有数据可读,函数立即返回,代码继续运行
【2】方法1:使用setsockopt设置网络超时检测
#include <sys/socket.h>


    int setsockopt(int socket, int level, int option_name,
              const void *option_value, socklen_t option_len);
功能:获取一个套接字的选项
参数:
socket:文件描述符
level:协议层次
SOL_SOCKET socket层次
IPPROTO_IP IP层次
IPPROTO_TCP tcp层次
option_name:选项的名称
SO_BROADCAST 设置是否允许发送广播
SO_REUSEADDR 是否允许重复使用本地地址
SO_SNDBUF 获取发送缓冲区大小
SO_RCVBUF 获取接收缓冲区大小
SO_RCVTIMEO 设置接收超时时间
struct timeval
{
__time_t tv_sec;        秒
__suseconds_t tv_usec;  微秒
};


SO_SNDTIMEO 设置发送超时时间
option_value:选项的具体值(就是获取的信息)
option_len:option_value的大小
返回值:
成功:0
失败:-1



//注意:使用setsockopt设置的超时时间,设置一次永久有效,并且队套接字的所有阻塞函数都有效
struct timeval time_out;
time_out.tv_sec = 5;
time_out.tv_usec = 0;

if(setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &time_out, sizeof(time_out)) < 0)
{
errlog("fail to setsockopt");
}
【3】方法2:使用select实现网络超时检测
#include <sys/select.h>
    #include <sys/time.h>
    #include <sys/types.h>
    #include <unistd.h>


    int select(int nfds, fd_set *readfds, fd_set *writefds,
               fd_set *exceptfds, struct timeval *timeout);
功能:同步IO操作,允许一个程序操作多个文件描述符,阻塞等待附件描述符准备就绪,
 如果有文件描述符准备就绪,函数立即返回,并执行相应的IO操作
参数:
nfds:最大的文件描述符加1
readfds:读文件描述符集合
writefds:写文件描述符集合
exceptfds:其他或者异常的文件描述符集合
timeout:超时
struct timeval
{
__time_t tv_sec;        秒
__suseconds_t tv_usec;  微秒
};
返回值:
成功:准备就绪的文件描述符的个数
失败:-1

void FD_ZERO(fd_set *set);
清空集合set

void FD_SET(int fd, fd_set *set);
将文件描述符fd添加到集合set里面

    void FD_CLR(int fd, fd_set *set);
将文件描述符fd从集合set里面移除

    int  FD_ISSET(int fd, fd_set *set);
判断文件描述符fd是否在集合set里面

//注意: 使用select设置的超时时间,设置一次只有效一次,所以需要每次都设置
// 由于一致阻塞在select函数这,所以没有办法分清到底时accept韩式recv
// 造成的超时


//第一步:清空集合
FD_ZERO(&readfds);


//第二步:将需要的文件描述符添加到集合里面
FD_SET(sockfd, &readfds);


maxfd = sockfd;


while(1)
{
struct timeval time_out;
time_out.tv_sec = 5;
time_out.tv_usec = 0;


tempfds = readfds;


//第三步:执行select函数,阻塞等待文件描述符准备就绪
if((ret = select(maxfd + 1, &tempfds, NULL, NULL, &time_out)) < 0)
{
errlog("fail to select");
}
else if(ret == 0)
{
printf("time out ...\n");
}
else
{
...
}
}
【4】方法3:使用alarm闹钟实现网络超时检测
#include <signal.h>


    int sigaction(int signum, const struct sigaction *act,
                     struct sigaction *oldact);
功能:获取或者设置一个信号的行为
参数:
signum:信号
SIGALRM
act:新的行为
oldact:旧的行为
struct sigaction {
void     (*sa_handler)(int);   信号处理函数
void     (*sa_sigaction)(int, siginfo_t *, void *);  信号处理函数(二者在一个共用体内定义,所以二者选一)
sigset_t   sa_mask;  掩码(有关阻塞的)
int        sa_flags; 标志位
SA_RESTART 自重启属性
void     (*sa_restorer)(void);  没有用
};
返回值:
成功:0
失败:-1


//如果直接使用alarm设置时间,当时间到达时,会结束整个进程,
//但是如果结合信号,如果时间到达时,触发SIGALEM信号,处理信号处理函数
//当处理完信号处理函数后,代码还会接着当前的位置继续运行,这个性质(标志位)
//叫做自重启属性,如果要设置超时,需要将其关闭

//注意:对寄存器或者位操作,执行读、改、写三步


//第一步:读取之前的行为(标志位)
struct sigaction act;
if(sigaction(SIGALRM, NULL, &act) < 0)
{
errlog("fail to sigaction");
}


//第二步:修改对应的行为
act.sa_handler = handler;
act.sa_flags = act.sa_flags & (~SA_RESTART);


//第三步:将新的行为写入
if(sigaction(SIGALRM, &act, NULL) < 0)
{
errlog("fail to sigaction");
}

while(1)
{
alarm(5);
...
}
*********************广播**************
【1】定义
前面介绍的数据包发送方式只有一个接受方,称为单播


如果同时发给局域网中的所有主机,称为广播


只有用户数据报(使用UDP协议)套接字才能广播

广播地址
以192.168.5.0 (255.255.255.0) 网段为例,最大的主机地址192.168.5.255代表该网段的广播地址
发到该地址的数据包被所有的主机接收
255.255.255.255在所有网段中都代表广播地址
【2】流程(基于udp)
发送者:
创建套接字 socket( )
填充广播信息结构体 sockaddr_in
设置允许发送广播权限 setsockopt( )
发送数据 sendto( )

接收者:
创建套接字 socket( )
填充广播信息结构体 sockaddr_in
将套接字与广播信息结构体绑定 bind( )
接收数据 recvfrom( )


#include <sys/socket.h>


    int setsockopt(int socket, int level, int option_name,
              const void *option_value, socklen_t option_len);
功能:获取一个套接字的选项
参数:
socket:文件描述符
level:协议层次
SOL_SOCKET socket层次
option_name:选项的名称
SO_BROADCAST 设置是否允许发送广播
option_value:选项的具体值(就是获取的信息)
option_len:option_value的大小
返回值:
成功:0
失败:-1
 







DAY 5


***************组播*************
【1】定义
单播方式只能发给一个接收方。


广播方式发给所有的主机。过多的广播会大量占用网络带宽,造成广播风暴,影响正常的通信。


组播(又称为多播)是一种折中的方式。只有加入某个多播组的主机才能收到数据。


多播方式既可以发给多个主机,又能避免象广播那样带来过多的负载(每台主机要到传输层才能判断广播包是否要处理)


D类地址(组播地址)
不分网络地址和主机地址,第1字节的前4位固定为1110
224.0.0.1 – 239.255.255.255
【2】流程(基于UDP)
发送者:
创建套接字 socket( )
填充组播信息结构体 sockaddr_in
发送数据 sendto( )

接收者:
创建套接字 socket( )
填充组播信息结构体 sockaddr_in
将套接字与组播信息结构体绑定 bind( )
加入多播组 setsockopt( )
接收数据 recvfrom( )

#include <sys/socket.h>


    int setsockopt(int socket, int level, int option_name,
              const void *option_value, socklen_t option_len);
功能:获取一个套接字的选项
参数:
socket:文件描述符
level:协议层次
IPPROTO_IP IP层次
option_name:选项的名称
IP_ADD_MEMBERSHIP 加入多播组
option_value:选项的具体值(就是获取的信息)
struct ip_mreq  {
struct in_addr imr_multiaddr;   组播地址
struct in_addr imr_interface;   本地地址
INADDR_ANY 表示任意主机地址
};   
option_len:option_value的大小
返回值:
成功:0
失败:-1
*********************本地通信*********************
【1】定义
socket同样可以用于本地通信

创建套接字时使用本地协议PF_UNIX(或PF_LOCAL)。

分为流式套接字和用户数据报套接字

和其他进程间通信方式相比使用方便、效率更高

常用于前后台进程通信
【2】本地信息结构体
#include <sys/un.h>

struct sockaddr_un
{
__SOCKADDR_COMMON (sun_);
==>
#define __SOCKADDR_COMMON(sa_prefix) \                                                                           
sa_family_t sa_prefix##family
===>
sa_family_t sun_family; 协议族 AF_UNIX


char sun_path[108];     路径名
};


例子:
struct sockaddr_un serveraddr;
serveraddr.sun_family = AF_UNIX;
strcpy(serveraddr.sun_path, "server_socket");
【3】TCP本地通信
流程:
服务器端:
创建套接字 socket( )
填充本地信息结构体 sockaddr_un
将套接字与本地信息结构体绑定 bind( )
将套接字设置为监听模式 listen( )
阻塞等待客户端的连接请求 accept( )
进行通信 recv( )/send( )
客户端:
创建套接字 socket( )
填充本地信息结构体 sockaddr_un
发送客户端连接请求 connect( )
进行通信 send( )/recv( )

【4】UDP本地通信
流程:
服务器:
创建套接字 socket( )
填充服务器本地信息结构体 sockaddr_un
将套接字与网络信息结构体绑定 bind( )
进行通信 recvfrom( )/sendto( )
客户端:
创建套接字 socket( )
填充服务器本地信息结构体 sockaddr_un
填充客户端本地信息结构体 sockaddr_un
将套接字与客户端本地信息结构体绑定 bind( ) (如果不绑定,则接受不到服务器发送的数据)
进行通信 sendto( )/recvfrom( )

*************************sqlite3**************************
1-- 安装数据库:
         sudo dpkg -i  *.deb
2-- 创建数据库
         sqlite3  stu.db
注意:必须指定数据库的名称
3-- sqlite命令
         系统命令 以 "."开头
         普通命令 ,以";"结束 


         .schema  查看表的结构
         .quit    退出数据库
         .exit    退出数据库
         .help    查看帮助信息
         .databases 查看数据库
         .tables  显示数据库中所有的表的表名
4-- sqlite3 的使用
    1-- 创建一张表
       create  table 表名(字段名称1 字段类型,字段名称2  字段类型, ....);
       create table stu(id int, name char, sex char , score int);
    2-- 向表中插入一条记录
       insert into 表名 values(字段值1,字段值2,...);
       insert into stu values(1001, 'zhangsan', 'm', 89);
    3-- 查询记录
       select * from stu;                                    // 查找所有的记录
       select * from stu where id=1001;                      // 查找符号条件的记录
       select * from stu where id=1001 and name='zhangsan';  // 字符串需要加引号
       select * from stu where name = 'zhangsan' or score=92; 
    4-- 删除记录
       delete from stu where id=1004;
    5-- 更新记录
       update stu set score=98 where id=1003;
    6-- 删除一张表
       drop  table  stu;
    7-- 添加一列
        alter table stu add column score int;
    8-- 删除一列
        sqlite3 不允许直接删除一列
        1)先创建一张新表
           create table stu1 as select id , name from stu;
        2)删除原来的旧表
           drop table stu;
        3)对新表重命名
           alter table stu1 rename to stu;
    9-- 数据库主键(既设置的数据将会是唯一存在的)
        create table usr(name char primary key , passwd int);
5-- sqlite3 API 函数接口
    
(1)int sqlite3_open(
         const char *filename,   /* Database filename (UTF-8) */
         sqlite3 **ppDb 是一个传出参数,本质将open之后打开的一个文件结构体 的地址传出来        /* OUT: SQLite db handle */
         );
    功能:打开一个数据库
    参数:filename   数据库名字(数据库的本质是exec表文件,一个exec文件可以有很多个
表格(也就是一个数据库文件有很多个表组成))
          ppdb      操作数据库的指针,句柄。
    返回值:成功 SQLITE_OK , 失败 error_code

例子:
sqlite3 *db;
db 指向的是数据库信息结构体(本质是一个数据库文件结构体)
if(sqlite3_open("stu.db", &db) != SQLITE_OK)
{
printf("%s\n", sqlite3_errmsg(db));
}
else
{
printf("the database has opened successful\n");
}


    (2)const char *sqlite3_errmsg(sqlite3* db);
    功能:获取错误信息描述
    
    (3)int sqlite3_close(sqlite3* db);
    功能:关闭一个数据库
    
    (4)int sqlite3_exec(
         sqlite3* db,                                  /* An open database */
         const char *sql,                           /* SQL to be evaluated */
         int (*callback)(void*,int,char**,char**),  /* Callback function */
         void * arg,                             /* 1st argument to callback */
         char **errmsg                          /* Error msg written here */
        );
    功能:执行一条sql语句
    参数:db  数据库的句柄指针
          sql  将要被执行sql语句 
          callback 回调函数, 只有在查询语句时,才给回调函数传参,sqlite 每查到一条记录,就调用一次这个回调
          arg  为callback 的第一个参数
          errmsg 错误信息的地址
    返回值:成功 SQLITE_OK
            出错 errcode 错误码
    ***********************************************************
    int (*callback)(void* arg ,int  ncolumn ,char** f_value,char** f_name)
    功能:得到查询结果
    参数:arg  为回调函数传递参数使用的(一般写null)
          ncolumn  记录中包含的字段的数目
          f_value  包含每个字段值的指针数组
          f_name   包含每个字段名称的指针数组
    返回值:成功 0,出错 非0
    *********************************************************
例子:
char *errmsg;
if(sqlite3_exec(db, "create table stu(id int, name char, score int)", NULL, NULL, &errmsg) != SQLITE_OK)
{
printf("%s\n", errmsg);
}

   (5)int sqlite3_get_table(
     sqlite3 *db,          /* An open database */
     const char *zSql,     /* SQL to be evaluated */
     char ***pazResult,    /* Results of the query */
     int *pnRow,           /* Number of result rows written here */
     int *pnColumn,        /* Number of result columns written here */
     char **pzErrmsg       /* Error msg written here */
     );
对于传出参数而言,高级指针改变低级指针的指向,
也就是说,被调函数中的高级指针(虚参)退化一级就是主调函数中的实参变量的原型,
比如(char ***pazResult),char ***pazResult是一个传出参数,而且虚参pazResult是
三级指针,所以主调函数中的实参必定是二级指针。
 
要分清 1:传入参数(指针输入(被调函数读取主调函数的内存块的内容))
2:传出参数(高级指针改变地址指针的值(普通变量是零级指针))
3:传值参数(普通的值传递)
 
    功能:查询数据库,它会创建一个新的内存区域来存放查询的结果信息
    参数:db       数据库操作句柄
          sql      数据库的sql语句
          azResult 查询的结果
          nRow     行数
          nColumn  列数
          errmsg   错误消息
返回值:
    成功 0
         出错 errcode
nrow的值为查询到的符合条件的记录数(不包括字段名)。
ncolumn的值为查询到的符合条件的字段数。
注意:nrow的值不包括字段名,如果打印时用for (i = 0; i < nrow; i++)会打印出字段名,但是会少打印出一条符合条件的记录。
              因此打印时要用 for (i = 0; i <nrow + 1; i++);将包括字段名在内的数据都打印出来。


   
   (6)void sqlite3_free_table(char **result);
功能:释放内存


 


















  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值