udp 网络通信
udp 服务端与客户端程序
udp 通信中使用的
socket函数
- recvfrom
- recv
- sendto
简要学习一下 udp 基础通信过程,实现一个服务端和客户端通信
udp 服务端
工作流程
- 使用
socket指明SOCK_DGRAM创建 基于udp的socket - 构建
sockaddr指明 监听的主机和端口 - 使用
bind函数绑定socket到sockaddr(其实也可以不用bind) - 使用
recvfrom函数接收数据,并获取发送端的信息 - 使用
sendto函数回应数据
udp客户端
工作流程
- 使用
socket指明SOCK_DGRAM创建 基于udp的socket - 构建
sockaddr指明 要连接的 服务端 用于sendto - 客户端不需要绑定端口,发送数据时操作系统自动分配
- 使用
sendto向服务端发送数据 - 使用 recvfrom 接受服务端响应数据
相关 api 函数可以使用 man 命令查看 linux man pages
使用过程中的个人理解
- 接收数据
recvfrom函数有5个参数参数1 为套接字
fd,参数2 为 接收数据缓冲区,参数3为 允许接收数据的最大长度参数4 为
sockaddr指针类型,用于向外 传递 发送端的socket信息,用于sendto参数5 为 指明 参数4 用来保存
socket信息的内存区域大小 (此参数既做输入,也做输出)
在 recvfrom 函数中,如果需要获取发送端信息,则需要指明 参数4,同时参数5需要特别注意(不要传入未赋初值得变量)
另外还有一个recv 函数,则是相当于 recvfrom(fd, buf, buf_size, flags, NULL, NULL) ,即不关注接收到的数据的来源
- 发送数据
由于 udp 数据传送之前不需要建立连接,所以在发送数据时需要使用 sendto 来指明目的主机与端口
也可以使用 connect和 send 发送数据报
udp socket使用connect后,会导致内核记录对端socket地址,后续使用writesend进行写操作,会发送到连接到的socket,使用recvread也只能读到绑定连接socket发来的数据,不再可以接收任意其他socket发送来的数据使用
connect后发送多次数据,相比 每次调用sendto效率会提高再次调用
connect会重新绑定
服务端相关程序
服务端接收客户端发来的信息,计算长度,并回送给客户端相关信息
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define PORT 8888
#define BUF_SIZE 1024
int main(int argc, char** argv)
{
struct sockaddr_in server; // 定义一个 套接字地址 结构体 用于保存服务端信息
// 创建 udp socket
int serverSocket = socket(AF_INET, SOCK_DGRAM, 0);
// 结构体清 0
memset(&server, 0, sizeof(struct sockaddr_in));
// 设置相关信息
server.sin_family = AF_INET; // ipv4
server.sin_addr.s_addr = htonl(INADDR_ANY); // 0.0.0.0 任意网卡地址
server.sin_port = htons(PORT);
// 绑定端口与主机
bind(serverSocket, (struct sockaddr*) & server, sizeof(server));
char msg[BUF_SIZE] = "hello world";
/**
* 用于保存 接收来源的套接字信息
* (其实也可以不用定义 client,使用上面的 server 即可,因为 server 绑定之后就没用了)
*/
struct sockaddr_in client;
while (1) {
/**
* 有兴趣的话可以修改下面一行为 int client_addr_len;
* 在我的测试中,不设初值,只有第一次接收数据拿到的发送端信息 会出错
* 这应该和编译器,运行堆栈相关
* 不过可以学到的是,内核在将 信息写入 client 结构体中时
会根据 client_addr_len 进行相应处理
client_addr_len 可以设置大一些,但是过小会出现问题,
比如 设置 client_addr_len = 0 会导致一直拿不到 client 相关信息
*/
int client_addr_len = sizeof(client);
/**
* recvfrom 函数成功执行,则会将 数据源的 信息 写入 client 结构体中
*/
int num = recvfrom(serverSocket, msg, BUF_SIZE, 0, (struct sockaddr*) &client, &client_addr_len);
if (-1 == num) {
perror("recv error");
exit(-1);
}
// recv 写入 buf 不会自动添加 '\0' 这里自己处理 字符串截止
// 当然这里有潜在的 num 越界问题
msg[num] = 0;
printf("from.addr = %s, from.port = %d, from.msg = %s\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port), msg);
/**
* 使用 sendto 函数指明 发送地址
* client, client_addr_len 基于 上面的 recvmsg 由内核向应用程序传递信息
*/
sprintf(msg, "bytes: %d", num);
int err = sendto(serverSocket, msg, strlen(msg), 0, (struct sockaddr*) &client, client_addr_len);
if (-1 == err) {
perror("back error");
exit(-1);
}
}
return 0;
}
客户端相关程序
客户端接收输入,发送给 服务端,并接收服务端回应
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h> // 以太网
#include <arpa/inet.h>
#define PORT 8888
#define BUF_SIZE 1024
#define SERVER_ADDR "127.0.0.1"
int main()
{
struct sockaddr_in aimServer;
aimServer.sin_family = AF_INET; // ipv4
aimServer.sin_addr.s_addr = inet_addr(SERVER_ADDR); // 主机地址
aimServer.sin_port = htons(PORT); // 端口号
// 创建一个 udp 套接字
int sock = socket(AF_INET, SOCK_DGRAM, 0);
char msg[BUF_SIZE];
while (1) {
// 接收输入 会带有 \n
fgets(msg, BUF_SIZE, stdin);
// 发送数据
int num = sendto(sock, msg, strlen(msg), 0, (struct sockaddr*) &aimServer, sizeof(aimServer));
if (-1 == num) {
perror("udp send fail");
exit(-1);
}
else {
/**
* 这里没有新建 sockaddr_in 结构体,而是利用了 aimServer
*/
int addr_len = sizeof(aimServer);
// 接受数据 将服务端的 地址信息填充到 aimServer 结构体
num = recvfrom(sock, msg, BUF_SIZE, 0, (struct sockaddr*) &aimServer, &addr_len);
if (-1 == num) {
perror("udp recv fail");
exit(-1);
}
// 字符串 终止符
msg[num] = 0;
printf("from.addr = %s, from.port = %d, from.msg = %s\n", inet_ntoa(aimServer.sin_addr), ntohs(aimServer.sin_port), msg);
}
// 通信终止 标志
if (0 == strcmp(msg, "\\q")) {
break;
}
}
return 0;
}
使用 connect 函数进行连接,之后使用 recv send write read 进行数据传送
代码片段
// 绑定对端 socket
// 绑定后 发送
connect(sock, (struct sockaddr*) &aimServer, sizeof(aimServer));
char msg[BUF_SIZE];
while (1) {
// 接收输入 会带有 \n
fgets(msg, BUF_SIZE, stdin);
send(sock, msg, strlen(msg), 0);
// write(sock, msg, strlen(msg));
// int num = recv(sock, msg, BUF_SIZE, 0);
int num = read(sock, msg, BUF_SIZE);
msg[num] = 0;
printf("recv %s\n", msg);
}
对于 udp 的通信而言,因为并不需要建立连接,所以只需要知道目标主机与端口,我们就可以发送,当然可能会失败(如果目的主机的端口并未开放或者监听)
对于 tcp 而言,通信的前提是需要建立连接,所以需要一个 server bind port ,然后 listen,accept,客户端则只需要建立一个 socket,进程会向操作系统申请 可用端口,用于建立连接进行通信
在 tcp 中,客户端请求连接,会将 socket 捆绑到一个 端口上
在 udp中,我们只需要利用 socket 发送一个 数据,那么操作系统就会绑定一个端口到此 socket,前面说的一点,udp 服务端其实不必调用 bind,bind的目的在于让 socket 绑定到一个 port,下面我们使用 sendto来获取一个绑定,用于通信
基本过程是服务端创建socket 后,随便发送一个 udp数据,这时 socket 会绑定到 port,我们拿到这个 port,使用 netcat 工具测试
还是先用 c 语言写一个 server ,用于上面的验证测试
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define PORT 8888
#define BUF_SIZE 1024
int main()
{
struct sockaddr_in server;
int serverSocket = socket(AF_INET, SOCK_DGRAM, 0);
// 结构体清 0
memset(&server, 0, sizeof(struct sockaddr_in));
server.sin_family = AF_INET;
server.sin_addr.s_addr = htonl(INADDR_ANY); // 0.0.0.0 任意网卡地址
server.sin_port = htons(PORT);
// 使用 sendto 使得操作系统为 serverSocket 绑定到一个 端口
sendto(serverSocket, "", 0, 0, (struct sockaddr*) &server, sizeof(server));
int num = sizeof(server);
int error = getsockname(serverSocket, (struct sockaddr*) & server, &num);
if (-1 == error) {
perror("get socket info error");
exit(-1);
}
// 输出 port 使用 netcat 发送数据
printf("%d\n", ntohs(server.sin_port));
char msg[BUF_SIZE];
// 死循环 只接收数据
while (1) {
int len = sizeof(serverSocket);
int num = recvfrom(serverSocket, msg, BUF_SIZE, 0, (struct sockaddr*) &server, &len);
msg[num] = 0;
printf("from.addr = %s, from.port = %d, from.msg = %s\n", inet_ntoa(server.sin_addr), ntohs(server.sin_port), msg);
}
return 0;
}
虽然没有显式绑定 port 让其监听,但是对于 udp 来说,只要有目标端口,我们就可以数据交互


这同样说明每一个 udp socket 既可做服务端,也可做客户端
另外一些就是 udp 基于报文,所以服务端recvfrom 顺序获取的结果可能来自不同的发送端,因为只要目标是 server,则被server接收了,recvfrom都会被调用成功。
可以使用 wireshrak 工具查看udp 通信
问题解决 的一些参考链接
http://www.360doc.com/content/15/0429/15/12697_466824081.shtml

浙公网安备 33010602011771号