目录
1.为什么要用网络通信?
在客户端-服务器架构模型中,客户端向服务器发送请求,服务器处理请求并返回结果。这种架构适用于Web服务、数据库查询等多种应用场景,而printf
只能用于本地输出,无法满足跨网络的数据交换需求。
2.IP和端口
所有的数据传输,都有三个要素:源、目的、长度。
如何表示源或者目的呢?
- 笔记本上两个浏览器访问同一个网站,浏览器发出的数据里,源IP、服务器IP、目的端口都相同。但是他们的源端口不一样,服务器根据源端口来区分一个IP下的两个连接。
- 笔记本上两个软件,SSH Client和浏览器访问同一个网站,SSH Client想使用SSH服务,浏览器想使用http服务,这两个程序要访问的服务器时同一个,IP相同。但是端口不一样,http服务是80端口,SSH服务是22端口。
所以,在网络传输中需要使用“ IP 和端口”来表示源或目的。
3.网络传输中的2个对象:server和client
在日常访问的浏览器中涉及2个对象:网站服务器,浏览器。网站服务器平时处于等待,浏览器主动发起数据请求。网站服务器、浏览器可以抽象成 2 个软件的概念: server 程序、 client 程ge序。
图 3.1 网络客户端和服务器
4.两种传输方式:TCP/UDP
在学习计算机网络的数据中,网络协议被分为5层。如图4.1所示:
图4.1网络协议层
此处只简单介绍运输层,对于其他4层不做介绍。
运输层:负责向两个主机中进程之间的通信提供服务。
运输层主要使用以下两种协议:
- 传输控制协议TCP(Transmission Control Protocol):面向连接的,数据传输的单位是报文段,提供可靠的交付。如命令传输。
- 用户数据包协议UDP(User Datagram Protocol):无连接的,数据传输的单位是用户数据报,不保证提供可靠交付,只能提供“尽最大努力交付”。
我们需要使用“运输层”编写应用程序,我们的应用程序位于“应用层”。使用“运输层”时,可以选择 TCP 协议,也可以选择 UDP 协议。
(1).TCP和UDP原理上的区别
- TCP 向它的应用程序提供了面向连接的服务。这种服务有 2 个特点:可靠传输、流量控制(即发送方/接收方速率匹配)。它包括了应用层报文划分为短报文,并提供拥塞控制机制。
- UDP 协议向它的应用程序提供无连接服务。它没有可靠性,没有流量控制,也没有拥塞控制。
(2). UDP 协议存在的意义
既然 TCP 提供了可靠数据传输服务,而 UDP 不能提供,但是有许多应用更适合用 UDP,例如,视频通话时,使用 UDP,偶尔的丢包、偶尔的花屏时可以忍受的;如果使用 TCP,每个数据包都要确保可靠传输,当它出错时就重传,这会导致后续的数据包被阻滞,视频效果反而不好。
UDP 特点:
- 发送数据控制的更为精细。采用 UDP 时只要应用进程将数据传递给 UDP, UDP 就会立即将其传递给网络层。而 TCP 有重传机制,不管可靠交付需要多长时间。但是实时应用通常不希望过分的延迟报文段的传送,且能容忍一部分数据丢失。
- 无需建立连接,不会引入建立连接时的延迟。
- 无连接状态,能支持更多的活跃客户。
- 分组首部开销较小。
(3). TCP/UDP 网络通信大概交互图
4.1 面向连接的 TCP 流模式
图4.2 UDP 用户数据包模式
5.网络编程主要函数介绍
bind,listen 和 accept 是服务器端用的函数。
(1).socket 函数(未涉及三要素)
int socket(int domain, int type,int protocol);
作用:用于创建一个套接字(socket),该套接字可以用于后续的网络通信。
参数domain(地址族):常见的值包括:
AF_INET
:IPv4 地址族AF_INET6
:IPv6 地址族AF_UNIX
:本地通信(Unix 域套接字)
参数type(套接字类型):常见的值包括:
SOCK_STREAM
:面向连接的流式套接字,通常用于 TCP 协议SOCK_DGRAM
:无连接的数据报套接字,通常用于 UDP 协议SOCK_RAW
:原始套接字,用于底层网络协议开发
参数protocol(协议):由于指定了 type,所以这个地方一般只要用 0 来代替就可以了。
返回值:此函数执行成功时返回文件描述符,失败时返回-1,看 errno 可知道出错的详细情况。
(2).bind 函数---绑定IP和端口
int bind(int sockfd, struct sockaddr *my_addr, int addrlen);
作用:用于将地址绑定到一个套接字。
参数sockfd (套接字描述符):这是由 socket
函数创建的套接字描述符。
参数my_addr(地址结构指针):
- 指向一个包含地址信息的结构体的指针。对于 IPv4,通常使用
struct sockaddr_in
结构体;对于 IPv6,通常使用struct sockaddr_in6
结构体。
sockaddr_in 的定义:
struct sockaddr_in{
unsigned short sin_family;
unsigned short sin_port;
struct in_addr sin_addr;
unsigned char sin_zero[8];
}
如果使用 Internet 所以 sin_family 一般为 AF_INET。
⚫ sin_addr 设置为 INADDR_ANY 表示可以和任何的主机通信。
⚫ sin_port 是要监听的端口号。
⚫ bind 将本地的端口同 socket 返回的文件描述符捆绑在一起.成功是返回 0,
失败的情况和 socket 一样。
参数addrlen (地址结构长度):指定地址结构体的长度。
返回值:成功时返回 0。失败时返回 -1,并设置 errno
变量以指示错误原因。
(3).listen 函数---监听
int listen(int sockfd,int backlog);
作用: 宣告服务器可以接受连接请求,将 bind 的文件描述符变为监听套接字。
参数sockfd : bind 后的文件描述符。
参数backlog: 设置请求排队的最大长度。当有多个客户端程序和服务端相连时,使用这个表示可以介绍的排队长度。
返回值:返回的情况和 bind 一样。
(4).accept 函数
int accept(int sockfd, struct sockaddr *addr,int *addrlen);
作用: 服务器使用此函数获得连接请求,并且建立连接。
参数sockfd:listen 后的文件描述符。
参数addr和addrlen:用来给客户端的程序填写的,服务器端只要传递指针就可以了
tips:accept 调用时,服务器端的程序会一直阻塞到有一个客户程序发出了连接。accept 成功时返回最后的服务器端的文件描述符,这个时候服务器端可以向该描述符写信息了,失败时返回-1 。
(5).connect 函数
int connect(int sockfd, struct sockaddr * serv_addr,int addrlen);
作用:建立一个连接,在 connect 中所指定的地址是想与之通信的服务器的地址。
参数sockfd:socket 函数返回的文件描述符。
参数serv_addr:储存了服务器端的连接信息,其中 sin_add 是服务端的地址。
参数addrlen:serv_addr 的长度
返回值:成功时返回 0,失败时返回-1。
(6). send 函数
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
作用:客户端和服务器都用send函数向另一端发送数据。
参数sockfd: 指定发送端套接字描述符;
参数buf: 指明一个存放应用程序要发送数据的缓冲区;
参数len: 指明实际要发送的数据的字节数;
参数flags: 一般置 0。
返回值:回实际发送的字节数。失败返回 -1,并设置 errno
变量以指示错误原因。
(7).recv 函数
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
作用:客户或者服务器应用程序都用 recv 函数从 TCP 连接的另一端接收数据。
参数sockfd: 指定接收端套接字描述符;
参数buf: 指明一个缓冲区,该缓冲区用来存放 recv 函数接收到的数据;
参数len: 指明 buf 的长度;
参数flags :一般置 0。
返回值:回实际发送的字节数。失败返回 -1,并设置 errno
变量以指示错误原因。
(8).recvfrom 函数
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
参数 recvfrom :通常用于无连接套接字,因为此函数可以获得发送者的地址。
参数 src_addr: 是一个 struct sockaddr 类型的变量,该变量保存源机的 IP 地址及端口号。
参数 addrlen: 常置为 sizeof ( struct sockaddr)。
返回值:回实际发送的字节数。失败返回 -1,并设置 errno
变量以指示错误原因。
(9).sendto 函数
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
sendto 和 send 相似,区别在于 sendto 允许在无连接的套接字上指定一个
目标地址。
参数dest_addr: 表示目地机的 IP 地址和端口号信息,
参数addrlen: 常常被赋值为 sizeof ( struct sockaddr)。
返回值:回实际发送的字节数。失败返回 -1,并设置 errno
变量以指示错误原因。