一、网络编程学习大纲。
1、网络体系结构模型。
2、专业术语:socket/IP地址/端口号。
3、网络编程通信协议:TCP协议/UDP协议。
4、IO模型:阻塞IO/非阻塞IO/多路复用/信号驱动。
5、超时接收数据的方法。
二、网络体系结构模型。
1、 模型有哪些?
一共有两种模型,分别是OSI模型(旧)、TCP/IP协议模型(新)。
2、这些模型是干嘛的?
只要系统能进行网络通信,那么系统内部肯定存在一个网络体系结构模型。
详细参考: 网络体系结构模型是干嘛的.jpg
3、OSI模型。
该模型一共有7层。
详细参考: OSI模型.jpg
4、TCP/IP协议模型。
该模型一共有4层。
详细参考: TCP-IP协议模型.jpg
三、专业术语:socket/IP地址/端口号。
1、什么是socket?
1)从英文角度分析:
socket在英文中被翻译为插座、套接字。
那么问题来了,网络编程也称之为socket编程,为什么网络编程叫插座编程?
详细参考:为什么网络编程叫socket编程.jpg
2)从函数接口角度分析:
socket其实也是一个函数接口,其作用是申请对应协议的套接字(两台相同协议的主机之间通信的桥梁)。
例如:
TCP套接字(TCP socket) = socket(TCP协议);
UDP套接字(UDP socket) = socket(UDP协议);
套接字是一个特殊的文件描述符,为什么说是特殊的?
普通文件描述符 = open();
套接字 = socket(); -> 因为套接字不是通过open得到的,所以说是特殊的文件描述符。
2、什么是IP地址?
1)含义。
例如: "192.168.19.10" --> 这种形态叫点分制。.
每一个IP地址都是32位,如果大家在网络编程中需要使用IP地址,那么就一定要将这32位转化为网络字节。
2)如何分配IP地址?
我们现在的课室是19课室,大家都是连在同一个路由器上,全班同学的IP地址都是"192.168.19.xx"。
中午,助教给每位同学分配三个IP地址,例如:给老关分配了2/3/4这三个数字,这三个IP地址,分别是给windows、linux、开发板。
例如,老关决定这样分配:
windows: 192.168.19.2
linux: 192.168.19.3
开发板: 192.168.19.4
1>. 如何配置windows的IP地址?
右键选择图标 --> 打开"网络与internet"设置 --> 以太网 --> 更改适配器选项 --> 双击"以太网" --> 属性 -> IPv4 -> 不要自动获取
按照以下的参数来配置:
IP地址: 192.168.19.x(以老关为例子,就写2)
子网掩码: 255.255.255.0 -> 照抄
默认网关: 192.168.19.1 -> 照抄
首选DNS: 202.96.134.133
备用DNS: 114.114.114.114
2>. 如何配置Ubuntu的IP地址?
=================================临时配置(重启虚拟机无效)=============================
第一步: 先查看一下当前IP地址是多少?
gec@ubuntu:~$ ifconfig
//以太网卡名称
ens33 Link encap:Ethernet HWaddr 00:0c:29:60:a8:c3
UP BROADCAST MULTICAST MTU:1500 Metric:1 -> 当前还没有设置IP地址。
第二步: 直接在终端输入命令即可。
gec@ubuntu:~$ sudo ifconfig ens33 192.168.19.3
[sudo] password for gec:
第三步:再次查看IP地址。
gec@ubuntu:~$ ifconfig
ens33 Link encap:Ethernet HWaddr 00:0c:29:60:a8:c3
inet addr:192.168.19.3 Bcast:192.168.19.255 Mask:255.255.255.0 -> 临时设置成功。
=================================永久配置(重启虚拟机依然有效)=============================
第一步: 先查看一下当前IP地址是多少?
gec@ubuntu:~$ ifconfig
//以太网卡名称
ens33 Link encap:Ethernet HWaddr 00:0c:29:60:a8:c3
UP BROADCAST MULTICAST MTU:1500 Metric:1 -> 当前还没有设置IP地址。
第二步:打开配置IP地址的文件。
文件路径: /etc/network/interfaces
gec@ubuntu:~$ sudo chmod 777 /etc/network/interfaces
gec@ubuntu:~$ vi /etc/network/interfaces
--------------打开文件之后,原本的内容--------------------
1 # interfaces(5) file used by ifup(8) and ifdown(8)
2 auto lo
3 iface lo inet loopback
---------------------------------------------------------
在这基础上,添加以下的内容即可:
auto ens33
iface ens33 inet static
address 192.168.19.x (以老关为例,就写3)
netmask 255.255.255.0
gateway 192.168.19.1
--------------添加完之后,文件的内容就要变成这样--------------
1 # interfaces(5) file used by ifup(8) and ifdown(8)
2 auto lo
3 iface lo inet loopback
4
5 auto ens33
6 iface ens33 inet static
7
8 address 192.168.19.3 --> 不要照抄我的,自己是什么数字,就写什么数字。
9 netmask 255.255.255.0
10 gateway 192.168.19.1
-------------------------------------------------------------
第三步:保存退出。
第四步:重启虚拟机。
第五步:再次查看IP地址,依然还在。
gec@ubuntu:~$ ifconfig
ens33 Link encap:Ethernet HWaddr 00:0c:29:60:a8:c3
inet addr:192.168.19.3 Bcast:192.168.19.255 Mask:255.255.255.0
3>. 如何配置开发板的IP地址?
=================================临时配置(重启开发板无效)=============================
第一步: 先查看一下当前IP地址是多少?
[root@GEC6818 /]#ifconfig
以太网卡名称
eth0 Link encap:Ethernet HWaddr F6:66:0D:B4:03:76
inet addr:192.168.20.3 Bcast:192.168.20.255 Mask:255.255.255.0 --> 之前设置过了,也没有关系。
第二步: 直接在终端输入命令即可。
[root@GEC6818 /]#ifconfig eth0 192.168.19.4
第三步:再次查看IP地址。
[root@GEC6818 /]#ifconfig
eth0 Link encap:Ethernet HWaddr F6:66:0D:B4:03:76
inet addr:192.168.19.4 Bcast:192.168.19.255 Mask:255.255.255.0
==================================永久设置(重启开发板之后,IP地址依然有效)===========================
第一步: 先查看一下当前IP地址是多少?
[root@GEC6818 /]#ifconfig
以太网卡名称
eth0 Link encap:Ethernet HWaddr F6:66:0D:B4:03:76
inet addr:192.168.20.3 Bcast:192.168.20.255 Mask:255.255.255.0 --> 之前设置过了,也没有关系。
第二步: 打开开发板的脚本文件。
脚本文件: /etc/profile -> 只要板子一开启,就会默认执行这个文件。
[root@GEC6818 /]#vi /etc/profile
在该文件末尾添加一下的内容:
ifconfig eth0 192.168.19.4
第三步: 保存退出。
第四步: 重启板子,再次查看IP地址
[root@GEC6818 /]#ifconfig
eth0 Link encap:Ethernet HWaddr F6:66:0D:B4:03:76
inet addr:192.168.19.4 Bcast:192.168.19.255 Mask:255.255.255.0 -> 无论重启多少次,IP地址依然有效。
3、什么是端口号?
如果某一个地址上,有一个进程在通信,假设这个进程内部有多个socket套接字,那么怎么知道跟谁通信呢?
解决方案: 通信时,除了确定IP地址之外,还要确定端口号。
两个进程之间需要通信,除了IP地址需要在同一个网段之外,还需要有相同的端口号。
主机A ---- 主机B
IP地址: 192.168.19.3 192.168.19.5 --> 必须处于相同网段。(前面三个数字一致,最后一个数字不一样)
端口号: 5000 5000 --> 必须一致。
端口号取值范围: 0~65535 (2的16次方) -> 16位
每一个端口号都是16位,如果大家在网络编程中需要使用端口号,那么就一定要将这16位转化为网络字节。
系统占用了的端口号: 0~1023 (用户不可以使用)
用户有效的端口号: 1024~65535 (用户随便使用) -> 用户使用完端口号之后,由于资源没有释放,至少等待1分钟,才可以再次使用。
四、网络编程通信协议。
1、什么是通信协议?
两个进程之间使用网络编程来通信之前,必须定义好一个关系。 -> 协议:传输层协议。
主机A使用TCP协议,那么主机B就必须使用TCP协议才可以跟主机A通信。
2、传输层协议有哪些?
分别是TCP协议与UDP协议,两者区别是:
TCP协议 --> Tranmission Control Protocol 这种协议特点: 面向于一种有连接的通信。 (打电话)
传输 控制 协议
UDP协议 --> User Data Protocol 这种协议特点: 面向于一种无连接的通信。 (寄信)
用户 数据包 协议
3、使用TCP协议来通信,服务器与客户端的设计流程是如何?
客户端: 一般用于发送数据。
服务器: 一般用于接收数据,然后根据接受到的数据,然后回发对应的内容给客户端。
详细步骤参考: TCP协议的设计流程.jpg
服务器提供自己的IP地址可以使用一个宏: INADDR_ANY
宏的路径在: /usr/include/linux/in.h
/* Address to accept any incoming messages. */
#define INADDR_ANY ((unsigned long int) 0x00000000)
服务器:
#include "head.h"
void *rcv_fun(void *arg)
{
int connfd = *(int *)arg;
//5. 不断从TCP协议中接受数据。
char buf[100];
while (1)
{
bzero(buf, sizeof(buf));
recv(connfd, buf, sizeof(buf), 0);
printf("from client:%s", buf);
if (strncmp(buf, "quit", 4) == 0)
{
exit(0);
}
}
//6. 挂断电话。
close(connfd);
}
void *snd_fun(void *arg)
{
int connfd = *(int *)arg;
//5. 不断从TCP协议中接受数据。
char buf[100];
while (1)
{
fgets(buf, sizeof(buf), stdin);
send(connfd, buf, strlen(buf), 0);
if (strncmp(buf, "quit", 4) == 0)
{
exit(0);
}
}
//6. 挂断电话。
close(connfd);
}
int main(int argc, char *argv[]) // ./Rose 50002
{
//1. 创建TCP套接字。
int sockfd;
sockfd = socket(AF_INET, SOCK_STREAM, 0); //sockfd -> 未连接套接字
//2. 绑定IP地址、端口号、协议到TCP套接字中。
struct sockaddr_in srvaddr;
socklen_t len = sizeof(srvaddr);
bzero(&srvaddr, len);
srvaddr.sin_family = AF_INET;
srvaddr.sin_port = htons(atoi(argv[1]));
srvaddr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(sockfd, (struct sockaddr *)&srvaddr, len);
//3. 设置铃声。
//sockfd -> 未连接套接字
listen(sockfd, 5);
//sockfd -> 监听套接字
//4. 阻塞地坐等对方的连接。
struct sockaddr_in cliaddr;
bzero(&cliaddr, sizeof(cliaddr));
int connfd;
connfd = accept(sockfd, (struct sockaddr *)&cliaddr, &len);
if (connfd > 0)
{
printf("new connection: %s\n", inet_ntoa(cliaddr.sin_addr));
}
//创建一个线程
pthread_t tid1, tid2;
pthread_create(&tid1, NULL, snd_fun, (void *)&connfd);
pthread_create(&tid2, NULL, rcv_fun, (void *)&connfd);
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
close(sockfd);
return 0;
}
客户端:
#include "head.h"
void*rcv_fun(void*arg){
int sockfd=*(int*)arg;
//5. 不断从TCP协议中接受数据。
char buf[100];
while(1)
{
bzero(buf,sizeof(buf));
recv(sockfd,buf,sizeof(buf),0);
printf("from client:%s",buf);
if(strncmp(buf,"quit",4) == 0)
{
exit(0);
}
}
//6. 挂断电话。
close(sockfd);
}
void*snd_fun(void*arg){
int sockfd=*(int*)arg;
//5. 不断从TCP协议中接受数据。
char buf[100];
while(1)
{
fgets(buf,sizeof(buf),stdin);
send(sockfd,buf,strlen(buf),0);
if(strncmp(buf,"quit",4) == 0)
{
exit(0);
}
}
//6. 挂断电话。
close(sockfd);
}
int main(int argc, char *argv[]) // ./Jack 192.168.19.5 50001
{
//1. 创建TCP协议套接字。
int sockfd;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
//2. 打电话。
struct sockaddr_in srvaddr;
socklen_t len = sizeof(srvaddr);
bzero(&srvaddr, len);
srvaddr.sin_family = AF_INET;
srvaddr.sin_port = htons(atoi(argv[2]));
inet_pton(AF_INET, argv[1], &srvaddr.sin_addr);
int ret = connect(sockfd, (struct sockaddr *)&srvaddr, len);
if (ret == -1)
{
printf("connect error!\n");
}
//创建一个线程
pthread_t tid1, tid2;
pthread_create(&tid1, NULL, snd_fun, (void *)&sockfd);
pthread_create(&tid2, NULL, rcv_fun, (void *)&sockfd);
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
close(sockfd);
return 0;
}
五、三次握手。
1、“三次握手”解析什么问题? 解析基于有连接的TCP协议是如何确定可靠性。
2、三次握手分别做了什么事情?
第一次握手: 客户端会发送syn包(seq=j)给服务器,然后等待服务器确定。
第二次握手: 服务器收到syn包之后,必须确定客户端的syn包(ack=seq+1),同时服务器在给客户端发送一个syn包(seq=k),此时服务器再处于等待的状态。
第三次握手: 客户端收到服务器的syn+ack包之后,向服务器发送确认包ack包(ack=seq+1),此包发送成功之后,客户端与服务器才会进入连接状态。