目录
前言
1. 网络字节序
2. socket编程接口
2.1 socket
2.2 bind
2.3 地址转换函数
2.4 recvfrom
2.5 sendto
2.6 TCP socket API
2.6.1 listen()
2.6.2 accept()
2.6.3 close()
2.6.4 connect()
总结
了解了计算机网络的基础知识,想要快速上手网络编程,那就很有必要了解一下网络套接字相关的接口;本文主要介绍一些socket编程必须的一些接口介绍;
为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络 字节序和主机字节序的转换。
#include
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位短整数。
比如:htonl表示将32位的长整数从主机字节序转换为网络字节序,例如将IP地址转换后准备发送。
套接字也分很多种:
// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
int socket(int domain, int type, int protocol);
// 绑定端口号 (TCP/UDP, 服务器)
int bind(int socket, const struct sockaddr *address, socklen_t address_len);
// 开始监听socket (TCP, 服务器)
int listen(int socket, int backlog);
// 接收请求 (TCP, 服务器)
int accept(int socket, struct sockaddr* address, socklen_t* address_len);
// 建立连接 (TCP, 客户端)
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
socket API是一层抽象的网络编程接口,适用于各种底层网络协议,如IPv4、IPv6,以及后面要讲的UNIX Domain Socket. 然而, 各种网络协议的地址格式并不相同;
想要把他们绑定在一起,所以都统一使用struct socketaddr类型;在判断时只需根据前两个字节进行判断是网络通信,还是域间 socket 识别后在接口内部,将struct socketaddr类型强转成对应的类型也就是C风格的多态;
struct sockaddr {
sa_family_t sa_family; /* Address family */
char sa_data[]; /* Socket address */
};
struct sockaddr_in {
sa_family_t sin_family; /* AF_INET */
in_port_t sin_port; /* Port number */
struct in_addr sin_addr; /* IPv4 address */
};
struct sockaddr_un {
sa_family_t sun_family; /* Address family */
char sun_path[]; /* Socket pathname */
};
struct in_addr {
in_addr_t s_addr;
};
typedef uint32_t in_addr_t;
函数原型:
#include
int socket(int domain, int type, int protocol);
返回值:
参数:
domain(地址域):
AF_INET
:表示 IPv4 协议。AF_INET6
:表示 IPv6 协议。AF_UNIX
:表示 Unix 域套接字,用于同一主机上的进程间通信。type(套接字类型):
SOCK_STREAM
:表示流式套接字,基于 TCP,提供可靠的、面向连接的通信。SOCK_DGRAM
:表示数据报套接字,基于 UDP,提供无连接的、不可靠的数据传输。SOCK_RAW
:表示原始套接字,允许直接访问传输层协议,通常需要超级用户权限。protocol(协议):
IPPROTO_TCP
)。使用示例:创建udp套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
函数原型:
#include
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
为了在通信中,能够让其他主机找到socket就需要绑定网络信息;
返回值:
参数:
sockfd:这是要绑定的套接字的文件描述符。它必须先通过 socket()
函数创建。
addr:这是指向 struct sockaddr
(或更具体的类型,比如用于 IPv4 地址的 struct sockaddr_in
)的指针,指明套接字应绑定到哪个地址。该结构包含地址族、端口号和 IP 地址。
addrlen:该参数指定 addr
指向的地址结构的大小。通常可以使用 sizeof(struct sockaddr_in)
或者你所使用的地址结构的适当大小。
使用示例:
//绑定指定网络信息
struct sockaddr_in local;
bzero(&local, sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(_port); //不能直接传,要本地主机转网络字节序
local.sin_addr.s_addr = inet_addr(_ip.c_str());// ip地址要转换成4字节整形
int n = ::bind(_sockfd, (struct sockaddr *)&local, sizeof(local));
sockaddr_in 结构体对象是在函数内定义的,只存在栈上,并没有设置到内核中,所以要使用bind把数据加载到内核中 ;
对结构体对象进行初始化,可以调用这个接口,作用是将数据置为0,s开始的n个字节;
#include
void bzero(void s[.n], size_t n);
我们使用的是ip都是字符串风格,系统中使用的是32位整形,所以我们需要进行转换,并且需要将ip转换为网络字节序我们自己转换就有点麻烦了,库里给我们提供了一系列的接口:
#include
#include
#include
in_addr_t inet_addr(const char *cp);
除此之外还有许多其他的接口比如:
int inet_aton(const char *cp, struct in_addr *inp);
char *inet_ntoa(struct in_addr in);
int inet_pton(int af, const char *src, void *dst);
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
感兴趣可以去了解一下;
recvfrom() 用于接收网络数据报套接字(UDP)的系统调用;
函数原型:
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
sockfd: 套接字描述符,代表一个已创建并绑定到特定地址和端口的套接字。通常通过 socket()
函数创建。
buf: 指向用来存储接收到的数据的缓冲区的指针。接收到的数据将被写入这个缓冲区。
len: 接收缓冲区的大小(以字节为单位)。这是最大可以接收的数据量。
flags: 控制接收操作的选项。可以使用以下标志:
MSG_CONFIRM
: 确认接收的消息,通常在处理同一套接字中的重复消息时使用。MSG_DONTWAIT
: 非阻塞模式下接收数据。src_addr: 一个指向 sockaddr
结构的指针,用于存储发送方的地址信息。如果不需要地址信息,可以将其设为 NULL
。
addrlen: 指向 socklen_t
类型的变量的指针,表示 src_addr
的长度。在调用 recvfrom()
之前应该初始化为 sizeof(struct sockaddr)
,接收到数据后,该值将会被更新为实际接收的地址长度。
返回值:
-1
,表示发生错误,可以通过 errno
获取错误信息。除此之外可以获取接收到的消息接口还有:
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t read(int fd, void *buf, size_t count);
用于在网络编程中发送数据的系统调用,适用于不同的协议和场景。通常用于 UDP 套接字,适合于无连接的通信场景,可以发送数据至任何指定的地址。它适用于实时应用、广播或多播等。
函数原型:
#include
#include
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
参数:
flags: 控制发送操作的选项。常见的标志包括:
MSG_OOB
: 发送紧急数据。MSG_DONTWAIT
: 非阻塞模式。dest_addr: 指向 sockaddr
结构的指针,表示目标地址(对于 UDP 等无连接协议时必需)。
其余参数和 recvfrom 相同;
返回值:
成功时返回已发送的字节数,失败时返回 -1
,并设置 errno;
除此之外可以用于发送消息的接口还有:
send()
主要用于 TCP 套接字,适用于已经建立连接的场景,保证了数据的顺序和完整性
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
flag以及返回值和sendto一样;
write()
是一个通用的写入系统调用,可以用于多种类型的文件描述符,也可以用于发送数据的系统调用,对于套接字,特别是 TCP 套接字,可以使用 write()
来发送数据。
ssize_t write(int fd, const void *buf, size_t count);
返回值:
成功时返回实际写入的字节数。如果返回值是 0
,则表示没有数据写入。如果返回 -1
,则表示发生错误,并且可以使用 errno
来获取错误信息
socket、bind、以及发送(send)和接收(recv)相关的接口都可以参考上述内容;接下来这些接口属于TCP专属的网络接口;
TCP和UDP不同,是面向连接的,UDP是无连接的;这些后续会专门出一期来详细介绍UDP和TCP;本文主要介绍他们的socket API;
在指定的套接字上开始监听传入连接;
函数原型:
int listen(int sockfd, int backlog);
参数:
sockfd
: 套接字描述符。backlog
: 指定等待连接的队列长度。返回值: 成功返回 0,失败返回 -1。
接受来自客户端的连接请求
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数:
sockfd
: 套接字描述符。addr
: 可选,指向一个用于存储客户端地址的结构体。addrlen
: 指向地址长度变量的指针。返回值: 成功返回新的套接字描述符,失败返回 -1。
关闭套接字
函数原型:
int close(int fd);
参数:fd
: 套接字描述符。
返回值: 成功返回 0,失败返回 -1
该接口主要是客户端使用,将客户端套接字连接到服务器;
函数原型:
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数:
socket()
创建的。struct sockaddr
结构的指针。通常,对于 IPv4,使用 struct sockaddr_in
。sizeof(struct sockaddr_in)
来获取 返回值:功时返回 0
,失败则返回 -1
,并设置 errno
以指明错误类型。
以上便是本文的全部内容,希望对你有所帮助,感谢阅读!