IP地址的基本格式为xxx.xxx.xxx.xxx
,由四段组成,每个字段是一个字节,每个字节8位,最大值是255。
IP地址由两大部分组成,即网络号(前三个字节)和主机号(最后一个字节),二者是主从关系。
127.0.0.1
,通常被称为本地回环地址(Loop back Address),不属于任何一个有类别地址类。它代表设备的本地虚拟接口,常用来检查本地网络协议、基本数据接口等是否正常的,也可以用来做网络通信的测试。
端口号是一个2字节16进制数,用来标识进程,告诉操作系统应当将对端通过网络传递过来的数据交给哪一个进程处理。
因此,使用IP+端口号
就可以标识网络上的一个唯一进程,网络通信本质就是服务器上的进程间通信。
注:一个进程可以对应多个端口号,而一个端口号只能对应一个进程。
传输层协议(TCP和UDP)的数据段中有两个端口号,分别叫做源端口号
和目的端口号
,用来描述 “数据是谁发的, 要发给谁”
TCP/IP协议规定:
网络字节序必须采用大端模式,即低位存在高地址,高位存在低地址。
因此,若主机host是小端模式,则必须先将数据转换为大端模式再发送。
uint32_t htonl(uint32_t hostlong)
uint16_t htons(uint16_t hostshort)
uint32_t ntohl(uint32_t netlong)
uint16_t ntohs(uint16_t netshort)
端口和ip都是多字节组合的数据类型,如int、uint16
;
但是在网络上,其它数据是序列化成字符串传递的。
字符串本质就是char类型的数组:一方面,char是单字节的,不存在字节序的问题;另一方面,数组的空间是连续开辟的,那么数据存放的顺序前后顺序也是固定的,所以与字节序没有任何关系!
套接字(socket)是应用层与TCP/IP协议簇通信的中间软件抽象层,应用程序可以像文件一样对其进行打开、读写和关闭等操作,这也就是建立网络连接和数据传输的本质。
在设计模式中,socket其实就是一个门面模式,它把复杂的TCP/IP协议簇隐藏在socket和相关接口后面,对用户来说,一组简单的接口就是全部,让socket去组织数据,以符合指定的协议。
1、int socket(int domain, int type, int protocol)
创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
①domain:指定协议簇,常用的协议族有AF_INET(IPv4协议)
、AF_INET6(IPv6协议)
,其中AF_INET表示用32位ipv4地址与16位端口号的组合
②type:指定socket的类型,常用的有SOCK_DGRAM(UDP协议)
、SOCK_STREAM(TCP协议)
③protocol:指定使用的协议,通常设为0,表示使用匹配前两个参数的协议
④RetVal:一个文件描述符(这里可以看作socket描述符),用于后续操作该socket;若创建失败则返回-1
2、int bind(int socket, const struct sockaddr *address, socklen_t address_len)
绑定端口号 (TCP/UDP, 服务器)
socket:指定要绑定的socket(文件描述符)
address:指向要绑定给socket的网络通信地址相关信息的结构体。这个结构体的类型根据创建socket时指定的domain而定,如AF_INET(IPv4),对应struct sockaddr_in
,AF_INET6(IPv6)对应struct sockaddr_in6
,但是在传参时,需要统一强转为struct sockaddr*
struct sockaddr_in
包括:
sin_family
:协议簇,在socket编程中只能是AF_INET
sin_port
:端口号(使用网络字节序)
sin_addr
:一个struct in_addr
类型的结构体。其中,sin_addr.s_addr
用来指定socket绑定的IP地址(使用网络字节序)补充:
INADDR_ANY
可以用来表示本主机所有的IP(适应主机拥有多张网卡的情况,比如云服务器)
in_addr_t inet_addr(const char *cp)
可以把点分式ip字符串转为网络字节序的整型ip
char *inet_ntoa(struct in_addr in)
可以把整型ip转化为点分式的ip字符串
address_len:地址的长度,即参数address的大小
RetVal:成功绑定则返回0,否则返回-1
// bind使用实例
sockaddr_in local;
memset(&local, 0, sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(_port);
local.sin_addr.s_addr = htonl(INADDR_ANY);
bind(_listenSock, (sockaddr *)&local, sizeof(local));
对于服务端而言,必须指定一个固定的端口,即必须调用bind函数,来为那些以该端口为目标端口的客户端提供服务。
而客户端可以不指定端口,即不必调用bind函数,而是由进程自主选择一个可用的端口来连接服务端。这样既可以得到服务,也避免了可能出现的端口被其他服务端占用的错误。
3、int listen(int socket, int backlog)
socket进入监听状态 (TCP, 服务器)
①socket:进行监听工作的套接字描述符,专门监听是否有客户端的连接请求
②backlog:等待连接队列的最大长度
③RetVal:成功则返回0,失败返回-1
4、int accept(int socket, struct sockaddr* address, socklen_t* address_len)
监听套接字接收请求 (TCP, 服务器)
①socket:监听是否有连接请求的监听套接字
②address、address_len:用来传递自身的sockaddr给对端,并获取对端的sockaddr及其大小(输入输出型参数)
③RetVal:如果接收成功,则返回一个专门用于处理该连接请求的套接字,否则返回-1
// accept使用示例
struct sockaddr_in peer;
memset(&peer, 0, sizeof(peer));
socklen_t len = sizeof(peer);
int sock = accept(listenSock, (struct sockaddr *)&peer, &len);
5、int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
建立连接 (TCP, 客户端)
①sockfd:客户端待连接的socket标识符
②addr、addrlen:用来传递自身的sockaddr给对端,并获取对端服务器的sockaddr及其大小(输入输出型参数)
③RetVal:成功连接则返回0,失败返回-1
1.ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr,
socklen_t *addrlen)
接收数据
①sockfd:socket描述符
②buf、len:接收数据的buffer及其大小
③flags:调用的操作方式,一般设置为0
④src_addr、addrlen:指向发送数据方的地址信息结构体及其大小(输入输出型参数)
⑤RetVal:成功接收的字符数,失败则返回-1
2.ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr,
socklen_t addrlen)
发送数据
①sockfd:socket描述符
②buf、len:发送数据的buffer及其大小
③flags:调用的操作方式,一般设置为0
④dest_addr、addrlen:指向接收数据方的地址信息结构体及其大小(输入输出型参数)
⑤RetVal:实际发送出去的字符数,失败则返回-1
1.ssize_t recv(int socket, void *buffer, size_t length, int flags)
接收数据
①socket:套接字描述符
②buffer、length:用于接收的缓冲区及其大小
③flags:指定消息传输的类型,一般设为0
④RetVal:返回成功发送的字节数,若无接收信息或对端关闭,则返回0,失败则返回-1
2.ssize_t send(int socket, const void *buffer, size_t length, int flags)
发送数据
①socket:套接字描述符
②buffer、length:用于发送数据的缓冲区及其大小
③flags:指定消息传输的类型,一般设为0
④RetVal:返回成功发送的字节数,失败则返回-1