UDP服务器模型和本地套接字使用
1.UDP和TCP的差异
- TCP:面向连接的、可靠数据包传输。对于不稳定的网络层,采取完全弥补的通信方式,具有丢包重传机制。
- 优点:数据流量稳定,数据传输速度稳定,数据传输的路径稳定。
- 缺点:传输的速度慢、效率低,系统资源的开销大。
- 使用场景:数据的完整性要求较高,但是不追求效率,比如大数据传输、文件传输等。
- UDP:无连接的,不可靠的数据报传递。对于不稳定的网络,采取完全不弥补的通信方式,默认还原网络状况。
- 优点:传输的速度快,效率高,系统资源的开销小。
- 缺点:数据流量不稳定,数据传输速度不稳定,数据传输路径不稳定。
- 使用场景:对时效性要求较高的场合,但是对稳定性要求不高。比如游戏、视频会议和视频电话等。
- 进阶:在应用层添加数据校验协议,弥补UDP的不足。
2.UDP实现的C/S模型
-
sever:
sockfd = socket(AF_INET, SOCK_DGRAM, 0); //报式协议 bind(); //listen(); //可有可无没有连接这个概念,就没有连接上限概念 while(1) { recvfrom(); //涵盖accept传出地址结构的功能 //小写转大写 sendto(); } close(sockfd);
-
client:
connfd = socket(AF_INET, SOCK_DGRAM, 0); //bind(); //没有bind while(1) { sendto(); //涵盖了connect中的绑定服务器地址结构的功能 //显示到屏幕 recvfrom(); } close(connfd);
-
关键函数
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr* src_addr, socklen_t *addrlen);
-
参数:
sockfd:创建的数据报套接字
buf:数据缓冲区
len:缓冲区的大小
flags:默认为0
src_addr:传出参数,传出对端(数据源)的地址结构
addrlen:传入传出参数,地址结构的长度
-
返回值:
成功:接收的数据字节数
失败:-1,errno设置
对端关闭:0
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
-
参数:
sockfd:创建的数据报套接字
buf:数据缓冲区
len:缓冲区的大小
flags:默认为0
src_addr:传入参数,传入对端(目标)的地址结构
addrlen:传入传出参数,地址结构的长度
-
返回值:
成功:成功发送的数据字节数
失败:-1,errno设置
-
3.本地套接字
-
同样利用socket函数
-
原型
int socket(int domain, int type, int protocol);
-
功能:创建本地套接字
-
参数:
domain:AF_UNIX
type:SOCK_STREAM、SOCK_DGRAM均可
protocol:通常为0
-
返回值:
成功:新套接字所对应的文件描述符
失败:-1,errno
-
-
使用示例
/****服务器操作****/ //1.创建本地套接字 int lsock = socket(AF_UNIX, SOCK_STREAM, 0); //2.绑定本地套接字的地址结构 struct sockaddr_un addr; struct sockaddr_un{ __kernel_sa_family_t sun_family; //地址结构类型 char sun_path[UNIX_PATH_MAX]; //socket文件名,最大108 }; add->sun_family = AF_UNIX; strcpy(add->sun_path, "local_socket"); //offsetof用于返回结构体中对应成员的偏移量,单位为字节 int len = offsetof(struct sockaddr_un, sun_path) + strlen("local_socket"); bind(lsock, (struct sockaddr*)&addr, len); //3.bind函数调用成功会返回一个socket,为了保证bind成功需要调用unlink函数 unlink("local_socket"); //4.建立连接 struct sockaddr_un p_addr; int p_len = sizeof(p_addr); int p_fd = accept(lsock, (struct sockaddr*)&p_addr, (socklen_t*)&len); p_len -= offsetof(struct sockaddr_un, sun_path); //记录对端文件名的长度 p_addr.sun_path[p_len] = '\0'; //保障没有乱码的发生 printf("p_name is %s\n", p_addr.sun_path); //5.通信,套接字是双向全双工而管道是半双工的 int ret = read(p_fd, buf, BUFSIZ); //中间可以对数据进行操作 write(p_fd, buf, ret); /****服务器操作****/ //前1-3步完全是相同的 //4.建立连接 //不能依赖隐式绑定,必须初始化两个地址结构,网络通信中的客户端不用初始化自身的地址结构 bzero(&servaddr, sizeof(servaddr)); //构造server 地址 servaddr.sun_family = AF_UNIX; strcpy(servaddr.sun_path,SERV_ADDR); //计算服务器端地址结构有效长度 len = offsetof(struct sockaddr_un, sun_path) + strlen(servaddr.sun_path); connect(cfd, (struct sockaddr *)&servaddr, len); //第5步也是相同的
-
本地套接字和网络套接字的不同
- domain不同,网络套接字用AF_NET,本地套接字用AF_UNIX。
- bind绑定的结构体不同,前者是sockaddr_un,后者是sockaddr_in。
- 本地套接字会生成一个s伪文件,而网络套接字是不会的。
- 本地套接字bind最后一个长度参数要使用offsetof()去求取,网络套接字直接可以传sizeof()。
- 本地套接字的客户端不能依赖隐式绑定,必须显式进行绑定。