TCPIP 网络编程-02-套接字类型与协议设置

第一章《TCPIP 网络编程-01-入门》讲到了 socket 函数,这里将详细看看套接字逻辑

1
2
3
4
5
6
7
8
#include <sys/socket.h>
/**
* @brief 创建套接字
* domain 套接字中使用的协议族信息
* type 套接字数据传输类型的信息
* protocol 计算机见通信中使用的协议信息
*/
int socket(int domain, int type, int protocol);

domain参数代表的协议族分类可分为以下几类

  • PF_INET: IPv4 互联网协议族
  • PF_INET6: IPv6 互联网协议族
  • PF_LOCAL: 本地通信的 UNIX 协议族
  • PF_PACKET: 底层套接字的协议族
  • PF_IPX: IPX Novell 协议族

type 参数代表的套接字数据的传输方式

  • SOCK_STREAM: 面向连接的套接字(TCP)。 面向连接的特点,传输过程中数据不会消失,按序传输数据,传输的数据不存在数据边界(面向连接的可靠字节流)
    • 可靠的(不丢失)
    • 按序传输数据
    • 传输的数据没有边界
  • SOCK_DGRAM: 面向消息的套接字(UDP)。有以下特点
    • 强调快速而非传输顺序
    • 传输的数据可能丢失也可能损毁
    • 传输数据有边界
    • 限制每次传输的数据大小

protocol 参数代表决定了最终的协议,为什么前两个参数无法决定?

因为同一协议族中可能存在多个数据传输方式相同的协议。所以一般可以传一个 0 表示使用默认协议

Tips:

  1. 为了接收数据,套接字内部有字节数组缓冲区。调用 read 函数从缓冲区读取部分数据,所以缓冲区并不总是满的。即使读的比较慢,缓冲区满了,套接字无法再接受数据,也不会发生数据丢失。因为套接字会根据接收端的状态传输数据(滑动窗口),传输错误也会有重传服务

示例地址:https://github.com/XBoom/network-ip.git 中的 apps/socket/03/

运行结果如下:

1
2
3
4
[03/server.c:44 main info] server recv 48 len msssage:
client message
client message
client message

可以看到,即使一个一个字符的读取也是能读到所有的内容(注意字符串结束符会影响打印)

问题 1:既然数据是没有边界的,那么上层是如何获取对应的数据结构的?

  • 固定长度的消息:每次读取固定长度,既浪费空间也不灵活
  • 消息头包含长度信息:在消息头包含一个字段,表示整个消息的长度。接收方先读消息头,然后根据长度信息读取对应长度的数据
  • 使用消息格式协议:使用 JSON、 XML 等消息格式协议
  • 使用消息起始标志与结束标志:通过检测这些标志来确定消息的边界

问题 2:如何使用 SOCK_DGRAM 又如何编程?

示例地址:https://github.com/XBoom/network-ip.git 中的 apps/socket/04/

区别在于 客户端并不需要执行连接请求,只需要在发送的时候指定发送地址。而服务端也不需要 bind/listen

1
2
3
4
5
6
7
8
for(int i = 0; i < 3; i++) 
{
char message[MAX_BUFF_LEN] = "client message \n";

ret = sendto(server_sock, message, strlen(message) + 1,
0, (const struct sockaddr*)&server_addr, server_addr_len);
CHECK_RET(ret == -1, "sendto failed");
}

而服务端也不需要监听,直接读取数据

1
2
3
4
5
6
while((read_len = recvfrom(server_sock, &message[str_len], MAX_BUFF_LEN, 0,
(struct sockaddr *)&client_addr, &clietn_addr_len)) > 0)
{
LOG_INFO("recv %d %d", str_len, read_len);
str_len += read_len;
}

而且数据也不一个一个读取,这是读取 MAX_BUFF_LEN 的结果

1
2
3
[04/server.c:32 main info] recv 0 17 
[04/server.c:32 main info] recv 17 17
[04/server.c:32 main info] recv 34 17

而这是一次读取一个的情况

1
2
3
[04/server.c:32 main info] recv 0 1 
[04/server.c:32 main info] recv 1 1
[04/server.c:32 main info] recv 2 1

每次读取一个之后整个数据其实就丢了

最后来看看正常情况下是不是实现了 UDP 通信

Snipaste_2024-01-01_18-59-41

可以看到 UDP 并不需要三次握手也就不需要 connect 以及 listen 这类操作直接进行数据发送与接收,详细的过程后续在介绍 UDP 在详细查看

参考文档

  1. 《TCPIP 网络编程》