前一篇文章,Linux进程间通讯——使用流套接字介绍了一些有关socket(套接字)的一些基本内容,并讲解了流套接字的使用,这篇文章将会给你们讲讲,数据报套接字的使用。php
socket,即套接字是一种通讯机制,凭借这种机制,客户/服务器(即要进行通讯的进程)系统的开发工做既能够在本地单机上进行,也能够跨网络进行。也就是说它可让不在同一台计算机但经过网络链接计算机上的进程进行通讯。也由于这样,套接字明确地将客户端和服务器区分开来。html
相对于流套接字,数据报套接字的使用更为简单,它是由类型SOCK_DGRAM指定的,它不须要创建链接和维持一个链接,它们在AF_INET中一般是经过UDP/IP协议实现的。它对能够发送的数据的长度有限制,数据报做为一个单独的网络消息被传输,它可能会丢失、复制或错乱到达,UDP不是一个可靠的协议,可是它的速度比较高,由于它并一须要老是要创建和维持一个链接。安全
使用数据报socket进行进程通讯的进程采用的客户/服务器系统是如何工做的呢?服务器
一、服务器端网络
与使用流套接字同样,首先服务器应用程序用系统调用socket()来建立一个套接安,它是系统分配给该服务器进程的相似文件描述符的资源,它不能与其余的进程共享。dom
接下来,服务器进程会给套接字起个名字(监听),咱们使用系统调用bind()来给套接字命名。而后服务器进程就开始等待客户链接到这个套接字。socket
不一样的是,而后系统调用recvfrom()来接收来自客户程序发送过来的数据。服务器程序对数据进行相应的处理,再经过系统调用sendto()把处理后的数据发送回客户程序。函数
与流套接字程序相比:ui
二、客户端spa
基于数据报socket的客户端比服务器端简单,一样,客户应用程序首先调用socket来建立一个未命名的套接字,与服务器同样,客户也是经过sendto()和recvfrom()来向服务器发送数据和从服务器程序接收数据。
与流套接字程序相比:
使用数据报套接字的客户程序并不须要使用connect()系统调用来链接服务器程序,它只要在须要时向服务器所监听的IP端口发送信息和接收从服务器发送回来的数据便可。
socket的接口函数声明在头文件 sys/types.h 和 sys/socket.h 中。
一、建立套接字——socket()系统调用
该函数用来建立一个套接字,并返回一个描述符,该描述符能够用来访问该套接字,它的原型以下:
int socket(int domain, int type, int protocol);
函数中的三个参数分别对应前面所说的三个套接字属性。protocol参数设置为0表示使用默认协议。
二、命名(绑定)套接字——bind()系统调用
该函数把经过socket()调用建立的套接字命名,从而让它能够被其余进程使用。对于AF_UNIX,调用该函数后套接字就会关联到一个文件系统路径名,对于AF_INET,则会关联到一个IP端口号。函数原型以下:
int bind( int socket, const struct sockaddr *address, size_t address_len);
成功时返回0,失败时返回-1;
三、发送数据——sendto()系统调用
该函数把缓冲区buffer中的信息给送给指定的IP端口的程序,原型以下:
int sendto(int sockfd, void *buffer, size_t len, int flags, struct sockaddr *to, socklen_t tolen);
buffer中储存着将要发送的数据,len是buffer的长度,而flags在应用中一般被设置为0,to是要发送数据到的程序的IP端口,tolen是to参数的长度。
成功时返回发送的数据的字节数,失败时返回-1。
四、接收数据——recvfrom()系统调用
该函数把发送给程序的信息储存在缓冲区buffer中,并记录数据来源的程序IP端口,原型以下:
int recvfrom(int sockfd, void *buffer, size_t len,int flags, struct sockaddr *src_from, socklen_t *src_len);
buffer用于储存接收到的数据,len指定buffer的长度,而flags在应用中一般被设置0,src_from若不为空,则记录数据来源程序的IP端口,若src_len不为空,则其长度信息记录在src_len所指向的变量中。
注意:默认状况下,recvfrom()是一个阻塞的调用,即直到它接收到数据才会返回。
五、关闭socket——close()系统调用
该系统调用用来终止服务器和客户上的套接字链接,咱们应该老是在链接的两端(服务器和客户)关闭套接字。
下面用多个客户程序实例和一个服务器程序来演示多个进程如何经过使用数据报socket来进行通讯。
sockserver2.c是一个服务器程序,它接收客户程序发来的数据,并建立一个子进程来处理客户发送过来的数据,处理过程很是简单,就是把大写字母改成小写。而后把处理后的数据(大写字母对应的小写字母)发送回给客户端。
sockclient2.c是一个客户程序,它向服务器程序发送数据,并接收服务器发送过来的处理后的数据(即小写字母),而后把接收到的数据输出到屏幕上。在运行客户程序时,你能够为它提供一个字符做为参数,此时客户程序将把些字符做为要处理的数据发送给服务器,若是不提供一个参数,则默认发送字符A。
sockserver2.c的源代码以下:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #include <signal.h> int main(int arc, char **argv) { int server_sockfd = -1; socklen_t server_len = 0; socklen_t client_len = 0; char buffer[512]; ssize_t result = 0; struct sockaddr_in server_addr; struct sockaddr_in client_addr; // 建立数据报套接字 server_sockfd = socket(AF_INET, SOCK_DGRAM, 0); // 设置监听的端口、IP server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = htonl(INADDR_ANY); server_addr.sin_port = htons(9739); server_len = sizeof(server_addr); // 绑定(命名)套接字 bind(server_sockfd, (struct sockaddr *)&server_addr, server_len); // 忽略子进程中止 或 退出 的信息 signal(SIGCHLD, SIG_IGN); while (1) { // 接收数据,用 client_addr 来储存数据来源程序的IP端口 result = recvfrom(server_sockfd, buffer, sizeof(buffer), 0, (struct sockaddr *)&client_addr, &client_len); if (fork() == 0) { // 利用子进程来处理数据 buffer[0] += 'a' - 'A'; sleep(1); // 发送处理后的数据 sendto(server_sockfd, buffer, sizeof(buffer), 0, (struct sockaddr *)&client_addr, client_len); printf("%c\n", buffer[0]); // 注意,必定要关闭子进程,不然程序运行会不正常 exit(0); } } // 关闭套接字 close(server_sockfd); }
sockclient2.c的源代码以下:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <netinet/in.h> #include <arpa/inet.h> int main(int argc, char **argv) { struct sockaddr_in server_addr; socklen_t server_len = 0; int sockfd = -1; char c = 'A'; // 取第一个参数的第一个字符 if (argc > 1) { c = argv[1][0]; } // 建立数据报套接字 sockfd = socket(AF_INET, SOCK_DGRAM, 0); // 设置服务器IP、端口 server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); server_addr.sin_port = htons(9739); server_len = sizeof(server_addr); // 向服务器发送数据 sendto(sockfd, &c, sizeof(char), 0, (struct sockaddr *)&server_addr, server_len); // 接收服务器处理后发送过来的数据,因为不关心数据来源,因此把后两个参数设为0 recvfrom(sockfd, &c, sizeof(char), 0, 0, 0); printf("char from server = %c\n", c); // 关闭套接字 close(sockfd); exit(0); }
运行结果以下:
先运行服务器程序,以下:
再运行三个客户端:以下:
在本例子中,咱们启动了一个服务器程序和三个客户程序,从运行的结果来看,客户端发送给服务器程序的全部请求都获得了处理,即把大写字母变成了小写。recvfrom()调用是阻塞的调用,即只有当接收到数据才会返回。
一、从使用的便利和效率来说
咱们能够看到使用数据报套接字的确是比使用流套接字简单,并且快速。
由于使用流套接字的程序,客户程序须要调用connect()来建立一个到服务器程序的链接,并须要维持这个链接,服务器程序也须要调用listen()来建立一个队列来保存未处理的请求,当有数据到达时,服务器也不须要调用accept()来接受链接并建立一个新socket描述符来处理请求。
再来看看使用数据报套接字的程序,服务器程序与客户程序所使用的系统调用大体相同,服务器程序只比客户程序多使用了一个bind()调用。基于数据报套接字的程序,只须要使用sendto()调用来向指定IP端口的程序发送信息,使用recvfrom()调用从指向的IP端口接收信息便可。由于它并不须要创建一个链接,接受链接等,因此省去了不少的功夫。
二、从使用场合来说
咱们知道流套接字是基于TCP/IP协议的,它是一种安全的协议,提供的是一个有序、可靠、双向字节流的链接,发送的数据能够确保不会丢失、重复或乱序到达,并且它还有必定的出错后从新发送的机制。因此它比较适合用来发送信息量大的数据文件,或对数据完整性要求较高的文件,如压缩文件、视频文件等
而数据报套接字是基于UDP/IP协议实现的。它对能够发送的数据的长度有限制,数据报做为一个单独的网络消息被传输,它可能会丢失、复制或错乱到达,UDP不是一个可靠的协议,可是它的速度比较高。因此它比较适合发送一些对实时性要求较高,可是对安全性和完整性要求不过高的数据。如咱们熟悉的聊天信息,即便有一点的丢失也不会形成理解上的大的问题。
参考: