1. UDP编程模型
(1)UDP客户端服务器模型
①客户端可以不调用bind()而直接与服务器通讯。
②UDP是无连接的,因此服务端不需要调用accept和listen,客户端也无需调用connect函数。
(2)数据传输
①发送数据
头文件 |
#include <sys/socket.h> |
函数 |
ssize_t send(int sockfd, const void* buf, size_t nbytes, int flag); ssize_t send(int sockfd, const void* buf, size_t nbytes, int flag, const struct sockaddr* destaddr, socklen_t destlen); ssize_t sendmsg(int sockfd, const struct msghdr* msg, int flag); |
参数 |
struct msghdr{ void* msg_name; //optional address socklen_t msg_namelen; //address size in bytes struct iovec* msg_iov; //array of I/O buffers int msg_iovlen; //number of elements in array void* msg_control; //ancillary data socklen_t msg_controllen; //number of ancillary bytes; int msg_flags; //flags for received message }; |
功能 |
发送数据 |
返回值 |
返回:成功返回发送字节数,出错返回-1。 |
②接收数据
头文件 |
#include <sys/socket.h> |
函数 |
ssize_t recv(int sockfd, const void* buf, size_t nbytes, int flag); ssize_t recvfrom(int sockfd, const void* buf, size_t nbytes, int flag, const struct sockaddr* addr, socklen_t addrlen); ssize_t sendmsg(int sockfd, const struct msghdr* msg, int flag); |
参数 |
与send*函数类似 |
功能 |
接收数据 |
返回值 |
返回消息的字节数,无消息返回0,出错返回-1。 |
【编程实验】利用UDP获取服务器的当前时间
//time_udp_server.c
#include <sys/socket.h> #include <netdb.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <signal.h> #include <time.h> #include <memory.h> int sockfd; void sig_handler(int signo) { if(signo == SIGINT){ printf("server close "); close(sockfd); exit(1); } } //输出客户端的信息 void out_addr(struct sockaddr_in* addr) { char ip[16]; int port = 0; memset(ip, 0, sizeof(ip)); inet_ntop(AF_INET, &addr->sin_addr.s_addr, ip, sizeof(ip)); port = ntohs(addr->sin_port); printf("client: %s(%d) ", ip, port); } //与客户端进行通信 void do_service() { struct sockaddr_in cliAddr; socklen_t len = sizeof(cliAddr); char buff[1024]; memset(buff, 0, sizeof(buff)); //接受客户端的数据报文 //使用recvfrom而不使用recv函数的主要目的是为了获取客户端信息 if(recvfrom(sockfd, buff, sizeof(buff), 0, (struct sockaddr*)&cliAddr, &len) < 0){ perror("recvfrom error"); }else{ out_addr(&cliAddr); printf("client send info: %s ", buff); //向客户端发送数据报文 long int t = time(0); char* ptr = ctime(&t); size_t size = strlen(ptr) * sizeof(char); if(sendto(sockfd, ptr, size, 0,(struct sockaddr*)&cliAddr, len) < 0){ perror("sendto error"); //cliAddr己经从recvfrom那里得到了客户端的信息 } } } int main(int argc, char* argv[]) { if(argc < 2){ printf("usage: %s port ", argv[0]); exit(1); } //注册ctrl-c信号处理函数 if(signal(SIGINT, sig_handler) == SIG_ERR){ perror("signal sigint error"); exit(1); } /*步骤1: 创建socket*/ sockfd = socket(AF_INET, SOCK_DGRAM, 0); //SOCK_DGRAM为UDP协议 if(sockfd < 0){ perror("socket error"); exit(1); } //设置socket的相关选项 int ret; int opt = 1;//1表示启动该选项 //设置为可重新使用端口,每次启动该端口时,原来对该端口使用将失效 if((ret = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) < 0){ perror("setsockopt error"); exit(1); } /*步骤2: 调用bind函数将socket和地址进行绑定*/ struct sockaddr_in servAddr; memset(&servAddr, 0, sizeof(servAddr)); servAddr.sin_family = AF_INET; //IPv4 servAddr.sin_port = htons(atoi(argv[1])); //port servAddr.sin_addr.s_addr = INADDR_ANY; //ip if(bind(sockfd, (struct sockaddr*)&servAddr, sizeof(servAddr)) < 0){ perror("bind error"); exit(1); } /*步骤3:与客户端进行双向的数据通信*/ while(1){ do_service(); } return 0; } /* 输出结果 * [root@localhost 14.udp]# gcc -o bin/time_udp_client src/time_udp_client.c * [root@localhost 14.udp]# bin/time_udp_server 8888 * client: 127.0.0.1(48929) * client send info: hello world! * client: 127.0.0.1(32953) * client send info: hello world! * ^Cserver close * [root@localhost 14.udp]# */
//time_udp_client.c
#include <sys/socket.h> #include <netdb.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <memory.h> int main(int argc, char* argv[]) { if(argc < 3){ printf("usage: %s ip port ", argv[0]); exit(1); } /*步骤1:创建socket*/ int sockfd = socket(AF_INET, SOCK_DGRAM, 0); if(sockfd < 0){ perror("socket error"); exit(1); } /*步骤2: 调用recvfrom和sendto等函数和服务端双向通信*/ struct sockaddr_in servAddr; //封装服务器的地址信息 memset(&servAddr, 0, sizeof(servAddr)); servAddr.sin_family = AF_INET; //IPv4 servAddr.sin_port = htons(atoi(argv[2]));//端口 inet_pton(AF_INET, argv[1], &servAddr.sin_addr.s_addr); //在UDP能否调用connect来与服务端连接? //TCP中调用connect会经过三次握手,建立起双方的连接。 //但UDP中调用connect并没有建立真正的连接,而是在内核中记录了通讯双方的地址信息(如IP、por) //当UDP中调用了connect后,以后可以直接使用send而不必使用sendto来发送消息给对方。 //此外,还有一个好处就是因为sock记录了客户端的IP,使用该sockfd可以只接收指定服务器发来的消息,而不会 //接收除服务器以外其他地方发来的消息。 if(connect(sockfd, (struct sockaddr*)&servAddr, sizeof(servAddr)) < 0){ perror("connect error"); exit(1); } char buff[1024] = "hello world!"; //向服务器发送数据报文 if((sendto(sockfd, buff, sizeof(buff), 0, (struct sockaddr*)&servAddr, sizeof(servAddr))) < 0){ perror("sendto error"); exit(1); }else{ //接受服务器端的数据报文 memset(buff, 0, sizeof(buff)); size_t size; //1.为什么recv里没有指定服务器地址,却可以发送成功? //因为如果之前的sendto发送成功,则sockfd(是个结构体)里将保存通讯双方的信息,这里就可以直接使用这个sockfd来通讯 //2.为什么不需要判断recv的返回值为0?(0表示对方己关闭连接) //因为UDP是无连接的通信,通信双方是没有建立连接的,数据被传到链路层以后发送方就可以关闭,因此这里不需判断是否为0. if((size = recv(sockfd, buff, sizeof(buff), 0)) < 0){ perror("recv error"); exit(1); }else{ printf("%s", buff); } } close(sockfd); return 0; } /*输出结果 * [root@localhost 14.udp]# bin/time_udp_client 127.0.0.1 8888 * Sat Mar 18 10:01:09 2017 * [root@localhost 14.udp]# bin/time_udp_client 127.0.0.1 8888 * Sat Mar 18 10:01:12 2017 * [root@localhost 14.udp]# */