协议与套接字类型
文章目录
本章主要讲的是套接字类型以及简单的协议
协议概念理解
计算机网络当中有各种各样的协议,每种协议都是为了解决特定问题而出现的。例如,相隔很远的两个人进行通信,如果一方采用电话,则另外一方也必须采用电话而非书信。因此采用电话进行通信就是两个人进行通信的协议。协议的具体内容可以去参照其他解答,这里不再赘述。
套接字
在计算机网络编程-基础篇01 当中我们使用了套接字,但是没有深入阐述套接字。因此本篇主要是阐述套接字的使用。
创建套接字
#include<sys/socket.h>
int socket(int domain,int type,int protocol);
domain: 套接字中使用的协议族信息。
type:套接字数据传输类型信息
protocol:计算机间通信中使用的协议信息
接下来我们一一分析该函数的前两个参数。
协议族(Protocol Family)
头文件sys/socket.h 中声明的协议族
名称 | 协议族 |
---|---|
PF_INET | IPv4 互联网协议族 |
PF_INET6 | IPv6互联网协议族 |
PF_LOCAL | 本地通信的UNIX协议族 |
PF_PACKET | 底层套接字的协议族 |
PF_IPX | IPX Novell协议族 |
我们主要记住前两个协议族信息就OK,其他的并不常用亦或是尚未普及。正如表中所述,协议族是许多相关具体协议的集群,而socket函数第三个参数(protocol)就是用来制定第一个参数(协议族)里面的某个具体的协议,第三个参数常用的协议有:IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、等等。一般我们设置第三个参数为0代表选择type类型(第二个参数)默认的协议,除非“同一协议族存在多个传输方式相同的协议”我们才将第三个参数设置成具体的 协议信息。
套接字类型(Type)
套接字类型指的是数据传输的方式,例如:面向连接的套接字(TCP套接字)、面向消息的套接字(UDP套接字)。也就是说,即使我们通过第一个参数指定协议族依然不能确定数据的传输方式,每种协议族都有各种数据传输的方式,因此需要特别指定。接下来我们主要介绍两种主要的传输方式的特点:TCP 、UDP(注意不仅仅有这两种传输方式,但这两种是比较常用的)
面向连接的套接字(SOCK_STREAM)
-
特点
-
传输过程中数据不会丢失(这块内容需要单独分出一块来讲,我会在接下来的博客当中讲)。
-
按序传输数据(类似于队列)。
-
传输的数据不存在边界。(可以看实例中的代码)
将数据分100批发送,但是接受者不会因为一个包达到之后接受,而是等到100个包到达之后才接受。在计算机里面的体现就是:传输数据的计算机通过调用100次write函数传递了100字节的数据,但接受者只通过调用一次read函数就把所有数据接受了。
其实接收者有一个缓冲数组用于存放来自发送者发送的包,只要缓冲数组没满,我们就可以继续接收直到数据接收完毕或者缓冲区满员才调用一次read函数读取(接收)
-
-
细节
-
套接字连接必须一一对应
面向连接的套接字只能与另外一个同样特性的套接字连接
-
综上所述,这种传输方式可以总结出一句话:可靠的、按序传递的、基于字节的面向连接的数据传输方式的套接字
面向消息的套接字(SOCK_DGRAM)
-
特点
-
强调快速传输而非传输顺序
-
传输数据可能丢失也可能被损毁
-
传输的数据有数据边界
即:发送方一个个包发送,接收方一个个接收。
-
限制每次传输的数据大小
综上所述:这种传输方式也可以总结为一句话:不可靠的、不按序传递的、以数据的高速传输为目的的套接字。
-
实例
本实例是基于前面计算机网络编程-基础篇01 中代码修改而来。具体差别:
hello_server.c :无任何变化
hello_client.c : 更改read函数的调用方式
该实例验证如下特性:
“传输的数据不存在数据边界”
-
客户端(tcp_client.c)
#include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<arpa/inet.h> #include<sys/socket.h> #include<string.h> void error_handling(char *message); int main(int argc, char* argv[]) { int sock; struct sockaddr_in serv_addr; char message[30]; int str_len=0; int idx=0,read_len=0; if(argc!=3) { printf("Usage:%s <port>\n",argv[0]); exit(1); } sock=socket(PF_INET,SOCK_STREAM,0); if(sock==-1) error_handling("socket() error"); memset(&serv_addr,0,sizeof(serv_addr)); serv_addr.sin_family=AF_INET; serv_addr.sin_addr.s_addr=inet_addr(argv[1]); serv_addr.sin_port=htons(atoi(argv[2])); if(connect(sock,(struct sockaddr*)&serv_addr,sizeof(serv_addr))==-1)error_handling("connect() error"); while(read_len=read(sock,&message[idx++],1)) { if(read_len==-1)error_handling("read() error"); str_len+=read_len; } printf("Message from server:%s \n",message); printf("Function read call count:%d \n",str_len); close(sock); return 0; } void error_handling(char *message) { fputs(message,stderr); fputc('\n',stderr); exit(1); }
-
服务端(tcp_server.c)
#include<stdio.h> #include<stdlib.h> #include<string.h> #include<unistd.h> #include<arpa/inet.h> #include<sys/socket.h> void error_handling(char *message); int main(int argc, char *argv[]) { int serv_sock; int clnt_sock; struct sockaddr_in serv_addr; struct sockaddr_in clnt_addr; socklen_t clnt_addr_size; char message[]="Hello world"; if(argc!=2) { printf("Usage:%s <port>\n",argv[0]); exit(1); } //调用socket函数创建套接字 serv_sock=socket(PF_INET,SOCK_STREAM,0); if(serv_sock==-1)error_handling("sock() error"); memset(&serv_addr,0,sizeof(serv_addr)); serv_addr.sin_family=AF_INET; serv_addr.sin_addr.s_addr=htonl(INADDR_ANY); serv_addr.sin_port=htons(atoi(argv[1])); //调用bind()函数分配IP地址以及端口号 if(bind(serv_sock,(struct sockaddr*)&serv_addr,sizeof(serv_addr))==-1) error_handling("bind() error"); //调用listen()函数将套接字转为可接收连接的状态 if(listen(serv_sock,5)==-1) error_handling("listen() error"); clnt_addr_size=sizeof(clnt_addr); //调用accept函数受理连接请求。该函数属于阻塞型函数,直到有连接请求才会执行下面的程序 clnt_sock=accept(serv_sock,(struct sockaddr*)&clnt_addr,&clnt_addr_size); if(clnt_sock==-1) error_handling("accept() error"); //连接完成之后,系统将套接字看做为一个文件(句柄为socket返回的文件描述符) write(clnt_sock,message,sizeof(message)); close(clnt_sock); close(serv_sock); return 0; } void error_handling(char *message) { fputs(message,stderr); fputc('\n',stderr); exit(1); }
编译运行方式:
gcc tcp_server.c -o tcp_server
gcc tcp_client.c -o tcp_client
运行:
先运行服务端:./tcp_server 9091
再运行客户端:./tcp_client 127.0.0.1 9091