一、绪论
1.网络编程的基本概念
1.1 socket编程概念
socket就是插座,运行在计算机中的两个程序通过sokect建立起来一个通道,数据在通道中传输;
socket将TCP/IP协议族隐藏起来,程序员只需要使用sockect相关函数就可以完成网络通信;Socket=Ip address+ TCP/UDP + port
1.2 socket分类
在几十年前网络条件很差,网速非常慢,很多应用只能选择UDP实现,但是基于现在的网络环境下很多应用都是采用可靠的TCP了,仅仅只有某些如视频、音频通信应用还使用UDP协议
1.3 C/S模式

2.计算机网络概述
2.1 计算机网络发展简史
2.1.1 早期的广域网
早期的广域网一定是通过电话线进行连接的, 在通信双方或多方之间,通过电路交换建立电路连接的网络。计算机数据是突发式出现在数据链路上的,而电路交换网的建立链接、使用链接、释放 链接的三个过程使得传输效率太低,故电路交换不适合传输计算机数据。 
特点:
- 建立链接->使用链接‐>释放链接
- 物理通路被通信双方独占
2.1.2 ARPA
该网络是美国为了防止苏联发射的卫星进而研发的, 最早的ARPA无法连接不同类型的计算机和不同类型的操作系统
2.1.3 分组交换
通过标有地址的分组进行路由选择传送数据,使通信通道仅在传送期间被占用的一种交换方式
2.2 TCP/IP协议简介
到现在为止,只要是能够通信的机器,能够上网的机器,一定用的就是tcp/ip协议族
2.2.1 分层结构
为了能够实现不同类型的计算机和不同类型的操作系统之间进行通信,引入了分层的概念,最早的分层体系结构是OSI开放系统互联模型,是由国际化标准组织(ISO)指定的,由于 OSI过于复杂,所以到现在为止也没有适用,而使用的是TCP/IP协议族 OSI一共分为7层,TCP/IP协议族一共四层,虽然TCP/IP协议族层次少,但是却干了OSI7层 所有任务
2.2.2 IP协议简介
IP协议也称之为网际层协议,特指为实现在一个相互连接的网络系统上从源地址到目的地传输数据包(互联网数据包)所提供必要功能的协议
IP数据包中含有发送它主机的IP地址(源地址)和接收它主机的IP地址(目的地址)
2.2.3 TCP协议简介
TCP协议,传输控制协议,TCP是一种面向连接的,可靠的传输层通信协议
特点:
1、建立链接->使用链接->释放链接(虚电路)
2、TCP数据包中包含序号和确认序号
3、对包进行排序并检错,而损坏的包可以被重传
2.2.4 UDP协议简介
UDP协议,用户数据报协议,UDP是一种面向无连接的传输层通信协议
2.3 网络应用程序开发流程
2.3.1 TCP—面向连接
电话系统服务模式的抽象
每一次完整的数据传输都要经过建立连接、使用连接、终止连接的过程
本质上,连接是一个管道,收发数据不但顺序一致,而且内容相同
保证数据传输的可靠性
2.3.2 UDP—面向无连接
邮件系统服务模式的抽象
每个分组都携带完整的目的地址
不能保证分组的先后顺序
不进行分组出错的恢复和重传
不保证数据传输的可靠性
2.3.3 C/S架构
无论采用面向连接的还是无连接,两个进程通信过程中,大多采用C/S架构
client向server发出请求,server接收到后提供相应的服务
在通信过程中往往都是client先发送请求,而server等待请求然后进行服务
(1)server工作过程
- 打开一通信通道并告知本地主机,它愿意在一特定端口(如80)上接收客户请求
- 等待客户请求到达该端口
- 接收客户请求,并发送应答信号,激活一新的线程处理这个客户请求
- 服务完成后,关闭新线程与客户的通信链路
(2)client工作过程
- 打开一通信通道并连接到服务器特定端口
- 向服务器发出服务请求,等待并接收应答
- 根据需要继续提出请求
- 请求结束后关闭通信通道并终止
3.字节序概述
字节序是指多字节数据的存储顺序。因为内存的每一块地址只能存储一个字节,所以需要将多字节数据拆分,靠近0x称为高字节序,远离0x称为低字节序。
3.1 字节序的分类
1.小端格式:将低位字节数据存储在低地址(LSB)
2.大端格式:将高位字节数据存储在低地址
3.2 字节序的转换
假如两个主机的字节序是不一样的,则在不同主机中传输数据时需要兼顾不同主机的字节序,并且在网络中传输的字节序一定是大端的,因此我们很有必要学习字节序转换函数
1、网络协议指定了通讯字节序——大端(必须是大端)
2、只有在多字节数据处理时才需要考虑字节序
3、运行在同一台计算机上的进程相互通信时,一般不用考虑字节序
4、异构计算机之间通讯,需要转换自己的字节序为网络字节序
host --> network(l表示长整型,s表示短整型)1.hton1语法:uint32_t htonl(uint32_t hostint32);功能:将32位主机字节序数据转换成网络字节序数据参数:uint32_t: unsigned int(这个前缀可写可不写)hostint32:待转换的32位主机字节序数据2.htons语法:uint16_t htons(uint16_t hostint16);功能:将16位主机字节序数据转换成网络字节序数据参数:uint16_t:unsigned short int//无符号短整型hostint16:待转换的16位主机字节序数据network --> host3.ntohl语法:uint32_t ntohl(uint32_t netint32);功能:将32位网络字节序数据转换成主机字节序数据参数:uint32_t: unsigned intnetint32:待转换的32位网络字节序数据4.ntohs语法:uint16_t ntohs(uint16_t netint16);功能:将16位网络字节序数据转换成主机字节序数据参数:uint16_t: unsigned short intnetint16:待转换的16位网络字节序数据
4.地址转换函数
人为识别的ip地址是点分十进制的字符串形式(192.168.3.103),但是计算机或者网络中识别的ip地址是整形数据(网路中用4字节保存该IP地址),所以需要转化
1.inet_pton函数语法:int inet_pton(int family,const char *strptr, void *addrptr);功能:将点分十进制数串转换成32位无符号整数参数:family 协议族AF_INET IPV4网络协议AF_INET6 IPV6网络协议strptr 点分十进制数串addrptr 32位无符号整数的地址2.inet_ntop函数语法:const char *inet_ntop(int family, const void *addrptr,char *strptr, size_t len);功能:将32位无符号整数转换成点分十进制数串参数:family 协议族addrptr 32位无符号整数strptr 点分十进制数串len strptr缓存区长度(区分是IPV4还是IPV6)
补充知识:
一般情况下是数字占一个字节,英文占一个字节,标点占一个字节,一个汉字占两个字节——这种叙述指的是在计算机内部中以字符串形式存储的容量大小,而如果是使用数字形式存储的容量大小则完全不一样,”192”字符串占3个字节,192数字占1个字节
3.inet_addr()语法:in_addr_t inet_addr(const char *cp);功能:将点分十进制ip地址转化为整形数据参数:cp:点分十进制的IP地址4.inet_ntoa()语法:char *inet_ntoa(struct in_addr in);功能:将整形数据转化为点分十进制的ip地址参数:in:保存ip地址的结构体
二、UDP编程
1.UDP概述
UDP协议是面向无连接的用户数据报协议,在传输数据前不需要先建立连接;目地主机的运输层收到 UDP报文后,不需要给出任何确认
1.1 UDP特点
1、相比TCP速度稍快些
2、简单的请求/应答应用程序可以使用UDP
3、对于海量数据传输不应该使用UDP
4、广播和多播应用必须使用UDP
1.2 UDP应用
DNS(域名解析)、NFS(网络文件系统)、RTP(流媒体)等
一般语音和视频通话都是使用udp来通信的
2.网络编程接口socket
2.0 socket简介
TCP/IP协议族包括运输层、网络层、链路层。socket是一个接口,在用户进程与TCP/IP协议之间充当中间人,完成TCP/IP协议的书写,用户只需理解接口即可(用户不需要理解下面的三层的工作原理,但是应用层和传输层用户仍然需要知道其原理)
2.1 socket作用
socket是对TCP/IP协议的封装,也就是TCP/IP网络的API函数(socket.h头文件中提供了很多封装好的函数)
可以实现蓝牙、无线通信等,提供不同主机上的进程之间的通信
2.2 socket特点
1、socket也称“套接字”,是int类型的文件描述符
2、是一种文件描述符,代表了一个通信管道的一个端点
3、类似对文件的操作一样,可以使用read、write、close等函数对socket套接字进行网络数据的收取和发送等操作(一般在网络编程中使用recvfrom这些专用的读写函数)
4、得到socket套接字(描述符)的方法——调用socket()
2.3 socket分类
- SOCK_STREAM,流式套接字,用于TCP
- SOCK_DGRAM,数据报套接字,用于UDP
- SOCK_RAW,原始套接字,对于其他层次的协议操作时需要使用这个类型
3.UDP编程C/S架构
3.1 基本流程

服务器:
- 创建套接字 socket( )
- 将服务器的ip地址、端口号与套接字进行绑定 bind( )——只有绑定后服务器的信息才能固定,客户端才能找到服务器
- 接收数据 recvfrom()
- 发送数据 sendto()
客户端:
- 创建套接字 socket()
- 发送数据 sendto()
- 接收数据 recvfrom()
- 关闭套接字 close()
3.2 创建套接字
int socket(int domain, int type, int protocol);功能:创建一个套接字,返回一个文件描述符参数:domain:通信域,协议族AF_UNIX 本地通信AF_INET ipv4网络协议AF_INET6 ipv6网络协议AF_PACKET 底层接口type:套接字的类型SOCK_STREAM 流式套接字(tcp)SOCK_DGRAM 数据报套接字(udp)SOCK_RAW 原始套接字(用于链路层)protocol:附加协议,如果不需要,则设置为0
- 创建套接字时,系统不会分配端口
- 创建的套接字默认属性是主动的,即主动发起服务的请求;当作为服务器时,往往需要修改为被动的
3.3 发送、绑定、接收数据
3.3.1 地址结构体
网络编程中IP地址和端口号等都是封装在一个结构体当中的,网络编程中常用的结构体是sockaddr_in
struct sockaddr_in{sa_family_t sin_family;//协议族 2字节in_port_t sin_port;//端口号 2字节struct in_addr sin_addr;//ip地址 4字节char sin_zero[8]//填充,不起什么作用 8字节};
为了使不同格式地址能被传入套接字函数,地址须要强制转换成通用套接字地址结构,原因是因为不同场合所使用的结构体不一样,但是调用的函数却是同一个,所以定义一个通用结构体,当在指定场合使用时,在根据要求传入指定的结构体即可
struct sockaddr{sa_family_t sa_family; // 2字节 协议族char sa_data[14] //14字节 字符数组};
两种地址结构使用场合
- 在定义源地址和目的地址结构的时候(即网络编程时),选用struct sockaddr_in;
struct sockaddr_in my_addr;
- 当调用编程接口函数,且该函数需要传入地址结构时需要用struct sockaddr进行强制转换;
bind(sockfd,(struct sockaddr*)&my_addr,sizeof(my_addr));
3.3.2 发送数据
#include <sys/types.h>#include <sys/socket.h>ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);功能:向dest_addr结构体指针中指定的ip发送UDP数据参数:sockfd:套接字即文件描述符,socket的返回值buf:要发送的数据/发送数据缓冲区len:buf的长度flags:标志位0 阻塞MSG_DONTWAIT 非阻塞dest_addr:目的网络信息结构体(需要自己指定要给谁发送)addrlen:dest_addr的长度返回值:成功:发送的字节数失败:‐1
3.3.3 绑定数据
接收端 使用bind函数,来完成地址结构(ip地址和port)与socket套接字的绑定,这样ip、port就固定了 ;
发送端 在sendto函数中指定接收端的ip、port,就可以发送数据了 ;
由于服务器是被动的,客户端是主动的,所以一般先运行服务器,后运行客户端,所以服务器需要固定自己的信息(ip地址和端口号),这样客户端才可以找到服务器并与之通信,但是客户端一般不需要bind绑定,因为系统会自动给客户端分配ip地址和端口号
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
功能:将套接字与网络信息结构体绑定
参数:
sockfd:文件描述符,socket的返回值
addr:网络信息结构体——指向特定协议的地址结构指针
通用结构体(一般不用)struct sockaddr
网络信息结构体(一般使用)sockaddr_in
addrlen:addr的长度
返回值:
成功:0
失败:‐1
3.3.4 接收数据
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
功能:接收数据
参数:
sockfd:文件描述符,socket的返回值
buf:保存接收的数据
len:buf的长度
flags:标志位
0 阻塞
MSG_DONTWAIT 非阻塞
src_addr:源的网络信息结构体(自动填充,定义变量传参即可)
addrlen:src_addr的长度
返回值:
成功:接收的字节数
失败:‐1
3.4 总结
在上述的demo中往往会产生一个疑问,client是否可以接收数据而server是否可以发送数据呢?这当然是可以的——并不是身份决定功能,而是由功能决定身份,在网络编程中常把提供服务的一方称为server,把接受服务的一方称为client
3.4.1 UDP客户端注意点
1、本地IP、本地端口(客户端是谁)
2、目的IP、目的端口(服务器是谁)
3、在客户端的代码中,我们只设置了目的IP、目的端口,因为客户端的本地ip、本地port是我们调用sendto的时候linux系统底层自动给客户端分配的;分配端口的方式为随机分配,即每次运行系统给的port不一样
3.4.2 UDP服务器注意点
1、服务器之所以要bind是因为它的本地port需要是固定,而不是随机的
2、服务器也可以主动地给客户端发送数据(知道客户端的ip地址和端口号即可)
3、客户端也可以用bind,这样客户端的本地端口就是固定的了,但一般不这样做
4. TFTP协议
4.1 TFTP概述
TFTP称为简单文件传送协议(应用层),最初用于引导无盘系统,被设计用来传输小文件(FTP协议一般用于传输大文件,FTP是基于TCP所以一般传送大文件以保证不会出现问题)
特点:
- 基于UDP协议实现
- 不进行用户有效性认证
数据传输模式:
- octet:二进制模式(使用较多)
- netascii:文本模式
- mail:已经不再支持
4.2 TFTP通信过程

1、服务器在69号端口等待客户端的请求
2、服务器若批准此请求,则使用临时端口与客户端进行通信(临时端口是为了实现并发)
3、每个数据包的编号都有变化(从1开始)
4、每个数据包都需要得到ACK的确认如果出现超时,则需要重新发送最后的包(数据或
ACK)
5、数据的长度以512Byte传输
6、小于512Byte的数据意味着传输结束(这个过程在应用层有介绍)
4.3 TFTP协议
(本课程学习的是如何将学习得到的知识在现实中实现,尽管计网部分的协议讲述非常清楚,但是本课程是重在利用协议写代码实现一些任务,所以不要认为这些没有意义,要详细理解协议内容参考计网笔记)
客户端给服务器发送的是读写请求,操作码01为读(下载)02为写(上传),0的用法是为了间隔字段,模式分为文本模式和二进制模式,选项及后面的内容一般都不使用
数据包就是服务器传给客户端的
客户端进行完下载/上传操作后给服务器发送ACK
假如中间出现了错误则会出现ERROR, 不同的差错码对应不同的错误信息
0 未定义,参见错误信息
1 File not found.
2 Access violation.
3 Disk full or allocation exceeded.
4 illegal TFTP operation.
5 Unknown transfer ID.
6 File already exists.
7 No such user.
8 Unsupported option(s) requested.
本节还附带一个学习任务是使用TFTP协议,编写客户端,实现从虚拟软件构成的服务器端下载名为hello.txt的文本文件并保存在当前目录下,具体实现过程和流程图见视频:https://www.bilibili.com/video/BV1pX4y1N7T4?p=36&spm_id_from=333.1007.top_right_bar_window_history.content.click
5.UDP广播
5.1 广播的概念
广播指由一台主机向该主机所在子网内的所有主机发送数据的方式
例如192.168.3.103主机发送广播信息,则192.168.3.1~192.168.3.254所有主机都可以接收到数据
广播只能用UDP或原始IP实现,不能用TCP
广播用于单个服务器与多个客户主机通信时减少分组流通,以下几个协议都用到广播
1、地址解析协议(ARP)
2、动态主机配置协议(DHCP)
3、网络时间协议(NTP)
广播的特点
1、处于同一子网的所有主机都必须处理数据
2、UDP数据包会沿协议栈向上一直到UDP层
3、运行音视频等较高速率工作的应用,会带来极大的负荷
4、局限于局域网内使用(路由器不会转发广播)
5.2 广播和单播的区别
5.3 实现广播流程
UDP广播和UDP单播实质上没什么区别,我们只需要设置一下套接字即可实现。一般情况下广播的发送者只负责发送数据,接收者只负责接收数据(一台主机只能绑定一个接收者)
发送者:
第一步:创建套接字 socket()
第二步:设置为允许发送广播权限 setsockopt() (必须设置发送广播权限才能发送广播,但多播不需要)
第三步:向广播地址发送数据 sendto()
接收者:
第一步:创建套接字 socket()
第二步:将套接字与广播的信息结构体绑定 bind()
第三步:接收数据 recvfrom()
int setsockopt(int socket, int level, int option_name,const void *option_value,
socklen_t option_len);
功能:设置一个套接字的选项(属性)
参数:
socket:文件描述符
level:协议层次
SOL_SOCKET 套接字层次
IPPROTO_TCP tcp层次
IPPROTO_IP IP层次
option_name:选项的名称
SO_BROADCAST 允许发送广播数据(SOL_SOCKET层次的)
SO_RCVBUF 接收缓冲区大小
SO_SNDBUF 发送缓冲区大小
option_value:设置的选项的值
int类型的值,存储的是bool的数据(1和0)
0 不允许
1 允许
option_len:option_value的长度
返回值:
成功:0
失败:‐1
6.UDP多播
6.1 多播的概念
多播:
数据的收发仅仅在同一分组中进行,所以多播又称之为组播
多播的特点:
1、多播地址标示一组接口
2、多播可以用于广域网使用
3、在IPv4中,多播是可选的
6.2 实现多播流程
比起广播,多播具有可控性,只有加入多播组的接收者才可以接收数据,否则接收不到 
发送者:
第一步:创建套接字 socket()
第二步:向多播地址发送数据 sendto()
接收者:
第一步:创建套接字 socket()
第二步:设置为加入多播组 setsockopt() (必须加入多播组才能接收到多播数据)
第三步:将套接字与多播信息结构体绑定 bind()
第四步:接收数据
int setsockopt(int socket, int level, int option_name,
const void *option_value, socklen_t option_len);
功能:设置一个套接字的选项(属性)
参数:
socket:文件描述符
level:协议层次
IPPROTO_IP IP层次
option_name:选项的名称
IP_ADD_MEMBERSHIP 加入多播组
IP_DROP_MEMBERSHIP 离开多播组
option_value:设置的选项的值
struct ip_mreq(这是一个结构体)
{
struct in_addr imr_multiaddr; //组播ip地址
struct in_addr imr_interface; //主机地址
INADDR_ANY 任意主机地址(自动获取你的主机地址)
};
option_len:option_value的长度
返回值:
成功:0
失败:‐1
三、TCP编程
1.TCP概述
1、面向连接的流式协议;可靠(仅仅只是相对的,针对小数据UDP出错概率仍然很小)、出错重传、且每收到一个数据都要给出相应的确认
2、通信之前需要建立链接
3、服务器被动链接,客户端是主动链接(尽管UDP没有链接但服务器仍然是被动的,客户端是主动的)

TCP编程流程
(不同的语言可能函数不同,但是网络编程的流程都是一样的)
服务器:
创建套接字 socket()
将套接字与服务器网络信息结构体绑定 bind()
将套接字设置为监听状态 listen()
阻塞等待客户端的连接请求 accept()
进行通信 recv()/send()
关闭套接字 close()
客户端:
创建套接字 socket()
发送客户端连接请求 connect()
进行通信 send()/recv() (使用read和write也可以,但是前两个函数使用的更多)
关闭套接字 close()
2.TCP套接字
int socket(int domain, int type, int protocol);
功能:创建一个套接字,返回一个文件描述符
参数:
domain:通信域,协议族
AF_UNIX 本地通信
AF_INET ipv4网络协议
AF_INET6 ipv6网络协议
AF_PACKET 底层接口
type:套接字的类型
SOCK_STREAM 流式套接字(tcp)
SOCK_DGRAM 数据报套接字(udp)
SOCK_RAW 原始套接字(用于链路层)
protocol:附加协议,如果不需要,则设置为0
返回值:
成功:文件描述符
失败:‐1
3.TCP客户端

TCP客户端分为创建套接字、发送客户端的一些请求、发送数据以及接收数据
3.1 connect函数
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
功能:给服务器发送客户端的连接请求
参数:
sockfd:文件描述符,socket函数的返回值
addr:要连接的服务器的网络信息结构体(需要自己设置)
addrlen:add的长度
返回值:
成功:0
失败:‐1
注意:
1、connect建立连接之后不会产生新的套接字
2、连接成功后才可以开始传输TCP数据
3、头文件:#include
3.2 send函数
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
功能:发送数据
参数:
sockfd:文件描述符
客户端:socket函数的返回值
服务器:accept函数的返回值
buf:发送的数据
len:buf的长度
flags:标志位
0 阻塞(默认值)
MSG_DONTWAIT 非阻塞
返回值:
成功:发送的字节数
失败:‐1
注意:
不能用TCP协议发送0长度的数据包
3.3 recv函数
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
功能:接收数据
参数:
sockfd:文件描述符
客户端:socket函数的返回值
服务器:accept函数的返回值
buf:保存接收到的数据
len:buf的长度
flags:标志位
0 阻塞
MSG_DONTWAIT 非阻塞
返回值:
成功:接收的字节数
失败:‐1
如果发送端关闭文件描述符或者关闭进程,则recv函数会返回0
4.TCP服务器
4.1 TCP服务器的前置条件

1、具备一个可以确知的IP地址和端口号(因为服务器是被动的所以需要有一个确定的地址以便客户端能找到)
2、让操作系统知道是一个服务器,而不是客户端
3、等待连接的到来
对于面向连接的TCP协议来说,连接的建立才真正意味着数据通信的开始
4.2 bind函数
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
功能:将套接字与网络信息结构体绑定
参数:
sockfd:文件描述符,socket的返回值
addr:网络信息结构体
通用结构体(一般不用)struct sockaddr
网络信息结构体 struct sockaddr_in
addrlen:addr的长度
返回值:
成功:0
失败:‐1
4.3 listen函数
int listen(int sockfd, int backlog);
功能:将套接字设置为被动监听状态,这样做之后就可以接收到连接请求
参数:
sockfd:文件描述符,socket函数返回值
backlog:允许通信连接的主机个数,一般设置为5、10
返回值:
成功:0
失败:‐1
4.4 accept函数
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
功能:阻塞等待客户端的连接请求
参数:
sockfd:文件描述符,socket函数的返回值
addr:接收到的客户端的信息结构体(自动填充,定义变量即可)
addrlen:addr的长度的地址
返回值:
成功:新的文件描述符(只要有客户端连接,就会产生新的文件描述符,这个新的文件描述符专门与指定的客户端进行通信的)
失败:‐1
注意:
在使用accept函数建立连接以后,服务器与客户端之间真正进行通信的套接字就不是原本的socket函数的返回值了,而是使用accept返回的新的套接字
5.TCP编程补充
5.1 close函数
1、使用close函数即可关闭套接字
关闭一个代表已连接套接字将导致另一端接收到一个0长度的数据包(如果关闭套接字则recv函数直接返回0)
2、close作用于服务器时
- 关闭监听套接字将导致服务器无法接收新的连接,但不会影响已经建立的连接
- 关闭accept返回的已连接套接字将导致它所代表的连接被关闭,但不会影响服务器的监听
3、close作用于客户端时
关闭连接就是关闭连接,不意味着其他
5.2 TCP三握手
在编程时我们不需要考虑这些,三握手的过程在内部已经被封装好了,可以使用如下情景对话模拟三握手
张三(客户端):你好我是张三,你能听见我说话吗?
李四(服务器):你好我是李四,我能听见你说话,你能听见我说话吗?
张三(客户端):我能听见你说话,我们已经建立了连接!
5.3 TCP四挥手
当客户端(服务器的过程也相同,客户端和服务器都可以主动执行close)执行close关闭文件描述符时,会给服务器发送FIN…
可以把四挥手看作将三握手中间的一步拆成了两步
6.TCP并发服务器
并发:QQ服务器只有一个,但是可以保证有多个客户端同时运行,服务器能与多个客户端同时进行通信,这就叫并发
TCP原本不是并发服务器,TCP服务器同一时间只能与一个客户端通信 (开多个客户端连接服务器只会显示第一个连接的客户端信息)——因为TCP服务器的代码中有两个阻塞函数,这两个阻塞函数不能交换位置,只能顺序执行,故导致无法实现并发(accept理论是用来支持多个服务器与客户端进行通信,但这取决于代码的编写)
在UDP中因为只有一个recvfrom在服务器端所以可以实现并发,不需要考虑其他阻塞函数
//第五步:阻塞等待客户端的连接请求
if((acceptfd = accept(sockfd, (struct sockaddr *)&clientaddr, &addrlen)) < 0)
//假如将while提前到accept前面会导致每进行一次通话就需要阻塞等待接收一次acceptfd
{
ERR_LOG("fail to accept");
}
//第六步:进行通信
char buf[N] = "";
ssize_t bytes;
while (1)//因为在进行通信时while里面使用的一直都是同一个accept得到的sokect所以无法实现并发
{
if((bytes = recv(acceptfd, buf, N, 0)) < 0)
{
ERR_LOG("fail to recv");
}
else if(bytes == 0)
{
printf("The client quited\n");
exit(1);
}
if(strncmp(buf, "quit", 4) == 0)
{
exit(0);
}
printf("from client: %s\n", buf);
strcat(buf, " ^_^");
if(send(acceptfd, buf, N, 0) < 0)
{
ERR_LOG("fail to send");
}
}
如何实现TCP并发服务器:
- 使用多进程实现TCP并发服务器
- 使用多线程实现TCP并发服务器
6.1 多进程实现
一个进程执行accept一个进程执行recv保证两个函数同时执行
我们不需要修改客户端,只需要修改服务器的代码即可
int sockfd = socket()
bind()
listen()
while(1)//要保证一直运行就需要一直循环
{
acceptfd = accept()
pid = fork();//父进程执行accept,保证一直在accept阻塞;子进程执行通信
if(pid > 0)
{//父进程什么也没有执行
}
else if(pid == 0)
{//子进程一直在while1里面,服务器的每一个子进程就对应一个客户端
while(1)
{recv()/send()}
}
}
6.2 多线程实现
创建多个子线程,每个子线程负责自己的一部分工作不会导致冲突
void *thread_fun(void *arg)
{//通信在子函数中执行
while(1)
{
recv() / send()
}
}
sockfd = socket()//创建套接字
bind()
listen()
while(1)
{
accept()//阻塞请求
//只要有客户端连接上,则创建一个子线程与之通信
pthread_create(, , thread_fun, );
pthread_detach();//将线程设置为分离态
}
四、Web编程
1.web服务器
1.1 web服务器简介
Web服务器又称WWW服务器、网站服务器等
特点
- 使用HTTP协议与客户机浏览器进行信息交流
- 不仅能存储信息,还能在用户通过web浏览器提供的信息的基础上运行脚本和程序
- 该服务器需可安装在UNIX、Linux或Windows等操作系统上(一般服务器是运行在linux系统上的)
- 著名的服务器有Apache、Tomcat、 IIS等
1.2 HTTP协议简介
HTTP是基于TCP的应用层协议
超文本传输协议HTTP是一种详细规定了浏览器和万维网服务器之间互相通信的规则,通过因特网传送万维网文档的数据传送协议
特点
1、支持C/S架构
2、简单快速:客户向服务器请求服务时,只需传送请求方法和路径 ,常用方法:GET(明文)、POST(密文)
3、无连接:限制每次连接只处理一个请求
4、无状态:即如果后续处理需要前面的信息,它必须重传,这样可能导致每次连接传送的数据量会增大(一般情况下采用多线程的方式解决这种问题)
1.3 Webserver通信过程

该通信过程也就是TCP的通信流程,服务器通过读取网页内容并发送给客户端进行应答
2.Web编程开发
这里我们将使用HTTP协议,实现编写一个服务器并能够响应客户端(浏览器)
流程:
- 客户端浏览器请求(默认使用GET方式)

- 服务器收到数据

- 服务器应答: 服务器接收到浏览器发送的数据之后,需要判断GET/后面跟的网页是否存在,如果存在则请求成功,发送指定的指令,并发送文件内容给浏览器,如果不存在,则发送请求失败的指令
"HTTP/1.1 200 OK\r\n" \ "Content‐Type: text/html\r\n" \ "\r\n";"HTTP/1.1 404 Not Found\r\n" \ "Content‐Type: text/html\r\n" \ "\r\n" \ "<HTML><BODY>File not found</BODY></HTML>"
五、网络通信
通过学习UDP和TCP能够完成实际项目中的网络功能开发;为了提高程序的稳定性以及效率等通常会搭配使用多线程、多进程开发;根据项目的功能不同我们可以选择C/S(客户端/服务器)或B/S(嵌入式)开发模式;要学习B/S开发模式我们首先需要掌握整个网络的通信过程,这也是本章的学习重点(具体完全可以看计网笔记讲的非常详细,学习本章主要是想听一听PacketTracer怎么用)
Packet Tracer 是由Cisco公司发布的一个辅助学习工具,提供了设计、配置、排除网络故障网络模拟环境可以直接使用拖曳方法建立网络拓扑,并可提供数据包在网络中行进的详细处理过程,观察网络实时运行情况 (如果不使用软件则需要准备多台电脑、路由器等,非常的不方便)
第五章 网络通信过程.pdf
六、原始套接字
1.原始套接字概述
原始套接字(SOCK_RAW)
1、一种不同于SOCK_STREAM、SOCK_DGRAM的套接字,它实现于系统核心
2、可以接收本机网卡上所有的数据帧(数据包),对于监听网络流量和分析网络数据很有作用( 流式套接字只能收发 TCP协议的数据 数据报套接字只能收发 UDP协议的数据 )
3、开发人员可发送自己组装的数据包到网络上(原始套接字可以全部由自己DIY)
4、广泛应用于高级网络编程
5、网络专家、黑客通常会用此来编写奇特的网络程序
2.创建原始套接字
int socket(int domain, int type, int protocol);
功能:创建链路层的原始套接字,返回文件描述符
参数:
domain:通信域,地址族
AF_PACKET/PF_PACKET
type:套接字类型
SOCK_RAW
protocol:指定可以接收或发送的数据包类型
ETH_P_ALL 所有协议对应的数据包
ETH_P_IP ip数据包
ETH_P_ARP arp数据包
返回值:
成功:文件描述符
失败:‐1

