UDP的编程流程

UDP编程流程:

服务器端:socket(),  bind(),  recvfrom()/sendto(),  close();

客户端:socket(),  sendto()/recvfrom(),  close();


以下是各个函数的具体介绍:

首先提一句:linux下一切皆文件,socket也不例外,它是可读、可写、可控制、可关闭的文件描述符。

创建socket

#include
#include
int socket(int domain,int type,int protocol);//创建socket
  • domain  表示底层协议族。其中IPv4用PF_INET(Protocol Family of Internet),IPv6用PF_INET6。
  • type 指定服务类型。服务类型主要有SOCK_STREAM(字节流服务)服务和SOCK_DGRAM服务(数据报服务)。对于TCP/IP协议族来说,SOCK_STREAM表示传输层使用TCP协议,SOCK_DGRAM表示传输层使用UDP协议。
  • protocol 在前两个参数构成的协议集合下,再选择一个具体的协议。一般设置为0,表示使用默认协议。

socket()系统调用成功时返回一个socket文件描述符,失败则返回-1并设置errno。

命名(绑定)socket

#include
#include
int bind(int sockfd,const struct sockaddr* my_addr,socklen_t addlen);//命名(绑定)socket

bind的作用是将未命名的sockfd文件描述符指向my_addr所指的socket地址。其中socket地址长度由参数addlen指出。

bind成功时返回0,失败则返回-1并设置errno。

其中struct sockaddr是通用的socket地址,而TCP/IP协议族有sockaddr_in和sockaddr_in6两个专用socket地址结构体,它们分别用于IPv4和IPv6,这里我们只介绍sockaddr_in:

struct sockaddr_in
{
    sa_family_t sin_family;  //地址族:AF_INET
    u_int16_t sin_port;      //端口号(要用网络字节序)
    struct in_addr sin_addr; //IPv4地址结构体
};
struct in_addr //IPv4地址结构体
{
    u_int32_t s_addr;  //IPv4地址(要用网络字节序)
};

所有专用socket地址类型的变量在实际使用时都需要转化为通用的socket地址类型sockaddr(强转)。我们看到结构体中的端口号和IP地址都要求用网络字节序。

首先看端口号,两台主机之间要通过TCP/IP协议进行通信的时候需要调用相应的函数进行主机序 和网络序的转换。因为主机字节序一般为小端模式(Little-Endian),而网络字节序为大端模式(Big-Endian),也就是说两者的存储方式不同。所以我们介绍4个函数来完成主机字节序和网络字节序之间的转换:

#include
unsigned long int htonl(unsigned long int hostlong);//主机字节序转网络字节序(32bit的长整型)
unsigned short int htonl(unsigned short int hostshort);//主机字节序转网络字节序(16bit的短整型)
unsigned long int ntohl(unsigned long int netlong);//网络字节序转主机字节序(32bit的长整型)
unsigned short int ntohs(unsigned short int netshort);//网络字节序转主机字节序(16bit的短整型)

这些函数的含义很明确,第一个函数 htonl 表示“host to network long”,即长整型(32bit)的主机字节序转化为网络字节序。

接下来我们看IP地址,我们习惯用点分十进制这样的可读性好的字符串来表示IPv4地址,这里介绍3个IP地址转换函数:

#include
in_addr_t inet_addr(const char* strptr);
int inet_aton(const char* cp,struct in_addr* inp);
char* inet_ntoa(struct in_addr in);

inet_addr函数将用点分十进制字符串表示的IPv4地址转化为用网络字节序整数表示的IPv4地址。

inet_aton函数完成inet_addr函数同样的功能,但是将转化结果存储于参数inp指向的地址结构中。该函数成功时返回1,失败返回-1。

inet_ntoa函数将用网络字节序整数表示的IPv4地址转化为用点分十进制字符串表示的IPv4地址。

数据读写

对文件的读写操作read和write同样适用于socket。但是socket编程接口提供了几个专门用于socket数据读写的系统调用,它们增加了对数据读写的控制。UDP与TCP不同,UDP通信没有连接的概念,所以我们每次读取数据都需要获取发送端的socket地址。所以UDP的读写函数比TCP的读写函数参数要多。

#include
#include
int recvfrom(int sockfd,void* buf,size_t len,int flags,struct sockaddr* src_addr,socklen_t* addrlen);//读取sockfd上的数据

recvfrom用于读取sockfd上的数据。

  • buf  指定读缓冲区的位置。
  • len 指定读缓冲区的大小。
  • src_addr 发送端的socket地址。 
  • addrlen 发送端的socket地址的长度。
  • flags 通常设置为0(具体含义见下)。

recvfrom 成功时返回实际读取到的数据的长度(recv返回0时表示通信对方已经关闭连接),出错时返回-1并设置errno。

#include
#include
int sendto(int sockfd,const void* buf,size_t len,int flags,const struct sockaddr* dest_addr,socklen_t addrlen);//往sockfd上写入数据

sendto 是往sockfd上写入数据。

  • buf  指定写缓冲区的位置。
  • len 指定写缓冲区的大小。
  • src_addr 接收端的socket地址。 
  • addrlen 接收端的socket地址的长度。
  • flags 通常设置为0(具体含义见下)。

sendto 成功时返回实际写入的数据的长度(recv返回0时表示通信对方已经关闭连接),出错时返回-1并设置errno。

flags为数据收发提供额外的控制,flag参数的可选值如下表,这些选项的具体使用这里不作详述。

ps:recvfrom/sendto系统调用也可以用于面向连接的socket数据读写,只需要把最后两个参数都设置为NULL以忽略发送端、接收端的socket地址(因为我们已经建立了连接,所以已经知道其socket地址了)。

关闭连接

#include
int close(int fd);//关闭连接
  • fd  待关闭的socket。

close系统调用并非总是立即关闭一个连接,而是将fd的引用计数-1;只有当fd的引用计数为0时,c才真正关闭连接。


UDP编程实例

实现客户端输入数据,服务器端打印客户输入的数据,并且每次打印数据后给客户反馈。

服务器端代码:

#include
#include
#include
#include
#include
#include
#include
#include
#include

int main()
{
	int sockfd=socket(PF_INET,SOCK_DGRAM,0);//创建socket
	assert(sockfd!=-1);

	struct sockaddr_in cli,ser;
	ser.sin_family=AF_INET;
	ser.sin_port=htons(6000);
	ser.sin_addr.s_addr=inet_addr("127.0.0.1");
	int res=bind(sockfd,(struct sockaddr*)&ser,sizeof(ser));//绑定socket

	while(1)//保持服务器常驻
	{
		char buff[128]={0};
		int len=sizeof(cli);
     	int n=recvfrom(sockfd,buff,127,0,(struct sockaddr*)&cli,&len);//接收客户端的数据
		if(n<=0)
		{
			printf("recvfrom error\n");
			continue;
		}

		printf("%s\n",buff);
		sendto(sockfd,"OK",2,0,(struct sockaddr*)&cli,len);//给客户端回馈
	}
	close(sockfd);//关闭服务器
}

客户端代码:

#include
#include
#include
#include
#include
#include
#include
#include
#include

int main()
{
	int sockfd=socket(PF_INET,SOCK_DGRAM,0);//创建socket
	assert(sockfd!=-1);

    struct sockaddr_in ser,cli;
	ser.sin_family=AF_INET;
	ser.sin_port=htons(6000);
	ser.sin_addr.s_addr=inet_addr("127.0.0.1");

	while(1)//实现与服务器端多次交互
	{
		printf("please input:");
		fflush(stdout);

		char buff[128]={0};
		fgets(buff,127,stdin);
		buff[strlen(buff)-1]=0;

		if(strncmp(buff,"end",3)==0)//客户输入end时关闭与服务器的交互
		{
			close(sockfd);//停止与服务器的连接
			break;
		}

		sendto(sockfd,buff,127,0,(struct sockaddr*)&ser,sizeof(ser));//向服务器发送数据

		memset(buff,0,128);
        recvfrom(sockfd,buff,127,0,NULL,NULL);//相当于已建立连接,可用NULL
		printf("%s\n",buff);
	}
}

运行结果:

客户端:

UDP的编程流程_第1张图片

服务器端:

你可能感兴趣的:(网络编程)