# 一、网络程序设计技术
# 1. 网络程序设计基本概念
# 1. 1 网络体系结构

# 三种结构模型比较

# 2.Windows Sockets
# 2.1Winsock2 需要的头文件
winsock2.h、ws2_32.lib、ws2_32.dll
# 2.2WinSock 初始化
1 | int WSAAPI WSAStartup(WORD wVersionRequested,LPWSADATA lpWSAData); |
# 2.3Winsock API 的错误检查与处理
1 | int WSAGetLastError(void); |
# 2.4IPv4 地址的表示
IPv4 是一个 32 位的二进制数
1 | struct sockaddr_in |
- sin_family
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 - sin_port 是网络字节次序的端口号
- sin_addr 是网络字节次序的 IPv4 的 32 位地址表示
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17struct in_addr
{
union
{
struct{u_char s_b1,s_b2,s_b3,s_b4;}S_un_b;
struct{u_short s_w1,s_w2;}S_un_w;
u_long S_addr;
}S_un;
}
//IP地址转换函数
unsigned long inet_addr(const char* cp);//功能:将“192.168.0.200”点分十进制表示法转换成32位的二进制数(IPV4)。
char* FAR inet_ntoa(struct in_addr in);//功能:将struct in_addr 所表示的32位的二进制数(IPV4)地址转换成如“192.168.0.200”这样的点分十进制表示法的IPV4地址。
# 2.5 主机字节顺序与网络字节顺序
主机字节顺序采用的方式有区别Internet规定的网络字节顺序采用大端方式
1 | u_long htonl(u_long hostlong); |
# 2.6 其余辅助函数
- 获取主机名:gethostname ()
1
2
3
4
5
6
7/*
功能:返回本地主机的标准主机名
参数:主机名
成功返回0
使用前WSAStartup()
*/
int gethostname(char*name,int namelen); - 返回主机名对应的主机信息:gethostbyname ()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16/*
返回值:若成功,则返回一个指向hostent的指针,否则返回:NULL。
注:1)必须首先使用:WSAStartup()。
2)所返回的结构的空间是在Winsock中分配的。不允许释放该结构中任何部分的指针空间。且若要一直使用,则首先在调用其它winsock函数之前,将它的内容保存到自己的变量中。
3)点分十进制表示的IP地址的串,是不能解析成功的。应先使用inet_addr()将其转换成真正IP地址(struct in_addr),然后使用gethostbyaddr()转成hostent结构。
*/
struct hostent* FAR gethostbyname(const char* name);
typedef struct hostent
{
char FAR* h_name; //主机名
char FAR FAR** h_aliases; //主机别名列表
short h_addrtype; //地址类型
short h_length; //地址长度
char FAR FAR** h_addr_list;//主机的IP地址列表
}hostnet; - 返回对应一个 IP 网络地址的主机的信息:gethostbyaddr ()
1
struct hostent*FAR gethostbyaddr(const char* addr,int len,int type);
# 2.7 传输控制协议 (TCP)
# 2.7.1TCP 的 C/S 通信模型
* 服务端
- 创建 Socket
- 绑定地址
- 监听连接请求
- 接受请求
- 使用 send () 和 recv () 进行通信
- 关闭连接
客户端
- 创建 Socket
- 连接到服务器
- 使用 send () 和 recv () 进行通信
- 关闭连接
# 2.7.2 基本套接字函数
- socket 函数
1
2
3
4
5
6
7
8
9
10
11/*
功能:创建一个未绑定的socket。SOCKET定义为u_int.
参数:1)af:[输入] 指定要创建的套接字的协议簇;TCP/IP协议使用:AF_INET
2)type:[输入]要创建的套接字类型:
字节流型:SOCK_STREAM
数据报型:SOCK_DGRAM
原始SOCKET:SOCK_RAW
3)protocol:[输入]指定使用哪种协议。通常设置为0,表示使用该类型SOCKET的默认协议。对字节流型的SOCK_STREAM意味着是TCP,对数据报型的SOCK_DGRAM意味着是UDP,对原始SOCKET的SOCK_RAW则必须指明(如:ICMP或IGMP等),因为此处填写的值将直接写入IP包的包头中。
返回值:若成功则返回一个正整数,称为套接字描述符,标识这个套接字;否则,返回INVALID_SOCKET(该值不是-1)
*/
SOCKET WSAAPI socket(int af,int type,int protocol); - bind 函数
1
2
3
4
5
6
7
8
9
10
11/*
功能:将本地地址和端口号与套接字绑定在一起。
参数:
1)s:[输入]要绑定的套接字描述符;
2)name:[输入]实际上是使用struct sockaddr_in的变量的地址。在其中填写地址与端口号。
3)namelen:[输入]套接字地址结构的长度。
返回值:若成功则返回0;否则返回SOCKET_ERROR(-1)。
*/
int bind(SOCKET s ,const struct sockaddr* name,int namelen);
//注意是使用:struct sockaddr_in myaddr;
//而不是:struct sockaddr myaddr;
绑定操作一般有以下方式
| 程序类型 | IP 地址 | 端口号 | 说明 |
|---|---|---|---|
| 服务器 | INADDR_ANY | 非零值 | 指定服务器的公认端口号 |
| 服务器 | 本机 IP 地址 | 非零值 | 指定服务器的 IP 地址和公认端口号 |
| 客户机 | INADDR_ANY | 非零值 | 指定客户机的连接端口号 |
| 客户机 | 本地 IP 地址 | 非零值 | 指定客户机的 IP 地址和连接端口号 |
| 客户机 | 本地 IP 地址 | 零 | 指定客户机的 IP 地址 |
1 | 1. **服务器指定套接字的公认端口号,不指定IP地址。** |
listen 函数
1
2
3
4
5
6
7
8/*
功能:将一个已绑定的套接字转换为倾听套接字。
参数:
1) sockfd:[输入]指定要转换的套接字描述符;
2) backlog:[输入]设置请求队列的最大长度(处于等待建立TCP全连接的请求,通常是半打开的TCP连接)。
返回值:成功则返回0;否则返回SOCKET_ERROR(-1)。
*/
int listen(SOCKET sockfd,int backlog);
服务器需要调用函数 listen 将套接字转换成倾听套接字,以便接收客户机请求。函数 listen 的功能有两个:
(1) 函数 socket 创建的套接字是主动套接字,可以用它来进行主动连接 (调用函数 connect),但是不能接收连接请求,而服务器的套接字必须能够接收客户机的请求。函数 listen 将一个尚未连接的主动套接字转换成为 — 个被动套接字:告诉 TCP 协议,这个套接字可以接收连接请求。执行函数 listen 之后,服务器的 TCP 状态由 CLOSED 状态转换成 LISTEN 状态。
(2) TCP 协议将到达的连接请求排队,函数 listen 的第二个参数指定这个队列的最大长度。 要创建一个倾听套接字,必须首先调用函数 socket 创建一个主动套接字,然后调用函数 bind 将它与服务器套接宇地址绑定在一起,最后调用函数 listen 进行转换。这 3 步操作是所有 TCP 服务器所必须的操作。若该请求队列已满,但又有新的请求到达时,则新的请求将被拒绝。backlog 的最大值由具体实现指定。若程序员指定的值超过该值,则系统会自动选择一个最接近用户值但系统又能支持的值。accept 函数
1 | SOCKET accept(SOCKET sockfd,struct sockaddr *addr,int *addrlen); |
- closesocket 函数
1 | int closesocket(SOCKET sockfd); |
- connect 函数
1 | int connect(SOCKET sockfd,struct sockaddr *servaddr, int addrlen); |
# 2.7.3 网络数据传输
- send 函数
1 | int WSAAPI send( |
- recv 函数
1 | int WSAAPI recv( |
# TCP 服务器设计
S1、调用 WSAStartup()装载 Winsock 相应版本的 DLL 库。
S2、调用 socket () 创建一个 socket。
S3、调用 bind () 绑定服务器的 IP 和 PORT。
S4、调用 listen () 变成倾听 socket。
S5、调用 accept () 等待客户机的连接。
S6、调用 send ()/recv () 按应用协议进行网络通信。
S7、调用 closesocket () 关闭相应的 socket。
# TCP 客户机设计
S1、调用 WSAStartup()装载 Winsock 相应版本的 DLL 库。
S2、调用 socket () 创建一个 socket。
S3、调用 connect 向服务器发起一个 TCP 连接。
S6、调用 send ()/recv () 按应用协议进行网络通信。
S7、调用 closesocket () 关闭相应的 socket。
# 2.8 用户数据流协议 (UDP)

# 2.8.1UDP 建立
1.UDPSOCKET 建立
1 | SOCKET udps=socket(AF_INET,SOCKE_DGRAM,0); |
- UDP 数据报的发送 sendto ()
1 | int sendto( |
3. 接受 UDP 包 recvfrom ()
1 | int recvfrom( |
# 2.8.2UDP 丢包
数据大小超过 64K
# 2.9MTU (最大可传输单元)
1 | 一个数据包穿过一个大的网络,它其间会穿过多个网络,每个网络的 MTU 值是不同的。这个网络中最小的 MTU 值,被称为路径 MTU。 |
# 2.10IP 数据包

# TCP 的使用场景:
- 可靠性要求高: 当应用程序需要可靠的、面向连接的数据传输时,通常选择 TCP。TCP 提供数据的可靠性,确保数据按顺序到达,并处理丢失的数据包的重传。
- 顺序性要求高: 如果应用程序需要确保数据按发送顺序到达,TCP 是更好的选择。TCP 使用序列号来确保数据包按正确的顺序传递。
- 适用于大数据量传输: TCP 适用于需要传输大量数据的场景,因为它可以自动调整传输速率,通过拥塞控制和流量控制来保证数据的可靠传输。
- 网页浏览、文件传输: 在需要确保数据准确传输的场景下,如网页浏览、文件传输、电子邮件等应用中,通常使用 TCP。
# UDP 的使用场景:
- 实时性要求高: 当应用程序对实时性要求较高,可以容忍少量数据包的丢失而不进行重传时,通常选择 UDP。UDP 是一种无连接的协议,没有建立和断开连接的过程,因此具有较低的延迟。
- 广播和多播: UDP 支持广播和多播通信,适用于向多个主机同时发送相同的数据。
- 音视频传输: 在实时音视频通信中,如 VoIP、视频会议等,通常使用 UDP。虽然 UDP 不提供可靠性,但在实时应用中,实时性更为重要。
- 简单的请求 - 响应通信: 当通信模式为简单的请求 - 响应模式,而且可以容忍一定的数据丢失时,UDP 是一个合适的选择。