目录
1.创建套接字 socket()
socket()
函数是网络编程中的一个基本且关键的函数,它用于创建一个新的套接字(socket)。套接字是网络通信中的端点,它允许不同主机上的进程之间进行数据的发送和接收。socket()
函数在多种编程语言中都有对应的实现,但最基础和广泛使用的是在C语言中的定义,它也被许多其他语言通过标准库或扩展库间接支持。
函数原型
在C语言中,socket()
函数的原型定义在 <sys/socket.h>
头文件中(在Windows上可能是 <winsock2.h>
并且需要初始化Winsock DLL):
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int socket(int domain, int type, int protocol);
参数
domain
:指定了协议族(protocol family)。最常用的协议族是AF_INET
,它代表IPv4协议;另一个是AF_INET6
,它代表IPv6协议。type
:指定了套接字类型。最常用的类型是SOCK_STREAM
,它表示面向连接的TCP协议;另一个是SOCK_DGRAM
,它表示无连接的UDP协议。protocol
:指定了具体的协议。大多数情况下,这个参数可以设置为0,让系统自动选择该类型套接字和协议族对应的默认协议。
返回值
- 成功时,
socket()
函数返回一个非负整数,它是套接字的描述符(socket descriptor)。这个描述符在后续的套接字操作中(如绑定、监听、接受连接、发送和接收数据)将被使用。 - 失败时,
socket()
函数返回-1,并设置全局变量errno
以指示错误原因。
2.bind()
bind()
函数在不同的编程环境和上下文中具有不同的作用,但主要可以分为两大类:网络编程中的 bind()
函数和编程语言(如C++、JavaScript)中的 bind()
函数。
一、网络编程中的 bind()
函数
在网络编程中,bind()
函数主要用于将一个套接字(socket)与一个地址(包括IP地址和端口号)绑定在一起。这是服务器端编程中的一个关键步骤,通常在创建套接字后使用。
主要用途
- 在服务器端,
bind()
函数通过给一个未命名套接口分配一个本地名字(即主机地址/端口号)来为套接口建立本地捆绑。
函数原型
在UNIX/Linux环境下,bind()
函数的原型如下:
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockfd
:标识要绑定的套接字的文件描述符。addr
:指向包含地址信息的结构体的指针,通常使用struct sockaddr_in
表示IPv4地址,或struct sockaddr_in6
表示IPv6地址。addrlen
:地址结构体的大小,可以用sizeof
操作符获得。
返回值
- 成功时,
bind()
函数返回0。 - 失败时,返回-1,并设置全局变量
errno
以指示错误原因。
3.recvfrom()
recvfrom()
函数是网络编程中的一个重要函数,特别是在使用UDP协议进行通信时。它用于从套接字接收数据,并可以返回发送数据的源地址。以下是recvfrom()
函数的详细解析:
一、函数原型
在C语言中,recvfrom()
函数的原型通常如下(以UNIX/Linux系统为例):
recvfrom() 函数是网络编程中的一个重要函数,特别是在使用UDP协议进行通信时。它用于从套接字接收数据,并可以返回发送数据的源地址。以下是recvfrom()函数的详细解析:
一、函数原型
在C语言中,recvfrom()函数的原型通常如下(以UNIX/Linux系统为例):
其中,参数的含义如下:
sockfd
:已打开并绑定的套接字的文件描述符。buf
:指向缓冲区的指针,用于存储接收到的数据。len
:缓冲区的大小(以字节为单位),表示函数最多可以接收的数据量。flags
:控制接收行为的标志位,如MSG_DONTWAIT
(非阻塞模式)、MSG_WAITALL
(阻塞模式,直到接收到指定大小的数据)等。src_addr
:一个指向sockaddr
结构体的指针,用于存储发送方的地址信息。如果不需要获取发送方的地址,可以设置为NULL。addrlen
:一个指向整型的指针,用于指定src_addr
结构体的大小。在调用recvfrom()
之前,应该将其设置为src_addr
结构体的大小,在调用之后,它将被设置为新接收到的地址的实际大小。
二、返回值
- 如果成功,
recvfrom()
返回接收到的字节数。 - 如果连接被对方优雅地关闭,返回0。
- 如果发生错误,返回-1,并设置全局变量
errno
来指示错误的原因。
三、常见错误
使用recvfrom()
时,需要处理可能出现的各种错误情况,例如:
- 如果套接字处于非阻塞模式并且没有数据可读,
recvfrom()
可能会返回EAGAIN
或EWOULDBLOCK
错误。 - 其他可能的错误包括
EBADF
(无效的套接字描述符)、EFAULT
(参数中有一指针指向无法存取的内存空间)、ENOTSOCK
(参数s为一文件描述词,非socket)等。
4.sendto()
sendto
函数是网络编程中用于向指定的目的地址发送数据报的函数,特别是在使用UDP协议时非常常见。以下是对 sendto
函数的详细解析:
一、函数原型
在C语言中,sendto
函数的原型通常如下(以UNIX/Linux系统为例):
#include <sys/types.h>
#include <sys/socket.h>
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
二、参数说明
sockfd
:已打开的套接字文件描述符,该套接字必须已经通过socket()
函数创建,并且对于UDP协议来说,不需要通过connect()
函数建立连接。buf
:指向要发送数据的缓冲区的指针。len
:要发送的数据的长度(以字节为单位)。flags
:发送选项的标志位,通常为0,表示使用默认行为。但也可以设置其他标志位,如MSG_DONTROUTE
(绕过本地路由表)、MSG_DONTWAIT
(非阻塞模式)等。dest_addr
:指向目的地址的指针,该地址必须是一个sockaddr
结构体(或其特定类型的变体,如sockaddr_in
用于IPv4),包含了目标主机的IP地址和端口号。addrlen
:目的地址的长度,对于sockaddr_in
结构体,其长度通常为sizeof(struct sockaddr_in)
。
三、返回值
- 如果成功,
sendto
返回发送的字节数。 - 如果发生错误,返回-1,并设置全局变量
errno
来指示错误的原因。
四、使用注意事项
- 确保套接字有效:在调用
sendto
之前,必须确保sockfd
是一个有效的、已经打开的套接字文件描述符。 - 目标地址正确:必须确保
dest_addr
指向的地址是正确的,包括IP地址和端口号。 - 检查返回值:在调用
sendto
后,应该检查其返回值以确保数据成功发送。 - 非阻塞模式:如果需要,可以通过设置
flags
参数为MSG_DONTWAIT
来使sendto
调用在非阻塞模式下工作。 - UDP特性:由于UDP是无连接的协议,
sendto
函数不会关心目标地址是否可达或是否存在,只是简单地将数据报发送到网络上。
UDP协议的特点
UDP(User Datagram Protocol,用户数据报协议)是一种无连接的传输层协议,它基于IP协议,提供了一种将数据包发送到网络上的方式。UDP协议的特点主要包括以下几个方面:
1. 无连接性
- UDP在发送数据之前不需要在源端和终端之间建立连接。发送方可以直接将数据包发送到目标主机,而无需等待确认或建立连接的过程。这减少了建立连接和断开连接的开销,并提高了传输效率。
2. 高效性
- UDP的头部开销较小,只有8字节,相比TCP的20字节头部开销更小。这使得UDP在传输大量数据时效率更高,尤其是在对实时性要求较高的应用中。
3. 不可靠性
- UDP不提供数据包的可靠性保证。数据包发送后,即使丢失也不会重新发送,也不保证数据包的顺序和完整性。这使得UDP在实时性要求高、丢失一些数据包不会影响整体传输效果的应用场景中更加适用。
4. 快速性
- 由于UDP不需要等待建立连接,数据包可以立即发送到目标主机,因此UDP的延迟较低。这使得UDP非常适用于对实时性要求较高的应用,如音频和视频的实时传输。
5. 面向报文
- UDP是面向报文的协议。发送方的UDP对应用程序交下来的报文,在添加首部后就向下交付给IP层。UDP对应用层交下来的报文,既不合并,也不拆分,而是保留这些报文的边界。这意味着应用程序需要选择合适的报文大小。
6. 支持多种交互通信
- UDP支持一对一、一对多、多对一和多对多的交互通信。这使得UDP在需要向多个客户端广播同一消息的应用场景中非常有用,如视频和音频的实时传输。
7. 无拥塞控制
- UDP不使用拥塞控制机制。这意味着当网络出现拥塞时,UDP的发送速率不会降低,这可能导致更多的数据包丢失。但在某些实时应用中,如在线游戏和视频直播,这种特性反而有利于减少延迟和提高用户体验。
8. 适用于特定应用场景
- 尽管UDP的不可靠性使得它不适用于所有类型的网络应用,但在实时性要求高、数据量较小且数据丢失对整体传输效果影响不大的场景中,UDP却表现出色。例如,UDP常用于DNS查询、DHCP服务器和客户端之间的通信、在线游戏和流媒体应用等。
综上所述,UDP协议以其无连接性、高效性、快速性等特点在特定应用场景中发挥着重要作用。然而,由于其不可靠性,UDP并不适用于所有类型的网络应用,特别是在对数据可靠性要求较高的场景中。
应用
#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <cstring>
#define PORT 8080
#define BUFFER_SIZE 1024
int main() {
int server_fd, new_socket;
struct sockaddr_in address;
int opt = 1;
int addrlen = sizeof(address);
char buffer[BUFFER_SIZE] = {0};
// 创建 socket 文件描述符
if ((server_fd = socket(AF_INET, SOCK_DGRAM, 0)) == 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
// 绑定 socket 到端口 8080
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(PORT);
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
perror("bind failed");
exit(EXIT_FAILURE);
}
// 设置 socket 选项
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {
perror("setsockopt");
exit(EXIT_FAILURE);
}
while (true) {
// 接收数据
int len = recvfrom(server_fd, (char *)buffer, BUFFER_SIZE, MSG_WAITALL, (struct sockaddr *)&address, (socklen_t*)&addrlen);
buffer[len] = '\0';
std::cout << "Message from client: " << buffer << std::endl;
// 发送响应(如果需要)
// sendto(server_fd, (const char *)buffer, strlen(buffer), MSG_CONFIRM, (const struct sockaddr *)&address, addrlen);
}
return 0;
}