1. TCP/IP协议栈
1.1 链路层
链路层是物理链路连接标准化的结果,也是最基本的一层,是所有网络协议栈的基石。
1.2 IP层
IP层通过使用链路层来发送数据,IP层面向的是一个个的数据包,通过选定发送路径将数据包发送出去。因为IP层只负责传输数据包,因此当传输路径发送错误时,IP层可以重新选择路径并重新发送,但如果数据包发生错误或丢失,IP层使无法解决的。
1.3 TCP/UDP层
因IP层无法解决数据丢失/发生错误的情况,因此由上层的TCP/UDP层来对数据包进行控制,利用IP层提供的网络路径信息完成数据。同时TCP由于存在三次握手、四次挥手、数据包接收确认、重传等机制,可以实现数据包的可靠传输。
1.4 应用层
应用层运行的是我们的应用程序,使用socket相关接口完成网络通信、数据传输。
2. 基于TCP的服务端函数调用
下图为基于TCP的服务端socket函数调用顺序:
下面我们一一介绍各API。
2.1 listen函数
listen
函数的函数原型:
/**
* @brief 监听连接请求
* @param[in] sock 要监听的套接字
* @param[in] backlog listen队列长度,若队列为5,表示最多可以使5个连接请求进入队列。
* @return 0:成功 -1:失败
*/
int listen(int sock, int backlog);
listen
函数用于创建队列,调用listen
后的套接字,系统会为其维护两个队列:未完成连接队列、已完成连接队列,两个队列的长度不能大于backlog
,其中未完成队列保存未完成三次握手的连接,已完成队列保存完成三次握手的队列。
2.2 accept函数
accept
函数的函数原型:
/**
* @brief 接收客户端连接
* @param[in] sock 服务器套接字
* @param[out] addr 客户端地址信息
* @param[out] addrlen 客户端地址信息长度
* @return -1:失败 其他:发起连接的客户端套接字
*/
int accept(int sock, struct sockaddr *addr, socklen_t *addrlen);
默认状态下,当调用accept
函数后,服务端程序会处于阻塞状态。`<br />
accept函数`负责受理客户端的连接请求,当连接成功建立后,函数会将高链接移入已完成连接队列,并返回。
3. 基于TCP的客户端函数调用
下图为函数调用顺序:
客户端不需要添加网络地址信息,这些都会在执行connect``函数
时由系统处理:
- 添加操作发生在操作系统内核中。
- 端口、IP信息由操作系统选取,IP地址为本机地址,端口为本机随机空闲端口。
3.1 connect函数``
connect
函数的函数原型为: ```cpp /**- @brief 发起连接请求
- @param[in] sock 客户端套接字
- @param[in] addr 服务端地址信息
- @param[in] addrlen 服务端地址信息长度
- @return 0:成功 -1:失败 */
int connet(int sock, struct sockaddr *addr, socklen_t addrlen);
`connect``函数`向服务器发起连接请求,若连接成功建立,则返回`0`,若连接建立失败,则返回`-1`。
<a name="pR6nz"></a>
### 4. 三次握手与socket API的关系
<br />我们可以看出:
- 三次握手请求由`客户端通过connet函数`发起,服务器只是在被动等待三次握手请求
- `connect``函数`返回发生在三次握手的第二步完成之后
- `accept``函数`返回发生在三次握手完成之后
<a name="zV8Gd"></a>
### 5. 实例程序:
```cpp
// server.c
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#define ACCEPT_NUM 5
#define BUFFER_SIZE 1024
int main(int argc, char *argv[])
{
if (argc < 2)
{
return -1;
}
int i = 0;
int read_len = 0;
socklen_t accept_len = 0;
char buf[BUFFER_SIZE];
int sock_server = 0;
int sock_client = 0;
struct sockaddr_in addr_server;
struct sockaddr_in addr_client;
// 初始化服务器套接字
sock_server = socket(PF_INET, SOCK_STREAM, 0);
if (-1 == sock_server)
{
printf("初始化套接字错误 \n");
return -1;
}
// 初始化网络地址
memset(&addr_server, 0, sizeof(struct sockaddr_in));
addr_server.sin_family = AF_INET;
addr_server.sin_addr.s_addr = inet_addr(argv[1]);
addr_server.sin_port = htons(atoi(argv[2]));
if (-1 == bind(sock_server, (struct sockaddr *)&addr_server, sizeof(addr_server)))
{
printf("绑定网络地址到套接字失败 \n");
return -1;
}
if (-1 == listen(sock_server, ACCEPT_NUM))
{
printf("监听网络套接字失败 \n");
return -1;
}
for (i = 0; i < ACCEPT_NUM; ++i)
{
sock_client = accept(sock_server, (struct sockaddr *)&addr_client, &accept_len);
if(-1 == sock_client)
{
printf("accept失败 \n");
}
else
{
printf("与套接字 %d 客户端建立连接 \n", sock_client);
}
while ((read_len = read(sock_client, buf, BUFFER_SIZE)) != 0)
{
buf[read_len] = '\0';
write(sock_client, buf, read_len);
}
close(sock_client);
}
close(sock_server);
return 0;
}
// client.c
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#define BUFFER_SIZE 1024
int main(int argv, char *argc[])
{
if (2 > argv)
{
return -1;
}
int i = 0, write_len = 0, read_len = 0;
char buf[BUFFER_SIZE] = {0};
int sock_client = 0;
struct sockaddr_in addr_server;
// 创建客户端套接字
sock_client = socket(PF_INET, SOCK_STREAM, 0);
if (-1 == sock_client)
{
printf("初始化套接字错误 \n");
return -1;
}
// 初始化要连接的服务器网络地址
memset(&addr_server, 0, sizeof(struct sockaddr_in));
addr_server.sin_family = AF_INET;
addr_server.sin_addr.s_addr = inet_addr(argc[1]);
addr_server.sin_port = htons(atoi(argc[2]));
if (-1 == connect(sock_client, (struct sockaddr*)&addr_server, sizeof(struct sockaddr)))
{
printf("连接服务器失败 \n");
return -1;
}
while (1)
{
fputs("请输入字符串:", stdout);
fgets(buf, BUFFER_SIZE, stdin);
if (0 == strcmp(buf, "q\n") || 0 == strcmp(buf, "Q\n"))
{
break;
}
write_len = write(sock_client, buf, strlen(buf));
while (read_len < write_len)
{
read_len += read(sock_client, &buf[read_len], BUFFER_SIZE);
}
buf[read_len] = '\0';
read_len = 0;
printf("接收到的字符串:%s \n", buf);
}
close(sock_client);
}