IO进程: 进程和进程之间的通信 - 信号 信号量 消息队列 有名管道 无名管道 共享内存 套接字
套接字: 不同主机 不同操作系统之间的 进程通信
干什么: 实现无线
局域网:同一局域网下IP网段一致
IP地址
-
IP地址 是 网络中的 主机的标识, 本质是二进制数字。
-
IP采用 点分十进制 表示
3) IP分类: 分为 IPV4 和 IPV6 (IPV6表示的范围更广)
4) IP长度 32位 (IPV4) 64位(IPV6)
5) 每个数据包 必须要携带 目标 IP 和 源IP地址
二级划分
IP=网络号+主机号
网络号:表示是否再同一个网段内(主要是同一网段,网络号是一样的)
主机号:本网段内主机的id(主机号在同一网段下,是唯一的)
二级划分的分类
IP二级划分所表示的范围
|----|-----------------------------|----------------------|
| A类 | 0.0.0.0 - 127.255.255.255 | 大型网络 |
| B类 | 128.0.0.0 - 191.255.255.255 | 中型网络 : 管理 和 监督互联网的组织 |
| C类 | 192.0.0.0 - 223.255.255.255 | 小型网络:家用网络 ,教室网络 |
| D类 | 224.0.0.0 - 239.255.255.255 | 用于 UDP的组播通信 |
| E | 240.0.0.0 - 255.255.255.255 | 保密单位:实验室, 军方 |
特殊的IP地址
不能分配给主机使用的地址
网络地址:标识网络的起始地址,该地址不会分配到主机。(表示方式:主机号全为0的地址,为网络地址)
例: 192.168.50. 0 主机号为 0 代表 网络地址, 不会被主机分配
广播地址:用于给局域网下的所有主机发送数据使用,不会分配到主机(表示方式:主机号全为1的地址,为广播地址)
练习:
IP为192.168.1.0,同一网段最多可以连接多少个主机?
c类,最后8位为主机号,网络地址和广播地址不会被分配,256-2=254
子网掩码
作用:辅助标记IP的网络号和主机号(更快的去区分网络和主机号)
特点:网络号全置为1,主机号全部置为0
练习:
子网掩码为 255.255.255.0, 该网段最多可以容纳多少台主机
c类,最后8位为主机号,网络地址和广播地址不会被分配,256-2=254
三级划分
作用:在二级划分的基础上,再次划分,提高IP的利用率
IP=网络号+子网号+主机号
子网号:从主机号中划分出几位,充当网络号。
练习:
- 已知一个子网掩码号为255.255.255.192,问,最多可以连接多少台主机?
C类 255.255.255.0 - 》三级划分 -》 192 -》 1100 0000
根据子网掩码特点: 主机号全为 0
0 - 63 -》 64 -》 去掉网络地址 和 主机地址 -》 62
- 有两台电脑主机,在最少浪费IP地址的情况下.将172.16.14.4与172.16.13.2划归为同一网段,子网掩码应该设置为
14: 0000 11 10 255.255.252.0
13: 0000 11 01
- 已知IP: 192.168.50.183, 子网掩码: 255.255.255.128, 问该IP的网段为多少?
192.168.50.183 1100 0000.1010 1000. 0011 0010. 1 011 0111
子网掩码: 1111 1111. 1111 1111. 1111 1111. 1 000 0000
193.168.50.128
- 通过子网掩码 255.255.255.128 该子网掩码可以划分几个子网网段?
C类 -》 1 000 0000 >> 两个子网网段
0
1
2
- 某公司有四个部门:行政、研发1、研发2、营销,每个部门各30台计算机接入公司局域网交换机,在192.168.1.0网
段内为每个部门划分子网,子网掩码应该怎么设置,每个子网的地址范围分别是什么?(4个部门之间不能通信)
255.255.255.192
范围:
192.168.1.0-192.168.1.63
192.168.1.128-192.168.1.192
等(自己算)
网络模型
网络模型采用分层的方式,每层实现不同的功能
网络中的数据包的传输流程
osI和TCP/IP的模型对应图
UDP和TCP
相同点:都属于传输层协议,都是全双工的通信
TCP-传输控制协议
该协议是一种高可靠性的有连接的协议
TCP容易粘包
*可靠性的评判标准:
数据无丢失, 数据无失误, 数据无失序, 数据无重复到达
TCP为什么具有高可靠性( 重点 )
三次握手和四次挥手机制
序列号和应答号机制
超时/错误重传机制
拥堵和流量控制机制
TCP的应用场景
对于传输质量要求较高的场合,以及大量数据传输的场合。
UDP-用户数据报协议
该协议是一种不可靠的无连接的协议
UDP是一种容易造成丢包的协议,不保证数据的完整性
UDP的应用场景
应答较为困难的网络中可以使用UDP。
socket套接字
什么是socket
- socket是一个编程接口
- socket是一个特殊的文件描述符
- socket是一种通信机制
- 一套用于建立连接的数字
socket的类型
流式套接字(SOCK_STREAM)对应协议-TCP
特点:面向连接,可靠性高的数据传输服务
数据报套接字(SOCK_DGRAM)对应协议-UDP
特点:无连接的,不可靠的数据传输服务
原始套接字(SOCK_RAM)
特点:较低层次的协议,例入ICMP中使用
socket的功能
socket可以在计算机中创建两个缓存区,一个是发送缓存区,一个是接收缓存区。
端口号
为了区分一台主机接收到的数据包,应该交给那个进程来处理
端口号的长度:2byte(IP长度为4byte)
众所周知端口(被占用): 0~1023(1~255之间为众所周知端口,通常由UNIX系统占用)
比如 文件传输端口 TFTP 端口号为 69
已登记端口:1024~49151 (---- 可用来建立与其它主机的会话**----)**
动态或私有端口:49152~65535--固定某些服务使用--
字节序
字节序:不同类型的CPU主机,内存存储大于一个字节类型的数据,在内存中的存放顺序
分类:
主机字节序(小端):地址低位存放数据低位,地址高位存放数据高位
网络字节序(大端):地址高位存放数据低位,地址地位存放数据高位
在主机上的字节序一般都为主机字节序。在网络中传输的数据,大于一个字节的数据类型,必须转换为网络字节序
端口号转换专用接口
头文件: #include<arpa/inet.h>
原型: u_long htonl (u_long hostlong) u_short htons (u_short short)
//端口号一般为 1024 - 49151, 两个字节足够,基本不需要long
功能: 主机字节序的端口 转 网络字节序
原型: u_long ntohl (u_long hostlong); u_short ntohs (u_short short);
功能: 网络字节序的端口 转 主机字节序
IP转换专用接口
in_addr_t inet_addr(const char *strptr);
头文件: #include<sys/socket.h> #include<netinet/in.h> #include<arpa/inet.h>
原型: //该参数是字符串
功能: 主机字节序(点分十进制形式的IP) 转为网络字节序
参数: const char *strptr: 字符串
typedef uint32_t in_addr_t; (通过vi -t 追一下)
struct in_addr {
in_addr_t s_addr;
};
返回值: 返回一个无符号长整型数(无符号32位整数用十六进制表示),
否则NULL
char *inet_ntoa(struct in_addr in);
头文件 : #include <arpa/inet.h> #include<sys/socket.h> #include<netinet/in.h>
功能: 将网络字节序二进制地址转换成主机字节序。
参数: stuct in_addr in addr : 只需传入一个结构体变量,该变量代表IP
返回值: 返回一个字符指针, 否则NULL;
TCP编程
socket创建套接字
函数原型:
int socket(int domain, int type, int protocol);
调用形式:
socket(协议族,套接字类型,0);
参数domain:协议族
参数type:套接字类型
参数protocol:传0,表示根据第二个参数自动匹配协议
返回值:创建成功返回sockfd描述符(0->标准输入,1->标准输出,2->标准错误,3->自定义创建的socket)
创建失败返回-1,更新errno
bind绑定套接字
vi -t :查询数据类型的shell指令
函数原型:
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
头文件: #include<sys/types.h> #include<sys/socket.h>
#include<netinet/in.h> #include<netinet/ip.h>
功能:绑定 IP 和 port
参数:
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; ----协议族
in_port_t sin_port; ----端口
struct in_addr sin_addr; ----ip结构体
};
struct in_addr {
uint32_t s_addr; --ip地址
};
listen监听
int listen(int sockfd, int backlog);
功能:监听,将主动套接字变为被动套接字
参数:
sockfd:套接字
backlog:等待连接队列中,客户端请求链接的最大个数,不能写0.
一般写6-8个
返回值:成功 0 失败-1,更新errno
accept阻塞等待连接
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
功能:阻塞函数,阻塞等待客户端的连接请求,如果有客户端连接,
则accept()函数返回,返回一个用于通信的套接字文件;
参数:
Sockfd :套接字
addr: 链接客户端的ip和端口号
如果不需要关心具体是哪一个客户端,那么可以填NULL;
addrlen:结构体的大小
如果不需要关心具体是哪一个客户端,那么可以填NULL;
返回值:
成功:通信的文件描述符;
失败:-1,更新errno新errno
recv接收数据
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
功能: 接收数据
参数:
sockfd: acceptfd ;
buf 存放位置
len 大小
flags 一般填0
MSG_DONTWAIT 非阻塞
返回值:
< 0 失败出错 更新errno
==0 表示客户端退出
>0 成功接收的字节个数
练习
-
服务器可以循环接收客户端的数据;
-
当客户端退出后, 服务器阻塞等待下一个客户端的连接,而后继续通信;
-
当有客户端连接时, 服务器端 打印客户端的IP 和 Port信息;
-
将代码的 send 和 recv 改为 write 和 read, 测试一下效果;
-
利用argv[ ] 采用终端输入的方式, 输入IP 和 Port ,从而bind绑定;
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pthread.h>
#include <semaphore.h>
#include <wait.h>
#include <signal.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <semaphore.h>
#include <sys/msg.h>
#include <sys/shm.h>
#include <sys/un.h>typedef struct sockaddr_in addr_in_t;
typedef struct sockaddr addr_t;
typedef struct sockaddr_un addr_un_t;
int main(int argc, const char *argv[])
{
while(1)
{
//创建套接字
//AF_INET---ipv4协议族
//SOCK_STREAM---流式套接字(TCP)
//0---根据第二个参数自动匹配协议
int sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd<0)
{
perror("socket is err");
return -1;
}//bind绑定套接字 struct sockaddr_in saddr; saddr.sin_family = AF_INET; //将主机字节序的端口号转换为网络字节序 int port; char p[128]={0}; printf("请输入Port:"); scanf("%d",&port); printf("请输入IP:"); scanf("%s",p); saddr.sin_port = htons(port); saddr.sin_addr.s_addr = inet_addr(p); //绑定填充的结构体属性到sockfd套接字上 int bindfd = bind(sockfd,(struct sockaddr *)&saddr,sizeof(saddr)); if(bindfd<0) { perror("bind is err"); //return -1; } printf("%ld %ld\n",sizeof(saddr),sizeof(struct sockaddr_in)); //listen监听 创建监听队列 int listenfd = listen(sockfd,5); if(listenfd < 0) { perror("listen is err"); return -1; } //建立通信 //如果没有客服端链接,accept回阻塞等待客户端链接 //第二个参数用于获取来链接的客服端的信息 //返回值为一个进行通信的文件描述符 printf("accept.......\n"); struct sockaddr_in aaddr; int len = sizeof(aaddr); aaddr.sin_family = AF_INET; //int acceptfd = accept(sockfd,NULL,NULL); int acceptfd = accept(sockfd,(struct sockaddr*)&aaddr,&len); int kfport = ntohs(aaddr.sin_port); char* kfip = inet_ntoa(aaddr.sin_addr); printf("客服端端口:%d\n客服端IP为:%s\n",kfport,kfip); if(acceptfd < 0) { perror("accept id err"); char buf[128]={0}; return -1; } //接收数据 //accept是建立通信的文件描述符(不能用sockfd) //第四个参数常用0和MSG_NOWAIT //0---表示阻塞,如果没有数据接收,则阻塞等待 //MSG_NOWAIT---表示非阻塞 while(1) { char buf[128]={0}; int recfd = recv(acceptfd,buf,sizeof(buf),0); if(recfd < 0) { perror("recv is err"); return -1; } else if(recfd == 0) { printf("client is exit\n"); break; } else { printf("client输入的数据为:%s\n",buf); } } close(sockfd); close(acceptfd); } return 0;
}