UDP 是User Datagram Protocol的简称, 中文名是用户数据报协议,是OSI(Open System Interconnection,开放式系统互联) 参考模型中一种无连接的传输层协议。
介绍
使用ss -u
查看 UDP连接,使用ss -ua
查看所有的包括未连接状态的UDP连接。
使用ss -t
查看 TCP连接,使用ss -ta
查看所有的包括监听状态的TCP连接。
使用ss -s
列出所有的socket连接。
使用cat /proc/net/sockstat
查看socket状态:
操作
创建socket
使用socket函数,其原型是int socket(int domain, int type, int protocol);
- domain参数(注:下面的AF换成PF效果一样):
- AF_UNIX/AF_LOCAL/AF_FILE: 本地通信
- AF_INET: 网络通信 ipv4
- AF_INET6: 网络通信 ipv6
- type参数为通信类型
- SOCK_DGRAM : UDP
- SOCK_STREAM: TCP
- protocol参数基本废弃,通常写0
1
2
3
4
5
6
7
8
9
#include <stdio.h>
#include <sys/socket.h>
int main(int argc,char *argv[]){
int fd = socket(AF_INET, SOCK_DGRAM , 0);
printf("socket's fd is %d!\n",fd);
while(1);
return 0;
}
绑定socket-服务端专用
使用bind函数绑定创建的socket和地址信息。原型是int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
- sockfd参数是socket的句柄,也就是socket函数的返回值;
- addr参数需要用struct sockaddr_in(ipv4下)或者struct sockaddr_in(unix socket下)中转一下。这里介绍struct sockaddr_in的成员:
- sin_family,一般写AF_INET;
- sin_port端口,一般写
htons(port)
,也就是需要用htons统一大小端再发出; - sin_addr是IP地址,它是一个联合体,一般写
addr.sin_addr.s_addr = inet_addr(p);
,其中p是地址的字符串,需要转换成整形,或者写addr.sin_addr.s_addr = htonl(INADDR_ANY);
,其中INADDR_ANY表示所有的本机IP,宏定义是0。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main(int argc,char *argv[]){
int sockfd;
struct sockaddr_in servaddr;//socket的地址端口,ipv4用这个结构体
sockfd = socket(AF_INET, SOCK_DGRAM , 0);
printf("socket's fd is %d!\n",sockfd);
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);//INADDR_ANY是0,代表*或者"0.0.0.0",指所有本机ip
servaddr.sin_port = htons(50001); //端口,存储中有大小端存储,这里转换为统一顺序
bind(sockfd, (struct sockaddr *)&servaddr, sizeof(struct sockaddr_in));//绑定
while(1);
return 0;
}
可以看到绑定后即可在ss-ua
的未连接列表中找到socket信息了,这时已经可以使用客户端进行连接。
接收数据并获取socket
使用recvfrom函数接收来自UDP连接的数据,并记录另一端的socket信息,原型是ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
该函数是recv的增强版,多了参数src_addr和addrlen,如果不需要记录来源的socket地址信息(比如只接收不发送),这两个参数就填NULL;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main(int argc,char *argv[]){
int sockfd;
struct sockaddr_in servaddr;//socket的地址端口,ipv4用这个结构体
sockfd = socket(AF_INET, SOCK_DGRAM , 0);
printf("socket's fd is %d!\n",sockfd);
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);//INADDR_ANY是0,代表*或者"0.0.0.0",指所有本机ip
servaddr.sin_port = htons(50001); //端口,存储中有大小端存储,这里转换为统一顺序
bind(sockfd, (struct sockaddr *)&servaddr, sizeof(struct sockaddr_in));//绑定
//接收
char recvline[64];
recvfrom(sockfd,recvline,sizeof(recvline),0, NULL, NULL);//不记录来源信息
printf("end!\n");
close(sockfd);
return 0;
}
执行后发现程序阻塞在recvfrom函数这里。
给目标socket发送数据
给目标发送数据使用sendto函数,原型是ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);
该函数是send的增强版,多了参数dest_addr和addrlen,这里需要填写目标的信息;
例程
server.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main(int argc,char *argv[]){
int sockfd;
struct sockaddr_in servaddr;//socket的地址端口,ipv4用这个结构体
sockfd = socket(AF_INET, SOCK_DGRAM , 0);
printf("socket's fd is %d!\n",sockfd);
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);//INADDR_ANY是0,代表*或者"0.0.0.0",指所有本机ip
servaddr.sin_port = htons(50002); //端口,存储中有大小端存储,这里转换为统一顺序
bind(sockfd, (struct sockaddr *)&servaddr, sizeof(struct sockaddr_in));//绑定
//接收
char recvline[64];
recvfrom(sockfd,recvline,sizeof(recvline),0, NULL, NULL);//不记录来源信息
printf("%s\n", recvline);
printf("end!\n");
close(sockfd);
return 0;
}
client.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main(int argc,char *argv[]){
int sockfd;
struct sockaddr_in servaddr;//socket的地址端口,ipv4用这个结构体
if(argc < 3){
printf("Please input: ./client <ip> <port>\n");
exit(1);
}
sockfd = socket(AF_INET, SOCK_DGRAM , 0);
printf("socket's fd is %d!\n",sockfd);
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = inet_addr(argv[1]);
servaddr.sin_port = htons(atoi(argv[2])); //端口,存储中有大小端存储,这里转换为统一顺序
//发送
char sendline[64] = "hello world";
sendto(sockfd, sendline, sizeof(sendline), 0, (struct sockaddr *)&servaddr, sizeof(struct sockaddr_in));
printf("send %s ok!\n",sendline);
printf("end!\n");
close(sockfd);
return 0;
}
-
先执行服务端程序,如果绑定socket成功,这时程序会阻塞到recvfrom函数。
-
然后执行客户端程序:
-
会发现服务端会打印信息然后关闭UDP(就不会显示在
ss -ua
中了)进程退出:
如果执行出现问题,请务必检查后台是否有其他有关进程驻留。