网络字节序
TCP/IP协议规定,网络数据流应才使用大端字节序,即低地址高字节。
为使网络程序具有可移植性,使同样的C代码在大小端计算机上编译后都能正常运行,可以调用一下库函数做网络字节序和主机字节序的转换
#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表示network。
l表示32位整数。
s表示16位短整数。
测试
#include <stdio.h>
#include <arpa/inet.h>
int main() {
int a = 0x12345678;
int b = htonl(a);
printf("a : %#x\nb : %#x\n", a, b);
short c = 0x1234;
short d = htons(c);
printf("c : %#x\nd : %#x\n", c, d);
return 0;
}
结果
a : 0x12345678
b : 0x78563412
c : 0x1234
d : 0x3412
socket地址的数据类型
- socket API是一层抽象的网络编程接口,适用于各种底层网络协议。
- 如 IPV4、IPV6,以及 UNIX Domain Socket。
- 然而,各种网络协议的地址格式并不像同,如下图所示:
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
tcp_socket = socket(AF_INET, SOCK_STREAM, 0);
udp_socket = socket(AF_INET, SOCK_DGRAM, 0);
raw_socket = socket(AF_INET, SOCK_RAW, protocol);
struct sockaddr_in {
sa_family_t sin_family; /* address family: AF_INET */
in_port_t sin_port; /* port in network byte order */
struct in_addr sin_addr; /* internet address */
};
/* Internet address. */
struct in_addr {
uint32_t s_addr; /* address in network byte order */
};
函数
socket()
- socket()创建用于通信的端点,并返回引用该端点的文件描述符。
- 成功调用返回的文件描述符将是进程当前未打开的编号最低的文件描述符。
- 类似open()函数,返回文件描述符。
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
domain:通信的协议族;协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址。可以选择以下:
Name Purpose Man page AF_UNIX Local communication unix(7) AF_LOCAL Synonym for AF_UNIX AF_INET IPv4 Internet protocols ip(7) AF_AX25 Amateur radio AX.25 protocol ax25(4) AF_IPX IPX - Novell protocols AF_APPLETALK AppleTalk ddp(7) AF_X25 ITU-T X.25 / ISO-8208 protocol x25(7) AF_INET6 IPv6 Internet protocols ipv6(7) AF_DECnet DECet protocol sockets AF_KEY Key management protocol, originally de‐ veloped for usage with IPsec AF_NETLINK Kernel user interface device netlink(7) AF_PACKET Low-level packet interface packet(7) AF_RDS Reliable Datagram Sockets (RDS) protocol rds(7) rds-rdma(7) AF_PPPOX Generic PPP transport layer, for setting up L2 tunnels (L2TP and PPPoE) AF_LLC Logical link control (IEEE 802.2 LLC) protocol AF_IB InfiniBand native addressing AF_MPLS Multiprotocol Label Switching AF_CAN Controller Area Network automotive bus protocol AF_TIPC TIPC, "cluster domain sockets" protocol AF_BLUETOOTH Bluetooth low-level socket protocol AF_ALG Interface to kernel crypto API AF_VSOCK VSOCK (originally "VMWare VSockets") vsock(7) protocol for hypervisor-guest communica‐ tion AF_KCM KCM (kernel connection multiplexor) in‐ terface AF_XDP XDP (express data path) interface
type:指定socket类型;有以下:
SOCK_STREAM Provides sequenced, reliable, two-way, connection-based byte streams. An out-of-band data transmission mechanism may be supported. SOCK_DGRAM Supports datagrams (connectionless, unreliable messages of a fixed maximum length). SOCK_SEQPACKET Provides a sequenced, reliable, two-way connection-based data transmission path for datagrams of fixed maximum length; a consumer is required to read an entire packet with each input system call. SOCK_RAW Provides raw network protocol access. SOCK_RDM Provides a reliable datagram layer that does not guarantee ordering. SOCK_PACKET Obsolete and should not be used in new programs; see packet(7).
protocol:故名思意,就是指定协议。常用的协议有,IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等。我们一般默认写0。
注意
并不是上面的type和protocol可以随意组合的,如SOCK_STREAM不可以跟IPPROTO_UDP组合。当protocol为0时,会自动选择type类型对应的默认协议。
bind()
- socket() 函数用来创建套接字,确定套接字的各种属性,然后服务器端要用 bind() 函数将套接字与特定的 IP 地址和端口绑定起来,只有这样,流经该 IP 地址和端口的数据才能交给套接字处理。
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
struct sockaddr_in {
sa_family_t sin_family; /* address family: AF_INET */
in_port_t sin_port; /* port in network byte order */
struct in_addr sin_addr; /* internet address */
};
struct sockaddr {
sa_family_t sa_family;
char sa_data[14];
}
- sockfd : 用于监听的文件描述符。
- addr 为 sockaddr 结构体变量的指针,addrlen 为 addr 变量的大小,可由 sizeof() 计算得出。
connect
- 参数和bind一样
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
服务端应用层流程
- 1、分配一个监听描述符 listenFd = socket()
- 2、绑定监听端口 bind(listenFd, 服务器端口)
- 3、开始监听listen(listenFd, 连接队列最大长度)
- 4、阻塞等待客户端连接 connectFd = accect(listenFd, 客户端地址端口),并返回连接成功的新的文件描述符
- 5、阻塞等待客户端数据请求 read(connectFd, buf, size)
- 6、处理客户端数据请求
- 7、write(connectFd, buf, size) 发送数据应答
- 8、read返回0,close(connectFd)关闭连接。
客户端应用层流程
- 1、分配一个用于连接的文件描述符cennectFd = socket()
- 2、发起连接请求 connect(connectFd, 服务器地址端口),并阻塞等待服务器应答
- 3、发送数据请求 write(connectFd, buf, size)
- 4、阻塞等待客户端数据应答 read(connectFd, buf, size)
- 5、关闭连接 close(connectFd)
小细节
INADDR_ANY
转换过来就是0.0.0.0,泛指本机的意思,也就是表示本机的所有IP,因为有些机子不止一块网卡,多网卡的情况下,这个就表示所有网卡ip地址的意思。
比如一台电脑有3块网卡,分别连接三个网络,那么这台电脑就有3个ip地址了,如果某个应用程序需要监听某个端口,那他要监听哪个网卡地址的端口呢?
如果绑定某个具体的ip地址,你只能监听你所设置的ip地址所在的网卡的端口,其它两块网卡无法监听端口,如果我需要三个网卡都监听,那就需要绑定3个ip,也就等于需要管理3个套接字进行数据交换,这样岂不是很繁琐?
所以出现INADDR_ANY,你只需绑定INADDR_ANY,管理一个套接字就行,不管数据是从哪个网卡过来的,只要是绑定的端口号过来的数据,都可以接收到。