计算机网络
- 什么是计算机网络
计算机网络是指将地理位置不同的具有独立功能的多台计算机及其外部设备,通过通信线路连接起来,在网络操作系统,网络管理软件及网络通信协议的管理协调下,实现资源共享和信息传递和计算机系统
-
计算机网络的功能
- 数据通信
- 资源共享
- 提高系统的可靠性
- 分布式网络处理和负载均衡
-
计算机网络的组成
- 通信子网 网卡,线缆,集线器,中继器,交换机,路由器
- 资源子网 网络中的计算机,打印机,电话等一些可以提供服务的设备
- 计算机网络软件
- 协议软件 它规定了计算机之间通信的规则 TCP/TP协议簇
- 网络通信软件 网络中实现计算机与设备之间通信的软件(网卡驱动)
- 网络管理软件 防火墙,SELinux
- 网络应用软件 浏览器,迅雷
- 网络操作系统 可以提供网络服务的计算机操作系统,windows server2003/2008,UNIX,Linux
-
计算机网络的分类
- 按网络作用范围划分 局域网,城域网,广域网
- 按网络技术划分 广播式网络,点到点网络
- 按传播介质划分 有线网,无线网,微波通信,卫星通信
-
计算机网络拓扑结构
- 星型
- 树型
- 总线型
- 环型
- 网状型
-
计算机网络的发展过程
- 以计算机为中心的联机系统
- 分组交换网络的诞生
- 网络体系结构与协议标准化
- 20世纪80年代ISO组织提出开放式系统互联参考模型
- 但由于这个模型照顾到了各方的利益,所以太过于庞大,因此至今都没有成熟的产品,目前我们使用的是一系列协议的集合,简称TCP/IP簇,通常也叫TCP/IP模型
- 目前所有的计算机系统都按照这份协议进行通信,所有不同的操作系统之间才可以进行网络通信
- C/C++/Java/python->windows->socket->TCP/IP<->TCP/IP->socket->Linux->C/C++/Java/python
- 高速计算机网络(5G)
-
网卡
- 它负责将数据发送到网络上,也负责从网络中获取数据,每个网卡都有一个独一无二的MAC地址
-
OSI参考模型与IPC/IP参考模型
- 网络协议是为网络数据交换而制定的规则,约束,标准,一个功能完备的计算机网络需要定制一整套复杂的协议集,目前的网络协议是按层次结构组织的,网络层次结构模型与各层协议的集合称为网络体系结构
- TCP/IP目前只实现了4层
OSI TCP/IP 应用层 应用层 主要是为用户提供针对性的服务,这一层的代表协议有HTTP,SMTP,FTP,SENMP,TELNET 表示层 ⬆ 会话层 ⬆ 传输层 传输层 机器之间建立用于会话的端到端连接(用于数据传输,该层的核心是IPC/UDP) 网络层 网络层 选择,流量控制,网络拥塞,ip协议是该层的核心 数据链路层 ⬇ 物理层 物理层 负责通信网络收发数据包 -
在计算机网络中每一台计算机都必须有一个唯一的标识(MAC地址不容易记忆),它就是IP地址,目前在计算机以.分十进制表示(4个不超过255的整数),但在程序中就是4字节的整数
分类 格式 范围 A 第1位 0
0.0.0.0~127.255.255.255
B 前2位 10
128.0.0.0~191.255.255.255
C 前3位 110
192.0.0.0~223.255.255.255
D 前4位 1110
224.0.0.0~239.255.255.255
E 前4位 1111
240.0.0.0~255.255.255.255
-
公有IP和私有IP
- 公有IP:在网络服务提供商登记过的叫公有IP
- 私有IP:由公司或组织自己分配,不能在网络中直接公开访问
-
子网掩码
- 由4个不超过255的整数,.分十进制表示
- 网络地址 = IP地址 & 子网掩码
- 只有在同一子网内的ip地址才可以直接进行通信,否则必须要经过路由器
-
网关地址
- 负责子网出口的计算机,一般由路由器担任(路由器就是一台具有路由功能的计算机)
-
端口号
- ip地址能决定我们与那一台计算机通信,而端口号决定了我们与计算机上的哪个进程通信
- 1~1024基本上已经被操作系统预定完了,因此我们在编程时一般1024以上
常用服务 端口 http 80 ftp 21 ssh 22 telnet 23
网络通信的基本概念
-
TCP/UDP的区别
- TCP
transmission control protocol
,面向连接的服务(打电话),安全,可靠(3次握手,响应+重传,4次挥手),速度相对较慢,一般应用在对安全性,完整性由严格要求的场景:ftp,SMTP,HTTP - 3次握手:A要确保A能到B,B也能到A;B也要确保A能到B,B能到A
- 4次挥手:确保关闭前发送完所有数据(应用层已经交给底层了,位底还没有完全发送出去)
A B 3次握手 1 听得到吗-> B知道A可到B 2 A知道A能到B,B能到A <-我听得到,你呢 3 我听得到-> B知道了B也能到A 4次挥手 1 发送关闭请求-> 2 <-发送请求响应 检查是否还有未发送数据 3 <-可以关闭 4 发送关闭信息-> - UDP
User Datagram Protocol
,面向无连接的服务(发短信),不保证安全,可靠,但大多数情况下是可靠的,相对较快,流媒体(在线的视频,音频)
- TCP
-
消息流
- 应用层->表示层->会话层->网络层->传输层->数据链路层->物理层–>物理层->数据链路层->传输层->网络层->会话层->表示层->应用层
-
消息包
- 当socket收到一个要送的数据时,先把数据进行拆分成Bit流,然后再组成(放丢失)数据包(可能丢包)
套接字
- socket是一种接口机制,可以让程序无论使用什么端口,协议,都可以从socket进出数据,它负责了进程与协议之间的连接
-
编程模型
点对点(p2p) 一对一通信 客户机/服务器(C/S) 一对多通信 -
创建socket
int socket(int domain, int type, int protocol);
- 创建socket描述符,可以把socket当作文件来看待,发送数据就是写文件,接收数据就是读文件
- domain 地址类型
- AF_UNIX/AF_LOCAL/AF_FILE 本地通信(进程间通信)
- AF_INET 基本32位ip地址通信,IPv4
- AF_INET6 基本128位ip地址通信,IPv6
- type 通信协议
- SOCK_STREAM 数据流协议,TCP
- SOCK_DGRAM 数据报协议,UDP
- protocol 特别通信协议,给0即可
- 返回值 socket 描述符,类似文件描述符
-
通信地址
- 函数定义的是sockaddr 而实际使用的是_un和_in
struct sockaddr { sa_family_t sa_family; char sa_data[14]; }; struct sockaddr_un { __SOCKADDR_COMMON (sun_); //地址类型,参考domain char sun_path[108]; //socket文件的路径 }; struct sockaddr_in{ __SOCKADDR_COMMON (sun_); in_port_t sin_port; //端口号,大端字节序 struct in_addr sin_addr; //ip地址,大端4字节整数 }; struct in_addr { in_addr_t s_addr; };
-
绑定
socket描述符与物理通信载体(网卡或socket文件)绑定在一起-
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
- sockfd socket描述符,socket函数的返回值
- addr 通信地址结构体,实际给的是sockaddr_un或sockaddr_in,需要强制类型转换
- addrlen 通信地址结构体类型的字节数,使用sizeof计算
-
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
- sockfd socket描述符
- addr 通信目标地址
- addrlen 通信地址结构体类型的字节数,使用sizeof计算
- 返回值 在不同的编程模型下返回值意义不同,在本地通信返回0,失败返回-1
-
-
数据接收与发送read/write
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
recv/send与read/write功能一样,flags多了是否阻塞的功能(0阻塞,1不阻塞)
-
关闭套接字close
- 如果是网络通信,端口号并不会立即回收,大概会占用3分钟左右
-
字节序转换
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
- h 本机字节序
- n 网络字节序
-
ip地址转换
int inet_aton(const char *cp, struct in_addr *inp);
- 把点分十进制的ip地址(字符串)转换成32位无符号整数
- 使用指针获取
in_addr_t inet_addr(const char *cp);
- 把点分十进制的ip地址(字符串)转换成32位无符号整数
- 返回值直接返回
char *inet_ntoa(struct in_addr in);
- 把32位无符号整数的ip地址转换成点分十进制的ip地址(字符串)
-
本地通信编程模型
进程A 进程B 创建套接字(AF_LOCAL) 创建套接字(AF_LOCAL) 准备地址(sockaddr_un) 准备地址(sockaddr_un) 绑定(自己的socket/地址) 绑定(自己的socket/地址) 接收数据 发送数据 关闭套接字 关闭套接字
a.c
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <string.h>
#define SOCK_FILE "/tmp/sock"
int main(){
//创建socket
int sockfd = socket(AF_LOCAL,SOCK_DGRAM,0);
if(0 > sockfd){
perror("socket");
return -1;
}
//准备地址
struct sockaddr_un addr = {};
addr.sun_familly = AF_LOCAL;
strcpy(addr.sun_path,SOCK_FILE);
//绑定
if(bind(sockfd,(struct socketaddr*)&addr,sizeof(addr))){
perror("bind");
return -1;
}
//接收数据
char buf[1024] = {};
for(;;){
int ret = read(sockfd,buf,sizeof(buf));
if(0 > ret){
perror("read");
reurn -1;
}
printf("read:%s\n,read");
if(0 == strcpy("quit",buf)){
printf("通信完成\n");
break;
}
}
//关闭
close(socketfd);
}
b.c
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <string.h>
#define SOCK_FILE "/tmp/sock"
int main(){
//创建socket
int sockfd = socket(AF_LOCAL,SOCK_DGRAM,0);
if(0 > sockfd){
perror("socket");
return -1;
}
//准备地址
struct sockaddr_un addr = {};
addr.sun_familly = AF_LOCAL;
strcpy(addr.sun_path,SOCK_FILE);
//绑定
if(connect(sockfd,(struct socketaddr*)&addr,sizeof(addr))){
perror("connect");
return -1;
}
//发送数据
char buf[1024] = {};
for(;;){
int ret = write(sockfd,buf,sizeof(buf)+1);
if(0 > ret){
perror("read");
reurn -1;
}
printf("read:%s\n,read");
if(0 == strcpy("quit",buf)){
printf("通信完成\n");
break;
}
}
//关闭
close(socketfd);
unlink(SOCK_FILE);
}
基于TCP协议的C/S模型
-
int listen(int sockfd, int backlog);
- 设置等待连接的最大数量
- sockfd 被监听的socket描述符
- backlog 等待连接的最大数量(排队的数量)
- 返回值 0成功 -1失败
-
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
- 等待连接,sockfd连接
- addr 获取连接的地址
- addrlen 设置连接地址的长度
- 返回值 专门用于通信的描述符
-
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
- 建立与指定socket的连接
- 参数与accept相同
编程模型
Server | Client |
---|---|
创建socket套接字 | 创建socket套接字 |
准备地址(sockaddr_in,本机地址) | 准备地址(服务端地址) |
绑定(bind) | … |
监听(listen) | … |
等待连接(accept,fork) | 连接(connect) |
接收请求(read/recv) | 发送请求(write/send) |
响应请求(write/send) | 接收响应(read/recv) |
关闭(close) | 关闭(close) |
server.c
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
int main(){
printf("服务器创建socket...\n");
int sockfd = socket(AF_INET,SOCK_STREAM,0);
if(0 > sockfd){
perror("socket");
return -1;
}
printf("准备地址...\n");
struct sockaddr_in addr = {};
addr.sin_family = AF_INET;
addr.sin_port = htons(7777);
addr.sin_addr.s_addr = inet_addr("");
socklen_t len = sizeof(addr);
printf("绑定socket与地址...\n");
if(bind(sockfd,(struct sockaddr*)&addr,len)){
perror("bind");
return -1;
}
printf("设置监听...\n");
if(listen(sockfd,5)){
perror("listen");
return -1;
}
printf("等待客户端连接...\n");
for(;;){
struct sockaddr_in addrcli = {};
int clifd = accept(sockfd,(struct sockaddr*)&addrcli,&len);
if(0 > clifd){
perror("accept");
continue;
}
if(0 == fork()){
char buf[1024] = {};
for(;;){
read(clifd,buf,sizeof(buf));
printf("read:%s,from%s\n",buf,inet_ntoa(addrcli.sin_addr));
write(clifd,buf,strlen(buf)+1);
/*
printf("read:");
read(clifd,buf,sizeof(buf));
printf("%s\n",buf);
if(0 == strcmp("quit",buf)){
close(clifd);
return 0;
}
printf(">");
gets(buf);
write(clifd,buf,strlen(buf)+1);
*/
}
}
}
}
client.c
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
int main(){
printf("服务器创建socket...\n");
int sockfd = socket(AF_INET,SOCK_STREAM,0);
if(0 > sockfd){
perror("socket");
return -1;
}
printf("准备地址...\n");
struct sockaddr_in addr = {};
addr.sin_family = AF_INET;
addr.sin_port = htons(7777);
addr.sin_addr.s_addr = inet_addr("");
socklen_t len = sizeof(addr);
printf("连接服务器...\n");
if(connect(sockfd,(struct sockaddr*)&addr,len)){
perror("connect");
return -1;
}
struct sockaddr_in addrcli = {};
int clifd = connect(sockfd,(struct sockaddr*)&addrcli,&len);
if(0 > clifd){
perror("accept");
continue;
}
while(1){
char buf[1024] = {};
printf(">");
gets(buf);
write(sockfd,buf,sizeof(buf));
if(0 == strcmp("quit",buf)){
printf("通信结束\n");
break;
}
printf("read:");
read(sockfd,buf,strlen(buf));
printf("%s\n",buf);
}
close(sockfd);
}
基于UDP协议的C/S模型
-
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
- 功能 UDP协议专用的数据发送函数
sockfd
套接字描述符buf
发送的缓冲区首地址len
发送的数据字节数flags
0 阻塞 1 不阻塞dest_addr
目标计算机的地址addrlen
地址结构体的字节数- 返回值 成功发送的字节数
-
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
- UDP协议专用的数据接收函数
sickfd
套接字描述符buf
数据存储位置len
最大字节数flags
0 阻塞 1 不阻塞src_addr
获取发送者的地址addrlen
设置地址结构体的字节数- 返回值 成功接收的字节数
-
编程模型
Server | Client |
---|---|
创建套接字(socket) | 创建套接字(socket) |
准备地址(本机地址sockaddr_in) | 准备地址(目标机地址sockaddr_in) |
绑定(bind(sockfd+addr)) | … |
接收请求(recvfrom) | 发送请求(sendto) |
响应请求(sendto) | 接收响应(recvfrom) |
关闭套接字(close) | 关闭套接字(close) |
- 注意 从服务器到客户端返回的路线是UDP协议自己设计的
练习1 获取当前计算机的ip地址,子网掩码
system("ifconfig > /tmp/ip.config");
lx12练习2 根据当前的ip地址和子网掩码,遍历出所有同一局域网的ip地址
作业1:指针银行升级成网络版
作业2:实现网络聊天室
作业3:实现网络传输文件
windows下的网络编程
一般的软件都是Linux/UNIX系统作为操作系统,而windows系统作为客户端,windows下的socket编程接口与Linux的基本一致,函数声明在winsock2.h下
-
int WSAStartup(WORD,LPWSADATA);
- 初始化网络库
- WORD 设置网络库的版本
- MAKEWORD(1,2),第一位主版本号,第二位副版本号
- LPWSADATA WSADATA数据结构的指针,用来获取网络的相关信息
-
closesocket(sockfd)
- 关闭
-
WSACleanup(void);
- 卸载网络库
-
注意 编译时添加 -lws2_32
练习1 实现windows下TCP协议的C/S通信
练习2 实现windows下UDP协议的C/S通信