当前位置:   article > 正文

【Linux网络编程(二)】UDP编程流程_linux c udp 设置源端口

linux c udp 设置源端口

1 UDP介绍、编程流程

1.1 UDP概述

UDP协议:面向无连接的用户数据报协议,在传输数据前不需要先建立连接;目地主机的运输层收到UDP报文后,不需要给出任何确认

UDP特点
1、相比TCP速度稍快些
2、简单的请求/应答应用程序可以使用UDP
3、对于海量数据传输不应该使用UDP
4、广播和多播应用必须使用UDP

UDP应用: DNS(域名解析)、NFS(网络文件系统)、RTP(流媒体)等


1.2 网络编程接口socket

网络通信需要解决3大问题(应用层)
 ​1.协议
 2.端口(port)
 3.IP地址

20世纪80年代初,加州大学Berkeley分校在BSD(一个UNIX OS版本)系统内实现了TCP/IP协议;其网络程序编程开发接口为socket。

随着UNIX以及类UNIX操作系统的广泛应用, socket成为最流行的网络程序开发接口

socket作用:提供不同主机上的进程之间的通信

	特点
		1、socket也称“套接字”
		2、是一种文件描述符,代表了一个通信管道的一个端点
		3、类似对文件的操作一样,可以使用read、write、close等函数对socket套接字进行网络数据的收取和发送等操作
		4、得到socket套接字(描述符)的方法调用socket()
  • 1
  • 2
  • 3
  • 4
  • 5

1.3 UDP编程C/S架构

在这里插入图片描述

  • 服务端要绑定确定的端口号
  • 客户端不能使用read、write,因为需要发送给指定的服务端地址。



2 UDP编程-创建套接字

2.1 创建socket套接字

创建一个用于网络通信的socket套接字(描述符)

//头文件:
#include <sys/socket.h>
    
int socket(int family,int type,int protocol);

/*
参数:
family:协议族(AF_INET4、AF_INET6、PF_PACKET等)

​			  ||流式套接字 用于TCP通信  ||报式套接字 用于UDP通信   ||原始套接字
type:套接字类( SOCK_STREAM、            SOCK_DGRAM、            	SOCK_RAW等)

				 ||一般放0  自动指定协议
protocol:协议类别(0、IPPROTO_TCP、IPPROTO_UDP等)
                
返回值:
	>0 通信的文件描述符(套接字)
	<0 创建失败
*/
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

特点:

	1.创建套接字时,系统不会分配端口
	2.创建的套接字默认属性是主动的,即主动发起服务的请求;当作为服务器
	3.时,往往需要修改为被动的
  • 1
  • 2
  • 3



3 UDP发送数据流程

#include <stdio.h>
#include <sys/socket.h>  //socket
#include <netinet/in.h>  //struct sockaddr_in
#include <string.h>      //memser
#include <arpa/inet.h>  //htons

int main()
{
	//创建通信的UDP的套接字(没有port、ip)
    int sockfd = socket(AF_INET, SOCK_DGRAM,0);
    printf("UDP套接字sockfd=%d\n",sockfd);
    
    //udp客户端 发送消息 给服务器
    //定义一个IPv4地址结构 存放服务器的地址信息(目的主机)
    struct sockaddr_in ser_addr;   
    memset(&ser_add, 0, sizeof(ser_add));
    ser_addr.sin_family = AF_INET;   //IPv4
    ser_addr.sin_port = htons(8000);  //服务器的端口
    inet_pton(AF_INET,"10.9.21.211", &ser_addr.sin_addr.s_addr);  //服务器的IP地址由字符串转换为整型数据存放到ser_addr
    
    //发送数据
    sento(sockfd, "hello net", strlen("hello net"), 0, \
          (struct sockaddr *)&ser_addr,sizeof(ser_addr));
    
    close(sockfd);
    return 0;
}
  • 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



4 UDP接收数据流程

#include <stdio.h>
#include <sys/socket.h> //socket
#include <netinet/in.h> //struct sockaddr_in
#include <string.h>     //memset
#include <arpa/inet.h>  //htos
#include <unistd.h>     //close

int main()
{
    //创建通信的UDP的套接字(没有port、ip)
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    printf("UDP套接字sockfd=%d\n", sockfd);
    
    //定义IPv4地址结构,存放本机信息
    struct sockaddr_in my_addr;
    bzero(&my_addr, sizeof(my_addr));
    my_addr.sin_family = AF_INET;
    my_addr.sin_port = htons(9000);
    my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    
    //给udp套接字 bind绑定一个固定的地址信息
    bind(sockfd, (struct sockaddr *)&my_addr, sizeof(my_addr));
    
    //接收udp消息
    while(1)
    {
        //定义一个IPv4地址结构,存放发送者的信息
        struct sockaddr_in from_addr;
        socklen_t from_len = sizeof(from_addr);
        
        unsigned char buf[1500] = "";
        int len = recvfrom(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&from_addr, &from_len);
        //from_addr存放的就是发送者的消息
        char ip[16] = "";
        //转换为点分十进制ip
        inet_ntop(AF_INET, &from_addr.sin_addr.s_addr, ip, 16);
        
        printf("消息来自%s %hu--->", ip, ntohs(from_addr.sin_port));
        printf("len:%d msg:%s\n", len, buf);
    }
    
    //关闭套接字
    close(sockfd);
    return 0;
    
}
  • 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
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46



5 同时收发数据__QQ聊天

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <pthread.h> //线程头文件
#include <arpa/inet.h>

void *send_function(void *arg)
{
    //获取套接字
    int sockfd = *(int *)arg;
    //定义地址目的结构
    struct sockaddr_in dst_addr;
    bzero(&dst_addr,sizeof(dst_addr));
    dst_addr.sin_family = AF_INET;
    while(1)
    {
        //获取键盘输入
   	 	fgets(buf,sizeof(buf),stdin);
   	    buf(strlen(buf)-1)=0;
    
  	   //判断是否是IP port
  	  //sayto IP port
  	  if(strncmp(buf,"sayto",5) == 0)
  	  {
        char ip[16] = "";
        unsigned short port = 0;
        //sayto 10.9.21.211 8000
        sscanf(buf,"sayto %s %hu", ip, &port);
        dst_addr.sin_port = htons(port);
        inet_pton(AF_INET, ip, &dst_addr.sin_addr.s_addr);
        continue;
      }
      else
      {
          sendto(socked, buf, strlen(buf), 0, 
                 \(struct sockaddr *)&dst_addr, sizeof(dst_addr));
          
          if(strcmp(buf,"bye")==0)
              break;
      }
    }
    
    return NULL;
}

void *recv_function(void *arg)
{
    int sockfd = *(int *)arg;   
    while(1)
    {
        struct sockaddr_in from_addr;
        socklen_t from_len = sizeof(from_addr);
        unsigned char buf[1500]="";
        char ip[16]="";
        int len = recvfrom(sockfd, buf,sizeof(buf), 0,\
                          (struct sockaddr *)&from_addr, &from_len);
        printf("%s %hu:%s\n",inet_ntop(AF_INET,&from_addr.sin_addr.s_addr,ip,16),\
               ntohs(from_addr.sin_port),buf);
        if(strcmp(buf,"bye")==0)
            break;
    }
    return NULL;
}

int main(int argc, char const *argv[])
{
    // 判断参数 ./a.out  8000\n");
   	if(arfc != 2)
    {
        printf("./a.out 8000\n");
        return 0;
    }
    
    //创建udp套接字
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    
    //bind绑定固定的端口, IP
    struct sockaddr_in my_addr;
    bzero(&my_addr, sizeof(my_addr));
    my_addr.sin_family = AF_INET;
    my_addr.sin_port = htons(atoi(argv[1]);  ///atoi 将字符串转为数字
    my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    bind(sockfd, (struct sockaddr *)&my_addr, sizeof(my_addr));
    
    //创建发送线程
    pthread_d send_tid;
    pthread_create(&send_tid, NULL, send_function, (void *)&sockfd);
                             
    //创建接收线程
    pthread_d recv_tid;
    pthread_create(&recv_tid, NULL, recv_function, (void *)&sockfd);    
                             
    pthread_join(send_tid, NULL);
    pthread_join(recv_tid, NULL);

    //关闭套接字
    close(sockfd);
    return 0;
}
  • 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
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101



6 编程流程中所用的函数

7、8、9、10章节

7 主机字节和网络字节相互转换

7.1 htons函数

将16位主机字节序数据转换成网络字节序数据
将主机字节序的端口转换成网络字节序

//头文件:
#include <arpa/inet.h>

uint16_t htons(uint16_t hostint16);

/*
参数:
	uint16_t:unsigned short int
	hostint16:待转换的16位主机字节序数据
返回值:
	成功:返回网络字节序的值
*/
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

7.2ntohl函数

将32位网络字节序数据转换成主机字节序数据

//头文件:
#include <arpa/inet.h>

uint32_t ntohl(uint32_t netint32);

/*
参数:
	uint32_t: unsigned int
	netint32:待转换的32位网络字节序数据
返回值:
	成功:返回主机字节序的值
*/
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

#include <stdio.h>
#include <arpa/inet.h>
int main(int argc, char *argv[])
{
	int  num = 0x01020304;//
	int sum = htonl(num);

	printf("%x\n",ntohl(sum));

	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

7.3ntohs函数

将16位网络字节序数据转换成主机字节序数据

//头文件:
#include <arpa/inet.h>

uint16_t ntohs(uint16_t netint16);
/*
参数:
	uint16_t: unsigned short int
	netint16:待转换的16位网络字节序数据
返回值:
	成功:返回主机字节序的值
*/
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

#include <stdio.h>
#include <arpa/inet.h>
int main(int argc, char *argv[])
{
	short  a = 0x0102;
	short b = htons(a);
	printf("%x\n",ntohs(b));
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9



8 地址转换函数

8.1 inet_pton函数

字符串ip地址转整型数据
将点分十进制数串转换成32位无符号整数

//头文件:
#include <arpa/inet.h>

int inet_pton(int af,const char *stc, void *dst);

/*
参数:
	af: 协议族  选IPV4对应的宏AF_INET   ,选IPv6对应的宏AF_INET6
	stc:点分十进制数串的首元素地址
	dst:转换为32位无符号整数的地址
返回值:
	成功返回1 、 失败返回其它
*/
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

#include <stdio.h>
#include <arpa/inet.h>
int main(int argc,char *argv[])
{
	char ip_str[] = "10.0.13.100";
	unsigned int ip_uint = 0;
	unsigned char * ip_p =NULL;//可以用char吗?
	
	inet_pton(AF_INET,ip_str,&ip_uint);
	printf("ip_uint = %d\n",ip_uint);
	
	ip_p = (unsigned char *) &ip_uint;
	printf("ip_uint = %d.%d.%d.%d\n",*ip_p,*(ip_p+1),*(ip_p+2),*(ip_p+3));
	
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

8.2 inet_ntop函数

整型数据转字符串格式ip地址
将32位无符号整型数据(默认大端)转成 点分十进制数组

//头文件:
#include <arpa/inet.h>

//len的宏定义
#define INET_ADDRSTRLEN   16  //for ipv4
#define INET6_ADDRSTRLEN  46  //for ipv6

const char *inet_ntop(int family, const void *addrptr,char *strptr, size_t len);

/*
参数:
	family	   协议族 AF_INET:IPv4  AF_INET6:IPv6
	addrptr	   32位无符号整数数据的地址
	strptr	   点分十进制数串的首元素地址
	len	   	   点分十进制数串的最大长度
返回值:
	成功:则返回字符串的首地址
	失败:返回NULL
*/
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

#include<stdio.h>
#include<arpa/inet.h>
int main()
{
	unsigned char ip[]={10,0,13,252};
	char ip_str[16];
	inet_ntop(AF_INET,(unsigned int *)ip,ip_str,16);
	printf("ip_str = %s\n",ip_str);
	return 0;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11



9 IPv4套接字地址结构

9.1 in_addr函数

存放IP地址(32位无符号整数)

//头文件:
#include <netinet/in.h>

struct in_addr
{
	in_addr_t s_addr;//4字节
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

9.2 sockaddr_in函数

存放IPv4协议通信的所址信息

//头文件:
#include <netinet/in.h>

struct in_addr
{
	in_addr_t s_addr;//4字节
};

struct sockaddr_in
{
	sa_family_t sin_family;	//2字节  协议AF_INE4   AF_INET6
	in_port_t sin_port;		//2字节  端口
	struct in_addr sin_addr;//4字节  IP地址(32位无符号整数)
	char sin_zero[8]		//8字节  全写0 
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

9.3 sockaddr函数

为了使不同格式地址能被传入套接字函数,地址须要强制转换成通用套接字地址结构

//头文件:
#include <netinet/in.h>

struct sockaddr
{
	sa_family_t sa_family;	// 2字节
	char sa_data[14]	//14字节
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

struct sockaddr_in //IPv4地址结构(存放客户端、服务器的地址信息(协议,port,IP))
struct sockaddr    //通用地址结构,不是存放数据 socket API 类型转换

//在定义源地址和目的地址结构的时候,选用struct sockaddr_in;
struct  sockaddr_in  my_addr;

//当调用编程接口函数,且该函数需要传入地址结构时需要用struct sockaddr进行强制转换
bind(sockfd,(struct sockaddr*)&my_addr,sizeof(my_addr));
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8



10 数据发送、接收、绑定

10.1 sendto函数

向to结构体指针中指定的ip,发送UDP数据

#include <sys/socket.h>
ssize_t sendto(int sockfd,
			   const void *message,
               size_t length,
               int flags,
               const struct sockaddr *dest_addr,        
               socklen_t dest_len);

/*
参数:
	sockfd:从那个套接字发出
	message:需要发送的消息的首元素地址
	length: 消息的实际长度
	flags:0 网络默认方式通信
	dest_addr:指向目主机的IPv4地址信息(协议、port、IP地址)
	dest_len:地址结构体的长度

返回值:
	成功:发送数据的字符数
	失败: -1

注意:
	通过dest_addr和dest_len确定目的地址
	可以发送0长度的UDP数据包
*/
  • 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

10.2 recvfrom函数

接收UDP数据,并将源地址信息保存在from指向的结构中(默认没消息,阻塞)

#include <sys/socket.h>

ssize_t recvfrom(int sockfd, void *buf,size_t nbytes,int flags,
					struct sockaddr *from,socklen_t *addrlen);

/*
参数:
	sockfd:		udp套接字
	buf:			用来存放接收消息的空间起始地址
	nbytes: 		能接收消息的最大字节数
	flags:		  套接字标志(常为0)
	from:		 存放发送者的IPv4地址信息(不关心发送者信息,可为NULL)
	addrlen:       地址结构长度

返回值:
成功:接收到的实际字节数
失败: -1

注意:
通过from和addrlen参数存放数据来源信息
from和addrlen可以为NULL, 表示不保存数据来源
*/
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

10.3 bind函数

bind给udp套接字绑定固定的port、IP信息

​服务器收到客户端的信息,客户端的port是随机的。如果udp套接字不使用bind函数绑定固定端口,那么在第一次调用sendto系统会自动给套接字分配一个随机端口。后续sendto调用继续使用前一次的端口。


将本地协议地址与sockfd绑定

#include <sys/socket.h>

int bind(int sockfd,const struct sockaddr *address,socklen_t address_len);

/*
参数:
	sockfd: socket套接字
	sockaddr: 指向特定协议的地址结构指针
	address_len:该地址结构的长度

返回值:
	成功:返回0
	失败:其他

注:只能绑定本地主机的
*/
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

千峰物联网___网络编程___UDP编程

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/知新_RL/article/detail/187190
推荐阅读
相关标签
  

闽ICP备14008679号