您现在的位置是:首页 >学无止境 >C语言之网络编程(必背知识点)网站首页学无止境
C语言之网络编程(必背知识点)
一、认识网络
1、网络发展史
ARPnetA--Internet--移动互联网--物联网
2、局域网和广域网
局域网(LAN)
局域网的缩写是LAN,local area network,顾名思义,是个本地的网络,只能实现小范围短距离的网络通信。我们的家庭网络是典型的局域网。电脑、手机、电视、智能音箱、智能插座都连在路由器上,可以互相通信。局域网,就像是小区里的道路,分支多,连接了很多栋楼。
广域网(Wan)
广域网(Wide Area Network)是相对局域网来讲的,局域网的传输距离比较近,只能是一个小范围的。如果需要长距离的传输,比如某大型企业,总部在北京,分公司在长沙,局域网是无法架设的。广域网,就像是大马路,分支可能少,但类型多,像国道、省道、高速、小道等,连接了很多大的局域网。
这时需要其它的解决方案。
第一,通过因特网,只需要办一根宽带,就实现了通信,非常方便,现在的宽带价格也比较便宜。
第二,通过广域网专线。
所以为了数据安全,不能连接因特网,需要用一条自己的专用线路来传输数据,这条线路上只有自己人,不会有其他人接入,且距离很远,这个网络就叫 “广域网”。
3、光猫
光猫是一种类似于基带modem(数字调制解调器)的设备,和基带modem不同的是接入的是光纤专线,是光信号。用于广域网中光电信号的转换和接口协议的转换,接入路由器,是广域网接入。
将光线插入左侧的灰色口,右侧网口接网线到路由器即可。
4、交换机与路由器
交换机(二层):用于局域网内网的数据转发路由器(三层):用于连接局域网和外网
路由器有交换机的功能,反之不成立,交换机没有IP分配和IP寻址的功能。
交换机各个口是平等的,所有接入的设备需要自己配置IP,然后组成局域网。
路由器需要区分WAN口和LAN口,WAN口是接外网的(从Modem出来的或者从上一级路由器出来的),LAN口是接内网的,现在路由器都带无线功能,本质上无线接入就是LAN。
5、网线
背过一种线序,了解网线的制作流程。
6、IP地址
6.1 基本概念
- IP地址是Internet中主机的标识
- Internet中的主机要与别的机器通信必须具有一个IP地址
- IP地址为32位(IPv4)或者128位(IPv6)
- 表示形式:常用点分形式,如202.38.64.10,最后都会转换为一个32位的无符号整数。
6.2 网络号/主机号
6.2.1 地址划分
主机号的第一个和最后一个都不能被使用,第一个作为网段号,最后一个最为广播地址。
A类:1.0.0.1~126.255.255.254
B类:128.0.0.1~~191.255.255.254
C类:192.0.0.1~~223.255.255.254
D类(组播地址):224.0.0.1~~239.255.255.254
6.2.2 特殊地址
0.0.0.0:在服务器中,0.0.0.0指的是本机上的所有IPV4地址,如果一个主机有两个IP地址,192.168.1.1 和 10.1.2.1,并且该主机上的一个服务监听的地址是0.0.0.0,那么通过两个ip地址都能够访问该服务。
127.0.0.1:回环地址/环路地址,所有发往该类地址的数据包都应该被loop back。
6.3 子网掩码
IP地址=网络号+主机号,使用子网掩码来进行区分
网络号:表示是否在一个网段内(局域网)
主机号:标识在本网段内的ID,同一局域网不能重复
- 子网掩码:是一个32位的整数,作用是将某一个IP划分成网络地址和主机地址;
- 子网掩码长度是和IP地址长度完全一样;
- 网络号全为1,主机号全为0;
- 公式:网络号=IP & MASK
思考一:上图中B类地址的子网掩码怎么写?
思考二:B类地址,同一网段最多可以连接多少个主机?
思考三:已知一个子网掩码号为255.255.255.192,问,最多可以连接多少台主机?
7、网络模型
7.1 网络的体系结构
- 网络采用分而治之的方法设计,将网络的功能划分为不同的模块,以分层的形式有机组合在一起。
- 每层实现不同的功能,其内部实现方法对外部其他层次来说是透明的。每层向上层提供服务,同时使用下层提供的服务
- 网络体系结构即指网络的层次结构和每层所使用协议的集合
- 两类非常重要的体系结构:OSI与TCP/IP
7.2 OSI模型
- OSI模型是一个理想化的模型,尚未有完整的实现
- OSI模型共有七层
- OSI现阶段只用作教学和理论研究
7.3 TCP/IP模型
网络接口和物理层:屏蔽硬件差异(驱动),向上层提供统一的操作接口。
网络层:提供端对端的传输,可以理解为通过IP寻址机器。
传输层:决定数据交给机器的哪个任务(进程)去处理,通过端口寻址
应用层:应用协议和应用程序的集合
OSI和TCP/IP模型对应关系图
7.4 常见网络协议
网络接口和物理层:
ppp:拨号协议(老式电话线上网方式)
ARP:地址解析协议 IP-->MAC
RARP:反向地址转换协议 MAC-->IP
网络层:
IP(IPV4/IPV6):网间互连的协议
ICMP:网络控制管理协议,ping命令使用
IGMP:网络分组管理协议,广播和组播使用
传输层:
TCP:传输控制协议
UDP:用户数据报协议
应用层:
SSH:加密协议
telnet:远程登录协议
FTP:文件传输协议
HTTP:超文本传输协议
DNS:地址解析协议
SMTP/POP3:邮件传输协议
注意:TCP和IP是属于不同协议栈层的,只是这两个协议属于协议族里最重要的协议,所以协议栈或者模型以之命名了。
8. TCP/UDP
TCP
TCP(即传输控制协议):是一种面向连接的传输层协议,它能提供高可靠性通信(即数据无误、数据无丢失、数据无失序、数据无重复到达的通信)。
适用场景
适合于对传输质量要求较高的通信
在需要可靠数据传输的场合,通常使用TCP协议
MSN/QQ等即时通讯软件的用户登录账户管理相关的功能通常采用TCP协议
UDP
UDP(User Datagram Protocol)用户数据报协议,是不可靠的无连接的协议。在数据发送前,因为不需要进行连接,所以可以进行高效率的数据传输。
适用场景
发送小尺寸数据(如对DNS服务器进行IP地址查询时)
适合于广播/组播式通信中。
MSN/QQ/Skype等即时通讯软件的点对点文本通讯以及音视频通讯通常采用UDP协议
9. 编程预备知识
9.1 socket定义
9.2 socket类型
流式套接字(SOCK_STREAM) TCP
提供了一个面向连接、可靠的数据传输服务,数据无差错、无重复的发送且按发送顺序接收。内设置流量控制,避免数据流淹没慢的接收方。数据被看作是字节流,无长度限制。
数据报套接字(SOCK_DGRAM) UDP
提供无连接服务。数据包以独立数据包的形式被发送,不提供无差错保证,数据可能丢失或重复,顺序发送,可能乱序接收。
原始套接字(SOCK_RAW)
可以对较低层次协议如IP、ICMP直接访问。
9.4 端口号
- 为了区分一台主机接收到的数据包应该转交给哪个进程来进行处理,使用端口号来区
- TCP端口号与UDP端口号独立
- 端口号一般由IANA (Internet Assigned Numbers Authority) 管理
- 端口用两个字节来表示
众所周知端口:1~1023(1~255之间为众所周知端口,256~1023端口通常由UNIX系统占用)
已登记端口:1024~49151
动态或私有端口:49152~65535
9.5 字节序
小端序(little-endian) - 低序字节存储在低地址
大端序(big-endian)- 高序字节存储在低地址
网络中传输的数据必须使用网络字节序,即大端字节序
面试题:写一个函数,判断当前主机的字节序?
int checkCPU()
{
union w{
int a;
char b;
}c;
c.a = 1;
return (c.b == 1);
}
主机字节序到网络字节序
u_long htonl (u_long hostlong);
u_short htons (u_short short); //掌握这个
网络字节序到主机字节序
u_long ntohl (u_long hostlong);
u_short ntohs (u_short short);
9.6 IP地址转换
typedef uint32_t in_addr_t;
struct in_addr {
in_addr_t s_addr;
};
in_addr_t inet_addr(const char *cp); //从人看的ip地址转为机器使用的32位无符号整数
char *inet_ntoa(struct in_addr in); //从机器到人
示例
int main()
{
in_addr_t addr;
addr = inet_addr("192.168.1.222");
printf("addr=0x%x
", addr);
struct in_addr tmp;
tmp.s_addr = addr;
printf("ip=%s
", inet_ntoa(tmp));
}
10. 复习
历史:
阿帕网:不能互联不同的主机、不同操作系统,没有纠错功能
TCP/IP:
IP
TCP
IP : A:首位固定为0。1byte网络号,3byte主机号
0000 0000 - 0111 1111 >0-127
123.0000...000 - 1111...111
255.0.0.0
B:首位固定为10。2byte网络号,2byte主机号
128.0 - 191.255.
172.125
255.255.0.0
C:首位固定为110。3byte网络号,1byte主机号
192.0.0-223.255.255
192.168.1
0-255 =》 254
255.255.255.0
子网掩码:网络全为1,主机号全为0.
22位网络号 10位主机号:
10 00...00 10 11...11
子网掩码:255.255.111111 00 .0000 0000
255.255.252.0
ip=网络号+主机号
网络号:是否处于同一网段
主机号: 唯一分配给主机的id
D(组播)E
port:端口。标识进程 udp 和 TCP端口独立
1-1023
>1023
socket - TCP/TP
IO-C b
网络设备 — socket - > fd
TCP流程:
服务器:
1.创建流式套接字socket .返回连接文件描述符
2.绑定(填充通信结构体)bind
3.监听。主动套接字变为被动套接字listen
4.阻塞等待客户端连接accept .返回通信文件描述符
5.收发消息
6.关闭套接字
客户端:
1.创建流式套接字socket
2.填充服务器的通信结构体
3.请求连接connect
4.收发消息
5.关闭套接字
【1】
基础理论:ip port socket 套接字类型 OSI TCP/IP udp TCP
核心编程框架:TCP UDP
UDP可以直接实现并发服务器
TCP-循环服务器
*TCP实现并发服务器。
引入linux IO模型4种:
1.阻塞IO:
特点-最常用、不能处理多路IO,效率低,不需要轮询,不浪费cpu资源
2.非阻塞:特点-不常用、能处理多路IO,需要轮询,耗费CPU
3.信号驱动IO:异步IO,需要底层驱动支持
4.IO多路复用 - 能实现TCP并发
select poll epoll
【2】UPD
服务器:
创建套接字(数据报套接字)
填充服务器的通信结构体
绑定
发收:
sendto
客户端:
创建套接字socket
填充服务器的通信结构体
发送
sendto(sockfd,buf,size,0,(struct sockaddr*)&saddr,
sizeof(saddr));
len=sizeof(caddr);
recvfrom(sockfd,buf,size,0,(struct sockaddr*)&caddr,&len);
非阻塞:
函数自带参数设置
fcntl(fd,功能选择,属性值(int))
F_GETFL F_SETFL F_SETOWN
IO多路复用:select
编程流程:
1.创建表
fd_set readfds,tempfds;
FD_ZERO(&readfds);
2.添加关心文件描述符到表中
FD_SET(0,&readfds);
// FD_SET(sockfd,&reafds);
...
3.调用函数检测
tempfds=readfds;
select(maxfd+1,&tempfds,NULL,NULL,NULL);
4.一个或多个文件描述符有事件产生返回
5.判断是那个文件描述符产生事件
if(FD_ISSET(0,&tempfds))
6.处理事件
{
fgets(buf,sizeof(buf),stdin);
}
if(FD_ISSET(sockfd,&tempfds))
{
acceptfd=accept();
}
select(检测文件描述符个数,读、写、异常,超时检测)
FD_SET:添加文件描述符到表中
FD_ZERO:清空表
FD_ISSET:判断对应文件描述符是否在表中
FD_CLR:从表中清除指定文件描述符
poll(表-结构体数组,数组有效元素的个数-检测文件描述符个数,-1->阻塞);
结构体:
fd
events:检测事件-POLLIN读 POLLOUT
revents:函数poll返回自动填充
如果对应fd有对应事件产生,将revents=events
如果对应fd没有对应事件产生,revents=0;
epoll:
int epfd=epoll_create(>0)
epoll_ctl(epfd,功能选择,fd,event-事件结构体)
功能选择:EPOLL_CTL_ADD 添加
EPOLL_CTL_MOD 修改已经添加事件
EPOLL_CTL_DEL 删除
event结构体:
data.fd
events: EPOLLIN|EPOLLET读
EPOLLOUT|EPOLLET 写
epoll_wait(epfd,事件存放的位置-事件结构体,数组元素个数,-1->阻塞);
多进程和多线程实现并发服务器思想:
每有一个客户端连接,创建一个子进程或线程和这个
客户端通信,父进程或主线程阻塞等待下一个客户端
连接。
fork创建进程的特点:
1.fork创建的子进程几乎拷贝了父进程所有的内容
三个段:正文、堆栈、数据段
2.fork之后父进程中返回子进程的PID,子进程中
返回0.
3.父进程先退出子进程孤儿进程,子进程先退出,
父进程没有回收资源,子进程僵尸进程。
4.fork之前的代码被复制,不会重新执行,fork之后
的代码会被复制并执行。
5.fork之前打开的文件,fork之后拿到的是同一个文件
描述符,操作同一个文件指针。
6.fork创建进程之后,两个进程就相互独立。
7.子进程状态发生改变会给父进程发送一个SIGCHLD信号
二、TCP编程
1.流程
服务器:
socket:创建一个用与链接的套接字
bind:绑定自己的ip地址和端口
listen:监听,将主动套接字转为被动套接字
accept:阻塞等待客户端链接,链接成功返回一个用于通信套接字
recv:接收消息
send:发送消息
close:关闭文件描述符
客户端:
socket:创建一个套接字
填充结构体:填充服务器的ip和端口
connect:阻塞等待链接服务器
recv/send:接收/发送消息
close:关闭
2.函数接口
1.socket
int socket(int domain, int type, int protocol);
功能:创建套接字
参数:
domain:协议族
AF_UNIX, AF_LOCAL 本地通信
AF_INET ipv4
AF_INET6 ipv6
type:套接字类型
SOCK_STREAM:流式套接字
SOCK_DGRAM:数据报套接字
protocol:协议 - 填0 自动匹配底层 ,根据type
系统默认自动帮助匹配对应协议
传输层:IPPROTO_TCP、IPPROTO_UDP、IPPROTO_ICMP
网络层:htons(ETH_P_IP|ETH_P_ARP|ETH_P_ALL)
返回值:
成功 文件描述符
失败 -1,更新errno
2.bind
int bind(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
功能:绑定 ipv4 ip和端口
参数
sockfd:文件描述符
addr:通用结构体,根据socket第一个参数选择的通信方式最终确定这需要真正填充传递的结构体是那个类型。强转后传参数。
addrlen:填充的结构体的大小
返回值:0 失败-1、更新errno
通用结构体:相当于预留一个空间
struct sockaddr {
sa_family_t sa_family;
char sa_data[14];
}
ipv4的结构体
struct sockaddr_in {
sa_family_t sin_family; //协议族AF_INET
in_port_t sin_port; //端口
struct in_addr sin_addr;
};
struct in_addr {
uint32_t s_addr; //IP地址
};
本地址通信结构体:
struct sockaddr_un {
sa_family_t sun_family; //AF_UNIX
char sun_path[108]; //在本地创建的套接字文件的路径及名字
};
ipv6通信结构体:
struct sockaddr_in6 {
sa_family_t sin6_family;
in_port_t sin6_port;
uint32_t sin6_flowinfo;
struct in6_addr sin6_addr;
uint32_t sin6_scope_id;
};
struct in6_addr {
unsigned char s6_addr[16];
};
3.listen
int listen(int sockfd, int backlog);
功能:监听,将主动套接字变为被动套接字
参数:
sockfd:套接字
backlog:同时响应客户端请求链接的最大个数,不能写0.
不同平台可同时链接的数不同,一般写6-8个
(队列1:保存正在连接)
(队列2,连接上的客户端)
返回值:成功 0 失败-1,更新errno
4.accept
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
accept(sockfd,NULL,NULL);
阻塞函数,阻塞等待客户端的连接请求,如果有客户端连接,
则accept()函数返回,返回一个用于通信的套接字文件;
参数:
Sockfd :套接字
addr: 链接客户端的ip和端口号
如果不需要关心具体是哪一个客户端,那么可以填NULL;
addrlen:结构体的大小
如果不需要关心具体是哪一个客户端,那么可以填NULL;
返回值:
成功:文件描述符; //用于通信
失败:-1,更新errno
5.recv
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
功能: 接收数据
参数:
sockfd: acceptfd ;
buf 存放位置
len 大小
flags 一般填0,相当于read()函数
MSG_DONTWAIT 非阻塞
返回值:
< 0 失败出错 更新errno
==0 表示客户端退出
>0 成功接收的字节个数
6.connect
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
功能:用于连接服务器;
参数:
sockfd:socket函数的返回值
addr:填充的结构体是服务器端的;
addrlen:结构体的大小
返回值
-1 失败,更新errno
正确 0
7.send
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
功能:发送数据
参数:
sockfd:socket函数的返回值
buf:发送内容存放的地址
len:发送内存的长度
flags:如果填0,相当于write();
3.代码实现
优化代码
1.去掉fgets获取的多余的'
'.
if(buf[strlen(buf)-1] == '
')//去掉fgets获取的'
'
buf[strlen(buf)-1] ='