《TCP/IP 网络编程》第四章——基于 TCP 的服务端/客户端(学习笔记)

代码链接

第四章 基于 TCP 的服务端/客户端(1)

本章将具体讨论这种面向连接的服务器端/客户端的编写。

4.1 理解 TCP 和 UDP

根据数据传输方式的不同,基于网络协议的套接字一般分为 TCP 套接字和 UDP 套接字。因为 TCP 套接字是面向连接的,因此又称基于流(stream)的套接字。

TCP 是 Transmission Control Protocol(传输控制协议)的简写,意为『对数据传输过程的控制』。因此,学习控制方法及范围有助于正确理解 TCP 套接字。

4.1.1 TCP/IP 协议栈

TCP/IP 协议栈共分 4 层,可以理解为数据收发分成了 4 个层次化过程。

TCP 套接字收发数据时需要借助:链路层 - IP 层 - TCP 层 - 应用层(由下至上)

UDP 套接字收发数据时需要借助:链路层 - IP 层 - UDP 层 - 应用层(由下至上)

4.1.2 TCP/IP 协议的诞生背景

4.1.3 链路层

链路层是物理链接领域标准化的结果,也是最基本的领域,专门定义 LAN、WAN、MAN 等网络标准。若两台主机通过网络进行数据交换,则需要物理连接,链路层就负责这些标准。

4.1.4 IP 层

准备好物理连接后就要传输数据。为了在复杂的网络中传输数据,首先要考虑路径的选择。向目标传输数据需要经过哪条路径?解决此问题的就是 IP 层,该层使用的协议就是 IP。

IP 本身是面向消息的、不可靠的协议。每次传输数据时会帮我们选择路径,但并不一致。如果传输中发生路径错误,则选择其他路径,但如果发生数据丢失或错误,则无法解决。换言之,IP 协议无法应对数据错误。

4.1.5 TCP/UDP 层

TCP 和 UDP 层以 IP 层提供的路径信息为基础完成实际的数据传输,故该层又称传输层(Transport)。UDP 比 TCP 简单,我们将在后续章节展开讨论,现只解释 TCP。TCP 可以保证可靠的数据传输,但它发送数据时以 IP 层为基础。

IP 层只关注 1 个数据包(数据传输的基本单位)的传输过程。因此,即使传输多个数据包,每个数据包也是由 IP 层实际传输的,也就是说传输顺序及传输本身是不可靠的。若只利用 IP 层传输数据,则有可能导致后传输的数据包 B 比先传输的数据包 A 提早到达。另外,传输的数据包 A、B、C 中有可能只收到 A 和 C,甚至收到的 C 可能已损毁 。反之,若添加 TCP 协议则按照如下对话方式进行数据交换。

还是直接看 《TCP/IP 详解》卷一。

4.1.6 应用层

上述内容是套接字通信过程中自动处理的。选择数据传输路径、数据确认过程都被隐藏到套接字内部。编程时无需考虑这些过程。

总之,向程序员提供的工具就是套接字,只需利用套接字编出程序即可。编写软件的过程中,需要根据程序的特点决定服务器端和客户端之间的数据传输规则,这便是应用层协议。

网络编程的大部分内容就是设计并实现应用层协议。

4.2 实现基于 TCP 的服务器端/客户端

本节实现完整的 TCP 服务器端,在此过程中将理解套接字使用方法及数据传输方法。

4.2.1 TCP 服务器端的默认函数调用顺序

socket()       // 创建套接字
bind()         // 分配套接字地址
listen()       // 等待连接请求状态
accept()       // 允许连接
read()/write() // 数据交换
close()        // 断开连接

4.2.2 进入等待连接请求状态

我们已经调用了 bind 函数给套接字分配了地址,接下来就是要通过调用 listen 函数进入等待连接请求状态。只有调用了 listen 函数,客户端才能进入可发出连接请求的状态。换言之,这时客户端才能调用 connect 函数(若提前调用将发送错误)。

#include <sys/socket.h>
int listen(int sock, int backlog);
/*
成功时返回0,失败时返回-1
sock:希望进入等待连接请求状态的套接字文件描述符,传递的描述符套接字参数成为服务器端套接字(监听套接字)
backlog:连接请求等待队列的长度,若为 5,则队列长度为 5,表示最多使 5 个连接请求进入队列
*/

4.2.3 受理客户端连接请求

调用 listen 函数后,若有新的连接请求,则应按序受理。受理请求意味着进入可接受数据的状态。进入这种状态所需部件是 『套接字』,但是此时使用的不是服务端套接字(服务端套接字继续监听)。此时需要另一个套接字,但是没必要亲自创建。下面这个函数将自动创建套接字,并连接到发起请求的客户端。

#include <sys/socket.h>
int accept(int sock, struct sockaddr *addr, socklen_t *addrlen);
/*
成功时返回创建的套接字文件描述符,失败时返回-1
sock:服务端套接字的文件描述符
addr:保存发起连接请求的客户端地址信息的变量地址值,函数调用完成后向传递来的地址变量参数填充客户端地址信息。
addrlen:第二个参数 addr 结构体的长度,但是存有长度的变量地址。函数调用完成后,该变量即被填入客户端地址长度。
*/

accept 函数受理连接请求等待队列中待处理的客户端连接请求。函数调用成功时,accept 函数内部将产生用于数据 I/O 的套接字,并返回其文件描述符。需要强调的是,套接字是自动创建的,并自动与发起连接请求的客户端建立连接。

4.2.4 回顾 Hello world 服务器端

代码参考 hello_server.c ,与第一章代码一致,增加了注释,注释也可直接参考下段文字整理。

  1. 服务端实现过程中首先要创建套接字。但此时的套接字尚并非是真正的服务器端套接字
  2. 为了完成套接字地址分配,初始化结构体变量并调用 bind 函数。
  3. 调用 listen 函数进入等待连接请求状态。连接请求等待队列的长度设置为 5。此时的套接字才是服务器端套接字。
  4. 调用 accept 函数从队头取 1 个连接请求与客户端建立连接,并返回创建的套接字文件描述符。另外,调用 accept 函数时若等待队列为空,则 accept 函数不会返回,直到队列中出现新的客户端连接。
  5. 调用 write 函数向客户端传送数据,调用 close 关闭连接

4.2.5 TCP 客户端的默认函数调用顺序

socket()       // 创建套接字
connect()      // 请求连接
read()/write() // 数据交换
close()        // 断开连接

与服务端相比,区别就在于『请求连接』,它是创建客户端套接字后向服务端发起的连接请求。服务端调用 listen 函数后创建连接请求等待队列,之后客户端即可请求连接。

#include <sys/socket.h>
int connect(int sock, struct sockaddr *servaddr, socklen_t addrlen);
/*
成功时返回0,失败时返回-1
sock:客户端套接字文件描述符
servaddr:保存目标服务器端地址信息的变量地址值
addrlen:以字节为单位传递给第二个结构体参数 servaddr 的变量地址长度
*/

客户端调用 connect 函数后,发生以下情况之一才会返回(完成函数调用)

  • 服务器端接收连接请求
  • 发生断网等异常情况而中断连接请求

注意『接受连接』并不意味着服务器端调用 accept 函数,其实是服务器端把连接请求信息记录到等待队列。因此 connect 函数返回后并不立即进行数据交换。

Q:客户端套接字地址信息在哪?

A:调用 connect 函数时分配地址、操作系统内核中分配、IP 用计算机(主机)的 IP,端口随机。所以无需调用 bind 函数进行分配。

4.2.6 回顾 Hello world 客户端

代码参考 hello_client.c ,与第一章代码一致,增加了注释,注释也可直接参考下段文字整理。

  1. 创建准备连接服务器的套接字,此时创建的是 TCP 套接字
  2. 结构体变量 serv_addr 中初始化 IP 和端口信息。初始化值为目标服务器端套接字的 IP 和端口信息
  3. 调用 connect 函数向服务器端发送连接请求
  4. 完成连接后,接收服务器端传输的数据
  5. 接收数据后调用 close 函数关闭套接字,结束与服务器端的连接

4.2.7 基于 TCP 的服务器端/客户端函数调用关系

见原书 P71,图 4-10。

大致重点是,客户端连接请求在服务器端 listen 函数调用之后,经过服务器端 accept 函数才建立连接。accept 函数是会进入阻塞状态。

4.3 实现迭代服务器端/客户端

本节编写回声(echo)服务器/客户端。顾名思义,服务器端将客户端传输的字符串数据原封不动地传回客户端,就像回声一样。在此之前,需要解释一下迭代服务器端。

4.3.1 实现迭代服务器端

在之前讨论的 Hello world 的例子中,连接请求等待队列实际没有太大意义。如果想继续受理后续的客户端连接请求,应怎样扩展代码?最简单的方式就是插入循环反复调用 accept 函数。

调用 accept 函数后,紧接着调用 I/O 相关的 read、write 函数,然后调用 close 函数。这并非针对服务器端套接字,而是针对 accept 函数调用时创建的套接字。

4.3.2 迭代回声服务器端/客户端

程序的基本运行方式:

  • 服务器端在同一时刻只与一个客户端相连,并提供回声服务。
  • 服务器端依次向 5 个客户端提供服务并退出。
  • 客户端接收用户输入的字符串并发送到服务器端。
  • 服务器端将接受的字符串数据传回客户端,即『回声』
  • 服务器端与客户端之间的字符串回声一直执行到客户端输入 Q 为止。

服务端代码参考 echo_server.c 文件

客户端代码参考 echo_clien.c 文件

运行结果

# 服务端
wzy@wzypc:~/TCP-IP-NetworkNote/chapter-04$ gcc echo_server.c -o eserver.exe
wzy@wzypc:~/TCP-IP-NetworkNote/chapter-04$ ./eserver.exe 9190
Connect client 1 
Connect client 2 
Connect client 3 
Connect client 4 
Connect client 5 
wzy@wzypc:~/TCP-IP-NetworkNote/chapter-04$ 


# 客户端
wzy@wzypc:~/TCP-IP-NetworkNote/chapter-04$ gcc echo_client.c -o eclient.exe
wzy@wzypc:~/TCP-IP-NetworkNote/chapter-04$ ./eclient.exe 127.0.0.1 9190
Connected...
Input message(Q to quit): 1
Message from server: 1
Input message(Q to quit): 2
Message from server: 2
Input message(Q to quit): q
wzy@wzypc:~/TCP-IP-NetworkNote/chapter-04$ 

4.3.3 回声客户端存在的问题

TCP 不存在数据边界。所以可能存在从服务器端收到多个字符串。

多次调用 write 函数传递的字符串可能一次发送

字符串太长需要分 2 个包发送。

解决办法在第 5 章

4.4 基于 Windows 的实现

4.5 习题

以下是我的理解

  1. 请说明 TCP/IP 的 4 层协议栈,并说明 TCP 和 UDP 套接字经过的层级结构差异。

数据链路层、网络层、传输层、应用层,由下至上。

TCP 套接字在传输层使用 TCP 协议

UDP 套接字在传输层使用 UDP 协议

  1. 请说出 TCP/IP 协议栈中链路层和 IP 层的作用,并给出二者关系。

链路层是物理链接领域标准化的结果,也是最基本的领域。若两台主机通过网络进行数据交换,则需要物理连接,链路层就负责这些标准。

向目标传输数据需要经过哪条路径?解决此问题的就是 IP 层,该层使用的协议就是 IP。

关系:链路层负责物理连接,IP 层负责选择可行的物理路径。

  1. 为何需要把 TCP/IP 协议栈分成 4 层(或 7 层)?结合开放式系统回答。

把协议分成多个层次,可以使协议设计更加容易。

通过标准化操作设计开放式系统,可以使不同方式实现具有同样的服务。

  1. 客户端调用 connect 函数向服务器端发送连接请求。服务器端调用哪个函数后,客户端可以调用 connect 函数?

服务端调用 listen 函数后。

  1. 什么时候创建连接请求等待队列?它有何种作用?与 accept 有什么关系?

服务端调用 listen 函数后。充当一个缓冲区。accept 函数受理连接请求等待队列中的客户端连接请求

  1. 客户端中为何不需要调用 bind 函数分配地址?如果不调用 bind 函数,那何时、如何向套接字分配 IP 地址和端口号?

调用 connect 函数时分配地址,在操作系统内核中分配,IP 用计算机(主机)的 IP,端口随机。所以无需调用 bind 函数进行分配。

  1. 修改第一章代码

见 4.3.2 节

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值