UDP下Socket编程
1、UDP的基础知识
UDP的特点:
(1) UDP是无连接的,即发送数据之前不需要建立连接。
(2) UDP 使用尽最大努力交付,即不保证可靠交付,同时也不使用拥塞控制。
(3) UDP 是面向报文的。UDP 没有拥塞控制,很适合多媒体通信的要求。
(4) UDP 支持一对一、一对多、多对一和多对多的交互通信。
(5) UDP 的首部开销小,只有 8 个字节。
UDP报文的组成

UDP通信的流程比较简单,因此要搭建这么一个常用的UDP通信框架比较简单,如下图:

由以上框图可以看出,客户端要发起一次请求,仅仅需要两个步骤(socket和sendto),而服务器端也仅仅需要三个步骤即可接收到来自客户端的消息(socket、bind、recvfrom)。
2、 SOCKET编程下两个关键的结构体
sockaddr:
struct sockaddr{
unsigned short sa_family; //通信协议类型族AF_xx
char sa_data[14]; //14字节协议地址,包含该socket的IP地址和端口号
};
sockaddr_in:
struct sockaddr_in{
short int sin_family; //通信协议类型族
unsigned short int sin_port; //端口号
struct in_addr sin_addr; //IP地址
unsigned char si_zero[8]; //填充0以保持与sockaddr结构的长度相同
};
两个结构体的区别与联系:
Socketaddr与socketaddr_in具有相同的长度,相同定义了地址族等参数,其实它们在一定情况下是通用的,使用Socketaddr兼容性好,使用socketaddr_in方则便数据引用。通常我们在赋值的时候用sockaddr_in,作为函数参数的时候用socket_addr.
3、UDP下的SOCKET编程涉及到的函数
socket()函数:创建一个socket
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
**参数domain:**设置网络通信的域,socket根据这个参数选择信息协议的族,如下列举所示:
Name Purpose
AF_UNIX, AF_LOCAL Local communication
AF_INET IPv4 Internet protocols //用于IPV4
AF_INET6 IPv6 Internet protocols //用于IPV6
AF_IPX IPX - Novell protocols
AF_NETLINK Kernel user interface device
AF_X25 ITU-T X.25 / ISO-8208 protocol
AF_AX25 Amateur radio AX.25 protocol
AF_ATMPVC Access to raw ATM PVCs
AF_APPLETALK AppleTalk
AF_PACKET Low level packet interface
AF_ALG Interface to kernel crypto API
对于该参数我们仅需熟记AF_INET和AF_INET6即可
参数type(只列出最重要的三个):
SOCK_STREAM Provides sequenced, reliable, two-way, connection-based byte streams. //用于TCP
SOCK_DGRAM Supports datagrams (connectionless, unreliable messages ). //用于UDP
SOCK_RAW Provides raw network protocol access. //RAW类型,用于提供原始网络访问
参数protocol:置0即可
**函数返回值:**成功返回一个非负的文件描述符,失败则返回-1
**bind()函数:**该函数就说把创建的socket,对其进行赋值/绑定ip地址喝端口号
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr* my_addr, socklen_t addrlen);
**第一个参数sockfd:**正在监听端口的套接口文件描述符,通过socket获得
**第二个参数my_addr:**需要绑定的IP和端口
**第三个参数addrlen:**my_addr的结构体的大小
**返回值:**成功:0,失败:-1
sendto()函数:
#include <sys/types.h>
#include <sys/socket.h>
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
**第一个参数sockfd:**正在监听端口的套接口文件描述符,通过socket获得
**第二个参数buf:**发送缓冲区,往往是使用者定义的数组,该数组装有要发送的数据
**第三个参数len:**发送缓冲区的大小,单位是字节
**第四个参数flags:**填0即可
第五个参数dest_addr:指向接收数据的主机地址信息的结构体,也就是该参数指定数据要发送到哪个主机哪个进程
**第六个参数addrlen:**表示第五个参数所指向内容的长度
**返回值:**成功返回发送成功的数据的长度,失败返回-1
recvfrom()函数:
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr c*src_addr, socklen_t *addrlen);
**第一个参数sockfd:**正在监听端口的套接口文件描述符,通过socket获得
**第二个参数buf:**接收缓冲区,往往是使用者定义的数组,该数组装有接收到的数据
**第三个参数len:**接收缓冲区的大小,单位是字节
**第四个参数flags:**填0即可
**第五个参数src_addr:**指向发送数据的主机地址信息的结构体,也就是我们可以从该参数获取到数据是谁发出的
**第六个参数addrlen:**表示第五个参数所指向内容的长度
**返回值:**成功:返回接收成功的数据长度,失败: -1
4、实现UDP下客户端与服务端发送指定数据
client.c:指定服务端的ip地址、发送数据的时间(秒),每秒发送512字节
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include<unistd.h>
#include <stdlib.h>
#include<arpa/inet.h>
#define SERVER_PORT 8888
#define BUFF_LEN 512
void udp_msg_sender(int fd, struct sockaddr* dst,int t)
{
int count=0;
int total=0;
socklen_t len;//定义结构体长度
struct sockaddr_in src;
while(count<t)
{
char buf[BUFF_LEN] = "TEST UDP MSG!\n";
len = sizeof(*dst);
printf("client:%s\n",buf); //打印自己发送的信息
sendto(fd, buf, BUFF_LEN, 0, dst, len);//第1个参数套节字文件描述符,第2个参数,发送缓存区,第3个发送缓存区的大小,第4个填0即可,第5个指向服务器端的主机地址信息的结构体,第6个参数表示第5个参数所指向内容的长度
memset(buf, 0, BUFF_LEN);//初始化缓存区
recvfrom(fd, buf, BUFF_LEN, 0, (struct sockaddr*)&src, &len); //接收来自server的信息
printf("server:%s\n",buf);
sleep(1); //一秒发送一次消息
count++;
}
}
/*
client:
socket-->sendto-->revcfrom-->close
*/
int main(int argc, char* argv[])
{
int time= atoi(argv[1]);
int client_fd;
int total=time*512;
struct sockaddr_in ser_addr;//服务器端的结构体
client_fd = socket(AF_INET, SOCK_DGRAM, 0);//建立客户端的套节字
if(client_fd < 0)
{
printf("create socket fail!\n");
return -1;
}
memset(&ser_addr, 0, sizeof(ser_addr));
ser_addr.sin_family = AF_INET;
ser_addr.sin_addr.s_addr = inet_addr(argv[2]);//将一个点分十进制的IP转换成一个长整数型数
// ser_addr.sin_addr.s_addr = htonl(INADDR_ANY); //在本机上可以使用该行,采用cs模式将改行注释掉,注意网络序转换
ser_addr.sin_port = htons(SERVER_PORT); //注意网络序转换
udp_msg_sender(client_fd, (struct sockaddr*)&ser_addr,time);
printf("%d s共发送%dbyte数据",time,total);
close(client_fd);
return 0;
}
server.c:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include<unistd.h>
#define SERVER_PORT 8888
#define BUFF_LEN 1024
void handle_udp_msg(int fd)
{
char buf[BUFF_LEN]; //接收缓冲区,1024字节
socklen_t len;//结构体长度
int count;
struct sockaddr_in clent_addr; //clent_addr用于记录发送方的地址信息
while(1)
{
memset(buf, 0, BUFF_LEN);//将buf 所指向的BUFF_LEN的内存单元用一个“整数” 0替换,即对buf初始化
len = sizeof(clent_addr);
/*recvform()用来接收远程主机经指定的socket 传来的数据, 并把数据存到由参数buf 指向的内存空间, BUFF_LEN为可接收数据的最大长度. 参数flags 一般设0, 其他数值定义请参考recv(). (struct sockaddr*)&clent_addr 用来指定欲传送的网络地址, 结构sockaddr 请参考bind(). len为sockaddr 的结构长度.成功则返回接收到的字符数, 失败则返回-1 */
count = recvfrom(fd, buf, BUFF_LEN, 0, (struct sockaddr*)&clent_addr, &len); //recvfrom是拥塞函数,没有数据就一直拥塞
if(count == -1)
{
printf("recieve data fail!\n");
return;
}
printf("client:%s\n",buf); //打印client发过来的信息
memset(buf, 0, BUFF_LEN);//初始化buff缓存区
sprintf(buf, "I have recieved %d bytes data!\n", count); //回复client的消息放到buf
printf("server:%s\n",buf); //打印自己发送的信息给client的消息
//sendto()用来将数据由指定的socket 传给对方主机,返回传送的字节数
sendto(fd, buf, BUFF_LEN, 0, (struct sockaddr*)&clent_addr, len); //发送信息给client,注意使用了clent_addr结构体指针
}
}
/*
server:
socket-->bind-->recvfrom-->sendto-->close
*/
int main(int argc, char* argv[1])
{
int server_fd, ret;
struct sockaddr_in ser_addr;//描述服务端的套节字结构体
server_fd = socket(AF_INET, SOCK_DGRAM, 0); //AF_INET:IPV4;SOCK_DGRAM:UDP
if(server_fd < 0)
{
printf("create socket fail!\n");
return -1;
}
memset(&ser_addr, 0, sizeof(ser_addr));//初始化服务端套节字结构体
/*
* 服务端结构体成员变量(ip、端口)赋值
* htonl():将主机的无符号长整形数转换成网络字节顺序
* htons(): 将一个无符号短整型数值转换为网络字节序,即大端模式(big-endian)
*/
ser_addr.sin_family = AF_INET;
ser_addr.sin_addr.s_addr = htonl(INADDR_ANY); //IP地址,需要进行网络序转换,INADDR_ANY:本地地址
ser_addr.sin_port = htons(SERVER_PORT); //端口号,需要网络序转换
ret = bind(server_fd, (struct sockaddr*)&ser_addr, sizeof(ser_addr));//将套节字与赋值后的套接字进行绑定
if(ret < 0)
{
printf("socket bind fail!\n");
return -1;
}
handle_udp_msg(server_fd); //处理接收到的数据
close(server_fd);
return 0;
}
查看服务端的ip地址:

开启服务端:

客户端进行连接并发送消息:

本文介绍UDP的特点及Socket编程基础知识,包括常用结构体与函数说明,并通过客户端和服务端实例展示数据交互过程。
4万+

被折叠的 条评论
为什么被折叠?



