136-基于 UDP 协议的通信
来源:互联网 发布:知党章党规,系列讲话 编辑:程序博客网 时间:2024/06/06 09:51
前面我们所实现的通信程序,是基于 TCP 协议的,有连接的方式。也就是在创建 socket 的时候,socket 函数第二个参数总是指定为 SOCK_STREAM,第三个 protocol 参数指定为 0,即默认使用 TCP 协议。
有连接,是指像打电话一样,首先拨通电话(connect),对方接听(accept),然后各自拿着自己的话筒(socket)开始通信。
而无连接不一样,事先不需要建立连接,它类似于发信息。如果要使用无连接的方式通信,在创建 socket 的时候,把 socket 类型指定为 SOCK_DGRAM,同时第三个参数 protocol 参数指定为 0,表示使用默认的 UDP 协议。
有关 TCP 和 UDP 协议的细节,将在 Linux 网络编程学习笔记中详细讨论,所以这里我们只关心怎么编程就行了。
1. 使用 UDP 协议通信的步骤
同样的,UDP 协议通信也需要有一个服务器程序。由于 UDP 协议不需要建立连接,这意味着就不需要被动 socket,因此函数 listen 自然也就派不上用场了。
另一方面,无论是 TCP 通信还是 UDP 通信,socket 都要与套接字地址进行绑定。所以对于服务器来说,如果不事先绑定套接字地址,客户端将找不到它。
- 服务器端程序编写步骤
(1) 创建 socket (socket 函数)
(2) 将 socket 与套接字地址绑定(bind 函数)
(3) 使用 recvfrom 函数接收数据
(4) 使用 sendto 函数发数据
(5) 关闭套接字
- 客户端程序编写步骤
(1) 创建 socket(socket 函数)
(2) 使用 sendto 函数发数据
(3) 使用 recvfrom 函数接收数据
(5) 关闭套接字
在客户端中,我们并没有主动的去 bind 套接字地址,当然你完全可以主动绑定一个,但是这样并没有什么意义,如果在同一台机器中启动多个客户端就肯定会出错。所以,当使用 sendto 函数后,系统为自动为客户端 bind 一个套接字地址(该地址的端口号随机分配)。
2. recvfrom 和 sendto
在 UDP 编程中,最关键的就是这两个函数了,sendto 函数可以指定发送端的套接字地址,recvfrom 函数可以返回对端的套接字地址。这两个函数原型如下:
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
上面的函数中,除了 flags 参数我们不懂,其它参数都有简单。目前我们只需要将 flags 参数设置为 0 就行了。
这两个函数在阻塞 IO 下,都可能会阻塞。比如 recvfrom 会一直阻塞直到有数据到来。sendto 函数也一样,如果缓冲区满的情况下,会阻塞。
3. 大写转换服务器程序的 UDP 版本
这里,我们将上一节的大写转换服务器程序改用 UDP 协议来实现。
3.1 serv 服务器程序
// serv.c#include <stdio.h>#include <stdlib.h>#include <arpa/inet.h>#define ERR_EXIT(msg) do { perror(msg); exit(1); } while(0)void upper(char* buf) { char* p = buf; while(*p) { *p = toupper(*p); ++p; }}int main() { struct sockaddr_in servaddr, cliaddr; int sockfd, clientfd, ret, n; socklen_t cliaddrlen; char buf[64]; // 1. create sockaddr puts("1. create sockaddr"); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(8080); // 2. create socket puts("2. create socket"); // 注意第 2 个参数已经改成了 SOCK_DGRAM sockfd = socket(AF_INET, SOCK_DGRAM, 0); if (sockfd < 0) ERR_EXIT("socket"); // 3. bind sockaddr puts("3. bind sockaddr"); ret = bind(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)); if (ret < 0) ERR_EXIT("bind"); while(1) { // 注意 recvfrom 的最后一个参数 addrlen 既是输入参数,也是输出参数,所以这里必须要传一个值给它。 cliaddrlen = sizeof(cliaddr); n = recvfrom(sockfd, buf, 63, 0, (struct sockaddr*)&cliaddr, &cliaddrlen); // 打印对端的 ip 地址和端口号 printf("%s:%d come in\n", inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port)); buf[n] = 0; puts(buf); upper(buf); // 将转换后的数据发送给对端 sendto(sockfd, buf, n, 0, (struct sockaddr*)&cliaddr, cliaddrlen); } close(sockfd); return 0;}
3.2 cli 客户端程序
#include <unistd.h>#include <stdio.h>#include <stdlib.h>#include <string.h>#include <arpa/inet.h>#define ERR_EXIT(msg) do { perror(msg); exit(1); } while(0)int main() { int sockfd, ret, n; char buf[64]; struct sockaddr_in servaddr; struct sockaddr_in cliaddr; socklen_t servaddrlen, cliaddrlen; memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); servaddr.sin_port = htons(8080); // 注意第 2 个参数已经改成了 SOCK_DGRAM sockfd = socket(AF_INET, SOCK_DGRAM, 0); if (sockfd < 0) ERR_EXIT("socket"); while(1) { scanf("%s", buf); if (buf[0] == 'q') break; // 将数据发送给服务器 sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr*)&servaddr, sizeof(servaddr)); // recvfrom 最后两个参数可以为空,表示我们并不关心对端的套接字地址(因为我们本来就知道……) n = recvfrom(sockfd, buf, 63, 0, NULL, NULL); buf[n] = 0; puts(buf); } close(sockfd); return 0;}
3.3 编译和运行
$ gcc serv.c -o serv$ gcc cli.c -o cli
图1 基于 UDP 协议的大写转换服务器
4. 总结
- 掌握基于 UDP 协议的基本编程方法
- 136-基于 UDP 协议的通信
- 基于QT的UDP协议的通信
- java基于UDP协议的通信
- 基于UDP协议的socket通信
- 基于UDP协议的Socket通信
- 基于QT的UDP协议的通信小程序
- 基于LINUX系统的SOCKET通信,使用UDP协议.
- android手机与服务器基于UDP协议的通信
- 基于Android系统的socket UDP协议通信
- 基于UDP协议的P2P模式即时通信软件1.0
- vs2015基于UDP协议的简单通信例程
- 基于STM32的无协议栈实现UDP通信
- RDP(Reliable--UDP)Library 一个基于UDP/IP协议的可靠网络通信库
- 基于UDP的DatagramSocket通信
- 基于UDP的socket通信
- 基于udp的传输通信
- 基于UDP的Socket通信
- 基于UDP的通讯协议
- tortoiseSVN使用遇到的问题
- Java集合之-Map集合
- 内核线程和用户线程
- 在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
- 中文维基语料训练获取
- 136-基于 UDP 协议的通信
- 安装gensim
- Python学习笔记(6)——列表、元组、字典
- 浅析Android四大组件之一:ContentProvider 内容提供者 源码
- Android studio 插件 ADB WIFI
- bzoj1492(cdq分治+凸包+优化dp)
- 欢迎使用CSDN-markdown编辑器
- (转)Android:控件WebView显示网页
- P3366 【模板】最小生成树