网络编程之套接字(udp)
Socket的英文原义是“孔”或“插座”。作为BSD UNIX的进程通信机制,取后一种意思。通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄,可以用来实现不同虚拟机或不同计算机之间的通信。在Internet上的主机一 般运行了多个服务软件,同时提供几种服务。每种服务都打开一个Socket,并绑定到一个端口上,不同的端口对应于不同的服务。
socket()函数原型:
int socket(int domain, int type, int protocol);
参数说明:
domain:协议域,又称协议族(family)。常用的协议族有AF_INET、 AF_INET6、AF_LOCAL(或称AF_UNIX,Unix域 Socket)、AF_ROUTE等。协议族决定了socket的地址类型,在通信 中必须采用对应的地址,如AF_INET决定了要用ipv4地(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地 址。
type:指定Socket类型。常用的sinocket类型有SOCK_STREAM、 SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、 SOCK_SEQPACKET等。流式Socket(SOCK_STREAM)是一种 面向连接的Socket,针对于面向连接的TCP服务应用。数 据报式Socket(SOCK_DGRAM)是一种无连接的Socket,对应于无连接的UDP服务应用。
protocol:指定协议。常用协议有IPPROTO_TCP、IPPROTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议。
注意:1.type和protocol不可以随意组合,如SOCK_STREAM不可以跟IPPROTO_UDP组合。当第三个参数为0时,会自动选择第二个参数类型对应的默认协议。
这次的学习任务主要是会使用UDP, TCP 进行通信;练习函数htonl(),htons(),inet_addr(),inet_hton(). 的使用,为了更深入的了解,可以自己编写代码实现;
下面代码是实现简单的UDP间的通信;
udp发送方:
1 #include <stdio.h> 2 #include <sys/socket.h> 3 #include <netinet/in.h> 4 5 int main() 6 { 7 int sockfd = 0; 8 int ret = 0; 9 unsigned char *data = "hao are you"; 10 11 /*创建套接口*/ 12 sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); 13 if(sockfd < 0) { 14 perror("socket"); 15 return 1; 16 } 17 18 /*接收方信息*/ 19 struct sockaddr_in mm; 20 mm.sin_family = PF_INET; 21 mm.sin_port = htons(2001); 22 mm.sin_addr.s_addr = htonl(0xc0a8010a); 23 24 /*发送消息*/ 25 ret = sendto(sockfd, data, 11, 0, (struct sockaddr *)&mm, sizeof(struct sockaddr_in)); 26 if(ret < 0) { 27 perror("sendto"); 28 return 1; 29 } 30 close(sockfd); //关闭套接口 31 }
UDP接收方:
1 #include <stdio.h> 2 #include <sys/socket.h> 3 #include <netinet/in.h> 4 5 int main() 6 { 7 int fd = 0; 8 int ret = 0; 9 /*建立套接口*/ 10 fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); 11 if(fd < 0) { 12 perror("socket"); 13 return 1; 14 } 15 /*接收方信息*/ 16 struct sockaddr_in mm; 17 struct sockaddr_in gg; 18 int len = 0; 19 mm.sin_family = AF_INET; 20 mm.sin_port = htons(2001); 21 mm.sin_addr.s_addr = htonl(0xc0a8010a); //192.168.1.10 (ip) 22 23 /*绑定*/ 24 ret = bind(fd, (struct sockaddr *)&mm, 16); 25 if(ret == -1) { 26 perror("bind"); 27 return 1; 28 } 29 /*接收发送来的消息*/ 30 unsigned char data[1024] = {0}; 31 ret = recvfrom(fd, data, 1024, 0, (struct sockaddr *)&gg, &len); 32 if(ret < 0) { 33 perror("recvfrom"); 34 return 1; 35 } 36 printf("send said: %s\n", data); 37 close(fd); 38 return 0; 39 40 }
从上面代码可以看出udp传输是不可靠传输,发送方只管发送,不知道接收端是否接收到,进行套接口编程的第一步就是通过socket()函数创建一个套接口,后面就需要使用函数sendto(),recvfrom()进行发送和接收,要注意的是,接收方需要通过bind()函数对自己的信息进行绑定;
下面这个程序是使用UDP进行大文件的传输:
发送方:
1 #include <stdio.h> 2 #include <sys/socket.h> 3 #include <netinet/in.h> 4 #include <fcntl.h> 5 #include <string.h> 6 7 int main() 8 { 9 int sock_fd = 0; 10 int ret = 0; 11 int fd = 0; 12 unsigned char data[1024] = {0}; 13 /*创建套接口*/ 14 sock_fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); 15 if(sock_fd < 0) { 16 perror("socket"); 17 return 1; 18 } 19 /*打开要传输的文件*/ 20 fd = open("yasm-0.8.0.tar.gz", O_RDWR); 21 if(fd < 0) { 22 perror("open"); 23 return 1; 24 } 25 26 /*添加接收方信息*/ 27 struct sockaddr_in mm; 28 mm.sin_family = AF_INET; 29 mm.sin_port = htons(2008); 30 mm.sin_addr.s_addr = inet_addr("192.168.1.10"); 31 32 unsigned char *file_name = "yasm-0.8.0.tar.gz"; 33 ret = sendto(sock_fd, file_name, strlen(file_name), 0, (struct sockaddr *)&mm, 16); 34 if(ret == -1) { 35 perror("sendto"); 36 return 1; 37 } 38 39 int sum = 0; 40 int file_size = 0; 41 while(1) { 42 usleep(2);//发送方每次睡2ms(接收方有足够时间接收) 43 file_size = read(fd, data, 1024); 44 if(file_size < 0) { 45 perror("read"); 46 return 1; 47 } 48 /*进行文件发送*/ 49 ret = sendto(sock_fd, data, file_size, 0, (struct sockaddr *)&mm, 16); 50 if(ret == -1) { 51 perror("sendto"); 52 return 1; 53 } 54 printf("file is:%d\n", file_size); 55 sum += file_size; 56 if(file_size < 1024) { 57 break; 58 } 59 memset(data, 0, 1024); //清空缓冲区 60 } 61 printf("file is:%d\n", sum); 62 close(fd); 63 close(sock_fd); 64 65 return 0; 66 }
接收方:
1 #include <stdio.h> 2 #include <sys/socket.h> 3 #include <netinet/in.h> 4 #include <fcntl.h> 5 6 int main() 7 { 8 int sock_fd = 0; 9 int fd = 0; 10 int ret = 0; 11 unsigned char file_name[18] = {0}; 12 struct sockaddr_in mm; 13 struct sockaddr_in gg; 14 int len = 0; 15 int sum = 0; 16 char data[1024] = {0}; 17 int file_size = 0; 18 19 /*创建套接口*/ 20 sock_fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); 21 if(sock_fd < 0) { 22 perror("socket"); 23 return 1; 24 } 25 /*接收方信息*/ 26 mm.sin_family = AF_INET; 27 mm.sin_port = htons(2008); 28 mm.sin_addr.s_addr =htonl(0xc0a8010a); 29 30 /*绑定信息*/ 31 ret = bind(sock_fd, (struct sockaddr *)&mm, 16); 32 if(ret == -1) { 33 perror("bind"); 34 return 1; 35 } 36 37 /*接收文件名*/ 38 ret = recvfrom(sock_fd, file_name, 17, 0, (struct sockaddr *)&gg, &len); 39 if(ret < 0) { 40 perror("recvfrom"); 41 return 1; 42 } 43 44 /*创建一个新文件用来接收*/ 45 fd = open(file_name, O_RDWR | O_CREAT, 0644); 46 if(fd < 0) { 47 perror("open"); 48 return 1; 49 } 50 51 while(1) { 52 /*接收*/ 53 file_size = recvfrom(sock_fd, data, 1024, 0, (struct sockaddr *)&gg, &len); 54 if(file_size < 0) { 55 perror("recvfrom"); 56 return 1; 57 } 58 ret = write(fd, data, file_size); 59 if(ret < 0) { 60 perror("write"); 61 return 1; 62 } 63 sum += file_size;//统计文件大小 64 if(file_size < 1024) { 65 printf("file_size: %d\n", sum); 66 break; 67 } 68 memset(data, 0, 1024); 69 } 70 close(fd); 71 close(sock_fd); 72 return 0; 73 }
传输文件的思想就是通过循环进行传输,在程序中需要注意的是:发送方在发送的时候要延迟一会儿,不然会造成接收端来不及接收,导致数据流失;