Linux 网络通信之UDP

介绍网络通信的使用

Posted by Jerry Chen on July 18, 2019

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;
}
  1. 先执行服务端程序,如果绑定socket成功,这时程序会阻塞到recvfrom函数。

  2. 然后执行客户端程序:

  3. 会发现服务端会打印信息然后关闭UDP(就不会显示在ss -ua中了)进程退出:

如果执行出现问题,请务必检查后台是否有其他有关进程驻留。