1. 网络体系
1.1. 简介
网络采用分而治之的方法设计,将网络的功能划分为不同的模块,以分层的形式有机组合在一起。 每层实现不同的功能,其内部实现方法对外部其他层次来说是透明的。每层向上层提供服务,同时使用下层提供的服务 网络体系结构即指网络的层次结构和每层所使用协议的集合 两类非常重要的体系结构:OSI与TCP/IP.
1.1.1. OSI开放系统互联模型
OSI模型相关的协议已经很少使用,但模型本身非常通用 OSI模型是一个理想化的模型,尚未有完整的实现 OSI模型共有七层.
1.1.2. TCP/IP协议族的体系结构
TCP/IP协议是Internet事实上的工业标准。 一共有四层
1.1.3. TCP协议通信模型
1.1.4. TCP/IP协议下的数据包
1.1.5. 数据的封装与传递过程
1.1.6. TCP/IP结构
1.1.7. TCP协议特点
TCP(即传输控制协议):是一种面向连接的传输层协议,它能提供高可靠性通信(即数据无误、数据无丢失、数据无失序、数据无重复到达的通信) 适用情况: 适合于对传输质量要求较高,以及传输大量数据的通信。 在需要可靠数据传输的场合,通常使用TCP协议 MSN/QQ等即时通讯软件的用户登录账户管理相关的功能通常采用TCP协议.
1.1.8. UDP协议特点
UDP(User Datagram Protocol)用户数据报协议,是不可靠的无连接的协议。在数据发送前,因为不需要进行连接,所以可以进行高效率的数据传输。 适用情况: 发送小尺寸数据(如对DNS服务器进行IP地址查询时) 在接收到数据,给出应答较困难的网络中使用UDP。(如:无线网络) 适合于广播/组播式通信中。 MSN/QQ/Skype等即时通讯软件的点对点文本通讯以及音视频通讯通常采用UDP协议 流媒体、VOD、VoIP、IPTV等网络多媒体服务中通常采用UDP方式进行实时数据传输.
1.2. TCP/IP网络编程预备知识
1.2.1. Socket
Socket 是一个编程接口 是一种特殊的文件描述符 (everything in Unix is a file) 并不仅限于TCP/IP协议 面向连接 (Transmission Control Protocol - TCP/IP) 无连接 (User Datagram Protocol -UDP 和 Inter-network Packet Exchange - IPX).
1.2.1.1. Socket类型
流式套接字(SOCK_STREAM) 提供了一个面向连接、可靠的数据传输服务,数据无差错、无重复的发送且按发送顺序接收。内设置流量控制,避免数据流淹没慢的接收方。数据被看作是字节流,无长度限制。 数据报套接字(SOCK_DGRAM) 提供无连接服务。数据包以独立数据包的形式被发送,不提供无差错保证,数据可能丢失或重复,顺序发送,可能乱序接收。 原始套接字(SOCK_RAW) 可以对较低层次协议如IP、ICMP直接访问.
1.2.1.2. Socket的位置
1.2.2. IP地址
IP地址是Internet中主机的标识 Internet中的主机要与别的机器通信必须具有一个IP地址 IP地址为32位(IPv4)或者128位(IPv6) 每个数据包都必须携带目的IP地址和源IP地址,路由器依靠此信息为数据包选择路由 表示形式:常用点分形式,如202.38.64.10,最后都会转换为一个32位的无符号整数。 IP地址分类 子网掩码.
1.2.3. 端口号
为了区分一台主机接收到的数据包应该转交给哪个进程来进行处理,使用端口号来区别 TCP端口号与UDP端口号独立 端口号一般由IANA (Internet Assigned Numbers Authority) 管理 众所周知端口:1~1023(1~255之间为众所周知端口,256~1023端口通常由UNIX系统占用) 注册端口:1024~49150 动态或私有端口:49151~65535.
1.2.4. 字节序
不同类型CPU的主机中,内存存储多字节整数序列有两种方法,称为主机字节序(HBO): 小端序(little-endian) - 低序字节存储在低地址 将低字节存储在起始地址,称为“Little-Endian”字节序,Intel、AMD等采用的是这种方式; 大端序(big-endian)- 高序字节存储在低地址 将高字节存储在起始地址,称为“Big-Endian”字节序,由ARM、Motorola等所采用 网络中传输的数据必须按网络字节序,即大端字节序 在大部分PC机上,当应用进程将整数送入socket前,需要转化成网络字节序;当应用进程从socket取出整数后,要转化成小端字节序.
网络字节序(NBO - Network Byte Order) 使用统一的字节顺序,避免兼容性问题 主机字节序(HBO - Host Byte Order) 不同的机器HBO是不一样的,这与CPU的设计有关 Motorola 68K系列、ARM系列,HBO与NBO是一致的 Intel X86系列,HBO与NBO不一致.
大端(Big-Endian):字节的高位在内存中放在存储单元的起始位置.
1.2.5. 字节序转换函数
把给定系统所采用的字节序称为主机字节序。为了避免不同类别主机之间在数据交换时由于对于字节序的不同而导致的差错,引入了网络字节序。
主机字节序到网络字节序:
u_long htonl (u_long hostlong);
u_short htons (u_short short);
网络字节序到主机字节序:
u_long ntohl (u_long hostlong);
u_short ntohs (u_short short);
1.2.6. IP地址的转换
inet_aton( )
将strptr所指的字符串转换成32位的网络字节序二进制值
#include <arpa/inet.h> int inet_aton(const char *strptr, struct in_addr *addrptr); inet_addr( )
功能同上,返回转换后的地址。
int_addr_t inet_addr(const char *strptr);
inet_ntoa( )
将32位网络字节序二进制地址转换成点分十进制的字符串。
char *inet_ntoa(stuct in_addr inaddr);
inet_pton()
将IPV4/IPV6的地址转换成binary格式
int inet_pton(int af, const char *src, void *dst);
2. 系统调用
2.1. 网络编程相关API
2.1.1. 网络编程常用函数
- socket() 创建套接字
- bind() 绑定本机地址和端口
- connect() 建立连接
- listen() 设置监听端口
- accept() 接受TCP连接
- recv(), read(), recvfrom() 数据接收
- send(), write(), sendto() 数据发送
- close(), shutdown() 关闭套接字
2.1.2. socket创建套接字
int socket(int domain, int type, int protocol);参数说明
domain (协议域):描述: 指定通信的协议域,即通信的地址族。
常见值:
AF_INET: IPv4协议族。
AF_INET6: IPv6协议族。
AF_UNIX 或 AF_LOCAL: 本地通信协议族,用于进程间通信(IPC)。
type (套接字类型):描述: 指定套接字的类型,即通信的语义。
常见值:
SOCK_STREAM: 提供面向连接的、可靠的、双向的、基于字节流的通信(如TCP)。
SOCK_DGRAM: 提供无连接的、不可靠的、基于数据报的通信(如UDP)。
SOCK_RAW: 提供原始网络协议访问。
SOCK_SEQPACKET: 提供面向连接的、可靠的、双向的、基于记录的通信。
protocol (协议):描述: 指定使用的具体协议。通常情况下,可以设置为0,表示使用默认协议。
常见值:
0: 使用默认协议。例如,对于AF_INET和SOCK_STREAM,默认协议是TCP;对于AF_INET和SOCK_DGRAM,默认协议是UDP。
具体协议值可以通过getprotobyname函数获取,但通常情况下设置为0即可。
返回值说明
成功:返回一个非负整数,表示新创建的套接字描述符(socket file descriptor)。
套接字描述符是一个整数,用于在后续的套接字操作中标识该套接字。
失败:返回-1,并设置errno以指示错误类型。
常见的错误码包括:
EAFNOSUPPORT: 指定的协议域不受支持。
EPROTONOSUPPORT: 指定的协议不受支持。
ESOCKTNOSUPPORT: 指定的套接字类型不受支持。
ENOBUFS 或 ENOMEM: 内存不足,无法创建套接字。
2.1.3. 地址相关的数据结构
通用地址结构struct sockaddr{ u_short sa_family; // 地址族, AF_xxxchar sa_data[14]; // 14字节协议地址};Internet协议地址结构struct sockaddr_in{ u_short sin_family; // 地址族, AF_INET,2 bytesu_short sin_port; // 端口,2 bytesstruct in_addr sin_addr; // IPV4地址,4 bytes char sin_zero[8]; // 8 bytes unused,作为填充};
IPv4地址结构
// internet address
struct in_addr
{in_addr_t s_addr; // u32 network address
};
2.1.4. bind绑定本机地址和端口
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数说明
sockfd (套接字描述符):描述: 表示要绑定的套接字的文件描述符。
类型: int
说明: 这个文件描述符通常是通过socket函数创建的。
addr (地址结构体指针):描述: 指向包含地址信息的struct sockaddr结构体的指针。
类型: const struct sockaddr *
说明: 这个结构体的具体类型取决于domain参数(例如,AF_INET使用struct sockaddr_in,AF_INET6使用struct sockaddr_in6)。
示例:
对于IPv4地址,通常使用struct sockaddr_in:struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(8888);
addr.sin_addr.s_addr = inet_addr("192.168.1.100");
对于IPv6地址,通常使用struct sockaddr_in6:struct sockaddr_in6 addr;
addr.sin6_family = AF_INET6;
addr.sin6_port = htons(8888);
inet_pton(AF_INET6, "2001:db8::1", &addr.sin6_addr);
addrlen (地址结构体长度):描述: 指定addr参数指向的地址结构体的长度。
类型: socklen_t
说明: 这个参数通常通过sizeof运算符获取。
示例:
对于struct sockaddr_in:socklen_t addrlen = sizeof(struct sockaddr_in);
对于struct sockaddr_in6:socklen_t addrlen = sizeof(struct sockaddr_in6);
返回值说明
成功:返回0,表示绑定成功。
失败:返回-1,并设置errno以指示错误类型。
常见的错误码包括:
EBADF: sockfd不是一个有效的文件描述符。
ENOTSOCK: sockfd不是一个套接字。
EADDRINUSE: 指定的地址已经被其他套接字使用。
EADDRNOTAVAIL: 指定的地址不可用。
EFAULT: addr指向的内存区域不可访问。
EINVAL: addrlen不正确或addr指向的地址结构体不正确。
EACCES: 权限不足,无法绑定到指定的地址。
2.1.5. 地址结构的一般用法
// 定义并清空 sockaddr_in 类型的变量
memset(&myaddr, 0, sizeof(myaddr)); // 清空 myaddr 结构体// 填充地址信息
myaddr.sin_family = AF_INET; // 设置协议族为 IPv4
myaddr.sin_port = htons(8888); // 设置端口号,并转换为主机字节序到网络字节序
myaddr.sin_addr.s_addr = inet_addr("192.168.1.100"); // 设置服务器 IP 地址// 绑定套接字
if (bind(listenfd, (struct sockaddr *)&myaddr, sizeof(myaddr)) < 0) {perror("bind failed"); // 打印错误信息close(listenfd); // 关闭套接字return -1; // 返回错误码
}
2.1.6. 地址转换函数
unsigned long inet_addr(char *address);address是以’\0’结尾的点分IPv4字符串。该函数返回32位的地址。如果字符串包含的不是合法的IP地址,则函数返回-1。例如:
struct in_addr addr;
addr.s_addr = inet_addr(" 192.168.1.100 ");char* inet_ntoa(struct in_addr address);address是IPv4地址结构,函数返回一指向包含点分IP地址的静态存储区字符指针。如果错误则函数返回NULL
2.1.7. listen设置监听端口
int listen(int sockfd, int backlog);
参数说明
sockfd (套接字描述符):描述: 表示要监听的套接字的文件描述符。
类型: int
说明: 这个文件描述符通常是通过socket函数创建的,并且已经通过bind函数绑定到一个地址和端口。
backlog (最大连接队列长度):描述: 指定等待接受(accept)的连接队列的最大长度。
类型: int
说明:
backlog参数定义了内核为相应套接字排队的最大连接数。
当多个客户端同时尝试连接到服务器时,这些连接会被放入一个队列中等待处理。
如果队列已满,新的连接请求可能会被拒绝。
常见的backlog值为5、10或20,具体值可以根据实际需求调整。
返回值说明
成功:返回0,表示监听成功。
失败:返回-1,并设置errno以指示错误类型。
常见的错误码包括:
EBADF: sockfd不是一个有效的文件描述符。
ENOTSOCK: sockfd不是一个套接字。
EOPNOTSUPP: sockfd不支持监听操作。
EADDRINUSE: 地址已经被其他套接字使用。
EINVAL: sockfd已经处于监听状态,或者backlog参数无效。
EACCES: 权限不足,无法监听指定的地址。
注意:完成listen()调用后,socket变成了监听socket(listening socket).
2.1.8. accept接受TCP连接
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数说明
sockfd (监听套接字描述符):描述: 表示要接受连接的监听套接字的文件描述符。
类型: int
说明: 这个文件描述符通常是通过socket函数创建的,并且已经通过bind函数绑定到一个地址和端口,然后通过listen函数设置为监听状态。
addr (客户端地址结构体指针):描述: 指向一个struct sockaddr类型的结构体,用于存储客户端的地址信息。
类型: struct sockaddr *
说明:
如果不需要客户端的地址信息,可以将此参数设置为NULL。
如果需要客户端的地址信息,需要先定义一个适当的结构体(如struct sockaddr_in),然后将其强制转换为struct sockaddr *类型。
例如,对于IPv4地址,可以使用struct sockaddr_in:struct sockaddr_in client_addr;
socklen_t client_addr_len = sizeof(client_addr);
int client_fd = accept(listenfd, (struct sockaddr *)&client_addr, &client_addr_len);
addrlen (地址结构体长度指针):描述: 指向一个socklen_t类型的变量,用于指定addr参数指向的地址结构体的长度。
类型: socklen_t *
说明:
在调用accept之前,需要初始化addrlen为地址结构体的实际大小。
例如,对于struct sockaddr_in,可以这样做:struct sockaddr_in client_addr;
socklen_t client_addr_len = sizeof(client_addr);
int client_fd = accept(listenfd, (struct sockaddr *)&client_addr, &client_addr_len);
在accept返回后,addrlen会被设置为实际存储的客户端地址信息的长度。
返回值说明
成功:返回一个新的套接字描述符,用于与客户端通信。
失败:返回-1,并设置errno以指示错误类型。
常见的错误码包括:
EBADF: sockfd不是一个有效的文件描述符。
ENOTSOCK: sockfd不是一个套接字。
EOPNOTSUPP: sockfd不支持接受操作。
ECONNABORTED: 连接被中断。
EINTR: 被信号中断。
EMFILE: 进程打开的文件描述符数量达到限制。
ENFILE: 系统打开的文件描述符数量达到限制。
ENOMEM: 内存不足,无法创建新的套接字。
2.1.9. connect 建立连接
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数说明
sockfd (套接字描述符):描述: 表示要连接的套接字的文件描述符。
类型: int
说明: 这个文件描述符通常是通过socket函数创建的,但尚未连接到任何远程地址。
addr (服务器地址结构体指针):描述: 指向一个struct sockaddr类型的结构体,用于存储服务器的地址信息。
类型: const struct sockaddr *
说明:
必须提供一个有效的服务器地址结构体,通常使用struct sockaddr_in(对于IPv4)或struct sockaddr_in6(对于IPv6)。
例如,对于IPv4地址,可以使用struct sockaddr_in:
c
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(8888);
server_addr.sin_addr.s_addr = inet_addr("192.168.1.100");
对于IPv6地址,可以使用struct sockaddr_in6:
c
struct sockaddr_in6 server_addr;
server_addr.sin6_family = AF_INET6;
server_addr.sin6_port = htons(8888);
inet_pton(AF_INET6, "2001:db8::1", &server_addr.sin6_addr);
addrlen (地址结构体长度):描述: 指定addr参数指向的地址结构体的长度。
类型: socklen_t
说明: 这个参数通常通过sizeof运算符获取。
示例:
对于struct sockaddr_in:socklen_t addrlen = sizeof(struct sockaddr_in);
对于struct sockaddr_in6:socklen_t addrlen = sizeof(struct sockaddr_in6);
返回值说明
成功:返回0,表示连接成功。
失败:返回-1,并设置errno以指示错误类型。
常见的错误码包括:
EBADF: sockfd不是一个有效的文件描述符。
ENOTSOCK: sockfd不是一个套接字。
EADDRINUSE: 本地地址已经被其他套接字使用。
EADDRNOTAVAIL: 本地地址不可用。
EAFNOSUPPORT: 地址族不支持。
EALREADY: 套接字是非阻塞的,并且连接操作正在进行中。
ECONNREFUSED: 连接被服务器拒绝。
EISCONN: 套接字已经连接。
ENETUNREACH: 网络不可达。
ETIMEDOUT: 连接超时。
EHOSTUNREACH: 主机不可达。
EINPROGRESS: 套接字是非阻塞的,并且连接操作正在进行中。
EINTR: 被信号中断。
EACCES: 权限不足,无法连接到指定的地址。
2.1.10. send数据发送
ssize_t send(int socket, const void *buffer, size_t length, int flags);
参数说明
socket (套接字描述符):描述: 表示要发送数据的套接字的文件描述符。
类型: int
说明: 这个文件描述符通常是通过socket函数创建的,并且已经通过connect函数连接到远程地址(对于TCP)或准备好发送数据(对于UDP)。
buffer (数据缓冲区):描述: 指向包含要发送数据的缓冲区的指针。
类型: const void *
说明:
buffer指向的数据可以是任意类型,但通常是一个字节数组。
使用const void *表示send函数不会修改缓冲区中的数据。
length (数据长度):描述: 指定要发送的数据长度(以字节为单位)。
类型: size_t
说明:
length参数指定buffer中要发送的字节数。
通常使用strlen函数获取字符串的长度,但要注意strlen不包括字符串结束符\0。
flags (标志位):描述: 指定发送操作的行为。
类型: int
说明:
flags参数可以是0,表示默认行为。
常见的标志位包括:
MSG_DONTROUTE: 不使用路由表查找目标地址。
MSG_OOB: 发送带外数据(仅适用于支持带外数据的协议,如TCP)。
MSG_NOSIGNAL: 如果发送操作导致对端关闭连接,不发送SIGPIPE信号。
MSG_CONFIRM: 确认连接(仅适用于某些协议)。
返回值说明
成功:返回实际发送的字节数(ssize_t类型)。
返回值可能小于length,表示部分数据已发送。
失败:返回-1,并设置errno以指示错误类型。
常见的错误码包括:
EBADF: socket不是一个有效的文件描述符。
ENOTSOCK: socket不是一个套接字。
EAGAIN 或 EWOULDBLOCK: 套接字是非阻塞的,并且当前不可写。
EDESTADDRREQ: 目标地址未指定(对于无连接的套接字,如UDP)。
EINTR: 发送操作被信号中断。
EINVAL: flags参数无效。
EISCONN: 套接字已经连接(对于无连接的套接字,如UDP)。
EMSGSIZE: 发送的数据长度超过协议允许的最大值。
ENOBUFS 或 ENOMEM: 内存不足,无法发送数据。
ENOTCONN: 套接字未连接(对于面向连接的套接字,如TCP)。
EPIPE: 对端关闭了连接(仅适用于面向连接的套接字,如TCP),并且flags未设置MSG_NOSIGNAL。
2.1.11. recv数据接收
ssize_t recv(int socket, void *buffer, size_t length, int flags);
参数说明
socket (套接字描述符):描述: 表示要接收数据的套接字的文件描述符。
类型: int
说明: 这个文件描述符通常是通过socket函数创建的,并且已经通过bind和listen函数设置为监听状态(对于TCP服务器),或者已经通过connect函数连接到远程地址(对于TCP客户端或UDP套接字)。
buffer (数据缓冲区):描述: 指向用于存储接收到的数据的缓冲区的指针。
类型: void *
说明:
buffer指向的数据可以是任意类型,但通常是一个字节数组。
recv函数会将接收到的数据存储在这个缓冲区中。
length (缓冲区长度):描述: 指定缓冲区的最大长度(以字节为单位),即最多可以接收的数据量。
类型: size_t
说明:
length参数指定buffer可以存储的最大字节数。
通常设置为缓冲区的实际大小减1,以确保有足够的空间存储字符串结束符\0(如果需要)。
flags (标志位):描述: 指定接收操作的行为。
类型: int
说明:
flags参数可以是0,表示默认行为。
常见的标志位包括:
MSG_DONTWAIT: 非阻塞模式,如果数据不可用,立即返回EAGAIN或EWOULDBLOCK。
MSG_OOB: 接收带外数据(仅适用于支持带外数据的协议,如TCP)。
MSG_PEEK: 查看数据而不从缓冲区中移除。
MSG_TRUNC: 返回实际接收的数据长度,即使数据被截断。
MSG_WAITALL: 等待直到接收指定数量的数据(仅适用于面向连接的套接字,如TCP)。
返回值说明
成功:返回实际接收的字节数(ssize_t类型)。
返回值可能小于length,表示部分数据已接收。
失败:返回-1,并设置errno以指示错误类型。
常见的错误码包括:
EBADF: socket不是一个有效的文件描述符。
ENOTSOCK: socket不是一个套接字。
EAGAIN 或 EWOULDBLOCK: 套接字是非阻塞的,并且当前没有数据可读。
EINTR: 接收操作被信号中断。
EINVAL: flags参数无效。
ENOMEM: 内存不足,无法接收数据。
ENOTCONN: 套接字未连接(对于面向连接的套接字,如TCP)。
EFAULT: buffer指向的内存区域不可访问。
2.1.12. read数据接收/write数据发送
read()和write()经常会代替recv()和send(),通常情况下,看自己的的偏好使用read()/write()和recv()/send()时最好统一.
ssize_t read(int fd, void *buf, size_t count);
参数说明
fd (文件描述符):描述: 表示要读取数据的文件描述符。
类型: int
说明: 这个文件描述符可以是任何类型的文件描述符,包括套接字、文件、管道等。
buf (数据缓冲区):描述: 指向用于存储读取数据的缓冲区的指针。
类型: void *
说明:
buf指向的数据可以是任意类型,但通常是一个字节数组。
read函数会将读取的数据存储在这个缓冲区中。
count (数据长度):描述: 指定要读取的最大字节数。
类型: size_t
说明:
count参数指定buf可以存储的最大字节数。
实际读取的字节数可能小于count,具体取决于文件或套接字的状态。
返回值说明
成功:返回实际读取的字节数(ssize_t类型)。
返回值可能小于count,表示部分数据已读取。
失败:返回-1,并设置errno以指示错误类型。
常见的错误码包括:
EBADF: fd不是一个有效的文件描述符。
EAGAIN 或 EWOULDBLOCK: 文件描述符是非阻塞的,并且当前没有数据可读。
EINTR: 读取操作被信号中断。
EINVAL: buf指向的内存区域不可访问。
EIO: I/O 错误。
EFAULT: buf指向的内存区域不可访问。
EISDIR: fd是一个目录。
ECONNRESET: 连接被对端重置(仅适用于套接字)。
ssize_t write(int fd, const void *buf, size_t count);
参数说明
fd (文件描述符):描述: 表示要写入数据的文件描述符。
类型: int
说明: 这个文件描述符可以是任何类型的文件描述符,包括套接字、文件、管道等。
buf (数据缓冲区):描述: 指向包含要写入数据的缓冲区的指针。
类型: const void *
说明:
buf指向的数据可以是任意类型,但通常是一个字节数组。
write函数不会修改缓冲区中的数据。
count (数据长度):描述: 指定要写入的最大字节数。
类型: size_t
说明:
count参数指定buf中要写入的字节数。
实际写入的字节数可能小于count,具体取决于文件或套接字的状态。
返回值说明
成功:返回实际写入的字节数(ssize_t类型)。
返回值可能小于count,表示部分数据已写入。
失败:返回-1,并设置errno以指示错误类型。
常见的错误码包括:
EBADF: fd不是一个有效的文件描述符,或者不是打开用于写入的文件描述符。
EAGAIN 或 EWOULDBLOCK: 文件描述符是非阻塞的,并且当前不可写。
EINTR: 写入操作被信号中断。
EINVAL: buf指向的内存区域不可访问。
EIO: I/O 错误。
EFAULT: buf指向的内存区域不可访问。
ENOSPC: 设备上没有足够的空间写入数据。
EPIPE: 对端关闭了连接(仅适用于套接字),并且fd未设置O_NONBLOCK。
示例代码
2.1.13. sendto数据发送/recvfrom数据接收
这两个函数一般在使用UDP协议时使用.
ssize_t sendto(int socket, const void *message, size_t length, int flags, const struct sockaddr *dest_addr, socklen_t dest_len);
参数说明
socket (套接字描述符):描述: 表示要发送数据的套接字的文件描述符。
类型: int
说明: 这个文件描述符通常是通过socket函数创建的,并且可以是UDP套接字或未连接的TCP套接字。
message (数据缓冲区):描述: 指向包含要发送数据的缓冲区的指针。
类型: const void *
说明:
message指向的数据可以是任意类型,但通常是一个字节数组。
sendto函数不会修改缓冲区中的数据。
length (数据长度):描述: 指定要发送的数据长度(以字节为单位)。
类型: size_t
说明:
length参数指定message中要发送的字节数。
通常使用strlen函数获取字符串的长度,但要注意strlen不包括字符串结束符\0。
flags (标志位):描述: 指定发送操作的行为。
类型: int
说明:
flags参数可以是0,表示默认行为。
常见的标志位包括:
MSG_DONTROUTE: 不使用路由表查找目标地址。
MSG_OOB: 发送带外数据(仅适用于支持带外数据的协议,如TCP)。
MSG_NOSIGNAL: 如果发送操作导致对端关闭连接,不发送SIGPIPE信号。
MSG_CONFIRM: 确认连接(仅适用于某些协议)。
dest_addr (目标地址结构体指针):描述: 指向包含目标地址信息的struct sockaddr类型的结构体的指针。
类型: const struct sockaddr *
说明:
这个结构体的具体类型取决于socket的协议族(例如,AF_INET使用struct sockaddr_in,AF_INET6使用struct sockaddr_in6)。
例如,对于IPv4地址,通常使用struct sockaddr_in:struct sockaddr_in dest_addr;
dest_addr.sin_family = AF_INET;
dest_addr.sin_port = htons(8888);
dest_addr.sin_addr.s_addr = inet_addr("192.168.1.100");
对于IPv6地址,通常使用struct sockaddr_in6:struct sockaddr_in6 dest_addr;
dest_addr.sin6_family = AF_INET6;
dest_addr.sin6_port = htons(8888);
inet_pton(AF_INET6, "2001:db8::1", &dest_addr.sin6_addr);
dest_len (目标地址结构体长度):描述: 指定dest_addr参数指向的地址结构体的长度。
类型: socklen_t
说明: 这个参数通常通过sizeof运算符获取。
示例:
对于struct sockaddr_in:socklen_t dest_len = sizeof(struct sockaddr_in);
对于struct sockaddr_in6:socklen_t dest_len = sizeof(struct sockaddr_in6);
返回值说明
成功:返回实际发送的字节数(ssize_t类型)。
返回值可能小于length,表示部分数据已发送。
失败:返回-1,并设置errno以指示错误类型。
常见的错误码包括:
EBADF: socket不是一个有效的文件描述符。
ENOTSOCK: socket不是一个套接字。
EAGAIN 或 EWOULDBLOCK: 套接字是非阻塞的,并且当前不可写。
EDESTADDRREQ: 目标地址未指定(对于无连接的套接字,如UDP)。
EINTR: 发送操作被信号中断。
EINVAL: flags参数无效。
EMSGSIZE: 发送的数据长度超过协议允许的最大值。
ENOBUFS 或 ENOMEM: 内存不足,无法发送数据。
ENOTCONN: 套接字未连接(对于面向连接的套接字,如TCP)。
EPIPE: 对端关闭了连接(仅适用于面向连接的套接字,如TCP),并且flags未设置MSG_NOSIGNAL。
ssize_t recvfrom(int socket, void *buffer, size_t length, int flags, struct sockaddr *address, socklen_t *address_len);
参数说明
socket (套接字描述符):描述: 表示要接收数据的套接字的文件描述符。
类型: int
说明: 这个文件描述符通常是通过socket函数创建的,并且可以是UDP套接字或未连接的TCP套接字。
buffer (数据缓冲区):描述: 指向用于存储接收到的数据的缓冲区的指针。
类型: void *
说明:
buffer指向的数据可以是任意类型,但通常是一个字节数组。
recvfrom函数会将接收到的数据存储在这个缓冲区中。
length (缓冲区长度):描述: 指定缓冲区的最大长度(以字节为单位),即最多可以接收的数据量。
类型: size_t
说明:
length参数指定buffer可以存储的最大字节数。
通常设置为缓冲区的实际大小减1,以确保有足够的空间存储字符串结束符\0(如果需要)。
flags (标志位):描述: 指定接收操作的行为。
类型: int
说明:
flags参数可以是0,表示默认行为。
常见的标志位包括:
MSG_DONTWAIT: 非阻塞模式,如果数据不可用,立即返回EAGAIN或EWOULDBLOCK。
MSG_OOB: 接收带外数据(仅适用于支持带外数据的协议,如TCP)。
MSG_PEEK: 查看数据而不从缓冲区中移除。
MSG_TRUNC: 返回实际接收的数据长度,即使数据被截断。
MSG_WAITALL: 等待直到接收指定数量的数据(仅适用于面向连接的套接字,如TCP)。
address (源地址结构体指针):描述: 指向一个struct sockaddr类型的结构体,用于存储发送方的地址信息。
类型: struct sockaddr *
说明:
如果不需要发送方的地址信息,可以将此参数设置为NULL。
如果需要发送方的地址信息,需要先定义一个适当的结构体(如struct sockaddr_in),然后将其强制转换为struct sockaddr *类型。
例如,对于IPv4地址,可以使用struct sockaddr_in:struct sockaddr_in src_addr;
socklen_t src_addr_len = sizeof(src_addr);
ssize_t bytes_received = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&src_addr, &src_addr_len);
对于IPv6地址,可以使用struct sockaddr_in6:struct sockaddr_in6 src_addr;
socklen_t src_addr_len = sizeof(src_addr);
ssize_t bytes_received = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&src_addr, &src_addr_len);
address_len (源地址结构体长度指针):描述: 指向一个socklen_t类型的变量,用于指定address参数指向的地址结构体的长度。
类型: socklen_t *
说明:
在调用recvfrom之前,需要初始化address_len为地址结构体的实际大小。
例如,对于struct sockaddr_in,可以这样做:struct sockaddr_in src_addr;
socklen_t src_addr_len = sizeof(src_addr);
ssize_t bytes_received = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&src_addr, &src_addr_len);
在recvfrom返回后,address_len会被设置为实际存储的发送方地址信息的长度。
返回值说明
成功:返回实际接收的字节数(ssize_t类型)。
返回值可能小于length,表示部分数据已接收。
失败:返回-1,并设置errno以指示错误类型。
常见的错误码包括:
EBADF: socket不是一个有效的文件描述符。
ENOTSOCK: socket不是一个套接字。
EAGAIN 或 EWOULDBLOCK: 套接字是非阻塞的,并且当前没有数据可读。
EINTR: 接收操作被信号中断。
EINVAL: flags参数无效。
ENOMEM: 内存不足,无法接收数据。
ENOTCONN: 套接字未连接(对于面向连接的套接字,如TCP)。
EFAULT: buffer或address指向的内存区域不可访问。
2.1.14. close关闭套接字
关闭双向通讯.
int close(int sockfd);
参数说明
sockfd (文件描述符):
描述: 表示要关闭的文件描述符。
类型: int
说明: 这个文件描述符可以是任何类型的文件描述符,包括套接字、文件、管道等。
返回值说明
成功:返回0,表示文件描述符关闭成功。
失败:返回-1,并设置errno以指示错误类型。
常见的错误码包括:
EBADF: sockfd不是一个有效的文件描述符。
EINTR: 关闭操作被信号中断。
2.1.15. shutdown关闭套接字
TCP连接是双向的(是可读写的),当我们使用close时,会把读写通道都关闭,有时侯我们希望只关闭一个方向,这个时候我们可以使用shutdown。 针对不同的howto,系统回采取不同的关闭方式。
int shutdown(int sockfd, int howto);
参数说明
sockfd (套接字描述符):描述: 表示要关闭的套接字的文件描述符。
类型: int
说明: 这个文件描述符通常是通过socket函数创建的,并且已经通过bind和listen函数设置为监听状态(对于TCP服务器),或者已经通过connect函数连接到远程地址(对于TCP客户端或UDP套接字)。
howto (关闭方式):描述: 指定关闭套接字的方式。
类型: int
说明:
howto参数可以是以下值之一:
SHUT_RD: 关闭套接字的接收端。后续对套接字的读操作将立即返回0(表示EOF)。
SHUT_WR: 关闭套接字的发送端。后续对套接字的写操作将返回EPIPE信号(对于TCP)。
SHUT_RDWR: 关闭套接字的接收端和发送端。等同于调用close(sockfd)。
返回值说明
成功:返回0,表示关闭操作成功。
失败:返回-1,并设置errno以指示错误类型。
常见的错误码包括:
EBADF: sockfd不是一个有效的文件描述符。
ENOTSOCK: sockfd不是一个套接字。
EINVAL: howto参数无效。
ENOTCONN: 套接字未连接(对于面向连接的套接字,如TCP)。