"ipcs" //man ipcs
功能:显示共享内存段、信号量数组、消息队列
命令行:ipcs
"ipcrm"
ipcrm [ -M key | -m id | -Q key | -q id | -S key | -s id ] ...
功能:移除消息队列
命令行:ipcrm -q id
"ipcmk"
ipcmk [-M size] [-S nsems] [-Q] [-p mode]
功能:创建一个不同的ipc资源
一、网络基础
1. 客户端和服务器端的架构
"client" 客户端
"server" 服务器端
2. 什么是协议? "协议就是规则"
3. 网络协议采用的是 "TCP/IP协议族" (4(2~5)|5(1~5) 层)
TCP/IP协议的分层:/** 软件分层 **/
5>"应用层":Telnet、FTP和e-mail等
4>"传输层":TCP和UDP
3>"网络层":IP、ICMP和IGMP
2>"链路层":设备驱动程序及接口卡
1>"物理层":————第5层,规定了电气协议
4. 物理层规范了电气规则(电气协议)
补充:
1)"网络架构"
C/S架构 - 客户端
B/S架构 - 浏览器
"B/S是C/S的一种"
2)网线:跑的是电流
模电 / "数电"(高电压是1,低电压是0)
3)"以太网数据帧的分用过程图 - 英文注解"
ARP 地址解析协议,把ipv4地址映射到硬件地址
RARP 反向地址解析协议,把硬件地址映射到ipv4地址
IP 网络协议
TCP 传输控制协议
UDP 用户数据报协议
ICMP Internet报文控制协议
IGMP 组管理协议
ICMPv6 网际控制消息协议版本6,综合了ICMP/IGMP/ARP的功能
BPF BSD分组过滤器,为应用程序提供访问数据链路层的接口,由源自BSD的系统内核提供
DLPI 数据链路提供者接口,为应用程序提供访问数据链路层的接口,由源自SVR4的系统内核提供
5. 客户端和服务器端需要有一个传输的基本单元,需要知道这个数据单元的起始标记和结束标记。这个单元就是"帧"。(以太网对应的是"以太网帧")
比如:农村家里接自来水,用水桶,接满一桶放到水缸。
/** 自来水 - 网络数据包;一水桶的水 - 帧;水缸 - 内存。 **/
6. 实现客户端和服务器端的通信,需要知道"对方的地址"和"端口号"。
不同的网络应用会使用不同的端口号。使用IP地址定位主机,再使用端口号定位定位运行在这台主机上的一个具体的网络应用。
0~65535 其中习惯上 0~5000 为系统所用,一般应用选择5000以上端口。
21 端口用于ftp服务,23 端口用于telnet服务,80 端口用于www服务等。
(IP地址和端口号:socket pair "通讯对")
7. 地址:逻辑地址/网络地址("IP地址")、物理地址("MAC地址")
每一块网卡都有自己的身份证号,即物理MAC地址(目的地址 - 6 个字节)
网络通信中最终认识的就是此MAC地址。
查看计算机的网络地址/物理地址:"sudo ifconfig"
网络/逻辑地址——inet地址,物理地址——硬件地址,"client一对一绑定"。
机器需要将逻辑地址和物理地址做绑定。
8. ip地址分为2种:ipv4("4个字节的32位地址")
ipv6("16个字节的128位地址")
ip地址包括两部分内容,"网络号和主机号"。
"A类":0~127.0~255.0~255.0~255 以0为首的8位网络地址+24位本地地址
0.0.0.0 "主机号全0不能使用",主机号全0代表的是网络号。
0.255.255.255 "主机号全1不能用",这代表的是该网络的广播地址。
127.0.0.0 127.255.255.255
"B类":128~191.0~255.0~255.0~255 以10为首的16位网络地址+16位本地
"C类":192~223.0~255.0~255.0~255 以110为首的24位网络地址+8位本地
"D类":以 1110 为首的32位多播地址(28位多播组号)
"E类":以 11110 为首的32位多播地址(27位留后待用)
将ip地址分为私有ip和共有ip。
9. 子网掩码:有的时候需要将一个大的网络划分为更小的几个网络,这时候需要到子网掩码的技术。
将ip地址和子网掩码做 "与&" 操作,就可以得到这个ip属于哪个子网段。
格式:"IP地址 & 子网掩码 == 子网地址"
举例1:ip(192.168.1.56) & 子网掩码(255.255.255.0) = 192.168.1.0
说明:ip为192.168.1.56的电脑属于192.168.1.0网段的一台机器。
举例2:ip(192.168.129/24, 前面24位全1,即子网掩码255.255.255.0)
ip(192.168.129/25, 子网掩码255.255.255.128)
192.168.1.129/25 & 255.255.255.128 == 192.168.1.128网段的局域网
192.168.1.123/25 & 255.255.255.128 == 192.168.1.0网段的局域网
以上两个局域网的主机号只有7位了,127-2(全0和全1) = 125 台机器。
10. 三种网络设备:
"集线器"hub,电流的放大和分流,属于电气层/一层交换。工作在物理层。
单个电脑占用网线数据时,其他电脑不可通过网线访问,也不可相互访问。
"交换机",交换的是网帧,帧属于链路层,属于二层交换。
单个电脑占用网线数据时,其他局域网内电脑互相访问不影响。
"路由器",交换的是ip报文,报文属于网络层,所以是三层交换。
实现了无限制访问。
11. 局域网内数据包的传输
从这台例如192.168.1.11的机器传输数据包给192.168.1.12的流程步骤:
第一步:
先判断192.168.1.12的网段和192.168.1.11是否相同,都与255.255.255.0做与&运算,均为192.168.1.0网段,说明是局域网内的传输。所以路由器不会将该数据包路由到外网。
第二步:
查看192.168.1.11机器上的arp表,如果arp表里面有192.168.1.12这条ip数据,那么将这条数据的对应的MAC地址添加到以太网帧的目的地址,这样数据包就被传送过去了,如果arp表中没有这条数据,192.168.1.11这台机器会发送arp请求广播,这时候192.168.1.12收到广播信息,查看与自己的ip地址是否一致。如果不是,不做任何反应;如果是,那就将自己的MAC地址通过arp应答回送给192.168.1.11,这时候192.168.1.11将帧的目的地址设置为该MAC地址,并且在arp表中添加该条记录。以此实现局域网内的数据包传输过程。
路由表 "sudo route -v" //查看路由表
arp表 "sudo arp -a" //查看arp表,ping后可查看对应ip的机器
"ping ip地址" //检测局域网是否连通
12. 三次握手
13. 四次分手
二、基于TCP的编程模型
TCP是面向连接的。客户端和服务器端通讯需要建立连接。
1. 基于TCP的"服务器端编程模型"
<1> 创建socket通讯端(socket) -> 初始化服务器(sockaddr_in成员)
<2> 将通讯端和服务器ip地址和端口号绑定(bind)
<3> 监听通讯端(listen)
<4> 等待客户端连接的到来,返回一个连接描述符(accept)
<5> 从连接描述符中读写数据(read/write)
<6> 对数据进行加工
<7> 关闭socket通讯端-连接描述符(close(参数);)
/** 整体是框架,用户自定的地方主要是<5><6>对数据的读写和处理 **/
2. 基于TCP的"客户端编程模型"
<1> 创建socket通讯端(socket) -> 初始化服务器(sockaddr_in成员)
<2> (inet_pton转换)-> 使用socket通讯端连接服务器
<3> 连接成功,通过socket通讯端向服务器发送数据,或从服务器获取数据
<4> 处理数据
<5> 关闭socket通讯端-连接描述符
/** 整体是框架,用户自定的地方主要是<4>对数据的读写和处理 **/
【服务器端编程模型】
/** 1.<1> 创建socket通讯端 **/
"socket"(2)
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
功能:创建一个通讯端点(返回一个文件描述符)
参数:
"domain"
AF_INET 用于ipv4的通讯
AF_INET6 用于ipv6的通讯
"type"
SOCK_STREAM 传输层选用的是TCP协议,面向数据流
SOCK_DGRAM 传输层选用的是UDP协议,面向数据包
"protocol" 0
返回值:
成功 - 返回新的文件描述符
失败 - 返回 -1,errno被设置
/** 1.<2> 将通讯端和服务器的ip地址和端口号绑定 **/
"bind"(2)
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd,const struct sockaddr *addr,socklen_t addrlen);
功能:将一个名字绑定到一个socket(第一个参数绑定到第二个参数)
参数:
"sockfd" socket(2)的返回值
"addr" socket pair通讯对,通用协议族 //ip地址和端口号构成的一个结构体
"addrlen" addr的长度
返回值:
成功 - 返回 0
失败 - 返回 -1,errno被设置
补充:"man in.h"头文件下包含以下3个结构体:
"<netinet/in.h>"
#include <netinet/in.h>
struct sockaddr 通用协议族结构体
struct sockaddr_in ipv4协议族的结构体
struct sockaddr_in6 ipv6协议族的结构体
struct sockaddr {
sa_family_t sa_family;
char sa_data[14];
};
struct sockaddr_in {
sa_family_t sin_family; /* 取值:AF_INET */
in_port_t sin_port; /* Port number:5000以下留给系统*/
struct in_addrsin_addr;/* IP address */
};
struct in_addr {
in_addr_t s_addr;/** INADDR_ANY 表示任意IP均可与服务器通信 **/
}
需要将ip地址的"点分十进制(ddd.ddd.ddd.ddd)"的字符串和无符号整形两种类型互相转换。
/** 点分十进制字符串 -> 网络字节序(无符号整数) **/ 转换
"inet_pton"(3)
#include <arpa/inet.h>
int inet_pton(int af, const char *src, void *dst);
功能:将ipv4或ipv6的字符串形势的ip地址转换为struct in_addr二元形式
参数:
"af"
AF_INET IPv4网络
AF_INET6 IPv6网络
"src" 点分十进制的字符串格式
"dst" struct in_addr类型的内容
返回值:
成功 - 返回 1
失败 - 返回 0,代表src是无效地址
失败 - 返回 -1,errno被设置
/** 网络字节序(无符号整数) -> 点分十进制字符串 **/
"inet_ntop"(3)
#include <arpa/inet.h>
const char *inet_ntop(int af,const void *src,char *dst,socklen_t size);
功能:将ipv4或ipv6的地址从binary(二进制)到text(字符串)
参数:
"af"
AF_INET IPv4网络
AF_INET6 IPv6网络
"src" struct in_addr类型的内容
"dst" 指定的字符串空间
"size" 拷贝到字符串的大小
返回值:
成功 - 返回字符串的首地址
失败 - 返回 NULL,errno被设置
"inet_ntoa"(3) //扩充
"inet_aton"(3) //扩充
一般情况下计算机配置的是小端,但是网络中使用的是大端。不管计算机使用的是大端还是小端,到"网络中必须使用大端"。
"htonl"(3)系列函数 /** 大小端的字节顺序byte order互相转换 **/
#include <arpa/inet.h>
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" 开头的:host主机字节序
"n" 开头的:net网络字节序
"l" 结尾的:long (长整型 32 位) - 可用于IP
"s" 结尾的:short (短整型 16 位) - 可用于端口
/** 1.<3> 监听通讯端 **/
"listen"(2)
#include <sys/types.h>
#include <sys/socket.h>
int listen(int sockfd, int backlog);
功能:监听socket连接
参数:
"sockfd" socket(2)返回值(type参数需指定SOCK_STREAM | SOCK_SEQPACKET)
"backlog" 允许的最大的未决连接数
返回值:
成功 - 返回 0
失败 - 返回 -1,errno被设置
重点小结:
1、TCP/IP协议的分层(4层或5层,5层包含物理层,7层是模型非实际分层)
2、子网掩码(划分局域网的网段)
3、IP地址的分类
4、三次握手
/** 1.<4> 等待客户端连接的到来,返回一个连接描述符 **/
"accept"(2)
#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
功能:创建一个新的连接socket(监听到连接)
参数:
"sockfd" socket(2)的返回值
"addr" 获取客户端的socket pairs(通讯对:ip地址和端口号)
如果addr为 NULL,addrlen需要置为 NULL
"addrlen" addr结构体变量的长度
返回值:
成功 - 返回 一个新的非负整数文件描述符
失败 - 返回 -1,errno被设置
【客户端编程模型】
/** 1.<1> 创建socket通讯端 **/
同服务器端函数。
/** 1.<2> 使用socket通讯端连接服务器 **/
同服务器端函数。
/** 1.<3> 连接成功,通过socket通讯端向服务器发送数据,或从服务器获取数据 **/
"connect"(2)
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, \
socklen_t addrlen);
功能:将sockfd连接到addr指定的地址空间
参数:
"sockfd" socket(2)的返回值
"addr" 要连接到的目的地址(struct sockaddr通讯对类型的)
"addrlen" addr的长度
返回值:
成功 - 返回 0 (连接或绑定成功)
失败 - 返回 -1,errno被设置
"127.0.0.1" 客户机连接本机服务器的本地环回地址。
三、"实现服务器的并发":使用多进程实现服务器的并发。
在accept之后,调用fork函数产生子进程:
"父进程"关闭连接描述符(关闭conn_fd),继续监听socket是否有客户端连接的到来。
"子进程"关闭socket连接(关闭s_fd),负责和客户端的通讯。
"select"(2) /** 待扩充,nigix使用的就是此多进程方式 **/
四、基于UDP的通讯
"UDP和TCP的区别":
UDP是面向数据包的,而TCP是面向数据流的。
TCP是面向连接的,相对与UDP传输比较"安全,不会丢包"。(如:QQ传文件)
UDP的"传输效率比较高",但是安全性比较差,"容易丢包"。(如:QQ视频)
1. 基于UDP的服务器端模型
<1> 创建socket通讯端
<2> 绑定socket描述符和服务器的socket pairs
<3> recvfrom等待客户端数据的到来
<4> 处理数据
<5> sendto响应客户端
2. 基于TCP的客户端模型
<1> 创建socket通讯端
<2> sendto发送信息给服务器端
<3> recvfrom等待服务器端的响应信息
<4> 处理数据
<5> 关闭socket通讯端-连接描述符(close(参数);)
/** 1.<3> recvfrom等待客户端数据的到来 **/
"recvfrom"(2) "其他的相关函数自行扩充"
#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);
功能:从socket接收消息
参数:
"sockfd" socket(2)的返回值
"buf" 接收消息的内存空间地址
"len" 接收消息的长度
"flags" 0
"src_addr" 源的socket pairs;如果为 NULL,addrlen也就为 NULL
"addrlen" src_addr的长度
返回值:
成功 - 返回接收到的字节数
失败 - 返回 -1,errno被设置
/** 1.<5> sendto响应客户端 **/
"sendto"(2)
#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);
功能:通过socket发送消息
参数:
"sockfd" socket(2)的返回值
"buf" 将buf指定的内存空间里的数据发送出去
"len" buf的长度
"flags" 0
"dest_addr" 目标地址
"addrlen" 目标地址的长度
返回值:
成功 - 返回成功发送的字节数
失败 - 返回 -1,errno被设置
功能:显示共享内存段、信号量数组、消息队列
命令行:ipcs
"ipcrm"
ipcrm [ -M key | -m id | -Q key | -q id | -S key | -s id ] ...
功能:移除消息队列
命令行:ipcrm -q id
"ipcmk"
ipcmk [-M size] [-S nsems] [-Q] [-p mode]
功能:创建一个不同的ipc资源
一、网络基础
1. 客户端和服务器端的架构
"client" 客户端
"server" 服务器端
2. 什么是协议? "协议就是规则"
3. 网络协议采用的是 "TCP/IP协议族" (4(2~5)|5(1~5) 层)
TCP/IP协议的分层:/** 软件分层 **/
5>"应用层":Telnet、FTP和e-mail等
4>"传输层":TCP和UDP
3>"网络层":IP、ICMP和IGMP
2>"链路层":设备驱动程序及接口卡
1>"物理层":————第5层,规定了电气协议
4. 物理层规范了电气规则(电气协议)
补充:
1)"网络架构"
C/S架构 - 客户端
B/S架构 - 浏览器
"B/S是C/S的一种"
2)网线:跑的是电流
模电 / "数电"(高电压是1,低电压是0)
3)"以太网数据帧的分用过程图 - 英文注解"
ARP 地址解析协议,把ipv4地址映射到硬件地址
RARP 反向地址解析协议,把硬件地址映射到ipv4地址
IP 网络协议
TCP 传输控制协议
UDP 用户数据报协议
ICMP Internet报文控制协议
IGMP 组管理协议
ICMPv6 网际控制消息协议版本6,综合了ICMP/IGMP/ARP的功能
BPF BSD分组过滤器,为应用程序提供访问数据链路层的接口,由源自BSD的系统内核提供
DLPI 数据链路提供者接口,为应用程序提供访问数据链路层的接口,由源自SVR4的系统内核提供
5. 客户端和服务器端需要有一个传输的基本单元,需要知道这个数据单元的起始标记和结束标记。这个单元就是"帧"。(以太网对应的是"以太网帧")
比如:农村家里接自来水,用水桶,接满一桶放到水缸。
/** 自来水 - 网络数据包;一水桶的水 - 帧;水缸 - 内存。 **/
6. 实现客户端和服务器端的通信,需要知道"对方的地址"和"端口号"。
不同的网络应用会使用不同的端口号。使用IP地址定位主机,再使用端口号定位定位运行在这台主机上的一个具体的网络应用。
0~65535 其中习惯上 0~5000 为系统所用,一般应用选择5000以上端口。
21 端口用于ftp服务,23 端口用于telnet服务,80 端口用于www服务等。
(IP地址和端口号:socket pair "通讯对")
7. 地址:逻辑地址/网络地址("IP地址")、物理地址("MAC地址")
每一块网卡都有自己的身份证号,即物理MAC地址(目的地址 - 6 个字节)
网络通信中最终认识的就是此MAC地址。
查看计算机的网络地址/物理地址:"sudo ifconfig"
网络/逻辑地址——inet地址,物理地址——硬件地址,"client一对一绑定"。
机器需要将逻辑地址和物理地址做绑定。
8. ip地址分为2种:ipv4("4个字节的32位地址")
ipv6("16个字节的128位地址")
ip地址包括两部分内容,"网络号和主机号"。
"A类":0~127.0~255.0~255.0~255 以0为首的8位网络地址+24位本地地址
0.0.0.0 "主机号全0不能使用",主机号全0代表的是网络号。
0.255.255.255 "主机号全1不能用",这代表的是该网络的广播地址。
127.0.0.0 127.255.255.255
"B类":128~191.0~255.0~255.0~255 以10为首的16位网络地址+16位本地
"C类":192~223.0~255.0~255.0~255 以110为首的24位网络地址+8位本地
"D类":以 1110 为首的32位多播地址(28位多播组号)
"E类":以 11110 为首的32位多播地址(27位留后待用)
将ip地址分为私有ip和共有ip。
9. 子网掩码:有的时候需要将一个大的网络划分为更小的几个网络,这时候需要到子网掩码的技术。
将ip地址和子网掩码做 "与&" 操作,就可以得到这个ip属于哪个子网段。
格式:"IP地址 & 子网掩码 == 子网地址"
举例1:ip(192.168.1.56) & 子网掩码(255.255.255.0) = 192.168.1.0
说明:ip为192.168.1.56的电脑属于192.168.1.0网段的一台机器。
举例2:ip(192.168.129/24, 前面24位全1,即子网掩码255.255.255.0)
ip(192.168.129/25, 子网掩码255.255.255.128)
192.168.1.129/25 & 255.255.255.128 == 192.168.1.128网段的局域网
192.168.1.123/25 & 255.255.255.128 == 192.168.1.0网段的局域网
以上两个局域网的主机号只有7位了,127-2(全0和全1) = 125 台机器。
10. 三种网络设备:
"集线器"hub,电流的放大和分流,属于电气层/一层交换。工作在物理层。
单个电脑占用网线数据时,其他电脑不可通过网线访问,也不可相互访问。
"交换机",交换的是网帧,帧属于链路层,属于二层交换。
单个电脑占用网线数据时,其他局域网内电脑互相访问不影响。
"路由器",交换的是ip报文,报文属于网络层,所以是三层交换。
实现了无限制访问。
11. 局域网内数据包的传输
从这台例如192.168.1.11的机器传输数据包给192.168.1.12的流程步骤:
第一步:
先判断192.168.1.12的网段和192.168.1.11是否相同,都与255.255.255.0做与&运算,均为192.168.1.0网段,说明是局域网内的传输。所以路由器不会将该数据包路由到外网。
第二步:
查看192.168.1.11机器上的arp表,如果arp表里面有192.168.1.12这条ip数据,那么将这条数据的对应的MAC地址添加到以太网帧的目的地址,这样数据包就被传送过去了,如果arp表中没有这条数据,192.168.1.11这台机器会发送arp请求广播,这时候192.168.1.12收到广播信息,查看与自己的ip地址是否一致。如果不是,不做任何反应;如果是,那就将自己的MAC地址通过arp应答回送给192.168.1.11,这时候192.168.1.11将帧的目的地址设置为该MAC地址,并且在arp表中添加该条记录。以此实现局域网内的数据包传输过程。
路由表 "sudo route -v" //查看路由表
arp表 "sudo arp -a" //查看arp表,ping后可查看对应ip的机器
"ping ip地址" //检测局域网是否连通
12. 三次握手
13. 四次分手
二、基于TCP的编程模型
TCP是面向连接的。客户端和服务器端通讯需要建立连接。
1. 基于TCP的"服务器端编程模型"
<1> 创建socket通讯端(socket) -> 初始化服务器(sockaddr_in成员)
<2> 将通讯端和服务器ip地址和端口号绑定(bind)
<3> 监听通讯端(listen)
<4> 等待客户端连接的到来,返回一个连接描述符(accept)
<5> 从连接描述符中读写数据(read/write)
<6> 对数据进行加工
<7> 关闭socket通讯端-连接描述符(close(参数);)
/** 整体是框架,用户自定的地方主要是<5><6>对数据的读写和处理 **/
2. 基于TCP的"客户端编程模型"
<1> 创建socket通讯端(socket) -> 初始化服务器(sockaddr_in成员)
<2> (inet_pton转换)-> 使用socket通讯端连接服务器
<3> 连接成功,通过socket通讯端向服务器发送数据,或从服务器获取数据
<4> 处理数据
<5> 关闭socket通讯端-连接描述符
/** 整体是框架,用户自定的地方主要是<4>对数据的读写和处理 **/
【服务器端编程模型】
/** 1.<1> 创建socket通讯端 **/
"socket"(2)
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
功能:创建一个通讯端点(返回一个文件描述符)
参数:
"domain"
AF_INET 用于ipv4的通讯
AF_INET6 用于ipv6的通讯
"type"
SOCK_STREAM 传输层选用的是TCP协议,面向数据流
SOCK_DGRAM 传输层选用的是UDP协议,面向数据包
"protocol" 0
返回值:
成功 - 返回新的文件描述符
失败 - 返回 -1,errno被设置
/** 1.<2> 将通讯端和服务器的ip地址和端口号绑定 **/
"bind"(2)
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd,const struct sockaddr *addr,socklen_t addrlen);
功能:将一个名字绑定到一个socket(第一个参数绑定到第二个参数)
参数:
"sockfd" socket(2)的返回值
"addr" socket pair通讯对,通用协议族 //ip地址和端口号构成的一个结构体
"addrlen" addr的长度
返回值:
成功 - 返回 0
失败 - 返回 -1,errno被设置
补充:"man in.h"头文件下包含以下3个结构体:
"<netinet/in.h>"
#include <netinet/in.h>
struct sockaddr 通用协议族结构体
struct sockaddr_in ipv4协议族的结构体
struct sockaddr_in6 ipv6协议族的结构体
struct sockaddr {
sa_family_t sa_family;
char sa_data[14];
};
struct sockaddr_in {
sa_family_t sin_family; /* 取值:AF_INET */
in_port_t sin_port; /* Port number:5000以下留给系统*/
struct in_addrsin_addr;/* IP address */
};
struct in_addr {
in_addr_t s_addr;/** INADDR_ANY 表示任意IP均可与服务器通信 **/
}
需要将ip地址的"点分十进制(ddd.ddd.ddd.ddd)"的字符串和无符号整形两种类型互相转换。
/** 点分十进制字符串 -> 网络字节序(无符号整数) **/ 转换
"inet_pton"(3)
#include <arpa/inet.h>
int inet_pton(int af, const char *src, void *dst);
功能:将ipv4或ipv6的字符串形势的ip地址转换为struct in_addr二元形式
参数:
"af"
AF_INET IPv4网络
AF_INET6 IPv6网络
"src" 点分十进制的字符串格式
"dst" struct in_addr类型的内容
返回值:
成功 - 返回 1
失败 - 返回 0,代表src是无效地址
失败 - 返回 -1,errno被设置
/** 网络字节序(无符号整数) -> 点分十进制字符串 **/
"inet_ntop"(3)
#include <arpa/inet.h>
const char *inet_ntop(int af,const void *src,char *dst,socklen_t size);
功能:将ipv4或ipv6的地址从binary(二进制)到text(字符串)
参数:
"af"
AF_INET IPv4网络
AF_INET6 IPv6网络
"src" struct in_addr类型的内容
"dst" 指定的字符串空间
"size" 拷贝到字符串的大小
返回值:
成功 - 返回字符串的首地址
失败 - 返回 NULL,errno被设置
"inet_ntoa"(3) //扩充
"inet_aton"(3) //扩充
一般情况下计算机配置的是小端,但是网络中使用的是大端。不管计算机使用的是大端还是小端,到"网络中必须使用大端"。
"htonl"(3)系列函数 /** 大小端的字节顺序byte order互相转换 **/
#include <arpa/inet.h>
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" 开头的:host主机字节序
"n" 开头的:net网络字节序
"l" 结尾的:long (长整型 32 位) - 可用于IP
"s" 结尾的:short (短整型 16 位) - 可用于端口
/** 1.<3> 监听通讯端 **/
"listen"(2)
#include <sys/types.h>
#include <sys/socket.h>
int listen(int sockfd, int backlog);
功能:监听socket连接
参数:
"sockfd" socket(2)返回值(type参数需指定SOCK_STREAM | SOCK_SEQPACKET)
"backlog" 允许的最大的未决连接数
返回值:
成功 - 返回 0
失败 - 返回 -1,errno被设置
重点小结:
1、TCP/IP协议的分层(4层或5层,5层包含物理层,7层是模型非实际分层)
2、子网掩码(划分局域网的网段)
3、IP地址的分类
4、三次握手
/** 1.<4> 等待客户端连接的到来,返回一个连接描述符 **/
"accept"(2)
#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
功能:创建一个新的连接socket(监听到连接)
参数:
"sockfd" socket(2)的返回值
"addr" 获取客户端的socket pairs(通讯对:ip地址和端口号)
如果addr为 NULL,addrlen需要置为 NULL
"addrlen" addr结构体变量的长度
返回值:
成功 - 返回 一个新的非负整数文件描述符
失败 - 返回 -1,errno被设置
"server"
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <string.h>
typedef struct sockaddr SA;
typedef struct sockaddr_in SA4;
int main (void)
{
SA4 serv, cli; // 定义服务器地址变量,客户端地址变量
int s_fd, conn_fd;
int ret = 0;
char buf[1024] = {0}, buf1[1024] = {0};
char ip[128] = {0}; // 存放ip
int port; // 存放端口
/* 1. 创建socket通讯端 */
s_fd = socket (AF_INET, SOCK_STREAM, 0);
if (-1 == s_fd) {
perror ("socket");
return 1;
}
/* 2. 初始化服务器信息 */
serv.sin_family = AF_INET; // ipv4
serv.sin_port = htons (7778); // port
serv.sin_addr.s_addr = htonl (INADDR_ANY); // 32bit long
/* 3. 将服务器ip地址和端口号与s_fd绑定 */
ret = bind (s_fd, (SA *)&serv, sizeof (serv));
if (-1 == ret) {
perror ("bind");
return 2;
}
/* 4. 监听s_fd */
listen (s_fd, 10); // 5个设备同时
/* 5. 阻塞等待客户端的连接 */
while (1) {
/* 将客户端的通讯对保存在了cli指向的地址里 */
int cli_len = sizeof (cli);
conn_fd = accept (s_fd, (SA*)&cli, &cli_len);
/* 将二进制ip转换为字符串ip地址打印 */
char *p = (char*)inet_ntop (AF_INET, &cli.sin_addr, ip, 128);
printf ("客户端ip:%s\n", p);
printf ("端口号:%d\n", ntohs (cli.sin_port));
if (-1 == conn_fd) {
perror ("accept");
return 3;
}
/* 6. 读取客户端发送过来的信息 */
int r = read (conn_fd, buf, sizeof (buf)-1);
/* 7. 数据处理/显示 */
write (1, buf, r);
// write (1, "\n", 2);
memset (buf, 0, sizeof (buf));
/* 8. 给客户端响应 */
fgets (buf1, sizeof (buf1), stdin);
write (conn_fd, buf1, sizeof (buf1));
memset (buf1, 0, sizeof (buf1));
}
/* 9. 连接结束,关闭连接 */
close (conn_fd);
return 0;
}
【客户端编程模型】
/** 1.<1> 创建socket通讯端 **/
同服务器端函数。
/** 1.<2> 使用socket通讯端连接服务器 **/
同服务器端函数。
/** 1.<3> 连接成功,通过socket通讯端向服务器发送数据,或从服务器获取数据 **/
"connect"(2)
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, \
socklen_t addrlen);
功能:将sockfd连接到addr指定的地址空间
参数:
"sockfd" socket(2)的返回值
"addr" 要连接到的目的地址(struct sockaddr通讯对类型的)
"addrlen" addr的长度
返回值:
成功 - 返回 0 (连接或绑定成功)
失败 - 返回 -1,errno被设置
"client"
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <string.h>
typedef struct sockaddr SA;
typedef struct sockaddr_in SA4;
int main (int argc, char *argv[])
{
SA4 server;
int s_fd;
int ret;
char buf[1024] = {0}, buf1[1024] = {0};
/* 1. 创建通讯端 */
s_fd = socket (AF_INET, SOCK_STREAM, 0);
if (-1 == s_fd) {
perror ("socket");
return 1;
}
/* 2. 初始化服务器端信息 */
server.sin_family = AF_INET; // ipv4
server.sin_port = htons (7778); // port
inet_pton (AF_INET, argv[1], &server.sin_addr); // copy ip 到服务器
/* 3. 将s_fd和目的地址连接 */
ret = connect (s_fd, (SA*)&server, sizeof (server));
if (-1 == ret) {
perror ("connect");
return 2;
}
while (1) {
/* 4. 向服务器发送数据 */
fgets (buf, sizeof (buf), stdin);
write (s_fd, buf, sizeof (buf));
memset (buf, 0, sizeof (buf));
/* 5. 等待获取服务器响应信息 */
int r = read (s_fd, buf1, sizeof (buf1));
/* 6. 将服务器的响应信息输出到显示器 */
write (1, buf1, r); // 1==STDOUT
// write (1, "\n", 2);
memset (buf1, 0, sizeof (buf1));
}
/* 7. 关闭连接描述符 */
close (s_fd);
return 0;
}
"127.0.0.1" 客户机连接本机服务器的本地环回地址。
三、"实现服务器的并发":使用多进程实现服务器的并发。
在accept之后,调用fork函数产生子进程:
"父进程"关闭连接描述符(关闭conn_fd),继续监听socket是否有客户端连接的到来。
"子进程"关闭socket连接(关闭s_fd),负责和客户端的通讯。
"server - 支持多用户并发"
/** 代码如下 server.c **/
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <string.h>
typedef struct sockaddr SA;
typedef struct sockaddr_in SA4;
int main(void) { /** 新增变量cli **/
SA4 serv, cli; //定义服务器的地址变量
int s_fd, conn_fd;
int ret;
char buf[128] = {0};
char ip[128] = {0}; /** 存放ip地址字符串 **/
int port; /** 存放端口号 **/
//<1> 创建socket通讯端
s_fd = socket(AF_INET, SOCK_STREAM, 0);
if(-1 == s_fd) {
perror("socket");
return 1;
}
//补充:初始化服务器的信息
serv.sin_family = AF_INET;
serv.sin_port = htons(7777); //端口5000以下留给系,可选5000以上
serv.sin_addr.s_addr = htonl(INADDR_ANY);//长整型主机号(ip)
//<2> 将服务器的IP地址和端口号与s_fd绑定
ret = bind(s_fd, (SA *)&serv, sizeof(SA4)); //SA4或serv
if(-1 == ret) {
perror("bind");
return 2;
}
//<3> 监听s_fd
listen(s_fd, 5);
//<4> 阻塞等待客户端的连接
while(1) {
/** 将客户端的socket pairs保存在了cli指向的地址里 **/
int cli_len = sizeof(cli);
conn_fd = accept(s_fd, (SA *)&cli, &cli_len);
if(-1 == conn_fd) {
perror("accept");
return 3;
}
pid_t pid = fork();
if(pid < 0) {
perror("fork");
return 4;
}
if(pid > 0) { /** 父进程:负责监听客户端的连接 **/
close(conn_fd);
continue;
} else { /** 子进程:负责和客户端通信 **/
close(s_fd); /** 取消关闭s_fd就不会报accept **/
/** 将二进制ip转换为字符串ip地址打印 **/
char *p = (char *)inet_ntop(AF_INET, &cli.sin_addr, ip, 128);
printf("客户端ip:%s\n", p);
printf("用户姓名:");
if(!strcmp(p, "176.135.11.138")) {
printf("唐发洪\n");
}
/** 从cli结构体中打印端口号 **/
printf("端口号:%d\n", ntohs(cli.sin_port));
//<5> 读取客户端发送过来的信息
int r = read(conn_fd, buf, 127);
//<6> 加工处理信息
for(int i = 0; i < r; i++) {
buf[i] = toupper(buf[i]);
}
//给客户端响应
write(conn_fd, buf, r);
printf("接收到信息:%s\n", buf);
//<7> 和这个客户端业务结束,关闭连接
sleep(10);
exit(0); //关闭子进程
close(conn_fd);
}
}
return 0;
}
"select"(2) /** 待扩充,nigix使用的就是此多进程方式 **/
四、基于UDP的通讯
"UDP和TCP的区别":
UDP是面向数据包的,而TCP是面向数据流的。
TCP是面向连接的,相对与UDP传输比较"安全,不会丢包"。(如:QQ传文件)
UDP的"传输效率比较高",但是安全性比较差,"容易丢包"。(如:QQ视频)
1. 基于UDP的服务器端模型
<1> 创建socket通讯端
<2> 绑定socket描述符和服务器的socket pairs
<3> recvfrom等待客户端数据的到来
<4> 处理数据
<5> sendto响应客户端
2. 基于TCP的客户端模型
<1> 创建socket通讯端
<2> sendto发送信息给服务器端
<3> recvfrom等待服务器端的响应信息
<4> 处理数据
<5> 关闭socket通讯端-连接描述符(close(参数);)
/** 1.<3> recvfrom等待客户端数据的到来 **/
"recvfrom"(2) "其他的相关函数自行扩充"
#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);
功能:从socket接收消息
参数:
"sockfd" socket(2)的返回值
"buf" 接收消息的内存空间地址
"len" 接收消息的长度
"flags" 0
"src_addr" 源的socket pairs;如果为 NULL,addrlen也就为 NULL
"addrlen" src_addr的长度
返回值:
成功 - 返回接收到的字节数
失败 - 返回 -1,errno被设置
/** 1.<5> sendto响应客户端 **/
"sendto"(2)
#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);
功能:通过socket发送消息
参数:
"sockfd" socket(2)的返回值
"buf" 将buf指定的内存空间里的数据发送出去
"len" buf的长度
"flags" 0
"dest_addr" 目标地址
"addrlen" 目标地址的长度
返回值:
成功 - 返回成功发送的字节数
失败 - 返回 -1,errno被设置
/** 举例验证:
编写程序,实现UDP的通讯,客户端发送字符串,服务器端将客户端的发送过来的字符串转换为大写,响应给客户端。客户端收到服务器端的响应,将响应信息输出到屏幕上。
服务器端 userver.c
客户端 uclient.c **/
"userver.c"
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <ctype.h>
typedef struct sockaddr SA;
typedef struct sockaddr_in SA4;
int main(void) {
int s_fd;
SA4 server, client;
char buf[128] = {0};
//<1> 创建socket
s_fd = socket(AF_INET, SOCK_DGRAM, 0);
if(-1 == s_fd) {
perror("socket");
return 1;
}
//初始化server
server.sin_family = AF_INET;
server.sin_port = htons(7779);
server.sin_addr.s_addr = htonl(INADDR_ANY);
//<2> 绑定s_fd和服务器的socket pair
int b = bind(s_fd, (SA *)&server, sizeof(server));
if(-1 == b) {
perror("bind");
return 2;
}
while(1) {
//<3> 接收数据
int cli_len = sizeof(client);
int r = recvfrom(s_fd, buf, 128, 0, (SA *)&client, &cli_len);
printf("接收到信息:%s\n", buf);
//<4> 数据处理
for(int i = 0; i < r; i++) {
buf[i] = toupper(buf[i]);
}
//<5> 发送给客户端
sendto(s_fd, buf, r, 0, (SA *)&client, sizeof(client));
printf("发出的信息:%s\n", buf);
}
close(s_fd);
return 0;
}
"uclient.c"
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <netinet/in.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
int main(void) {
int s_fd;
char buf[20] = "hello,world!";
//<1> 创建socket通讯端
s_fd = socket(AF_INET, SOCK_DGRAM, 0);
if(-1 == s_fd) {
perror("socket");
return 1;
}
struct sockaddr_in server;
//初始化server的信息
server.sin_family = AF_INET;
server.sin_port = htons(7779);
inet_pton(AF_INET, "127.0.0.1", &server.sin_addr);
//<2> 发送信息给服务器
sendto(s_fd, buf, strlen(buf)+1, 0, (struct sockaddr *)&server, sizeof(server));
//<3> 从服务器端等待响应信息
bzero(buf, 20);
int r = recvfrom(s_fd, buf, 20, 0, NULL, NULL);
//<4> 处理数据
write(1, buf, r);
write(1, "\n", 2);
close(s_fd);
return 0;
}