跳到主要内容

socket函数详解

socket() 创建一个套接字

/**
* @brief 创建一个套接字
*
* 此函数用于创建一个新的套接字。
*
* @param domain 协议族/域,如 AF_INET(IPv4)、AF_INET6(IPv6)、AF_UNIX(Unix 域)等
* @param type 套接字类型,如 SOCK_STREAM(面向连接的流套接字)、SOCK_DGRAM(无连接的数据报套接字)等
* @param protocol 协议,通常为 0,表示使用默认协议
* @return 成功时返回套接字描述符,失败时返回 -1
*/
int socket(int domain, int type, int protocol);
C++

bind() 绑定地址到套接字

/**
* @brief 绑定地址到套接字
*
* 此函数用于将指定的地址绑定到已创建的套接字上。
*
* @param sockfd 套接字描述符
* @param addr 地址结构体指针,包含要绑定的 IP 地址和端口号
* @param addrlen 地址结构体大小
* @return 成功时返回 0,失败时返回 -1
*/
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
C++

listen() 监听连接请求

/**
* @brief 监听连接请求
*
* 此函数用于将套接字设置为监听状态,等待客户端连接请求。
*
* @param sockfd 套接字描述符
* @param backlog 允许的等待连接队列长度
* @return 成功时返回 0,失败时返回 -1
*/
int listen(int sockfd, int backlog);
C++

accept 服务端: 连接客户端

/**
* @brief 接受客户端连接请求
*
* 此函数用于接受客户端的连接请求,并创建一个新的套接字与客户端建立连接。
*
* @param sockfd 监听套接字描述符
* @param addr 客户端地址结构体指针
* @param addrlen 客户端地址结构体大小指针
* @return 成功时返回新的套接字描述符,失败时返回 -1
*
* @note 调用此函数会阻塞,直到有客户端连接请求到达
*/
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
C++

函数说明:

  • accept: 函数用于接受客户端的连接请求,并创建一个新的套接字与客户端建立连接。
  • sockfd: 参数是已经监听的套接字描述符,即服务器套接字的文件描述符。
  • addr: 参数是一个指向存储客户端地址信息的结构体 sockaddr 的指针,如果不需要获取客户端地址信息,可以传入 NULL。
  • addrlen: 参数是一个指向表示 addr 结构体大小的整数指针,如果不需要获取客户端地址信息,可以传入 NULL。
  • accept: 函数返回一个新的套接字描述符,可以通过该描述符与客户端进行数据交互。

返回值:

  • 如果连接成功,accept 函数返回一个新的套接字描述符,该描述符用于与客户端进行数据交互。
  • 如果连接失败,accept 函数返回 -1,并设置 errno 为相应的错误码。

注意事项:

  • accept 函数是一个阻塞函数,当没有客户端连接请求时,函数会一直等待直到有连接请求到达。
  • accept 函数返回一个新的套接字描述符后,原始的监听套接字 sockfd 仍然可用,可以继续调用 accept 函数接受其他客户端的连接请求。
  • 在多线程或多进程环境中使用 accept 函数时,需要注意使用适当的同步机制,以避免竞争条件和并发问题。

connect() 客户端: 建立与服务器的连接

/**
* @brief 建立与服务器的连接
*
* 此函数用于建立与远程服务器的连接。
*
* @param sockfd 套接字描述符
* @param servaddr 服务器地址结构体指针,包含服务器的 IP 地址和端口号
* @param addrlen 服务器地址结构体大小
* @return 成功时返回 0,失败时返回 -1
*/
int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen);
C++

read() 从文件描述符或套接字读取数据 (不推荐)

/**
* @brief 从文件描述符或套接字读取数据
*
* 此函数用于从套接字中读取数据。
*
* @param fd 文件描述符或套接字描述符
* @param buf 数据缓冲区指针
* @param count 期望读取的字节数
* @return 成功时返回实际读取的字节数,失败时返回 -1
*/
ssize_t read(int fd, void *buf, size_t count);
C++

read 原则:

数据在不超过指定的长度的时候有多少读多少,没有数据则会一直等待。所以一般情况下:我们读取数据都需要采用循环读的方式读取数据,因为一次read 完毕不能保证读到我们需要长度的数据,read 完一次需要判断读到的数据长度再决定是否还需要再次读取。

recv() 从套接字接收数据 (推荐)

/**
* @brief 从套接字接收数据
*
* 此函数用于从套接字中接收数据。
*
* @param sockfd 套接字描述符
* @param buf 数据缓冲区指针
* @param len 缓冲区长度
* @param flags 接收标志,用于控制数据传输的行为
* @return 成功时返回实际接收的字节数,失败时返回 -1, 返回0为对方已调用close()关闭了连接
*/
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
C++

recv() 函数用于从套接字中接收数据,并将数据存储到指定的缓冲区 buf 中。len 参数指定了缓冲区的长度,而 flags 参数用于控制数据传输的行为,例如设置阻塞或非阻塞模式、指定接收数据的特性等。

recv() 函数返回实际接收的字节数,如果出现错误,返回 -1。

在非阻塞模式下,如果没有可用的数据,recv() 可能会返回 错误码 ? 混淆怎么办?

char buffer[1024]; int recv_size = recv(client_socket, buffer, sizeof(buffer), 0); if (recv_size == 0) { // 对方已关闭连接 printf("Peer closed the connection\n"); close(client_socket); } else if (recv_size < 0) { if (errno == EWOULDBLOCK || errno == EAGAIN) { // 没有可用的数据 printf("No data available\n"); } else { // 接收数据出错 printf("Recv error: %s\n", strerror(errno)); close(client_socket); } } else { // 成功接收到数据 // 处理接收到的数据 }


[知乎: linux 下用非阻塞的 socket 编程,如何用 recv 接收数据?](https://www.zhihu.com/question/596124033)

需要注意的是,recv() 函数是在网络编程中常用的函数,适用于 TCP 和 UDP 协议的套接字。


recv 原则:

recv 中有一个MSG_WAITALL的参数:

正常情况下recv是会等待直到读取到buff_size长度的数据,但是这里的WAITALL也只是尽量读全,在有中断的情况下recv还是可能会被打断,造成没有读完指定的buff_size的长度。所以即使是采用recv + WAITALL参数还是要考虑是否需要循环读取的问题,在实验中对于多数情况下recv (使用了MSG_WAITALL)还是可以读完buff_size

所以相应的性能会比直接read 进行循环读要好一些。[read 与 recv 区别]

read(sockfd, buff, buff_size);       
write(sockfd, buff, buff_size);
recv(sockfd, buff, buff_size,MSG_WAITALL); //阻塞模式接收
send(scokfd, buff, buff_size,MSG_WAITALL); //阻塞模式发送
recv(sockfd, buff, buff_size,MSG_DONTWAIT); //非阻塞模式接收
send(scokfd, buff, buff_size,MSG_DONTWAIT); //非阻塞模式发送
recv(sockfd, buff, buff_size,0);
send(scokfd, buff, buff_size,0);
C++

socket编程经验(还是上面链接说的)

  • 1)尽量使用recv(,,MSG_WAITALL), read必须配合while使用,否则数据量大(240*384)时数据读不完

  • 2)编程时写入的数据必须尽快读出,否则后面的数据将无法继续写入

  • 3)最佳搭配如下:

    nbytes = recv(sockfd, buff, buff_size, MSG_WAITALL);
    nbytes = send(scokfd, buff, buff_size, MSG_WAITALL);
    C++

write() 向套接字写入数据 (不推荐)

/**
* @brief 向文件描述符或套接字描述符写入数据
*
* 此函数用于向套接字中写入数据。
*
* @param fd 文件描述符或套接字描述符
* @param buf 数据缓冲区指针
* @param count 要写入的字节数
* @return 成功时返回实际写入的字节数,失败时返回 -1
*/
ssize_t write(int fd, const void *buf, size_t count);
C++

参考: Linux下,write/read,recv/send,recvfrom/sendto的区别 (应用场景:read/wirte是通用的文件描述符操作;recv/send 通常应用于TCP;recvfrom/sendto通常应用于UDP)

需要注意的是,write()函数是一个底层的系统调用,它没有提供像send()函数那样的额外的网络选项(如flags参数)。因此,在使用write()函数发送数据时,你可能需要自己处理一些与网络相关的细节,例如设置套接字为非阻塞模式、处理部分写入的情况等。

如果你正在编写网络编程代码,通常建议使用send()函数而不是write()函数,因为send()函数更适合网络通信,并且提供了更多的可选标志和错误处理机制。

send() 发送数据到目标地址 (推荐)

/**
* @brief 发送数据到目标地址
*
* 该函数用于发送数据到指定的目标地址。
*
* @param sockfd 套接字描述符,用于标识一个已连接的网络端点
* @param data 指向要发送的数据缓冲区的指针
* @param size 要发送的数据大小(字节数)
* @param flags 可选的标志参数,默认为0
* @return 成功发送的字节数,如果发生错误则返回-1,并且errno被设置为相应的错误代码
*
* @note 这个函数可能会在数据发送过程中返回少于请求的字节数,需要进行错误处理和重发操作
*/
ssize_t send(int sockfd, const void *data, size_t size, int flags);
C++

close() 关闭套接字或文件描述符

/**
* @brief 关闭套接字或文件描述符
*
* 此函数用于关闭已打开的套接字或文件描述符。
*
* @param fd 套接字描述符或文件描述符
* @return 成功时返回 0,失败时返回 -1
*/
int close(int fd);
C++

inet_pton() 将文本格式的 IP 地址转换为二进制格式

/**
* @brief 将文本格式的 IP 地址转换为二进制格式
*
* 此函数用于将提供的文本格式的 IP 地址转换为二进制格式。
*
* @param af 协议族/域,如 AF_INET(IPv4)、AF_INET6(IPv6)等
* @param src 文本格式的 IP 地址字符串
* @param dst 存储转换后二进制格式的地址的指针
* @return 成功时返回 1,失败时返回 0
*/
int inet_pton(int af, const char *src, void *dst);
C++
请作者喝奶茶:
Alipay IconQR Code
Alipay IconQR Code
本文遵循 CC CC 4.0 BY-SA 版权协议, 转载请标明出处
Loading Comments...