第二章 套接字类型与协议设置
2.1 套接字协议及其数据传输特性
关于协议
协议就是为了完成数据交换而定好的约定。
创建套接字
#include<sys/socket.h>
int socket(int domain, int type, int protocol);
domain 套接字中使用的协议族信息
type 套接字数据传输类型信息
protocol 计算机通信中使用的协议信息
协议族(domain)
sys/socket.h中声明的协议族名称 | 协议族 |
---|---|
PF_INET | IPv4互联网协议族(最常用) |
此处只列出最常用的,其他不常用的就不列出了。
套接字类型(type)
套接字类型 == 套接字的数据传输方式。
同一个协议族,如PF_INET协议族中,也存在多种数据传输方式。
两种常见的套接字类型(数据传输方式)如下:
套接字类型1:面向连接的套接字(SOCK_STREAM)
特点:
- 传输过程中数据不会消失
- 按序传输数据
- 传输的数据不存在数据边界
- 面向连接的套接字只能与另外一个同样特性的套接字连接
总结起来就是:可靠的、按序传递的、基于字节的、面向连接的数据传输方式的套接字
关于不存在数据边界的问题解释:
①传输数据的计算机通过3次调用write函数传递了100字节数据,但接收数据的计算机仅通过1次read函数调用就接收了全部100字节。
②收发数据的套接字内部有缓冲(就是字节数组)。通过套接字传输的数据将保存到该数组,因此,收到数据不意味着立马调用read函数。只要不超过数组容量,则有可能在数据填充满缓冲区后通过1次read函数调用读取全部,也有可能分成多次read函数调用进行读取。
③面向连接的套接字中,read函数和write函数的调用次数并无太大意义。也就是,面向连接的套接字不存在数据边界。
套接字缓冲已满是否意味着数据丢失
由于可以调用read从缓冲区读取部分数据,所以,缓冲区并不全是满的。但read函数读取速度若比接收数据的速度慢,那么缓冲区有可能被填满。此时,套接字无法再次接收数据。
但是!!即使这样,也不会发生数据丢失,因为传输端套接字将停止传输。也就是说,面向连接的套接字会根据接收端的状态传输数据,如果传输出错还能提供重传服务。
因此,面向连接的套接字除特殊情况外不会发生数据丢失。
套接字类型2:面向消息的套接字(SOCK_DGRAM)
特点:
- 强调快速传输而非传输顺序
- 传输的数据可能丢失也可能损毁
- 传输的数据有数据边界
- 限制每次传输的数据大小
有数据边界: 发送者发送两次,接受者也得接收两次。这种特性就是传输的数据有数据边界。
总结起来就是:不可靠的、不按序传递的、以数据的高速传输为目的的套接字。
面向消息的套接字不存在连接的概念。
协议的最终选择
int tcp_socket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); 建立一个TCP套接字
int udp_socket = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); 建立一个UDP套接字
面向连接的套接字:TCP套接字示例
更改hello_client.c
中read
的次数,每次读取一个字节,后来发现共读取了13次。(Hello World!)
2.2 Windows平台下的实现及验证
Windows操作系统的socket函数
函数名和参数名都与Linux平台相同,只有返回值类型稍有不同。
#include<winsock2.h>
SOCKET socket(int af, int type, int protocol);
成功时返回socket句柄,失败时返回INVALID_SOCKET
SOCKET
可以看做是保存套接字句柄的一个数据类型,其实socket返回整数,可以通过int接收,但是考虑到以后的扩展性,还是用SOCKET接收。
失败返回值INVALID_SOCKET,是个常数,一般为-1,但是最好还是写成INVALID_SOCKET,否则如果微软更改了这个常数值,那么修改工作量会特别大。
2.3 习题
参考
(1)什么是协议?在收发数据中定义协议有何意义?
协议就是为了完成数据交换而定好的约定。因此,定义协议意味着对数据传输所必需的的承诺进行定义。
(2)面向连接的TCP套接字传输特性有3点,请分别说明。
- 传输过程中数据不会丢失
- 按序传输数据
- 传输的数据不存在数据边界(Boundary)
(3)面向消息的套接字的特性?
- 传输数据可能丢失
- 有数据边界
- 以快速传递为目标
- 限制每次传递数据的大小
- 与面向连接的套接字不同,不存在连接的概念
(5)何种类型的套接字不存在数据边界?这类套接字接收数据时需要注意什么?
连接指向型TCP
套接字不存在数据边界。
因此输入输出函数的响应次数不具有意义。重要的不是函数的响应次数,而是数据的收发量。因此,必须将传输数据的量和接收数据的量制作成编码,保证发送数据的量和接收数据的量是一致的,特别要注意是制作依赖函数响应次数判断代码。
(6)修改代码,使客户端仅用一次read读取服务器端多次write发出的内容:
tcp_serv.c
/*****************************tcp_serv.c*********************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.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);
}
serv_sock=socket(PF_INET, SOCK_STREAM, 0);
if(serv_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=htonl(INADDR_ANY);
serv_addr.sin_port=htons(atoi(argv[1]));
if(bind(serv_sock, (struct sockaddr*) &serv_addr, sizeof(serv_addr))==-1)
error_handling("bind() error");
if(listen(serv_sock, 5)==-1)
error_handling("listen() error");
clnt_addr_size=sizeof(clnt_addr);
clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_addr,&clnt_addr_size);
if(clnt_sock==-1)
error_handling("accept() error");
write(clnt_sock, message, 4);
write(clnt_sock, message+4, 4);
write(clnt_sock, message+8, 4);
write(clnt_sock, message+12, sizeof(message)-12);
close(clnt_sock);
return 0;
}
void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
tcp_clnt.c
/*****************************tcp_clnt.c*********************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.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, i;
if(argc!=3){
printf("Usage : %s <IP> <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!");
for(i=0; i<100; i++) // busy waiting!!
printf("Wait time %d \n", i);
read(sock, message, sizeof(message));
printf("Message from server: %s \n", message);
close(sock);
return 0;
}
void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}