Chap.4 基于TCP的服务器端/客户端(1)

实现基于TCP的服务器端

#include 
/* 
 * @params
 *   sock: 服务器套接字文件描述符。
 *   backlog: 等待连接的请求的队列长度
 */
int listen(int sock, int backlog);    // 0: 成功。 -1: 失败。
#include 
/* 
 * @params
 *   sock: 服务器套接字文件描述符。这里的套接字只是为了接收连接的,因为连接本身也是一种数据,需要用套接字来接收。
 *   addr: 客户端地址。传入时是空值,接收到连接后填入该客户端的地址信息。
 *   addrlen: 第二个参数addr结构的长度。在调用后填入。
 */
int accept(int sock, struct sockaddr *addr, socklen_t *addrlen);    // 成功返回新创建的套接字文件描述符,失败返回-1。


实现基于TCP的客户端

#include 
/* 
 * @params
 *   sock: 客户端套接字文件描述符。这里的套接字只是为了接收连接的,因为连接本身也是一种数据,需要用套接字来接收。
 *   addr: 目标服务器地址。
 *   addrlen: 第二个参数addr结构的长度。
 */
int connect(int sock, struct sockaddr *addr, socklen_t addrlen);    // 0: 成功。 -1: 失败。

客户端的IP地址就是主机的IP地址,端口在调用connect()时自动由内核随机分配。
发生以下两种情况之一就会返回:

  1. 服务器端将请求记录到等待队列。
  2. 发生断网等异常情况而中断请求。

实现迭代服务器/客户端

# ./eserver 9190
Connected client 1 
Connected client 2 
Connected client 3 
Connected client 4 
Connected client 5 
# ./eclient 127.0.0.1 9190
Connected
Input message (Q to quit): orange
Message from server: orange
Input message (Q to quit): bottle
Message from server: bottle
Input message (Q to quit): q
# ./eclient 127.0.0.1 9190
Connected
Input message (Q to quit): when      
Message from server: when
Input message (Q to quit): where
Message from server: where
Input message (Q to quit): q
# ./eclient 127.0.0.1 9190
Connected
Input message (Q to quit): FIRST
Message from server: FIRST
Input message (Q to quit): Q
# ./eclient 127.0.0.1 9190
Connected
Input message (Q to quit): sunny
Message from server: sunny
Input message (Q to quit): smile
Message from server: smile
Input message (Q to quit): q
# ./eclient 127.0.0.1 9190
Connected
Input message (Q to quit): last one
Message from server: last one
Input message (Q to quit): q

这只是一个简单的例子而已。实际上客户端服务器的处理不是很谨慎,如果他们不在同一台机器上,发生了网络延迟等状况,可能会粘包或分包。

习题

  1. 请说明TCP/IP的4层协议栈,并说明TCP和UDP套接字经过的层级结构差异。
    从低到高依次是数据链路层、网络层、传输层和应用层。TCP和UDP的差异在传输层。
  2. 请说出TCP/IP协议栈中链路层和IP层的作用,并给出两者关系。
    链路层提供物理连接,IP层基于物理链路选择合适的路径。
  3. 为何需要把TCP/IP协议栈分成4层(或7层)?结合开放式系统回答。
    为了通过标准化操作设计开放式系统。
  4. 客户端调用connect函数向服务器端发送连接请求。服务器端调用哪个函数后,客户端可以调用connect函数?
    必须在服务器端调用listen函数后。
  5. 什么时候创建连接请求等待队列?它有何作用?与accept有什么关系?
    listen函数创建连接请求等待队列。使得同时只能处理一个客户端连接的服务器暂存其他客户端的连接,以待后续处理。accept从队列中取第一个进行服务。
  6. 客户端中为何不需要调用bind函数分配地址?如果不调用bind函数,那何时、如何向套接字分配IP地址和端口号?
    因为在这里客户端是主动发起连接的一端,它不需要在某个固定的地址和端口去监听连接。在调用connect的时候内核会自动随机分配地址和端口,服务器可以根据收到的消息解析出客户端的地址和端口。
  7. 把第1章的hello_server.c改成迭代服务器段,并利用客户端测试更改是否准确。
    同本章例子。


我的问题

  1. 服务器端是阻塞在listen()还是accept()?
    阻塞在accept()。listen()的作用只是使主动连接套接字变为被连接套接字,使得一个进程可以接受其它进程的请求。因此客户端的connect()可以发生在listen()的前面或后面。如果在前面则进入等待队列,如果在后面则直接被服务器接受请求。
  2. 为什么循环接收同一个client的数据,判断read()返回值用0?
    当接收队列为空,且本端或对端调用shutdown或close连接,read()才会返回零。并不是接收队列为空就返回0。所以该函数能一直读到client退出输入循环。


附录

[1] 探讨read的返回值的三种情况
[2] Github

你可能感兴趣的:( Chap.4 基于TCP的服务器端/客户端(1))