六、信号与管道
七、IPC通讯
八、多线程
这三章内容已单独发布过;
九、网络编程
1.局域网和广域网
局域网:在某一个小的范围内的主机组成的网络。一个路由器下挂载的设备组成的网络,是一个局域网。
广域网:就是把无数个局域网连接在一起,组成的就是广域网。
信号、管道、消息队列、共享内存、信号量都是在主机范围内的进程间的通讯。而网络存在的意义跨主机的进程间进行数据的交互。
2:如何链接百度服务器
DNS:域名解析
1)输入www.baidu.com;
2)访问DNS服务器;
3)DNS服务器将域名解析成IP;
4)通过拿到的ip访问百度服务器,真正能访问到百度服务器的,是ip地址。
3:IP地址的分类
IP地址:IPV4的地址,4个字节(0.0.0.0-255.255.255.255),IPV4最多能分配42亿左右的ip地址,IPV4是远远不够使用的。
引入IPV6,128Bit,16字节,ipv6还没有铺开。
IPV4,4字节,32bit,ip地址用点分十进制来表示,IP地址 = 网络号+主机号。IP地址根据网络号和主机号来分,分为A、B、C三类及特殊地址D、E。全0和全1的都保留不用。
A类:
地址范围:1.0.0.0-127.255.255.255
(00000001.00000000.00000000.00000000~01111110.111111111.11111111.11111110)(这种写法是把全0全1都算进去了,其它几类类似)
网络号范围: 1~127 (0000 0001 ~ 0111 1111)
默认子网掩码:255.0.0.0 或 0xFF000000(十六进制)
前1个字节(8位)为网络号,后3个字节(24位)为主机号。
A类第1位必须是0。A类地址一般是超大型的企业会使用。
B类:
地址范围:128.1.0.1-191.255.255.255
网络号范围:128.1 ~ 191.255 (可用范围)
默认子网掩码:255.255.0.0 或 0xFFFF0000(十六进制)
前2个字节(16位)为网络号,后2个字节(16位)为主机号。
前两位固定为10。
最大网络数:2^14-1
最大主机数:2^16-2
一般用于中等规模网络。
C类:
地址范围:192.0.1.1-223.255.255.254
网络号段范围:192.0.1 ~ 223.255.255
子网掩码:255.255.255.0 或 0xFFFFFF00 (十六进制)
前3个字节(24位)为网络号,后1个字节(8位)为主机号。
前3位固定为110。
最大网络数:2^21-1
最大主机数:2^8-2
一般用于小型网络。
D类:
地址范围:224.0.0.1-239.255.255.254
是多播地址。该类IP地址的最前面为“1110”,所以地址的网络号取值于224~239之间。一般用于多路广播用户。
E类:
是保留地址。该类IP地址的最前面为“1111”,所以地址 的网络号取值于240~255之间。
4:端口号的概念
区分一台主机接收到的数据包应该转交给哪个进程来处理。 端口号为16位的无符号整形数,范围0-65535,1-1023 系统占用,1024-49150,是我们可用的端口。
访问一个服务器的时候,假如走的是http或者是https协议
http:常见的端口 80 、8080
https:常见的端口 443 、8443
5:网络字节序
编程界有两种数据的存储的方式
大端存储:高字节存放在低地址,低字节存放在高地址
小端存储:高字节存放在高地址,低字节存放在低地址
电脑:用的是小端模式
网络里:大端模式
6:TCP和UDP通讯(重点)
TCP:
在通讯之前必须要建立一个网络链接;
提供一个可靠的文件传输;
效率略低;
UDP:
在通讯之前不需要建立网络链接;
提供的是不可靠的文件传输;
效率略高;
TCP和UDP的区别:
tcp提供面向链接的可靠文件传输;
udp提供面向非链接的不可靠文件传输。
1)客户端先发送syn包到服务器;
2)服务端发送ack包和自己的syn包;
3)向服务器发送确认包 ack包。
7:ip地址的网络字节序的转换
inet_addr():将小端模式的ip(点分十进制的ip)地址转换成大端模式(32位2进制)
头文件:
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
原型:in_addr_t inet_addr(const char *cp);
参数:const char *cp:要转换的ip地址,"192.168.1.1"
其它转换函数:
int inet_aton(const char *cp, struct in_addr *inp);
//点分十进制IP-->网络字节的32位二进制数值
char *inet_ntoa(struct in_addr in);//32-->点分十进制
int inet_pton(int af, const char *src, void *dst);
//类比inet_aton 192.168.10.1 -> 11000000 10101000 00001010 00000001
const char *inet_ntop(int af, const void *src,char *dst, socklen_t cnt);
//11000000 10101000 00001010 00000001 > 192.168.10.1
af:AF_INET:表示为ipv4
AF_INET6:表示为ipv6
8:套接字的概念
linux下文件的分类(linux下一切皆文件)
普通文件 -
目录文件 d
链接文件 l
管道文件 p
字符设备文件 c
块设备文件 b
套接字文件 s
套接字:他是一个特殊的文件,文件就能读、能写。普通的文件创建的时候,open即可。套接字文件,创建的时候需要借助socket函数。
作用:
1)能够绑定ip;
2)绑定端口;
3)监听整个网络的信息;
4)设置网络的属性;
5)可以被别人链接;
6)可以链接别人。
9:套接字的创建
socket():创建一个套接字
头文件:
#include <sys/types.h>
#include <sys/socket.h>
原型:int socket(int domain, int type, int protocol);
参数:
int domain:IP的类型,IPV4 IPV6
AF_INET:表示为ipv4
AF_INET6:表示为ipv6
int type:IP协议的类型,TCP 和UDP
SOCK_STREAM:TCP协议 流式套接字
SOCK_DGRAM:UDP协议 数据报
int protocol:固定填 0
返回值:
返回的就是套接字的文件的描述符(整型数);
文件描述符跟文件操作里(非缓冲区文件操作)文件描述符起始位置是一样的,套接字也是从3开始的,0 1 2默认的被分配为:标准输入、标准输出、标准错误
10:如何链接服务器
connect():连接服务器
头文件:
#include <sys/types.h>
#include <sys/socket.h>
原型:
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数:
nt sockfd:创建的套接字
const struct sockaddr *addr:服务端信息的结构体
结构体里包含了{
ip的类型;
ip地址;
服务器的端口};
socklen_t addrlen :服务端信息结构体的大小
返回值:成功返回 0 失败返回 非零
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
服务端信息的结构体:
struct sockaddr //此结构体不常用
{
unsigned short int sa_family; //调用 socket()时的 domain 参数,即 AF_INET 值。
char sa_data[14]; //最多使用 14 个字符长度
};
struct sockaddr_in //常用的结构体
{
unsigned short int sin_family; //AF_INET
uint16_t sin_port; //为使用的 port 编号
struct in_addr sin_addr; //为 IP 地址
unsigned char sin_zero[8]; //未使用
};
struct in_addr
{
uint32_t s_addr; // =inet_addr("192.168.1.22")
};
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
因为connect()函数里面调用的是第一个结构体,所以需要把第二类强转为第一类。
struct sockaddr_in seraddr; //给第二类结构体赋初值
seraddr.sin_family=AF_INET;
seraddr.sin_port=htons(9999);
seraddr.sin_addr.s_addr=inet_addr("192.168.15.1");
connect(sfd, (struct sockaddr *)&seraddr, sizeof(struct sockaddr));//强转
11:Tcp服务端(客户端实现函数)
TCP协议编程框架
++++++++++++++++++++++++++++++++++++++++++++++++++++++
socket():创建一个套接字
头文件:
#include <sys/types.h>
#include <sys/socket.h>
原型:int socket(int domain, int type, int protocol);
参数:
int domain:IP协议
AF_INET:ipv4
AF_INET6:ipv6
int type:IP协议的类型
SOCK_STREAM: tcp 流式套接字
SOCK_DGRAM: udp 数据报
int protocol:固定填0
返回值:成功创建的套接字
++++++++++++++++++++++++++++++++++++++++++++++++++++++++
bind():绑定服务器的属性(端口,ip),绑定ip 绑定自己的ip即可。
头文件:
#include <sys/types.h>
#include <sys/socket.h>
原型:int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
参数:
int sockfd:套接字
const struct sockaddr *addr:服务器的信息的结构体
socklen_t addrlen:结构体的大小
返回值:成功返回0 失败返回-1
struct sockaddr {
sa_family_t sa_family; //ipv4或者ipv6
char sa_data[14]; //包含ip端口
}:
struct sockaddr_in {
sa_family_t sin_family; //ipv4还是ipv6
in_port_t sin_port; //端口号
struct in_addr sin_addr; //存放ip的结构体
};
struct in_addr {
uint32_t s_addr; //ip地址
};
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
listen():监听整个网络
头文件:
#include <sys/types.h>
#include <sys/socket.h>
原型:int listen(int sockfd, int backlog);
参数:
int sockfd:套接字
int backlog:能接受的最大的链接数 最大128
返回值:成功返回0 失败返回-1
++++++++++++++++++++++++++++++++++++++++++++++++++++++
accept():接收客户端的链接
头文件:
#include <sys/types.h>
#include <sys/socket.h>
原型:int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数:
int sockfd:套接字
struct sockaddr *addr:客户端的ip,端口信息存放的结构体
socklen_t *addrlen:用来存放客户端结构体长度的指针
返回值:服务器用来跟客户端进行通讯的新的套接字。
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
recv():接收发来的消息
头文件:
#include <sys/types.h>
#include <sys/socket.h>
原型:ssize_t recv(int sockfd, void *buf, size_t len, int flags);
参数:
int sockfd:新的套接字
void *buf:读取到内容存放的位置
size_t len:读取的长度
int flags :0,读不到就阻塞
返回值:成功返回 读取到的字节数
失败返回 -1
返回 0 表示客户端下线
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
send():发送一条消息
头文件:
#include <sys/types.h>
#include <sys/socket.h>
原型:ssize_t send(int sockfd, const void *buf, size_t len, int flags);
参数:
int sockfd:新的套接字
const void *buf:要发送的内容
size_t len:长度
int flags:0
返回值:成功返回 成功发送的字节数 失败返回 -1
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
connect():客户端对服务器发起链接
头文件:
#include <sys/types.h>
#include <sys/socket.h>
原型:int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
参数:
int sockfd:创建的套接字
const struct sockaddr *addr:服务器的ip、端口
socklen_t addrlen:结构体的大小
返回值:成功返回 0 失败返回 -1
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
12.udp通讯
1)udp通讯的特点
速度快、不可靠、udp可以进行组播和广播
2)udp服务端模型:
(1)创建套接字
(2)绑定服务端相关的信息
(3)接受客户端发来的消息
(4)发送消息给客户端
udp客户端模型:
(1)创建套接字
(2)向服务端发送消息
(3)接收服务端的消息
3)udp相关的函数
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
recvfrom():接收udp的消息
函数的头文件
#include <sys/types.h>
#include <sys/socket.h>
函数的原型
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
函数的参数
int sockfd:udp的套接字
void *buf:接受到的消息存放的位置
size_t len:接受的长度,
取值,len=(socklen_t)sizeof(struct sockaddr);
int flags:0 ,表示读不到就阻塞
struct sockaddr *src_addr:如果服务端用,就是客户端的ip,端口的相关的信息结构体
socklen_t *addrlen:客户端信息的结构体的大小
函数的返回值
成功返回读取到的字节数
失败返回 -1
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
函数功能:发送一条udp消息
函数的头文件
#include <sys/types.h>
#include <sys/socket.h>
函数的原型
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);
函数的参数
int sockfd:套接字
const void *buf:要发送的内容
size_t len:发送内容的长度
int flags:0,发不出则阻塞
const struct sockaddr *dest_addr:发送的对象的ip,端口信息结构体
socklen_t addrlen:结构体的大小
函数的返回值
成功返回 成功发送的字节数
失败返回 -1
+++++++++++++++++++++++++++++++++++++++++++++++++++++++
13.udp的组播
组播:
假如一个人创建了一个组播;
另外一个进程只要加入了这个组播;
创建者往组播里发东西;
加入组播的所有的人都能收到创建者发的消息
udp默认的不打开组播和广播,要想打开组播和广播功能,需要借助一个函数setsockopt();还需要有一个组播的地址,(224.0.0.0-239.255.255.255).
函数的功能:设置套接字的属性
函数的头文件:
#include <sys/types.h>
#include <sys/socket.h>
函数的原型:
int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen);
函数的参数:
int sockfd:套接字
int level所设置的网络层
IPPROTO_IP:IP层 用于设置广播以及组播
SOL_SOCKET:用于快速的释放底层ip
int optname:
属于ip层的有两个宏定义
IP_MULTICAST_IF:创建一个多播组
IP_ADD_MEMBERSHIP:加入一个多播组
属于SOL_SOCKET:
SO_BROADCAST:开启套接字的广播
SO_REUSEADDR:快速的释放底层ip
const void *optval:根据前边的选项不同,这个参数也是不同的
加入是要加入一个多播组,就需要如下结构体:
struct ip_mreqn {
struct in_addr imr_multiaddr; //要加入的多播组的ip
struct in_addr imr_address; //自己的ip
int imr_ifindex; //物理网卡的索引号
//if_name_toindex("ens33")
};
socklen_t optlen:表示前边一个参数的长度
函数的返回值:成功返回 0 失败返回 -1
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
十、多路io复用
1.多路io复用的作用及意义
说明:
tcp服务端,共涉及两类的套接字。第一类:它自己的套接字这个套接字是专门用来处理新的客户端的链接的,绑定服务器的ip和端口;第二类:在有客户端连接我的tcp服务器之后,服务端通过调用accept()函数接收客户端发起的链接后,会产生一个新的套接字,这个套接字就是用来跟客户端收发消息的。当有很多个客户端同时来连服务端的时候:不借助多线程,当有一个客户端连上我之后,假如客户端不发消息,服务端就一直阻塞在接收消息那。借助多线程,当我的客户端有很多个的时候(有128个),就需要起129线程,管理起来就会很不方便,多线程有一个比较致命的缺陷:一死俱死,容易造成服务器的不稳定。
多路io复用作用:用来解决服务器的多并发的。
2.套接字文件阻塞的问题
服务器多并发存在的问题,本质上是我的套接字文件阻塞的问题,
第一个阻塞的点:accept():
当没有客户端连服务器的时候,会阻塞,当有客户端来连接我的时候,会解除阻塞,相当于套接字(服务器本身的套接字)文件产生了异动(可读性),解除阻塞;
第二阻塞的点,recv():
当客户端没有给服务端发消息的时候会陷入阻塞,当客户端发了消息,会解除阻塞,相当于套接字(用来跟客户端通讯的套接字)产生了异动 ,可读性。
多路io复用(一种机制):当有客户端要来链接服务器的时候再去调用accept(),当有客户端给服务器发消息的时候再去调用recv(),这样的话服务端的进程就不会阻塞了。
多路io重点掌握两类:select和poll轮询
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3.select的机制
select他的最主要的功能就是监测文件,监测文件的异动。
select的机制工作步骤:
1)会创建一个表,这个表最大能有1024个格;
2)当服务器起来之后,就有了自己的第一个套接字,这个就是我的第一类的套接字文件,将这个套接字放到select表里,select就会监测我的这个套接字的异动,select函数则会进入阻塞;
3)假如有客户来链接我的服务器的时候,select表就会产生异动,就会解除select的阻塞,服务端就可以去调用accept函数来接受客户端的链接,会产生一个新的套接字;
4)我们需要将新产生的这个套接字放入select这张表里;
5)再调用select函数的时候,我们就会监测两个套接字 ,一个一类,一个二类的,对应的就是服务端本身的套接字和用来跟客户端通讯的套接字。
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
重点函数:
函数功能:多路io复用
函数的头文件
#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);
函数的参数
int nfds:表格里的最大的文件描述符+1
fd_set *readfds:监测文件可读性的表格,一般只用这个
fd_set *writefds:监测文件的可写性的表格,一般并不,NULL
fd_set *exceptfds:监测文件异常的表格,一般不用,填写NULL
struct timeval *timeout:监测表格的最大的时间,在规定的时间里去监测表格里的所有文件,在这个规定的时间内产生了异动,则返回正常值;在这个规定的时间内没有产生异动则返回出错(超时),一般也是填写NULL,表示无限期的等待。
函数的返回值:超时返回,0 失败返回 -1
有事件发生,则返回事件的个数
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
表格操作系统提供了一些宏
void FD_CLR(int fd, fd_set *set);从表格里删除一个文件描述符
int FD_ISSET(int fd, fd_set *set);检查文件描述符是否在这个表格中
void FD_SET(int fd, fd_set *set);添加一个文件描述符到表格里
void FD_ZERO(fd_set *set);清空一个表格++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
在使用select表格的时候,需要注意的点:
假如我的表格里有很多个文件描述符,当我的表格里的文件描述符有产生异动的时候,会将没有产生异动的文件描述符全部清除掉,所以需要多创建一个select表格做备份。
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4.poll轮询的特点:
poll轮询思路跟select大致是一样的,也是监测文件的异动,包括文件的可读、可写、出错。
缺点:相对于select来说,poll比较不灵活,效率也没有select高;添加新的文件到监测的表里比select要难;反应没有select快。
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
poll轮询函数
函数功能:多路io复用
函数的头文件:#include <poll.h>
函数的原型:int poll(struct pollfd *fds, nfds_t nfds, int timeout);
函数的参数
struct pollfd *fds, 结构体
struct pollfd {
int fd; //要监测的文件描述符
short events; //请求事件(监测的事件)
POLLIN:监测文件是否产生可读性
POLLOUT:监测文件是否产生可写性
POLLERR:监测文件是否发生异常
short revents; //返回事件
};
nfds_t nfds:监测的结构体的数组的大小。
int timeout:超时时间,到了这个时间没有异动发生,则返回 错误。一般填写 -1 表示永久等待
函数的返回值
成功返回发生异动的事件的个数;
失败返回 -1;
超时返回 0;