20191320-2021-2022-1-diocs 学习笔记11
第13章 TCP/IP和网络编程
TCP/IP协议
TCP/IP 是互联网的基础。TCP代表传输控制协议。IP代表互联网协议。目前有两个版本的IP,即IPv4和IPv6。IPv4使用32位地址,IPv6则使用128位地址。本节围绕IPv4进行讨论,它仍然是目前使用最多的IP版本。TCP/IP的组织结构分为几个层级,通常称为TCP/IP堆栈。
顶层是使用TCP/IP的应用程序。用于登录到远程主机的ssh、用于交换电子邮件的邮件、用于Web页面的http等应用程序需要可靠的数据传输。通常,这类应用程序在传输层使用TCP。另一方面有些应用程序,例如用于查询其他主机的ping命令,则不需要可靠性。这类应用程序可以在传输层使用UDP来提高效率。
主机:主机是支持TCP/IP协议的计算机或设备。每个主机由一个32位的IP地址来标识。
IP地址:IP地址分为两部分,即NetworkID字段和HostID字段。根据划分,IP地址分为A~E 类。
例如,一个B类IP地址被划分为一个16位网络号,其中前2位是10,然后是一 个16位的主机号字段。发往IP地址的数据包首先被发送到具有相同网络号的路由器。 路由器将通过主机号将数据包转发到网络中的特定主机。每个主机都有一个本地主机名localhost,默认IP地址为127.0.0.1。
路由器是接收和转发数据包的特殊IP主机。如果有的话, 一个IP数据包可能会经过许多路由器,或者跳跃到达某个目的地。
UDP
UDP是用户数据报协议。
UDP在IP上运行,用于发送/接收数据报。与IP类似,UDP不能保证可靠性,但是快速高效。它可用于可靠性不重要的情况。
ping使用的是UDP协议。
TCP
TCP(传输控制协议)是一种面向连接的协议,用于发送/接收数据流。TCP也可在IP上运行,但它保证了可靠的数据传输。通常,UDP类似于发送邮件的USPS,而TCP类似于电话连接。
端口和应用
应用程序=(主机IP,协议,端口号)
TCP/IP网络中的数据流
应用程序层的数据被传递到传输层,传输层给数据添加一个TCP或UDP报头来标识使用的传输协议。合并后的数据被传递到IP网络层,添加一个包含IP地址的IP报头来标识发送和接收主机。然后,合并后的数据再被传递到网络锥路层,网络链路层将数据分成多个帧,并添加发送和接收网络的地址,用于在物理网络之间传输:IP地址到网络地址的映射由地址解析协议(ARP)执行。在接收端,数据编码过程是相反的。 每一层通过剥离数据头来解包接收到的数据,重新组装数据并将数据传递到上层。发送主机上的应用程序原始数据最终会被传递到接收主机上的相应应用程序。
网络编程
服务器-客户机计算模型:大多数网络编程任务都基于服务器-客户机计算模型。
套接字:
struct sockaddr_in ( sa_family_t sin_family; in_port_t sin_port; struct in_addr sin_addr;
);
struct in_addr {
uint32_t s_addr;
在套接字地址结构中,
- TCP/IP 网络的 sin_family 始终设置为 AF_INET。
- sm_port包含按网络字节顺序排列的端口号。
- sin_addr是按网络字节顺序排列的主机IP地址。
Lint套接字(int域,int类型,int协议)
int udp_sock = socket(AF_INET, SOCK_DGRAM, 0);
将会创建一个用于发送/接收UDP数据报的套接字。
int tcp_sock = socket(AF_INET, SOCK_STREAM, 0);
将会创建一个用于发送/接收数据流的面向连接的TCP套接字。
实践与问题解决
编程练习:使用C语言实现简单UDP传输。
本实践可以通过C语言,在客户机和服务器之间实现UDP的数据传输。
代码如下:
客户端:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <arpa/inet.h>
#define MAXBUF 256
int main(int argc, char const *argv[])
{
int s = 0;
int n = 0;
int reuse = 1;
int port = 1987;
struct sockaddr_in srv;
char buf[MAXBUF] = {0};
/*解析参数*/
if (argc != 2)
{
printf("Usage:%s ServerIP\n", argv[0]);
return -1;
}
bzero(&srv, sizeof(srv));
srv.sin_family = PF_INET;
srv.sin_addr.s_addr = inet_addr(argv[1]);
srv.sin_port = htons(port);
/*创建 UDP 套节字*/
s = socket(AF_INET, SOCK_DGRAM, 0);
if(s<0){
perror("socket");
return -1;
}
while(1){
memset(buf, 0, MAXBUF);
/*读取用户输入到buf中*/
fgets(buf, MAXBUF, stdin);
/*通过套节字 s 向服务器发送数据*/
if ((n = sendto(s, buf, strlen(buf), 0, (struct sockaddr *) &srv, sizeof(struct sockaddr))) < 0)
{
perror("sendto");
return -1;
}else{
printf("send to %s(port=%d) len %d:%s\n", argv[1], port, n, buf);
}
}
}
服务器端:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <arpa/inet.h>
#define MAXBUF 256
int main(int argc, char const *argv[])
{
int s = 0;
int n = 0;
int reuse = 1;
int cli_len = sizeof(struct sockaddr);
int port = 1987;
char buf[MAXBUF] = {0};
struct sockaddr_in addr, cli;
/*初始化本地监听端口信息*/
bzero(&addr, sizeof(addr));
addr.sin_family = PF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(port);
/*创建UDP套节字*/
s = socket(AF_INET, SOCK_DGRAM, 0);
if (s<0)
{
perror("socket");
return -1;
}
/*允许端口复用*/
setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
/*绑定指定端口*/
if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0)
{
perror("bind");
return -1;
}
while(1){
memset(buf, 0, MAXBUF);
/*从套节字s中读取数据*/
n = recvfrom(s, buf, MAXBUF, 0, (struct sockaddr *)&cli, &cli_len);
if(n<0){
perror("recvfrom");
return -1;
}else{
printf("receive msg from %s(port=%d) len %d: %s\n",inet_ntoa(cli.sin_addr), port, n, buf);
}
}
return 0;
}
实践截图:
代码链接
代码包括一些以前的代码,在码云。链接:https://gitee.com/Ressurection20191320/code/tree/master/IS