目录
12.1 互联的计算机
12.2 ISO/OSI 和TCP/IP 参考模型
12.3 通过套接字通信
12.3.1 创建套接字
12.3.2 使用套接字
12.3.3 UDP套接字
12.4 网络实现的分层模型
本专栏文章将有70篇左右,欢迎+关注,查看后续文章。
网络相关的头文件数目巨大,所以单独放在 include/net,而不是 include/linux。
12.1 互联的计算机
分层处理各种传输问题。
12.2 ISO/OSI 和TCP/IP 参考模型
ISO:国际标准化组织。
该组织定义了7层模型,即 OSI 模型。
OSI:开放系统互联。
实际使用上图左侧的4层模型。
物理层:
网卡和线缆等物理设备。(PHY层)
数据链路层:
网卡驱动程序中实现。(MAC层)
网络层和传输层:
内核协议栈中实现。
12.3 通过套接字通信
网络不能通过 open("/dev/net")来通信。
创建socket,将生成一个文件描述符,存在有对应inode。
然后像操作文件一样,进行read / write读写。
12.3.1 创建套接字
创建套接字时,须指定地址族(即Domain),通信类型(即Type)。
int socket(int domain, int type, int protocol);
domain 值有:
AF_UNIX、AF_LOCAL 本地通信使用。
AF_INET、AF_INET6 IP协议。
AF_NETLINK netlink 报文。
AF_PACKET RAW 报文。
type 值有:
SOCK_STREAM 数据流,如 TCP。
SOCK_DGRAM 数据报,如 UDP。
SOCK_RAW 当domain 为AF_PACKET时使用。
protocol:
若为0,则内核选择默认协议。
AF_PACKET:
作用:
1. 允许应用层直接操作数据链路层的报文。如以太网帧。
2. 可绕过网络层和传输层,进行直接收发,避免被过滤掉。
使用场景:
1. 高性能的数据包处理。
2. 网络监控,抓包( tcpdump )。
3. 应用层自定义特殊网络协议。
socket 系统调用实现:
sock_create:
1. sock = sock_alloc( );
sock->type = type;
2. struct net_proto_family *pf = net_families [ family ];
pf->create(net, sock, protocol, kern);
// 即调用 inet_create 或 inet6_create 或 netlink_create 等。
bind 用于将socke 与本地IP/端口绑定。
int bind(int sockfd, struct sockaddr *addr, socklen_t addrlen);
struct sockaddr { // 可容纳任意地址族的通用地址结构。
sa_family_t sa_family;
char sa_data [14];
}
struct sockaddr_in { // IPv4专用的地址结构体,可用struct sockaddr强转得到。
sa_family_t sin_family;
__be16 sin_port; // 大端。
struct in_addr sin_addr;
unsigned char __pad[ ];
};
struct in_addr {
in_addr_t s_addr; // 具体IPv4的地址值。
};
网络字节序是大端。
长度为1个字节的数据不区分大小端。
如何判断CPU是大端?小端?
int main( )
{
unsigned int i = 0x12345678;
if ( *(( unsigned char * ) &i ) == 0x78)
printf("Little Endian \n");
else (* (( unsigned char * ) &i ) == 0x12)
printf("Big Endian \n");
}
小端:低位字节在低地址,高位字节在高地址。
大端:高位字节在低地址,低位字节在高地址。
12.3.2 使用套接字
如何将一个套接字关联到eth0等网络接口?
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
addr:即本地接口的IP。
组播可以跨网段通信。
广播分为:
物理广播:
局域网所有设备。目的MAC为:FF-FF-FF-FF-FF-FF。
逻辑广播:
特定子网的所有设备。如 192.168.1.255。
编程举例: 将本地接口加入 224.0.0.1多播组。
struct ip_mreq {
struct in_addr imr_multiaddr; // 请求加入或退出的组播IP(目)
struct in_addr imr_interface; // 发送接口的 IP 地址(源)
};
struct ip_mreq mreq;
mreq.imr_multiaddr.s_addr = inet_addr("224.0.0.1");
mreq.imr_interface.s_addr = htonl(INADDR_ANY);
setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq));
recvfrom(sockfd, buffer, sizeof(buffer), 0, NULL, NULL);
TCP客户端与服务器编程
1. TCP客户端
sockfd = socket(AF_INET, SOCKET_STREAM, 0);
connect(sockfd, (struct sockaddr *)server, sizeof(*server));
//server中填充了服务器IP/PORT
write(sockfd, , );
read(sockfd, , );
注意:
客户端无需使用 bind 函数。即不将 socket和 IP/port 绑定。因为:
通常客户端主动发送报文。
发送时,内核根据目的IP,查找路由表可确定出接口的IP,作为报文源IP。
而源端口号由内核动态分配一个可用。
2. TCP服务器
sockfd = socket(AF_INET, SOCKET_STREAM, 0);
bind(sockfd, (struct sockaddr *)server, sizeof(*server));
//socket与服务器IP、port 绑定。
listen(sockfd, SOMAXCONN);
// SOMAXCONN,即为创建的等待队列长度。
clientfd = accept(sockfd, (struct sockaddr *)client, &client_size);
read(clientfd, buf, 1000);
write(clientfd, buf, 1000);
netstat:查看TCP/IP连接状态。
netstat -atnp:tcp连接状态。
-a display all sockets
-n 不解析域名。
-p display PID/Program name for sockets
-t TCP
-u UDP
netstat -antp 查看tcp连接状态。
netstat -anup 查看udp连接状态。
12.3.3 UDP套接字
12.4 网络实现的分层模型
建议阅读 fs_initcall(inet_init);
1. 网络层:
struct packet_type ip_packet_type;
struct packet_type ipv6_packet_type;
struct packet_type ip_packet_type = {
.type = cpu_to_be16(ETH_P_IP), // IP协议
.func = ip_rcv,
};
2. 传输层
struct proto tcp_prot;
struct proto udp_prot;
struct inet_protosw inetsw_array[ ] =
{
{
.type = SOCK_STREAM,
.protocol = IPPROTO_TCP,
.prot = &tcp_prot, //函参为:struct sock *sk;
.ops = &inet_stream_ops, //函参为:struct socket *sock;
},
{
.type = SOCK_DGRAM,
.protocol = IPPROTO_UDP,
.prot = &udp_prot,
.ops = &inet_dgram_ops,
},
...
}
const struct proto_ops inet_dgram_ops = {
.family = PF_INET,
.bind = inet_bind,
.connect = inet_dgram_connect,
.accept = sock_no_accept,
.listen = sock_no_listen,
.setsockopt = sock_common_setsockopt,
.getsockopt = sock_common_getsockopt,
.sendmsg = inet_sendmsg,
.recvmsg = inet_recvmsg,
};
struct proto udp_prot = {
.name = "UDP",
.connect = ip4_datagram_connect,
.destroy = udp_destroy_sock,
.setsockopt = udp_setsockopt,
.getsockopt = udp_getsockopt,
.sendmsg = udp_sendmsg,
.recvmsg = udp_recvmsg,
};
struct proto_ops inet_dgram_ops 中的
.connect = inet_dgram_connect,
int inet_dgram_connect(struct socket *sock, struct sockaddr *uaddr,
int addr_len, int flags)
{
struct sock *sk = sock->sk;
return sk->sk_prot->connect(sk, uaddr, addr_len);
//即struct proto udp_prot中的 ip4_datagram_connect。
}