目录
局域网和广域网
局域网
局域网LAN(local area network),顾名思义,是个本地的网络,只能实现小范围短距离的网络通信。我们的家庭网络是典型的局域网。电脑、手机、电视、智能音箱、智能插座都连在路由器上,可以互相通信。局域网,就像是小区里的道路,分支多,连接了很多栋楼。
广域网
广域网WAN(Wide Area Network)是相对局域网来讲的,局域网的传输距离比较近,只能是一个小范围的。如果需要长距离的传输,比如某大型企业,总部在北京,分公司在长沙,局域网是无法架设的。广域网,就像是大马路,分支可能少,但类型多,像国道、省道、高速、小道等,连接了很多大的局域网。
家庭用网
1.光猫
光猫是一种类似于基带modem(数字调制解调器)的设备,和基带modem不同的是接入的是光纤专线,是光信号。用于广域网中光电信号的转换和接口协议的转换,接入路由器,是广域网接入
2.交换机与路由器
交换机(二层):用于局域网内网的数据转发
路由器(三层):用于连接局域网和外网
路由器有二层交换机的功能,反之不成立,二层交换机没有IP分配和IP寻址的功能。
交换机各个口是平等的,所有接入的设备需要自己配置IP,然后组成局域网。路由器需要区分WAN口和LAN口,WAN口是接外网的,LAN口是接内网的,现在路由器都带无线功能,本质上无线接入就是LAN。
设备通信的要素
找主机依赖的是:IP地址
找进程依赖的是端口号
IP地址
基本概念
1.IP地址是Internet中主机的标识
2.Internet中的主机要与别的机器通信必须具有一个IP地址
3.IP地址为32位(IPv4)或者128位(IPv6)
4.表示形式:常用点分十进制形式,最后都会转换为一个32位的无符号整数。
五类:A B C D E
ip地址划分(IPv4)
二级划分 ip=网络号+主机号
网络号:表示是否在一个网段内(局域网)
主机号:标识在本网段内的ID,同一局域网不能重复
主机号的第一个和最后一个都不能被使用,第一个作为网段号,最后一个最为广播地址。
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
主机号:全零不能用,全零为网络标识
全一不能用,全一为广播地址
A类:
网络号最小:0000 0000
网络号最大:1111 1110
网络号:0-126主机号最小:0000 0000.0000 0000.0000 0001
主机号最大:1111 1111.1111.1111.1111 1110
主机号:0.0.1-255.255.254所以A类IP范围为0.0.0.1-126.255.255.254
B类:
网络号最小:1000 0000.0000 0000
网络号最大:1011 1111.1111 1111
网络号:128.0-191.255主机号最小:0000 0000.0000.0001
主机号最大:1111 1111.1111.1110
主机号:0.1-255.254所以B类IP范围为128.0.0.1-191.255.255.254
C类:
网络号最小:1100 0000.0000 0000.0000 0000
网络号最大:1101 1111.1111 1111.1111 1111
网络号:192.0.0-223.255.255主机号最小:0000 0001
主机号最大:1111 1110
主机号:1-254所以C类IP范围为192.0.0.1-223.255.255.254
特殊地址
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。
NAT
私有地址
内网不可以随便分配地址,因此需要设置一定的规则,规定某些地址是用于内网的,这些地址叫作私有地址,而原来的固定地址则叫作公有地址。私有地址的规则其实并不复杂,在内网中可用作私有地址的范围仅限以下这些。
10.0.0.0 ~ 10.255.255.255
172.16.0.0 ~ 172.31.255.255
192.168.0.1 ~ 192.168.255.254
相对地,内网部分则分配私有地址,内网中的设备不能和互联网直接收发网络包,而是通过一种特别的机制进行连接,这个机制就叫地址转换(NAT,Network Address Translation)。
NAT的基本原理
地址转换的基本原理是在转发网络包时对 IP 头部中的 IP 地址和端口号进行改写.
DHCP服务器和NAT设备是网络中两种不同的功能,分别负责不同的任务。
1. DHCP服务器(动态主机配置协议)
功能:DHCP服务器的主要任务是自动分配IP地址、子网掩码、默认网关和DNS服务器ip等网络配置给网络中的设备(客户端),简化网络管理。
工作原理:
当一个设备(如电脑或手机)首次连接到网络时,它会发送一个DHCP请求。
DHCP服务器接收到请求后,从预设的IP地址池中分配一个可用的IP地址,并返回给客户端
作用对象:DHCP服务器专注于局域网内部的IP地址管理。
2. NAT设备(网络地址转换)
功能:NAT设备的主要功能是将内部私有IP地址转换为公共IP地址,或反向操作,以便在局域网和外部网络(如互联网)之间进行通信。
工作原理:
当局域网内的设备访问互联网时,NAT设备记录设备的内部IP地址和源端口,并用公共IP地址替换。
响应的流量也会经过NAT设备,NAT通过记录的映射关系将数据包发送回正确的内部设备。
作用对象:NAT设备用于管理与外部网络(如互联网)的通信,通常作为路由器的一部分。
职责不同:DHCP服务器主要负责IP地址的分配,而NAT设备负责地址转换和流量转发。
使用场景不同:DHCP通常在局域网内部使用,NAT则主要用于连接局域网与外部网络。
子网掩码
网络号对应位全为1,主机号对应位全为0;
子网掩码的格式
与IP地址一样长的32位整数,由一串连续的1,后面跟着一串连续的0组成。
默认子网掩码中,1的个数与IP地址中网络号的个数一致。0的个数与IP地址中主机号的个数一致。
A类:255.0.0.0
B类:255.255.0.0
C类:255.255.255.0
作用
1.计算网段和主机ID
ip&子网掩码=网络号
ip&(~子网掩码)=主机号
2.划分子网
TCP和UDP
共同点:都工作在传输层
TCP:传输控制协议
TCP(Transmission Control Protocol)是一种面向连接的传输层协议,它能提供高可靠性通信(即数据无误、数据无丢失、数据无失序、数据无重复到达的通信)
适用情况:
1、适合于对传输质量要求较高,以及传输大量数据的通信。
2、在需要可靠数据传输的场合,通常使用TCP协议
3、MSN/QQ等即时通讯软件的用户登录账户管理相关的功能通常采用TCP协议
缺点:
发送量较大,效率低
UDP :用户数据报协议
UDP(User Datagram Protocol)用户数据报协议,是不可靠的无连接的协议。在数据发送前,因为不需要进行连接,所以可以进行高效率的数据传输。
适用情况:
1、发送小尺寸数据(如对DNS服务器进行IP地址查询时)
2、在接收到数据,给出应答较困难的网络中使用UDP。
3、适合于广播/组播式通信中。
4、流媒体、VOD、VoIP、IPTV等网络多媒体服务中通常采用UDP方式进行实时数据传输
网络编程预先知识
Socket
1、是一个编程接口,API---->函数Socket();
2、是一种特殊的文件描述符 (everything in Unix is a file)
3、并不仅限于TCP/IP协议
4、面向连接 (Transmission Control Protocol - TCP)
5、无连接 (User Datagram Protocol -UDP )
Socket是一种通用的网络编程接口
socket类型
流式套接字(SOCK_STREAM) TCP
提供了一个面向连接、可靠的数据传输服务,数据无差错、无重复的发送且按发送顺序接收。内设置流量控制,避免数据流淹没慢的接收方。数据被看作是字节流,无长度限制。
数据报套接字(SOCK_DGRAM) UDP
提供无连接服务。数据包以独立数据包的形式被发送,不提供无差错保证,数据可能丢失或重复,顺序发送,可能乱序接收。
原始套接字(SOCK_RAW)
可以对较低层次协议如IP、ICMP直接访问。
端口号 port
1.为了区分一台主机接收到的数据包应该转交给哪个进程来进行处理,使用端口号来区分
2.TCP端口号与UDP端口号独立
3.端口用两个字节来表示 2byte 16bit
众所周知端口:1~1023(1~255之间为众所周知端口,256~1023端口通常由UNIX系统占用)
已登记端口:1024~4915(选1000以上10000以下)
动态或私有端口:49152~65535套接字创建以后,套接字对应的端口号可以指定,如果不指定,则随机分配
字节序(大小端)
发送时需要将主机字节序转换成网络字节序(大端)
1.小端字节序:低序字节存储在低地址上;
2.大端字节序:低序字节存储在高地址上;
注意:数据的读取顺序都是从低地址往高地址读取,通过大小端转换得到结果。
1.主机字节序到网络字节序
u_long htonl (u_long hostlong);
u_short htons (u_short short);
2.网络字节序到主机字节序
u_long ntohl (u_long hostlong);
u_short ntohs (u_short short);
IP地址转换
typedef uint32_t in_addr_t;
struct in_addr{
in_addr_t s_addr;
};in_addr_t inet_addr(const char *cp); //从人看的到机器识别的32位无符号整数
char * inet_ntoa(struct in_addr in); //从机器到人
1.将点分十进制转换成32位无符号整数
2.改变字节序
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
int main(int argc, char const *argv[])
{
// 将点分十进制转换为无符号整数
in_addr_t ip32 = inet_addr("192.168.50.121");
printf("%#x\n", ip32);
// 将无符号整数转换成点分十进制
struct in_addr in;
in.s_addr = ip32;
char *p = inet_ntoa(in);
printf("%s\n", p);
return 0;
}
TCP编程
C/S 客户端服务器架构
流程
1.通信是基于套接字进行的通信,所以首先要有一个套接字 Socket();
2.服务器为了便于客户端寻找到自己,所以服务器要对外公开自己的IP和端口号,公开的前提是已经指定好了IP和端口号,这一步通过函数bind实现,名为绑定
3.每次连接都是客户端给服务器发送连接请求,而服务器套接字接收连接请求的前提是自己要进行变身,从主动套接字变为被动套接字,然后就可以等待连接请求发送过来了,此步由listen函数实现,名为监听。
4.如果接收到连接请求并评估可以建立连接,则产生一个新的套接字,这一步由accept函数实现,accept是一个阻塞函数,当没有连接可以建立的时候就阻塞住,有连接建立,就解除阻塞,同时创建一个新的套接字,此套接字用于通信,该套接字是accept的返回值
5.接收和发送,其实就是对通信的套接字进行读写
6.回收资源
函数接口
1、socket
int socket(int domain, int type, int protocol);
功能:创建套接字
参数:
domain:协议族
AF_UNIX, AF_LOCAL 本地通信
AF_INET IPV4
AF_INET6 IPV6
type:套接字类型
SOCK_STREAM:流式套接字----TCP
SOCK_DGRAM:数据报套接字---UDP
protocol:协议 - 填0 自动匹配底层 ,根据type系统默认自动帮助匹配对应协议
返回值:
成功:文件描述符
失败:-1
2、bind
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
功能:绑定IPV4的IP和端口号参数
sockfd:文件描述符
addr:通用结构体,根据socket第一个参数选择的通信方式最终确定这需要真正填充传递的结构体是那个类型。强转后传参数。addrlen:填充的结构体的大小
返回值成功:0
失败:-1
通用结构体:相当于预留一个空间
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地址
};
3、listen
int listen(int sockfd, int backlog);
功能:监听,将主动套接字变为被动套接字
参数:
sockfd:套接字
backlog:同时响应客户端请求链接的最大个数,不能写0,一般写6-8个
返回值:成功:0
失败:-1
4、accept
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
一般写:accept(sockfd,NULL,NULL);
阻塞函数,阻塞等待客户端的连接请求,如果有客户端连接,则accept()函数返回,返回一个用于通信的套接字文件;
参数:
Sockfd :套接字
addr:链接客户端的ip和端口号,如果不需要关心具体是哪一个客户端,那么可以填NULL;
addrlen:结构体的大小,如果不需要关心具体是哪一个客户端,那么可以填NULL;
返回值:
成功:文件描述符; //用于通信
失败:-1
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
正确 0
7、send
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
功能:发送数据
参数:
sockfd:socket函数的返回值
buf:发送内容存放的地址
len:发送内存的长度
flags:如果填0,相当于write();
案例:
server.c服务端:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
// 1.创建套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
printf("sorkfd:%d\n", sockfd);
// 2.bind绑定IP和Port端口号
struct sockaddr_in saddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(8888);
saddr.sin_addr.s_addr = inet_addr("192.168.50.213");
socklen_t addrlen = sizeof(saddr);
if (bind(sockfd, (struct sockaddr *)&saddr, addrlen) < 0)
{
perror("bind失败");
return -1;
}
printf("bind成功\n");
// 3.监听listen将主动套接字变为被动套接字
if (listen(sockfd, 7) < 0)
{
perror("lisren失败");
return -1;
}
printf("listen成功\n");
// 4.accept阻塞等待链接
int acceptfd = accept(sockfd, NULL, NULL);
if (acceptfd < 0)
{
perror("accept失败\n");
return -1;
}
printf("acceptfd:%d\n", acceptfd);
// 5.发送
#define N 64
char buf[N];
char buf1[N];
while (1)
{
int ret = recv(acceptfd, buf, N, 0);
if (ret < 0)
{
perror("recv失败\n");
return -1;
}
else if (ret == 0)
{
printf("客户端退出\n");
return -1;
}
else
{
printf("客户端:%s\n", buf);
}
printf("服务器:");
scanf("%s", buf1);
send(acceptfd, buf1, N, 0);
memset(buf, 0, N);
memset(buf1, 0, N);
}
close(sockfd);
close(acceptfd);
return 0;
}
client.c客户端:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
// 1.创建套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
printf("sorkfd:%d\n", sockfd);
// 2.连接
unsigned short post = 0;
char ip[15];
printf("请输入ip地址");
scanf("%s", ip);
getchar();
printf("请输入端口号");
scanf("%hd", &post);
getchar();
struct sockaddr_in saddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(post);
saddr.sin_addr.s_addr = inet_addr(ip);
socklen_t addrlen = sizeof(saddr);
if (connect(sockfd, (struct sockaddr *)&saddr, addrlen) != 0)
{
perror("connect失败\n");
return -1;
}
// 3.发送
#define N 64
char buf[N];
char buf1[N];
while (1)
{
printf("客户端:");
scanf("%s", buf);
send(sockfd, buf, N, 0);
int ret = recv(sockfd, buf1, N, 0);
if (ret < 0)
{
perror("recv失败\n");
return -1;
}
else if (ret == 0)
{
printf("客户端退出\n");
return -1;
}
else
{
printf("服务器:%s\n", buf1);
}
memset(buf, 0, N);
memset(buf1, 0, N);
}
close(sockfd);
return 0;
}