微信公众号开发是否需要建立网站,网页游戏吧,网站建立价格,门户网站建设招标公告原始套接字#xff08;Raw Socket#xff09;
原始套接字#xff08;Raw Socket#xff09;是一种提供较低级别网络访问的套接字。通过使用原始套接字#xff0c;应用程序可以直接发送或接收网络层如IP的数据包#xff0c;或者传输层如TCP、UDP的段#xff0c;而无需通…原始套接字Raw Socket
原始套接字Raw Socket是一种提供较低级别网络访问的套接字。通过使用原始套接字应用程序可以直接发送或接收网络层如IP的数据包或者传输层如TCP、UDP的段而无需通过常规的套接字API提供的协议处理。
以下是原始套接字的一些关键点 协议独立性使用原始套接字我们可以操作或构建自己的协议或者直接与现有协议如ICMP交互。 绕过内核处理通常当发送或接收数据包时操作系统内核会为我们处理很多细节例如TCP的三次握手或IP头的填充。但是使用原始套接字我们可以直接构建或解析这些协议从而绕过标准的内核处理。 特权由于原始套接字提供了对网络的低级访问使用它们通常需要特权例如root权限。 应用场景 网络诊断和测试工具例如ping使用原始套接字发送和接收ICMP回显请求和回显响应。定制协议的实现例如如果我们想实验一个新的传输层协议。安全研究和网络攻击例如执行某些类型的DoS攻击或网络扫描。 创建原始套接字在Linux中我们可以使用socket函数并为其提供AF_INET对于IPv4或AF_INET6对于IPv6以及SOCK_RAW来创建一个原始套接字。 手动头部处理使用原始套接字需要手动构建或解析协议头部。例如如果我们正在发送一个TCP段我们需要手动构建IP和TCP头部并设置所有必要的字段。同样当从一个原始套接字接收数据时我们将获取整个数据包需要自己解析它。 混杂模式如果我们想使用原始套接字捕获一个接口上的所有流量而不仅仅是发给特定地址的流量我们需要将接口设置为混杂模式。
需要注意的是虽然原始套接字提供了强大的功能但也需要小心使用。手动处理协议细节容易导致错误并可能引起网络问题或安全隐患。
Socket()
socket()函数是计算机网络编程中的核心函数之一用于创建一个新的套接字。套接字是端到端的通信链路是进程之间进行网络通信的主要手段。
下面详细介绍socket()函数
函数原型
int socket(int domain, int type, int protocol);参数 domain或称为family指定使用哪种地址族。常见的选择包括 AF_INETIPv4 地址族。用于IPv4网络通信。AF_INET6IPv6 地址族。用于IPv6网络通信。AF_UNIX本地套接字UNIX 域套接字地址族。用于同一机器上的进程间通信。 type指定套接字的类型。常见的选择包括 SOCK_STREAM提供面向连接、可靠、双向的字节流服务。典型的协议有TCP。SOCK_DGRAM提供无连接的、不可靠的数据报服务。典型的协议有UDP。SOCK_RAW提供原始套接字访问允许直接发送或接收协议如IP的数据包。SOCK_SEQPACKET提供面向连接的、可靠的、固定最大长度的记录序列。 protocol指定要使用的协议。通常当给定了套接字的类型时可以将此参数设置为0让系统自动选择合适的协议。例如当type是SOCK_STREAM时系统通常选择TCP作为协议。
返回值
成功返回一个非负描述符代表新创建的套接字。失败返回-1并设置相应的错误码。
使用示例
创建一个用于IPv4 TCP通信的套接字
int sockfd socket(AF_INET, SOCK_STREAM, 0);
if (sockfd -1) {perror(socket creation failed);exit(EXIT_FAILURE);
}创建一个用于IPv4 UDP通信的套接字
int sockfd socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd -1) {perror(socket creation failed);exit(EXIT_FAILURE);
}注意事项
创建套接字只是第一步。为了实际上进行通信还需要其他函数如bind(), listen(), accept(), connect(), send(), 和 recv()来配置并操作这个套接字。对于TCP服务端通常在socket()之后会调用bind(), listen()和accept()来绑定地址、监听连接和接受连接。对于TCP客户端通常在socket()之后会调用connect()来连接到服务器。对于UDP没有建立或接受连接的概念所以只需创建套接字然后可以直接使用sendto()和recvfrom()进行通信。
socket()函数是网络编程中的基础几乎所有的网络应用程序都会在某个地方使用它来开始其网络通信。
bind()
bind() 是套接字编程中的一个关键函数用于将套接字与特定的IP地址和端口号绑定。它通常在服务器设置期间使用以指定服务器将在哪个地址和端口监听即将到来的客户端连接。
函数原型
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);参数 sockfd: 这是一个套接字描述符它表示要绑定的套接字。 addr: 这是一个指向struct sockaddr的指针该结构定义了套接字的地址IP和端口。在实践中通常使用特定于协议的结构如struct sockaddr_in对于IPv4来填充这个参数并将其指针类型强制转换为struct sockaddr *。 addrlen: 这是地址结构的大小例如对于IPv4这将是sizeof(struct sockaddr_in)。
返回值
成功时bind() 返回0。失败时返回-1并设置errno以指示错误的原因。
常见的使用模式
在服务器中通常首先创建一个套接字然后使用bind()将它绑定到一个地址和端口。以下是一个简化的示例演示如何使用bind()为IPv4地址绑定套接字
#include sys/types.h
#include sys/socket.h
#include netinet/in.hint main() {int sockfd socket(AF_INET, SOCK_STREAM, 0); // Create a TCP socketif (sockfd 0) {perror(Error creating socket);return 1;}struct sockaddr_in server_addr;server_addr.sin_family AF_INET; // Address family for IPv4server_addr.sin_addr.s_addr INADDR_ANY; // Listen on any interfaceserver_addr.sin_port htons(8080); // Listen on port 8080if (bind(sockfd, (struct sockaddr *)server_addr, sizeof(server_addr)) 0) {perror(Error binding socket);return 1;}// ... the server can then proceed to listen and accept connectionsreturn 0;
}常见错误和注意事项 Address already in use: 如果尝试绑定到已被另一个套接字使用的地址和端口将会出现此错误。这通常发生在服务器崩溃并尝试重新启动但由于之前的套接字仍处于“TIME_WAIT”状态所以它不能立即绑定。使用setsockopt()和SO_REUSEADDR可以帮助解决此问题。 Permission denied: 通常只有root用户才能绑定到低于1024的端口。 确保在绑定套接字之前填充了整个sockaddr_in结构并正确设置了sin_family、sin_addr.s_addr和sin_port字段。 使用htons()函数确保端口号是网络字节顺序。
通过合理地使用bind()函数开发人员可以确保他们的服务器监听特定的IP地址和端口从而等待客户端的连接。
listen()
listen() 是套接字API中的一个函数用于让一个套接字进入监听模式从而能够接收来自客户端的连接请求。这是创建服务器应用程序的必要步骤之一。
函数原型
int listen(int sockfd, int backlog);参数 sockfd: 一个套接字描述符它应该先前已经使用 socket() 创建并使用 bind() 绑定到一个特定的地址和端口。 backlog: 这个参数定义了等待队列的大小也就是说系统应该允许等待处理未accept()的的连接数量。当有更多的客户端尝试连接超过了backlog指定的数量时系统会开始拒绝这些新的连接请求。
返回值
如果函数调用成功则返回0。如果出现错误则返回-1并设置errno以指示出现的特定错误。
使用
一旦使用bind()函数将套接字绑定到一个地址和端口后我们可以调用listen()以进入监听模式。在此模式下套接字准备接受来自客户端的连接请求。
这是一个简单的示例
int sockfd socket(AF_INET, SOCK_STREAM, 0); // Create a socket// ... (bind the socket to an address using bind() here)if (listen(sockfd, 5) 0) { // Allow up to 5 pending connectionsperror(Error while trying to listen);return 1;
}此示例创建了一个套接字并设置其最大待处理连接数为5。当超过5个客户端连接并等待被accept()时任何进一步的连接请求都将被拒绝直到有一个连接被accept()为止。
注意
在调用 listen() 之前必须先调用 bind()。backlog 参数的具体含义和行为可能因操作系统而异。在某些系统上它表示待处理的连接数量而在其他系统上它可能包括已被accept()但尚未由应用程序处理的连接。当 backlog 队列已满进一步的连接请求可能会被拒绝。因此为了避免这种情况服务器应该尽快处理连接。通常在 listen() 之后会进入一个循环不断地调用 accept() 来接受并处理来自客户端的连接。
总之listen() 函数是服务器套接字编程中的关键步骤使得服务器能够开始接受客户端的连接请求。
accept()
accept() 是套接字编程中的一个关键函数用于从已经处于监听模式的套接字中提取连接请求并返回一个新的套接字描述符该描述符代表与客户端之间的新连接。此函数在服务器应用程序中经常使用以处理来自客户端的连接请求。
函数原型
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);参数 sockfd: 这是一个处于监听模式的套接字描述符通常先前已经通过 socket() 创建并通过 bind() 和 listen() 函数配置。 addr: 这是一个指向struct sockaddr的指针当 accept() 调用返回时这个结构将被填充与已经接受的连接的远程端客户端的地址信息。 addrlen: 这是一个值-结果参数。在调用 accept() 之前它应该被设置为addr指向的地址结构的大小。当函数返回时addrlen 将被设置为实际的地址大小。
返回值 成功时accept() 返回一个新的套接字描述符代表与客户端的新连接。此新描述符应用于后续的所有通信例如 send() 和 recv() 调用。 失败时返回 -1并设置 errno 以指示出现的错误。
使用和注意事项 在服务器应用程序中通常在 listen() 函数调用后立即调用 accept()等待客户端的连接。 accept() 函数是阻塞的这意味着它将等待直到一个连接请求可用除非套接字已被配置为非阻塞。 返回的新套接字描述符与原始的监听套接字是独立的。应使用新的套接字描述符进行与客户端的所有通信并继续使用原始的监听套接字来接受其他连接请求。 通常服务器将为每个接受的连接启动一个新的线程或进程以并行处理多个连接。 addr 和 addrlen 参数是可选的如果我们不关心客户端的地址我们可以设置这两个参数为 NULL。
示例
#include stdio.h
#include sys/types.h
#include sys/socket.h
#include netinet/in.hint main() {int server_sock, client_sock;struct sockaddr_in server_addr, client_addr;socklen_t client_addr_len sizeof(client_addr);// Assuming server_sock has been created and set up for listening...client_sock accept(server_sock, (struct sockaddr *) client_addr, client_addr_len);if (client_sock 0) {perror(Error on accept);return 1;}// Use client_sock for communication with the client...close(client_sock);return 0;
}这个简单的例子展示了如何使用 accept() 函数从 server_sock 监听套接字中接受一个新的连接并使用 client_sock 与客户端进行通信。
connect()
connect() 是套接字编程中的一个函数主要用于客户端应用程序。该函数使客户端尝试与服务器端的指定地址建立连接。
函数原型
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);参数 sockfd: 这是我们想要与远程主机连接的套接字的描述符。 addr: 这是一个指向struct sockaddr的指针包含我们想要连接的远程主机的地址信息。在实际应用中通常使用特定于协议的结构如struct sockaddr_in对于IPv4并将其类型强制转换为struct sockaddr *。 addrlen: 这是地址结构的大小例如对于IPv4地址这通常是sizeof(struct sockaddr_in)。
返回值 成功时connect() 返回0。 失败时返回-1并设置 errno 以指示错误原因。
使用和注意事项 在客户端应用程序中我们通常首先使用 socket() 函数创建一个套接字然后使用 connect() 函数尝试与服务器连接。 如果 connect() 成功客户端可以开始使用 send() 和 recv() 或其他相关函数与服务器通信。 如果连接尝试失败connect() 将返回-1。这可能是由于多种原因例如服务器未在指定的地址和端口上运行网络故障或服务器拒绝连接。 connect() 在默认情况下是阻塞的这意味着它会等待直到连接成功或发生错误。但是我们可以将套接字设置为非阻塞模式使 connect() 立即返回并后续使用 select() 或 poll() 来等待连接完成。
示例
#include stdio.h
#include sys/types.h
#include sys/socket.h
#include netinet/in.h
#include arpa/inet.hint main() {int client_sock;struct sockaddr_in server_addr;// Assuming client_sock has been created...server_addr.sin_family AF_INET;server_addr.sin_port htons(8080); // server portinet_pton(AF_INET, 192.168.1.1, server_addr.sin_addr); // server IPif (connect(client_sock, (struct sockaddr *) server_addr, sizeof(server_addr)) 0) {perror(Error on connect);return 1;}// Now client_sock is connected and can be used to send or receive data...return 0;
}这个简单的例子展示了如何使用 connect() 函数尝试与运行在192.168.1.1的服务器上的服务连接该服务监听端口8080。
send()
send() 是套接字编程中的一个函数用于向一个已连接的套接字发送数据。它通常用于TCP套接字但也可以与其他类型的套接字一起使用。
函数原型
ssize_t send(int sockfd, const void *buf, size_t len, int flags);参数 sockfd: 这是一个已连接的套接字的描述符。 buf: 这是一个指针指向我们想要发送的数据的缓冲区。 len: 这是buf中我们想要发送的数据的字节数。 flags: 这是一个修改函数操作的标志集合。常见的标志包括 MSG_OOB (用于发送out-of-band数据) 和 MSG_NOSIGNAL (阻止在连接断开时发送SIGPIPE信号)。大多数情况下我们可以简单地将此参数设置为0。
返回值 成功时send() 返回实际发送的字节数。请注意这可能少于我们请求发送的数量。 失败时返回-1并设置 errno 以指示错误原因。
使用和注意事项 在一个已连接的TCP套接字上使用send()之前必须先成功地调用connect()对于客户端或accept()对于服务器。 TCP是一个流协议这意味着没有消息边界。连续的send()调用可能会在接收方看起来像一个连续的数据流而不是单独的消息。 如果套接字是阻塞的默认情况send()可能会阻塞直到有足够的网络缓冲区可用以发送数据。如果套接字是非阻塞的而网络缓冲区不可用则send()将立即返回-1并将errno设置为EAGAIN或EWOULDBLOCK。 在连接断开的套接字上调用send()将导致发送一个SIGPIPE信号除非设置了MSG_NOSIGNAL标志。此信号的默认行为是终止进程但可以捕获或忽略它。
示例
#include stdio.h
#include sys/types.h
#include sys/socket.hint main() {int sockfd; // assuming its already connectedconst char *message Hello, server!;ssize_t bytes_sent;bytes_sent send(sockfd, message, strlen(message), 0);if (bytes_sent 0) {perror(Error on send);return 1;}printf(Sent %zd bytes to server.\n, bytes_sent);return 0;
}这个简单的示例展示了如何使用send()函数将一条消息发送到一个已连接的服务器。
recv()
recv() 函数用于从已连接的套接字接收数据。它主要用于 TCP 套接字但也可以与其他类型的套接字一起使用。
函数原型
ssize_t recv(int sockfd, void *buf, size_t len, int flags);参数 sockfd: 这是一个已连接的套接字的描述符。 buf: 这是一个指针指向一个缓冲区用于存储接收到的数据。 len: 这是缓冲区的大小即我们期望接收的最大字节数。 flags: 这是一个修改函数操作的标志集合。一些常见的标志包括 MSG_PEEK: 查看即将到来的数据但不从队列中删除它。MSG_WAITALL: 尝试接收指定的len字节。与默认行为不同该标志会使函数等待直到请求的字节数量可用或发生某些错误。MSG_OOB: 用于接收 “out-of-band” 数据。 在大多数常规操作中我们可以简单地将此参数设置为0。
返回值 成功时recv() 返回实际接收到的字节数。如果连接已关闭返回0。 失败时返回-1并设置 errno 以指示错误原因。
使用和注意事项 在一个已连接的 TCP 套接字上使用 recv() 之前我们需要先成功调用 connect()对于客户端或 accept()对于服务器。 TCP 是一个流协议这意味着没有消息边界。连续的 recv() 调用可能会接收到之前调用的数据的剩余部分。 如果套接字是阻塞的默认情况并且没有数据可用recv() 会阻塞直到数据可用。如果套接字是非阻塞的并且没有数据可用recv() 会立即返回-1并将 errno 设置为 EAGAIN 或 EWOULDBLOCK。 当连接断开或关闭时recv() 将返回0。因此接收到0字节通常意味着对端关闭了连接。
示例
#include stdio.h
#include string.h
#include sys/types.h
#include sys/socket.hint main() {int sockfd; // assuming its already connectedchar buffer[1024];ssize_t bytes_received;bytes_received recv(sockfd, buffer, sizeof(buffer) - 1, 0);if (bytes_received 0) {perror(Error on recv);return 1;} else if (bytes_received 0) {printf(The peer has closed the connection.\n);return 0;}buffer[bytes_received] \0; // Null-terminate the stringprintf(Received: %s\n, buffer);return 0;
}这个简单的示例展示了如何使用 recv() 函数从已连接的服务器接收消息并将其打印出来。
综合案例
下面是一个简单的TCP套接字编程的例子其中包括一个服务器和一个客户端。服务器接收来自客户端的消息然后返回相同的消息。
服务器端
#include stdio.h
#include stdlib.h
#include unistd.h
#include string.h
#include arpa/inet.h#define SERVER_PORT 8080
#define BUFFER_SIZE 1024int main() {int server_sock, client_sock;struct sockaddr_in server_addr, client_addr;char buffer[BUFFER_SIZE];int bytes_read;// 创建套接字server_sock socket(AF_INET, SOCK_STREAM, 0);if (server_sock -1) {perror(Socket creation failed);exit(1);}// 设置服务器地址结构memset(server_addr, 0, sizeof(server_addr));server_addr.sin_family AF_INET;server_addr.sin_port htons(SERVER_PORT);server_addr.sin_addr.s_addr INADDR_ANY;// 绑定套接字if (bind(server_sock, (struct sockaddr*)server_addr, sizeof(server_addr)) -1) {perror(Bind failed);exit(1);}// 监听连接请求listen(server_sock, 5);printf(Server is listening on port %d...\n, SERVER_PORT);socklen_t client_addr_len sizeof(client_addr);client_sock accept(server_sock, (struct sockaddr*)client_addr, client_addr_len);if (client_sock -1) {perror(Accept failed);exit(1);}// 读取和响应客户端的消息bytes_read recv(client_sock, buffer, BUFFER_SIZE, 0);buffer[bytes_read] \0;printf(Received from client: %s\n, buffer);send(client_sock, buffer, bytes_read, 0);close(client_sock);close(server_sock);return 0;
}
客户端
#include stdio.h
#include stdlib.h
#include string.h
#include unistd.h
#include arpa/inet.h#define SERVER_PORT 8080
#define SERVER_IP 127.0.0.1
#define BUFFER_SIZE 1024int main() {int client_sock;struct sockaddr_in server_addr;char buffer[BUFFER_SIZE];// 创建套接字client_sock socket(AF_INET, SOCK_STREAM, 0);if (client_sock -1) {perror(Socket creation failed);exit(1);}// 设置服务器地址结构memset(server_addr, 0, sizeof(server_addr));server_addr.sin_family AF_INET;server_addr.sin_port htons(SERVER_PORT);inet_pton(AF_INET, SERVER_IP, server_addr.sin_addr);// 连接到服务器if (connect(client_sock, (struct sockaddr*)server_addr, sizeof(server_addr)) -1) {perror(Connect failed);exit(1);}strcpy(buffer, Hello, Server!);send(client_sock, buffer, strlen(buffer), 0);int bytes_received recv(client_sock, buffer, BUFFER_SIZE, 0);buffer[bytes_received] \0;printf(Received from server: %s\n, buffer);close(client_sock);return 0;
}
运行结果如下 上述示例中服务器创建一个套接字绑定到本地地址并监听连接。当客户端连接时服务器接收来自客户端的消息并将相同的消息发送回客户端。客户端则发送一个简单的消息并从服务器接收响应。