vlambda博客
学习文章列表

UDP socket 编程以及IO多路复用

一)什么是UDP

UDP(User Datagram Protocol,用户数据报协议)作为TCP/IP协议栈中传输层的协议,具有以下特点:
  • 提供无连接,不可靠的信息传输服务,

  • 该协议不对传输的数据包排序,不需要接收方应答确认信息

  • 应用程序需自己构建发送数据的顺序和确认机制

  • 与TCP协议相比具有速度优势

UDP通常用在资源消耗小,处理速度要求快的场合,如音频和视频的传输。

二)UDP socket 编程

由于UDP协议具有无连接的特性,与TCP socket 编程相比,不再需要监听(listen),等待连接(accept)和和连接(connect)三个步骤。
具体步骤如下:
  • 服务器:

    1.创建服务器套接字用于唯一标识服务器

 int connfd = socket(AF_INET,SOCK_DGRAM,0);//SOCK_DGRAM:描述要创建的套接字类型为数据报套接字类型 if(connfd < 0){ perror("socket"); return -1;  }
2.绑定主机IP和端口号
struct sockaddr_in saddr; saddr.sin_family = SOCK_DGRAM; saddr.sin_port = htons(12345); saddr.sin_addr.s_addr = inet_addr("0.0.0.0");
int ret = bind(connfd,(struct sockaddr *)&saddr,sizeof(saddr)); if(ret < 0){ perror("bind"); return -1;  }
3.接收客户端的消息并保存客户端的IP及端口号
 struct sockaddr_in caddr; int clen = sizeof(caddr); char buf[56] = {0}; if(recvfrom(connfd,buf,sizeof(buf),0,(struct sockaddr *)&caddr,&clen) < 0){ perror("recvfrom"); return -1; } printf("client->%s\n",buf); printf("已接收到客户端消息,成功与客户端建立连接!\n");
4.向客户端发送反馈信息
 strcpy(buf,"I Love You"); ret = sendto(connfd,buf,sizeof(buf),0,(struct sockaddr *)&caddr,sizeof(caddr)); if(ret < 0){ perror("sendto"); return -1; }
  • 客户端

    1.创建套接字唯一标识客户端

     int connfd = socket(AF_INET,SOCK_DGRAM,0); if(connfd < 0){ perror("connfd"); return -1; }
     struct sockaddr_in saddr; saddr.sin_family = AF_INET; saddr.sin_port = htons(12345); saddr.sin_addr.s_addr = inet_addr("192.168.64.137");

    3.向服务器发送信息以及客户端的IP和端口号

     char buf[56] = "xieting"; int ret = sendto(connfd,buf,sizeof(buf),0,(struct sockaddr *)&saddr,sizeof(saddr)); if(ret < 0){ perror("sendto"); return -1; }

    4.接收服务器的反馈消息

     struct sockaddr_in oaddr; int olen = sizeof(oaddr); char back[56] = {0}; ret = recvfrom(connfd,back,sizeof(buf),0,(struct sockaddr *)&oaddr,&olen); if(ret < 0){ perror("recvfrom"); return -1; }  printf("server->%s\n",back);
  • 结果

    client->xieting已接收到客户端消息,成功与客户端建立连接!
    server->I Love You

    二)IO多路复用

    1.什么是IO多路复用

    I/O多路复用就是通过一种机制,一个进程可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。

    2.IO多路复用的基本步骤
  • 创建一个文件描述符集合
  • 将要关注的文件描述符加入这个集合
  • 调用select函数,将不活跃的描述符清除掉,
  • 循环遍历select后的描述符集合,判断关注的文件描述符是否还在集合里
    • 若在,执行相应的操作
    • 若不在,continue执行下一次判断

3.代码如下

    服务器

    /*=============================================================== * 文件名称:server_TCP.c * 创 建 者:zhongyue * 创建日期:2020年08月03日 ================================================================*/#include <stdlib.h>#include <string.h>#include <stdio.h>
    #include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>#include <unistd.h>#include <errno.h>
    int main(void){ //1.创建一个套接字 int listenfd = socket(AF_INET,SOCK_STREAM,0); if(listenfd < 0){ perror("socket"); return -1; } //2.绑定 struct sockaddr_in saddr; saddr.sin_family = AF_INET; saddr.sin_port = htons(12345); saddr.sin_addr.s_addr = inet_addr("0.0.0.0"); int ret = bind(listenfd,(struct sockaddr *)&saddr,sizeof(saddr)); if(ret < 0){ perror("bind"); return -1; } //3.设置监听套接字 int backlog = 20; ret = listen(listenfd,backlog); if(ret < 0){ perror("listen"); return -1; } //4.创建描述符集合 fd_set readfds,writefds;//创建描述符读集合和写集合 FD_ZERO(&readfds);//清空描述符集合 FD_ZERO(&writefds);//清空描述符集合 FD_SET(listenfd,&readfds);//将监听套接字写入集合 int maxfd = listenfd;//初始化最大文件描述符 fd_set tmpfds = readfds;//定义一个中间变量记录最初的文件间描述符集合,因为readsfds一直在改变 struct sockaddr_in caddr; int clen = sizeof(caddr); while(1){ readfds = tmpfds;//readfds重新赋值 int ret = select(maxfd+1,&readfds,NULL,NULL,NULL);//调用select函数清除不活跃的描述符 if(ret < 0){ perror("select"); return -1; } for(int i=3;i<maxfd+1;i++){ //循环遍历描述符 if(FD_ISSET(i,&readfds)){ if(i == listenfd){ //如果套接字存在且是监听套接字,则调用accept连接客户端 int connfd = accept(listenfd,(struct sockaddr *)&caddr,&clen); if(connfd < 0){ perror("accept"); return -1; } printf("客户端已连入!\n"); printf("客户端的IP地址为:%s\n",inet_ntoa(caddr.sin_addr)); printf("客户端的端口号为:%d\n",ntohs(caddr.sin_port)); FD_SET(connfd,&tmpfds);//将连接套接字加入描述符集合 maxfd = maxfd > connfd?maxfd:connfd;//更新最大描述符 } else{ //如果是连接套接字,则处理数据的收发 char buf[128] = {"xieting"}; ret = read(i,buf,sizeof(buf));//接收客户端发来的数据 if(ret < 0){ perror("read"); continue; } printf("%s\n",buf); if(ret == 0){ //如果客户端断开连接,清除这个连接套接字 printf("客户端已断开!\n"); printf("-------------\n"); close(i); FD_CLR(i,&tmpfds); //更新最大描述符maxfd } write(i,buf,sizeof(buf));//向客户端反馈接收到的数据 if(errno == EDESTADDRREQ){ continue; } } } } }}

    客户端

    /*=============================================================== * 文件名称:client.c * 创 建 者:zhongyue * 创建日期:2020年08月03日 ================================================================*/#include <stdlib.h>#include <string.h>#include <stdio.h>#include <sys/socket.h>#include <sys/types.h>#include <netinet/in.h>#include <arpa/inet.h>#include <unistd.h>
    int main(void){ //1.创建一个套接字 int connfd = socket(AF_INET,SOCK_STREAM,0); if(connfd < 0){ perror("fail to socket"); return -1; } //2.连接服务器 struct sockaddr_in saddr; saddr.sin_family = AF_INET; saddr.sin_port = htons(12345); saddr.sin_addr.s_addr = inet_addr("192.168.64.137");
    int ret = connect(connfd,(struct sockaddr *)&saddr,sizeof(saddr)); if(ret < 0){ perror("connect"); return -1; } printf("已连接服务器!\n"); while(1){ char buf[128] = "nice to meet you"; ret = write(connfd,buf,sizeof(buf)); if(ret < 0){ perror("write"); return -1; } sleep(1); read(connfd,buf,sizeof(buf)); printf("%s\n",buf); } return 0;}