什么是UDP
- UDP,是User Datagram Protocol的简称, 中文名是用户数据包协议,是 OSI 参考模型中一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务。UDP 协议基本上是IP协议与上层协议的接口。它是IETF RFC 768是UDP的正式规范。UDP协议的主要作用是将网络数据流量压缩成数据包的形式。一个典型的数据包就是一个二进制数据的传输单位。每一个数据包的前8个字节用来包含报头信息,剩余字节则用来包含具体的传输数据。
TCP与UDP的区别
TCP(传输控制协议):
- 提供IP环境下的数据可靠传输(一台计算机发出的字节流会无差错的发往网络上的其他计算机,而且计算机A接收数据包的时候,也会向计算机B回发数据包,这也会产生部分通信量),有效流控,全双工操作(数据在两个方向上能同时传递),多路复用服务,是面向连接,端到端的传输;
- 面向连接:正式通信前必须要与对方建立连接。事先为所发送的数据开辟出连接好的通道,然后再进行数据发送,像打电话。
- TCP支持的应用协议:Telnet(远程登录)、FTP(文件传输协议)、SMTP(简单邮件传输协议)。TCP用于传输数据量大,可靠性要求高的应用。
UDP(用户数据报协议,User Data Protocol)
- 面向非连接的(正式通信前不必与对方建立连接,不管对方状态就直接发送,像短信,QQ),不能提供可靠性、流控、差错恢复功能。UDP用于一次只传送少量数据,可靠性要求低、传输经济等应用。
- UDP支持的应用协议:NFS(网络文件系统)、SNMP(简单网络管理系统)、DNS(主域名称系统)、TFTP(通用文件传输协议)等。
总结:
- TCP:面向连接、传输可靠(保证数据正确性,保证数据顺序)、用于传输大量数据(流模式)、速度慢,建立连接需要开销较多(时间,系统资源)。
- UDP:面向非连接、传输不可靠、用于传输少量数据(数据包模式)、速度快。
select
什么是I/O操作
- 不是对 外围设备直接进行操作,而是对设备与cpu连接的接口电路的操作。
select的函数原型
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
- nfds:一个整型的变量,它比所有文件描述符集合中的文件描述符的最大值加1.
- readfds:这个文件描述符集合监视文件描述符集中的每个文件是否有数据可读,当select()函数返回的时候,readfds将清除其中不可读的文件描述符,只留下可读的文件描述符
- writefds:这个文件描述符集合监视文件描述符集中的每个文件是否有数据可写,当select()函数返回的时候,readfds将清除其中不可写的文件描述符,只留下可写的文件描述符
- exceptfds:文件描述符集合监视文件描述符集中的每个文件是否发生错误
- FD_CLR:将文件描述符从fd_set集里清楚
- FD_ISSET:判断这个文件描述符是否在这个集里
- FD_SET:将文件描述符fd加入集合set中
- FD_ZERO:将一个文件描述符集合清零
- 程序的作用是:从标准设备读入数据并打印出来
#include <stdio.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/select.h>
#include <unistd.h>
#include <string.h>
void setMaxFd(int *pMax,int fd)
{
if(*pMax<fd)
{
*pMax=fd;
}
}
int main(void)
{
int iMaxFd=0;
fd_set readSet;
int ret=-1;
#if 0
FD_ZERO(&readSet);
FD_SET(STDIN_FILENO,&readSet);
setMaxFd(&iMaxFd,STDIN_FILENO);
//判断文件描述符中是否包含了某个文件描述符,若包含了返回1,不包含返回0
int ret=FD_ISSET(STDIN_FILENO,&readSet);
printf("ret=%d\n",ret);
// ret=FD_ISSET(STDOUT_FILENO,&readSet);
// printf("ret=%d\n",ret);
# endif
char caBuf[32]={'\0'};
while(1)
{ //select函数将会阻塞,直到有指定的文件描述符集合中有相应文件描述符要进行I/O操作
FD_ZERO(&readSet);
FD_SET(STDIN_FILENO,&readSet);
setMaxFd(&iMaxFd,STDIN_FILENO);
ret=select(iMaxFd+1,&readSet,NULL,NULL,NULL);
if(ret==-1)
{
perror("select");
return -1;
}
//若函数成功返回,文件描述符集合中只会包含将要进行I/O操作的文件描述符,其他没有要进行I/O操作的文件描述符会被清除掉
//如果需要再监控,需要重新将清除掉的文件描述符添加到集合中
//推荐做法,将集合清空
//完成将所有需要监控的文件描述符都添加一遍
if(FD_ISSET(STDIN_FILENO,&readSet))
{
memset(caBuf,'\0',sizeof(caBuf));
read(STDIN_FILENO,caBuf,sizeof(caBuf));
printf("%s\n",caBuf);
}
}
printf("Hello World\n");
return 0;
}
- 从标准设备循环输入数据并且在标准设备循环输出数据
#include <stdio.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/select.h>
#include <unistd.h>
#include <string.h>
void setMaxFd(int *pMax,int fd)
{
if(*pMax<fd)
{
*pMax=fd;
}
}
int main(void)
{
int iMaxFd=0;
fd_set readSet;
fd_set writeSet;
int ret=-1;
int fd=-1;
#if 0
FD_ZERO(&readSet);
FD_SET(STDIN_FILENO,&readSet);
setMaxFd(&iMaxFd,STDIN_FILENO);
//判断文件描述符中是否包含了某个文件描述符,若包含了返回1,不包含返回0
int ret=FD_ISSET(STDIN_FILENO,&readSet);
printf("ret=%d\n",ret);
// ret=FD_ISSET(STDOUT_FILENO,&readSet);
// printf("ret=%d\n",ret);
# endif
int sockfd=-1;
char caBuf[32]={'\0'};
while(1)
{ //select函数将会阻塞,直到有指定的文件描述符集合中有相应文件描述符要进行I/O操作
FD_ZERO(&readSet);
FD_ZERO(&writeSet);
FD_SET(STDIN_FILENO,&readSet);
setMaxFd(&iMaxFd,STDIN_FILENO);
FD_SET(STDOUT_FILENO,&writeSet);
setMaxFd(&iMaxFd,fd);
ret=select(iMaxFd+1,&readSet,&writeSet,NULL,NULL);
if(ret==-1)
{
perror("select");
return -1;
}
//若函数成功返回,文件描述符集合中只会包含将要进行I/O操作的文件描述符,其他没有要进行I/O操作的文件描述符会被清除掉
//如果需要再监控,需要重新将清除掉的文件描述符添加到集合中
//推荐做法,将集合清空
//完成将所有需要监控的文件描述符都添加一遍
if(FD_ISSET(STDIN_FILENO,&readSet))
{
memset(caBuf,'\0',sizeof(caBuf));
read(STDIN_FILENO,caBuf,sizeof(caBuf));
printf("%s\n",caBuf);
}
}
printf("Hello World\n");
return 0;
}
- 就从标准设备输入一次,并且输出
#include <stdio.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/select.h>
#include <unistd.h>
#include <string.h>
void setMaxFd(int *pMax,int fd)
{
if(*pMax<fd)
{
*pMax=fd;
}
}
int main(void)
{
int iMaxFd=0;
fd_set readSet;
fd_set writeSet;
int ret=-1;
int fd=-1;
#if 0
FD_ZERO(&readSet);
FD_SET(STDIN_FILENO,&readSet);
setMaxFd(&iMaxFd,STDIN_FILENO);
//判断文件描述符中是否包含了某个文件描述符,若包含了返回1,不包含返回0
int ret=FD_ISSET(STDIN_FILENO,&readSet);
printf("ret=%d\n",ret);
// ret=FD_ISSET(STDOUT_FILENO,&readSet);
// printf("ret=%d\n",ret);
# endif
int sockfd=-1;
char caBuf[32]={'\0'};
//select函数将会阻塞,直到有指定的文件描述符集合中有相应文件描述符要进行I/O操作
FD_ZERO(&readSet);
FD_ZERO(&writeSet);
FD_SET(STDIN_FILENO,&readSet);
setMaxFd(&iMaxFd,STDIN_FILENO);
FD_SET(STDOUT_FILENO,&writeSet);
setMaxFd(&iMaxFd,fd);
ret=select(iMaxFd+1,&readSet,&writeSet,NULL,NULL);
if(ret==-1)
{
perror("select");
return -1;
}
//若函数成功返回,文件描述符集合中只会包含将要进行I/O操作的文件描述符,其他没有要进行I/O操作的文件描述符会被清除掉
//如果需要再监控,需要重新将清除掉的文件描述符添加到集合中
//推荐做法,将集合清空
//完成将所有需要监控的文件描述符都添加一遍
if(FD_ISSET(STDIN_FILENO,&readSet))
{
memset(caBuf,'\0',sizeof(caBuf));
read(STDIN_FILENO,caBuf,sizeof(caBuf));
printf("%s\n",caBuf);
}
printf("Hello World\n");
return 0;
}
数据报套接字(流程见书P127页)
运用UDP实现客户端与服务器之间的通信(PDU)
/*socket()*/
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h> //struct sockaddr_in
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <pthread.h>
#include <stdlib.h>
#include <netdb.h>
#include <sys/wait.h>
//要连接的端口号
#define MYPORT 5000
//能够接收的最长数据
#define MAXBUFLEN 100
int main(void)
{
int sockfd;
//本机的地址信息
struct sockaddr_in serverAddr;
//连接者的地址信息
struct sockaddr_in clientAddr;
int iLen;
int numbytes;
char caBuf[MAXBUFLEN];
//取得一个文件描述符
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0))==-1)
{
//如果取得套接字描述符失败,则给出错误信息,退出
perror("socket");
exit(1);
}
//主机字节顺序
serverAddr.sin_family = AF_INET; //ipv4
//网络字节顺序,短整型
serverAddr.sin_port = htons(MYPORT); //port
//server ip
//自动设置为自己的IP
serverAddr.sin_addr.s_addr = INADDR_ANY;
//将结构的其余空间清零
bzero(&(serverAddr.sin_zero), 8);
//将sockfd和地址进行绑定
if(bind(sockfd, (struct sockaddr *)&serverAddr
, sizeof(serverAddr))==-1)
{
//如果绑定端口出错,则显示错误信息然后退出
perror("bind");
exit(1);
}
iLen=sizeof(struct sockaddr);
//接收数据
if((numbytes=recvfrom(sockfd,caBuf,MAXBUFLEN,0,(struct sockaddr *)&clientAddr,&iLen))==-1)
{
//如果recvfrom()调用出错,则显示错误信息后退出
perror("recvfrom");
exit(1);
}
//显示接收到的数据
printf("packet is %d bytes long\n",numbytes);
caBuf[numbytes]='\0';
printf("packet contains %s \n",caBuf);
//关闭套接字连接
close(sockfd);
}
/*socket()*/
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h> //struct sockaddr_in
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <pthread.h>
#include <stdlib.h>
#include <netdb.h>
#include <sys/wait.h>
#define MYPORT 5000
int main(int argc,char *argv[])
{
int sockfd;
//连接者的地址信息
struct sockaddr_in serverAddr;
struct hostent *he;
int numbytes;
int ret=-1;
if(argc!=3)
{
//检查是否有参数,如果没有则显示使用方法后退出
fprintf(stderr,"usage:talker hostname message\n");
exit(1);
}
if((he=gethostbyname(argv[1]))==NULL)
{
//取得主机的信息,如果失败则显示错误信息后退出
herror("gethostbyname");
exit(1);
}
if ((sockfd=socket(AF_INET, SOCK_DGRAM, 0))==-1)
{
//申请一个数据报套接字描述符,失败则退出
perror("socket");
exit(1);
}
//主机字节顺序
serverAddr.sin_family = AF_INET; //ipv4
//网络字节顺序,短整型
serverAddr.sin_port = htons(MYPORT); //port
//server ip
serverAddr.sin_addr= *((struct in_addr *)he->h_addr);
//将结构中未用的部分清零
bzero(&(serverAddr.sin_zero), 8);
//发送数据
if((numbytes=sendto(sockfd,argv[2],strlen(argv[2]),0,(struct sockaddr *)&serverAddr,sizeof(struct sockaddr)))==-1)
{
//把信息发送到指定的主机指定端口,如出错则提示退出
perror("recvfrom");
exit(1);
}
//关闭套接字描述符后退出
printf("send %d bytes\n",numbytes);
close(sockfd);
return 0;
}