在学习本章之前,可以查看 《计算机网络基础》这篇文章进行学习。
一、基础知识点
1. IP地址
IP 地址的作用是标识计算机的网卡地址,每一台计算机都有一个 IP 地址。在程序中是通过IP 地址来访问一台计算机的。 本节将讲述 IP 地址的一些知识。 IP 地址是用来标识全球计算机地址的一种符号,就比如一个手机的号码,使用这个地址可以访问一个计算机。
IP 地址具有统一的格式。 IP 地址是 32 位长度的二进制数值, 存储空间是 4 个字节。例如:11000000 10101000 00000001 00000110
是一台计算机的 IP 地址,但二进制的数值是不便于记忆的,可以把每个字节用一个十进制的整数来表示,既192.168.1.6
。
在同一个网络中, IP 地址是唯一的。因为需要根据 IP 地址来访问一台计算机,所以在可以访问的范围以内,每一台计算机的 IP 地址是唯一的。在Linux终端中输入命令ifconfig
可以查看本机 IP 信息,在Windows下的cmd命令行使用ipconfig
查看IP信息。
2. 端口
在网络通信中,IP地址帮助我们定位到特定的计算机,而端口号则帮助我们在这台计算机上找到具体的应用程序或服务。每个应用程序监听不同的端口,以便接收和处理来自网络的请求。端口号通常以数字形式表示,并与IP地址一起使用,以标识网络通信的不同端点。
通俗举例:在同一个主机上,IP地址是固定的,但是主机上有很多程序功能,这些功能有不同的端口号。我们为了访问主机的某个功能程序,就必须指定其功能对应的端口号。例如主机的IP为:192.168.12.4
,QQ的端口号为10,微信的端口号为13。我们为了使用微信这个功能,我们就需要使用:192.168.12.4: 13
来使用其功能。
3. 域名
域名是互联网中用于标识一个网站或服务器的友好名称,它是IP地址的可读形式。域名系统(DNS)将域名转换为IP地址,使用户能够通过简单易记的名字访问网站,而不需要记住难记的IP地址。例如百度的域名为
www.baidu.com
,我们可以使用这个域名来代替百度的IP地址。小知识:可以使用 ping 命令来查看一个域名所对应的 IP 地址,例:ping www.baidu.com
,我们就可以查看其真实的ip地址。
4. 网络协议类型
(1)TCP协议:提供可靠的、面向连接的数据传输,确保数据按顺序传递、不丢失、不重复,并进行错误检测和纠正。常用于需要可靠数据传输的应用,如Web、电子邮件和文件传输。
TCP 是面向连接的协议。所谓连接,就是两个对等实体为进行数据通信而进行的一种结合。面向连接服务是在数据交换之前,必须先建立连接。当数据交换结束后,则应终止这个连接。面向连接服务具有:连接建立、数据传输和连接释放这三个阶段。在传送数据时是按序传送的。建立链接:三次握手(这个过程我们并不看得到)。
(2)UDP协议:提供不可靠的、面向无连接的数据传输,数据包可能会丢失、重复或乱序,不进行错误检测和纠正。传输速度比TCP快!常用于实时性要求高、数据量小、丢失少不影响的应用,如视频流、音频流和游戏通信。
5. IP协议类型
IPv4和IPv6是两种不同版本的互联网协议(IP),用于标识网络设备的地址并进行数据包路由。它们的主要区别在于地址格式和容量。
(1)IPV4:是第四版互联网协议,目前仍是最广泛使用的IP协议。
●它使用32位地址空间,格式如下:
地址格式:四个十进制数,每个数范围从0到255,由点分隔。例如:192.168.1.1。
地址数量:IPv4的地址空间约有42亿个唯一地址(2^32)。
●IPv4的特点
地址空间有限:由于地址空间较小,IPv4地址逐渐耗尽,尤其随着互联网设备数量的爆炸性增长。
地址分配:IPv4地址的分配方式包括公共地址和私有地址,私有地址用于局域网内通信,不可在互联网中直接使用(如192.168.x.x)。
NAT(网络地址转换):由于地址空间有限,NAT技术被广泛应用,使多个设备可以共享一个公共IP地址访问互联网。
(2)IPV6:是第六版互联网协议,设计为IPv4的继任者,提供更大的地址空间和其他改进。
● 它使用128位地址空间,格式如下:
地址格式:八组十六进制数,每组四个十六进制数字,由冒号分隔。例如:2001:0db8:85a3:0000:0000:8a2e:0370:7334
地址数量:IPv6的地址空间极其庞大,有约3.4×1038个地址(2128)。
●IPv6的特点
几乎无限的地址空间:IPv6提供了极其庞大的地址空间,足以满足未来互联网设备的需求。
简化的地址配置:支持无状态地址自动配置(SLAAC),设备可以自动生成IPv6地址,不需要手动配置或DHCP。
内置安全性:IPv6支持IPSec协议,提供更好的安全性。
无NAT需求:由于地址空间充足,不再需要NAT,大大简化了网络结构和提高了传输效率。
改进的路由和网络配置:IPv6改进了路由选择和地址分配机制,简化了网络配置和管理。
6. 字节序
顾名思义就是字节的存放顺序,就是字节数大于1的数据在内存中的存放顺序,字节数为1的数据就没有顺序的问题。
(1)主机字节序
数据在主机(计算机)内部的字节顺序。在计算机系统中,数据存储在内存中,字节的存储顺序取决于计算机的体系结构。
●有两种主要的字节序:大端序和小端序。在大端序中,高位字节存储在低地址,低位字节存储在高地址。在小端序中,低位字节存储在低地址,高位字节存储在高地址。X_86系统一般都是小端序。
(2)网络字节序
是一种特定的字节序,用于在网络中传输数据。在网络通信中,数据在传输过程中需要保持一致的字节序,以确保不同主机之间能够正确解释和处理数据。网络字节序规定使用大端序作为标准字节序。
为了确保在网络通信中使用统一的字节序,常见的网络编程库提供了一系列函数来进行字节序转换,以确保数据在传输过程中采用网络字节序。
htons() //将16位整数从主机字节序转换为网络字节序(Host to Network Short)。
htonl() //将32位整数从主机字节序转换为网络字节序(Host to Network Long)。
ntohs() //将16位整数从网络字节序转换为主机字节序(Network to Host Short)。
ntohl() //将32位整数从网络字节序转换为主机字节序(Network to Host Long)。
inet_ntoa() // 是一个用于将网络字节序的二进制 IPv4 地址转换为点分十进制字符串表示形式的函数。
inet_addr() //函数用于将点分十进制字符串形式的 IPv4 地址转换为网络字节序的二进制形式。
7. socket套接字
(1)定义
套接字(Socket)是在网络编程中用于实现通信链接的一种机制。它是网络通信的一种约定和规范,能够实现不同计算机之间的通信和数据交换。通过套接字,计算机可以在网络上发送和接收数据,并建立各种类型的网络连接,如TCP连接、UDP连接等。
套接字提供了一种类似于文件的接口,使得应用程序可以通过读写套接字来进行数据交换。在进行网络编程时,通过套接字,程序可以指定通信的目的地址和端口,实现数据的发送和接收。套接字是网络编程中非常重要的概念,它为数据在网络中的传输提供了基础支持。
Socket的工作原理基于客户端-服务端模式,其中一个程序充当客户端,另一个充当服务器。通常,服务器在一个主机上运行并侦听特定的端口,客户端则连接到服务器的IP地址和端口号。
(2)类型
①流式 socket(
SOCK_STREAM
):提供可靠的、面向连接的通信流;它使用TCP
协议,从而保证了数据传输的正确性和顺序性。
②数据报 socket(SOCK_DGRAM
):定义了一种无连接的服务,数据通过相互独立的报文进行传输,是无序的,并且不保证是可靠、无差错的。它使用数据报协议UDP
。
③原始 socket:原始套接字允许对底层协议如 IP 或 ICMP 进行直接访问,它功能强大但使用较为不便,不常用。
二、TCP 常用API
●头文件
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <strings.h>
#include <arpa/inet.h>
1. socket套接字描述符
返回值:套接字描述符
int socket(int domain, int type, int protocol)
//int domain ::代表一个协议族。AF_INET 决定了要用 ipv4 地址;
//int type:指定IP协议类型,常见的是TCP: SOCK_STREAM。 UDP:SOCK_DGRAM等。
//int protocol:当为 0 时,会自动选择 type 类型对应的默认协议。
(1)domain协议簇
可选项 | 说明 |
---|---|
AF_INET | IPv4 互联网协议族 |
AF_INET6 | IPv6 互联网协议族 |
AF_UNIX 、AF_LOCAL | 本地通信(Unix域套接字) |
AF_PACKET | 用于底层数据包的访问(通常用于网络通信) |
AF_NETLINK | 用于用户空间与内核之间的通信。 |
(2)type协议类型
可选项 | 说明 |
---|---|
SOCK_STREAM | 提供面向连接的稳定数据传输(TCP) |
SOCK_DGRAM | 提供数据包的无连接传输(UDP) |
SOCK_RAW | 提供对 IP 层直接操作的原始套接字(需要root权限) |
SOCK_SEQPACKET | 有序且可靠的数据包传输,适合固定大小的消息 |
2. bind套接字绑定
将套接字绑定到指定的本地地址上,这样就可以在该地址上进行数据收发操作。通常,在服务器程序中,使用bind()函数将服务器套接字绑定到服务器的IP地址和端口号上,以便客户端可以连接到该地址并与服务器通信。
int bind(int sockfd, struct sockaddr* my_addr, int addrlen);
//int sockfd:socket套接字描述符
//struct sockaddr* my_addr :本地ip地址结构体,包含ip地址、端口等。
// int addrlen: struct sockaddr 结构的大小。
使用举例:
//下述结构体已经在#include <sys/socket.h>头文件中定义,不需要程序员自己定义。
/*
struct sockaddr_in {
sa_family_t sin_family; // address family: IP协议类型:AF_INET(IPV4)、AF_INET6(IPV6)。
in_port_t sin_port; // port in network byte order 端口号
struct in_addr sin_addr; //internet address IP 地址
};
struct in_addr {
in_addr_t s_addr; //ip地址
*/
//填写绑定的结构体内容
sockaddr_in server_info; //初始化结构体
server_info.sin_family =AF_INET; //IPV4类型
server_info.sin_port=htons(50000); //htons()将一个主机字节序端口号转为网络字节序下的端口号。
server_info.sin_addr.s_addr=htonl(INADDR_ANY); //所有人都可以连接。
bind(sockfd, (struct sockaddr*)&server_info, sizeof(server_info)); //绑定ip以及端口号
注意:使用10000以内的端口号可能会与系统服务或其他常见的应用程序发生冲突,导致绑定失败或服务异常。因此,在开发自定义应用程序时,建议选择一个不太常用的端口号,通常在49152到65535之间。
3. listen设置最大排队数
设置最大排队长度:即多个客户端连接服务端时,当服务端没有接收这个请求,则客户端会进入排队等待中,那么这个排队长度就是最大可以容忍排队客户端的数量。这个并不是客户端连接的最大数量。
int listen(int sockfd, int backlog);
//int sockfd:socket套接字描述符。
// int backlog:设置请求排队的最大长度,当有多个客户端程序和服务端相连时, 使用这个表示可以介绍的排队长度。
4. accept接收客户端请求
监听是否有客户端链接,调用 accpet 之后会一直阻塞,直到有用户连接。
返回值:客户端的文件描述符。
int accept(int sockfd, void *addr, int *addrlen);
//int sockfd:socket套接字描述符。
//void *addr :用于存放客户端的信息
//int *addrlen :接收客户端的信息长度。
举例:
sockaddr_in client_info; //用于存储连接的客户端的信息
int client_socket;
client_socket=accept(sockfd, (struct sockaddr*)&client_info, &(sizeof(client_info)));
printf("客户端的socket:%d, ip:%s, 端口:%d\n",client_socket, client_info.sin_addr, client_info.sin_port);
5. connect连接服务端
用于在客户端与服务器进行端连接。0 :成功。1 :失败。
int connect(int sockfd, struct sockaddr * serv_addr,int addrlen);
//int sockfd:socket()返回的文件描述符。
//struct sockaddr * serv_addr:服务端的地址,通常传递 struct sockaddr_in 结构体类型。
//int addrlen) :serv_addr长度。
6. read读取数据
成功时:返回实际读取的字节数,这个值可能比请求的字节数少,特别是在读取文件尾或读少于请求字节数的数据时。
出错时:返回 -1,并将 errno 设置为合适的错误码。
#include <unistd.h>
ssize_t read(int fildes, void *buf, size_t nbyte);
//int fildes:文件描述符,标识要读取的文件或其他可读对象(如套接字、管道等)。
//void *buf:读取的数据将存储在此缓冲区中。
//size_t nbyte:缓冲区的大小。
7. write发送数据
成功时:返回实际写入的字节数。这可能比请求的字节数少,尤其是在写入到非阻塞文件描述符时。
出错时:返回 -1,并将 errno 设置为合适的错误码。
#include <unistd.h>
ssize_t write(int fildes, const void *buf, size_t nbyte);
//int fildes:文件描述符,标识要写入的文件或其他可读对象(如套接字、管道等)。
//const void *buf:要写的数据。
// size_t nbyte:数据的大小。
三、UDP 常用API
●头文件
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <strings.h>
#include <arpa/inet.h>
1. socket套接字描述符
返回值:套接字描述符
int socket(int domain, int type, int protocol)
//int domain ::代表一个协议族。AF_INET 决定了要用 ipv4 地址;
//int type:指定IP协议类型,常见的是TCP: SOCK_STREAM。 UDP:SOCK_DGRAM等。
//int protocol:当为 0 时,会自动选择 type 类型对应的默认协议。
2. bind套接字绑定
将套接字绑定到指定的本地地址上,这样就可以在该地址上进行数据收发操作。
int bind(int sockfd, struct sockaddr* my_addr, int addrlen);
//int sockfd:socket套接字描述符
//struct sockaddr* my_addr :本地ip地址结构体,包含ip地址、端口等。
// int addrlen: struct sockaddr 结构的大小。
3. recvfrom接收数据
通过发送方的socket,将数据存储到buff中,并将发送方的地址信息存储起来。
ssize_t recvfrom(int socket, void *restrict buffer, size_t length, int flags,
struct sockaddr *restrict address,
socklen_t *restrict address_len);
/*
int socket :指明用哪个套接字接收数据。
oid *restrict buffer:recvfrom函数将接收到的数据存储在此缓冲区中。
size_t length:这是一个指定接收数据缓冲区大小。
int flags:通常为0.
struct sockaddr *restrict address:用于存储发送方的地址信息。如果不需要发送方的地址信息,可以将其设为NULL。
socklen_t *restrict address_len:存储发送方信息结构体的大小。
*/
例如:
int sock; //发送方的socket
ssize_t size;
char buff[64]; //将接收的消息存到buff中
struct sockaddr_in send_info; //存储发送方的信息
socklen_t length = sizeof(send_info);//结构体大小
size=recvfrom(sock, buff, sizeof(buff), 0, (struct sockaddr *)&send_info, &length);
if(size < 0){
perror("recvfrom error");
break;
}
printf("%s:%d [%s]\n", inet_ntoa(send_info.sin_addr), ntohs(send_info.sin_port), buff);
4. sendto发送数据
通过自身的socket,将buff中的数据发送给目标地址。
ssize_t sendto(int socket, const void *message, size_t length,int flags,
const struct sockaddr *dest_addr,
socklen_t dest_len);
/*
int socket :指明用哪个套接字发送数据。
const void *message:sendto函数将此缓冲区中的数据发送出去。
size_t length:发送数据的长度。
int flags:通常为0
const struct sockaddr *dest_addr:指定目标地址。这个结构包含了目标的IP地址和端口号。
socklen_t dest_len:目标地址的长度。
*/
举例:
int sock; //自身的sock