目录
一、使用<sys/socket.h>实现UDP通信
创建与绑定套接字
创建UDP套接字的过程与创建TCP套接字相似,只是在socket()
函数中指定SOCK_DGRAM
作为套接字类型。由于UDP是无连接的协议,无需调用listen()
和accept()
函数来等待连接请求和接受连接。
创建套接字:
#include <sys/socket.h>
int sockfd = socket(AF_INET, SOCK_DGRAM, 0); // 创建UDP套接字
if (sockfd == -1) {
perror("socket");
exit(EXIT_FAILURE);
}
地址结构与绑定:
使用sockaddr_in
结构填写本地IP地址和端口号,然后调用bind()
函数将其绑定到创建的UDP套接字上:
#include <netinet/in.h>
#include <arpa/inet.h>
struct sockaddr_in local_addr;
memset(&local_addr, 0, sizeof(local_addr));
local_addr.sin_family = AF_INET;
local_addr.sin_port = htons(UDP_PORT); // 替换为实际使用的端口号
local_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 绑定到所有本地IPv4地址
if (bind(sockfd, (struct sockaddr *)&local_addr, sizeof(local_addr)) == -1) {
perror("bind");
exit(EXIT_FAILURE);
}
数据发送:sendto()函数
sendto()
函数用于向指定的目标地址直接发送UDP数据报,无需预先建立连接。由于UDP是非连接性的,数据报可能会丢失、重复或乱序到达。
#include <sys/socket.h>
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t dest_len);
// 示例
char message[] = "Hello, UDP!";
struct sockaddr_in remote_addr;
// ... 填充remote_addr结构,包括目标IP地址和端口号 ...
ssize_t bytes_sent = sendto(sockfd, message, strlen(message), 0,
(struct sockaddr *)&remote_addr, sizeof(remote_addr));
if (bytes_sent == -1) {
perror("sendto");
} else {
printf("Sent %ld bytes to %s:%hu\n", bytes_sent,
inet_ntoa(remote_addr.sin_addr), ntohs(remote_addr.sin_port));
}
强调点:
- 非连接性:每次发送数据都需要指定目标地址,无需建立连接,也不保证数据一定能到达目的地。
- 可能的数据丢失:由于UDP不提供可靠传输保证,数据报在传输过程中可能会因网络拥塞、错误等原因丢失,且不进行重传。
数据接收:recvfrom()函数
recvfrom()
函数用于从任意远程地址接收UDP数据报,并同时返回发送方的地址信息:
#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
// 示例
char buffer[MAX_DATAGRAM_SIZE];
struct sockaddr_in sender_addr;
socklen_t sender_len = sizeof(sender_addr);
ssize_t bytes_received = recvfrom(sockfd, buffer, MAX_DATAGRAM_SIZE, 0,
(struct sockaddr *)&sender_addr, &sender_len);
if (bytes_received == -1) {
perror("recvfrom");
} else {
printf("Received %ld bytes from %s:%hu\n", bytes_received,
inet_ntoa(sender_addr.sin_addr), ntohs(sender_addr.sin_port));
// ... 处理接收到的数据 ...
}
讨论:UDP数据报的最大尺寸限制
UDP数据报的大小受到最大传输单元(MTU)和IP头部的影响。一般来说,以太网的MTU为1500字节,IPv4头部最小为20字节,UDP头部为8字节。因此,理论上一个UDP数据报的最大尺寸约为1472字节(1500 - 20 - 8)。但实际上,考虑到中间路由器可能设置更低的MTU(如路径MTU发现机制),以及为防止IP分片,建议将UDP数据报大小限制在512字节或更低。
可以通过查询sysctl net.ipv4.tcp_mtu_probing
或net.ipv6.tcp_mtu_probing
来了解系统对MTU探测的设置,以及通过setsockopt()
函数设置SO_RCVBUF
和SO_SNDBUF
选项来调整接收和发送缓冲区大小,以适应特定应用的需求。
关闭套接字:close()函数
关闭UDP套接字与关闭TCP套接字相同,均使用close()
函数:
#include <unistd.h>
close(sockfd); // 关闭UDP套接字
通过上述步骤,可以使用C语言的<sys/socket.h>
库实现基本的UDP通信。需要注意UDP的非连接性、可能的数据丢失以及数据报尺寸限制等特点,在编写代码时进行相应的错误处理和性能优化。
二、C语言实战示例
简单TCP服务器示例
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#define PORT 8000
#define MAX_CLIENT_MSG_LEN 256
void error(const char *msg) {
perror(msg);
exit(1);
}
int main() {
int server_sockfd, client_sockfd;
struct sockaddr_in server_addr, client_addr;
socklen_t client_len;
char client_message[MAX_CLIENT_MSG_LEN];
// 创建TCP套接字
server_sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (server_sockfd < 0)
error("ERROR opening socket");
// 设置服务器地址结构
bzero((char *) &server_addr, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
server_addr.sin_addr.s_addr = INADDR_ANY;
// 绑定套接字到本地地址
if (bind(server_sockfd, (struct sockaddr *) &server_addr, sizeof(server_addr)) < 0)
error("ERROR on binding");
// 开始监听连接请求
listen(server_sockfd, 5);
while (1) {
// 接受客户端连接
client_len = sizeof(client_addr);
client_sockfd = accept(server_sockfd, (struct sockaddr *) &client_addr, &client_len);
if (client_sockfd < 0)
error("ERROR on accept");
printf("Accepted connection from %s:%d\n",
inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
// 接收客户端消息
memset(client_message, 0, MAX_CLIENT_MSG_LEN);
ssize_t bytes_received = recv(client_sockfd, client_message, MAX_CLIENT_MSG_LEN - 1, 0);
if (bytes_received > 0) {
client_message[bytes_received] = '\0';
printf("Received message from client: %s\n", client_message);
}
// 回复客户端
char server_response[] = "Server received your message.";
send(client_sockfd, server_response, strlen(server_response), 0);
// 关闭与客户端的连接
close(client_sockfd);
}
// 关闭服务器套接字
close(server_sockfd);
return 0;
}
简单TCP客户端示例
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#define SERVER_ADDR "127.0.0.1"
#define SERVER_PORT 8000
#define MAX_CLIENT_MSG_LEN 256
void error(const char *msg) {
perror(msg);
exit(1);
}
int main() {
int client_sockfd;
struct sockaddr_in server_addr;
char client_message[MAX_CLIENT_MSG_LEN], server_response[MAX_CLIENT_MSG_LEN];
// 创建TCP套接字
client_sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (client_sockfd < 0)
error("ERROR opening socket");
// 设置服务器地址结构
bzero((char *) &server_addr, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVER_PORT);
if (inet_aton(SERVER_ADDR, &server_addr.sin_addr) == 0)
error("ERROR on inet_aton()");
// 连接到服务器
if (connect(client_sockfd, (struct sockaddr *) &server_addr, sizeof(server_addr)) < 0)
error("ERROR connecting");
printf("Connected to server at %s:%d\n", SERVER_ADDR, SERVER_PORT);
// 发送客户端消息
printf("Enter a message to send to the server: ");
fgets(client_message, MAX_CLIENT_MSG_LEN, stdin);
send(client_sockfd, client_message, strlen(client_message), 0);
// 接收服务器回复
memset(server_response, 0, MAX_CLIENT_MSG_LEN);
ssize_t bytes_received = recv(client_sockfd, server_response, MAX_CLIENT_MSG_LEN - 1, 0);
if (bytes_received > 0) {
server_response[bytes_received] = '\0';
printf("Received message from server: %s\n", server_response);
}
// 关闭客户端套接字
close(client_sockfd);
return 0;
}
UDP发送端示例
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#define SERVER_ADDR "127.0.0.1"
#define SERVER_PORT ½000
void error(const char *msg) {
perror(msg);
exit(1);
}
int main() {
int client_sockfd;
struct sockaddr_in server_addr;
char client_message[] = "Hello, UDP!";
// 创建UDP套接字
client_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (client_sockfd < 0)
error("ERROR opening socket");
// 设置服务器地址结构
bzero((char *) &server_addr, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVER_PORT);
if (inet_aton(SERVER_ADDR, &server_addr.sin_addr) == 0)
error("ERROR on inet_aton()");
// 发送UDP数据报
if (sendto(client_sockfd, client_message, strlen(client_message), 0,
(struct sockaddr *) &server_addr, sizeof(server_addr)) < 0)
error("ERROR sending datagram");
printf("Sent message to server: %s\n", client_message);
// 关闭客户端套接字
close(client_sockfd);
return 0;
}
UDP接收端示例
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#define PORT 5000
#define MAX_DATAGRAM_SIZE 256
void error(const char *msg) {
perror(msg);
exit(1);
}
int main() {
int server_sockfd;
struct sockaddr_in server_addr, client_addr;
socklen_t client_len;
char client_message[MAX_DATAGRAM_SIZE];
// 创建UDP套接字
server_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (server_sockfd < 0)
error("ERROR opening socket");
// 设置服务器地址结构并绑定套接字
bzero((char *) &server_addr, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
server_addr.sin_addr.s_addr = INADDR_ANY;
if (bind(server_sockfd, (struct sockaddr *) &server_addr, sizeof(server_addr)) < 0)
error("ERROR on binding");
printf("Listening for datagrams on port %d...\n", PORT);
while (1) {
// 接收UDP数据报
client_len = sizeof(client_addr);
ssize_t bytes_received = recvfrom(server_sockfd, client_message, MAX_DATAGRAM_SIZE - 1, 0,
(struct sockaddr *) &client_addr, &client_len);
if (bytes_received > 0) {
client_message[bytes_received] = '\0';
printf("Received message from %s:%d: %s\n",
inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port), client_message);
}
}
// 关闭服务器套接字
close(server_sockfd);
return 0;
}
以上代码分别展示了简单的TCP服务器和客户端,以及UDP发送端和接收端的实现。这些示例展示了如何使用<sys/socket.h>
库中的函数进行实际的网络通信。请注意,在实际应用中,应添加适当的错误处理和资源管理代码。