上一篇文章我们简单讲了TCP服务端和客户端程序的编写,但是对于里面函数并没有具体的说明。接下来具体说明一下 socket 函数。
socket 函数主要用于创建套接字,它的基本形式如下:
- int socket (int domain, int type, int protocel)
- domain: 套接字中使用的协议族信息
- type: 套接字传输类型信息
- protocol: 计算机间通信中使用的协议信息
- 返回值: 成功时返回文件描述符,失败时返回-1
1. 协议族
socket 函数的第一个参数为套接字中使用的协议分类信息,成为协议族,可分为如下几类:
- PF_INET : IPv4互联网协议族
- PF_INET6: IPv6互联网协议族
- PF_LOCAL : 本地通信的UNIX协议族
- PF_PACKET : 底层套接字的协议族
- PF_IPX : IPX Novell协议族
之前的例子中,使用的是 PF_INET 协议族,另外套接字中实际采用的最终协议信息是通过 socket 函数的第三个参数传递的。在指定的协议族范围内通过第一个参数决定第三个参数。
2. 套接字类型
套接字类型指的是套接字数据的传输方式,即使用TCP还是UDP传输。
(1)面向连接的套接字(SOCK_STREAM)
如果向 socket 函数的第二个函数传递 SOCK_STREAM , 将创建面向连接套接字。面向连接套接字具有如下特点
- 传输过程中数据不会丢失。
- 按顺序传输数据。
- 传输的数据不存在数据边界。
- 套接字必须一一对应。
收发数据的套接字中具有缓冲区,通过套接字传输的数据保存在缓冲区中,因此收到数据并不意味之马上调用 recv 函数,只要不超过缓冲区的数据容量,则有可能在数据填充满缓冲后调用一次read函数读取全部数据。也可能分多次read函数进行读取。因此调用 read 和 write 函数的次数没有太大的意义。所以说 传输的数据是不存在数据边界的。
如果缓冲区的数据满了会怎么样呢? 首先调用 read 函数读取缓冲区的部分数据,如果 read 函数的读取速度比接受的速度满,则缓冲取可能被填满。此时套接字无法再接受数据,但即使这样也不会发生数据丢失,因此传输数据的套接字将停止传输。也就是说,面向连接的套接字会根据接收端的状态传输数据,如果传输出错还会重传。
用一句话总结面向连接的套接字就是:
可靠的、按序传输的、基于字节的面向连接的数据传输方式的套接字
(2)面向消息的套接字(SOCK_DGRAM)
如果向 socket 函数的第二个参数传递 SOCK_DGRAM 参数,则创建面向消息的套接字。面向消息的套接字有如下特点:
- 强调快速传输而非传输顺序。
- 传输的数据可能丢失也可能被销毁。
- 传输的数据有数据边界。
- 限制每次传输数据的大小。
面向消息的套接字比面向连接的套接字具有更快的传输速度,但是无法避免的数据丢失和销毁。另外,每次传输数据大小具有一定的限制,并存在数据边界。存在数据边界意味着接收数据的次数和传输次数相同,总结面向消息的套接字一句话就是:
不可靠的、不按序传递的、以数据的高速传输为目的的套接字。
3. 协议的最终选择
下面介绍 socket 函数的最后一个参数,该参数决定最终采用的协议。前面的程序中,最后一个参数填的0,除非遇到如下情况:
同一协议族中存在多个数据传输方式相同的协议
在参数 PF_INET 指 IPv4 网络族中,SOCK_STREAM 面向连接的数据传输。满足以上两个条件的只有 IPPROTO_TCP,因此可以使用下面的方式创建 TCP套接字
int tcpsocket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
SOCK_DGRAM 指向的面向消息的传输方式中,满足以上条件的只有 IPPROTO_UDP, 因此可以使用下面的方式创建 UDP套接字
int udpsocket = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
4. Windows中的socket函数
上面介绍的为 Linux 的socket函数,windows中socket函数跟Linux中的使用是相同,唯一不同的是返回值,这里再次给出 socket 函数的声明:
- #include <winsock2.h>
- SOCKET socket(int domain, int type, int protocel)
- 返回值:成功返回socket句柄,失败返回 INVALID_SOCKET