CS模型UDP流程:
服务器: socket->bind->recvfrom->sendto->close
客户端: socket->sendto->recvfrom->close
代码说明:
1 未使用更先进的服务器模型,用基础单线程阻塞(recvfrom)作为演示
2 服务器使用while循环接收客户端消息
3 适用于同一网段的不同主机间通信
4 使用SO_REUSEADDR选项消除了测试环境下服务器短时间内无限重启的顾虑
5 运行路径:客户端发射一条消息->服务器收到消息后回射给客户端一条消息->客户端打印出消息
完成一次基础通信
6 先运行服务器 再运行客户端
#define _GNU_SOURCE
#include <stdio.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>
// 服务器地址
#define SERVER_IP "192.168.142.132"
// 服务器端口
#define SERVER_PORT 55550
// 服务器代码
int main()
{
int server_sockfd;
struct sockaddr_in server_sockaddr, client_sockaddr;
memset(&server_sockaddr, 0, sizeof(server_sockaddr));
memset(&client_sockaddr, 0, sizeof(client_sockaddr));
socklen_t client_sockaddr_len = sizeof(client_sockaddr);
ssize_t send_bytes, recv_bytes;
char send_buf[1024] = "server say : give me money.";
char recv_buf[1024] = {0};
// 创建一个 ipv4 udp 的socket
server_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (server_sockfd == -1)
{
perror("socket");
return 1;
}
// 启用socket选项,避免测试环境中Address already in use
// 参数1:已创建的socket 参数2:指定级别 SOL_SOCKET表示适用于套接字本身而非特定协议
// 参数3:表示地址及端口可重用 参数4:非0表示启用SO_REUSEADDR 参数5:int的大小4字节
// 参数4 和 参数5 的设计目的主要是考虑到未来接口的兼容性
int optval = 1;
setsockopt(server_sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));
// ipv4主机网络序转换
inet_pton(AF_INET, SERVER_IP, &server_sockaddr.sin_addr.s_addr);
// 端口主机网络序转换
server_sockaddr.sin_port = htons(SERVER_PORT);
// 指定地址族为ipv4
server_sockaddr.sin_family = AF_INET;
// 绑定
if ((bind(server_sockfd, (struct sockaddr *)&server_sockaddr, sizeof(server_sockaddr))) == -1)
{
perror("bind");
close(server_sockfd);
return 1;
}
// 此时可以通过ss -tuln 命令观察到55550 端口的状态为UNCONN(未连接)
printf("server start...\n");
while (1)
{
// recvfrom函数返回干了3件事
// 1 接收数据存到recv_buf 2 将客户端的地址信息存到client_sockaddr 3更新client_sockaddr_len的实际长度
recv_bytes = recvfrom(server_sockfd, recv_buf, sizeof(recv_buf), 0, (struct sockaddr *)&client_sockaddr, &client_sockaddr_len);
if (recv_bytes == -1)
{
perror("recvfrom");
continue;
}
printf("%s\n", recv_buf);
memset(recv_buf, 0, sizeof(recv_buf));
// udp是无连接的要向哪发送呢?就是recvfrom返回的客户端地址信息client_sockaddr
send_bytes = sendto(server_sockfd, send_buf, strlen(send_buf), 0, (struct sockaddr *)&client_sockaddr, client_sockaddr_len);
if (send_bytes == -1)
{
perror("sendto");
continue;
}
printf("%s\n", send_buf);
}
// 关闭socket,socket是一个fd所以可以用close关闭
if (close(server_sockfd) == -1)
{
perror("close");
return 1;
}
printf("server close...\n");
return 0;
}
#define _GNU_SOURCE
#include <stdio.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>
// 服务器地址
#define SERVER_IP "192.168.142.132"
// 服务器端口
#define SERVER_PORT 55550
// 客户端
int main()
{
int client_sockfd;
struct sockaddr_in server_sockaddr;
memset(&server_sockaddr, 0, sizeof(server_sockaddr));
socklen_t server_sockaddr_len = sizeof(server_sockaddr);
ssize_t send_bytes, recv_bytes;
char send_buf[1024] = "client say : How can I help you today ?";
char recv_buf[1024] = {0};
// 建立一个ipv4 udp 的套接字用于客户端
client_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (client_sockfd == -1)
{
perror("socket");
return 1;
}
printf("client start...\n");
// 主机网络序转换ip地址并存入server_sockaddr.sin_addr.s_addr
inet_pton(AF_INET, SERVER_IP, &server_sockaddr.sin_addr.s_addr);
// 主机网络序转换端口并存入server_sockaddr.sin_port
server_sockaddr.sin_port = htons(SERVER_PORT);
// 指定地址族为ipv4
server_sockaddr.sin_family = AF_INET;
// 向服务器发送数据,其中server_sockaddr包含预设的服务器地址信息
send_bytes = sendto(client_sockfd, send_buf, strlen(send_buf), 0, (struct sockaddr *)&server_sockaddr, server_sockaddr_len);
if (send_bytes == -1)
{
perror("sendto");
close(client_sockfd);
return 1;
}
// 打印出来发送内容
printf("%s\n", send_buf);
// 接收服务器处理后的数据,server_sockaddr表示谁给你发的消息,在此案例中与sendto的server_sockaddr内容相同
recv_bytes = recvfrom(client_sockfd, recv_buf, sizeof(recv_buf), 0, (struct sockaddr *)&server_sockaddr, &server_sockaddr_len);
if (recv_bytes == -1)
{
perror("recvfrom");
close(client_sockfd);
return 1;
}
// 打印消息
printf("%s\n", recv_buf);
// 关闭客户端socket
if (close(client_sockfd) == -1)
{
perror("close");
return 1;
}
printf("client close...\n");
return 0;
} // 检查一下客户端