udp编程
UDP编程流程:
服务器端:socket(), bind(), recvfrom()/sendto(), close();
客户端:socket(), sendto()/recvfrom(), close();
以下是各个函数的具体介绍:
首先提一句:linux下一切皆文件,socket也不例外,它是可读、可写、可控制、可关闭的文件描述符。
创建socket
#include<sys/types.h>
#include<sys/socket.h>
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<sys/types.h>
#include<sys/socket.h>
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<netinet/in.h>
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<arpa/inet.h>
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<sys/types.h>
#include<sys/socket.h>
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<sys/types.h>
#include<sys/socket.h>
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<unistd.h>
int close(int fd);//关闭连接
- fd 待关闭的socket。
close系统调用并非总是立即关闭一个连接,而是将fd的引用计数-1;只有当fd的引用计数为0时,c才真正关闭连接。
UDP编程实例
实现客户端输入数据,服务器端打印客户输入的数据,并且每次打印数据后给客户反馈。
服务器端代码:
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<bits/socket.h>
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<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
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);
}
}
运行结果:
客户端:
服务器端:
文章最后发布于: 2018-11-15 19:46:25
相关阅读
与或非——编程语言中的!|| && 与离散数学中的!v ∧ 优
1.问题背景:一同事让看一段逻辑有没有问题,其中一个if如下: if(A || B && C){ ... } 是的,没有括号,平时根据需要会写成 if((A
最难学的10大编程语言排行榜,Java只排第三,第一名出乎意
2018年12月的TIOBE编程语言排行榜已经出炉,Python重回前三,Go语言跌出前十,Visual Basic.NET涨幅明显,保持第五名。TIOBE排行榜是根据
今年9月,深圳点猫科技有限公司(以下简称为编程猫)与北京故宫宫廷文化发展有限公司(以下简称为故宫宫廷文化)在故宫文化推广方面展
关于QtGraphics编程的几点经验总结_qgraphicsscene
转载自:http://www.niubb.com/riji/2015/10-01/12524.htmlQtGraphics模块用于显示二维的图形图像,所以三维的事情就不要找它了,应该
1.定义数组是一种数据结构,用来存储同一类型值的集合。通过一个整型下标可以访问数组中的每一个值。例如:如果a是一个整型数组,a[i]