- 网络编程是指编写运行在多个网络节点上的程序,实现网络通信的程序。
- 网络编程的目的是实现网络通信,网络通信需要通过网络协议进行数据交换。
- 网络编程的实现需要考虑网络通信的效率、可靠性、安全性等问题。
- 上述OSI的七层网络模型是一种理想模型,在实际应用中效率较低,在实际应用中主要使用TCP/IP四层协议栈:
TCP/IP 四层模型是现行普适的网络层次模型。由顶层到底层是:
- 应用层:包含 OSI 模型中的最顶部三层,加密、压缩、编码、会话、语义等等功能统统是应用程序 APP 所辖范畴。
应用程序 APP 可以很简单,也可以很复杂。
- 传输层(位与操作系统内核):与 OSI 模型对应,负责传输控制。
- 网络层(位与操作系统内核):与 OSI 模型对应,负责路由选择。
- 网络接口与物理层(位与操作系统内核):这包括实际数据传输的物理媒介,及其对应的驱动层软件。
数据包在网络中传输时,发送方逐层对数据包进行对应协议的封装,这个过程类似于寄包裹的时候往包裹上贴标签。而接收方执行相反的过程,接收方拆包裹并读取对应协议信息(一般称为协议头)。
| 层次 |
协议 |
| 应用层 |
HTTP, HTTPS, FTP, SMTP, SNMP, DNS, TFTP, … |
| 传输层 |
TCP, UDP, … |
| 网络层 |
IP, ICMP, ARP, RARP, … |
| 数据链路层 |
SLIP, CSLIP, PPP, … |
- 套接字(socket)是网络通信中进程间进行双向通信的端点。
- 套接字是通信的基石,是支持 TCP/IP 协议的网络通信的基本操作单元。
- 它允许位于不同计算机上的进程进行双向通信。
- 套接字是支持 TCP/IP 协议的网络通信的基本操作单元,也是支持各种应用层协议的网络通信的基本操作单元,因此套接字也称为“网络套接字”。
- IP地址是IP协议提供的一种统一的地址格式,它为互联网上的每一个网络和每一台主机分配一个逻辑地址,以此来屏蔽物理地址的差异。
- 互联网上的主机和路由器使用IP地址进行通信。
- IP地址的格式:IP地址通常被写成“点分十进制”的形式,例如:192.168.0.1。
- 点分十进制形式中,每个点之间的数字代表该IP地址的子网掩码。
- 子网掩码:子网掩码用来屏蔽IP地址的“网络部分”,从而得到该IP地址的“主机部分”。
- 子网掩码是一个32位的二进制数,它由连续的1和连续的0组成。
- 子网掩码的格式为“点分十进制”形式,例如:
255.255.255.0。
IP地址与子网掩码相与之后的结果被称为网段:
上述例子中,子网掩码是 255.255.255.0,因此网段就是 192.168.9.x,局域网内通信的主机应该都要处于同一网段内,否则数据无法被该网段所属的网关(192.168.9.1)路由转发。
在很多场合下,子网掩码会以比特位的形式跟IP地址写在一起,例如上述子网属性可以写成:
后面的 /24 代表该IP地址所在的网段是前24比特位,也就是说子网掩码是 255.255.255.0
IP 地址可以唯一标识一台计算机,但通信的双方并不是两台计算机,而是计算机内部的进程。很明显,为了区分一台主机接收到的数据包应该转交给哪个进程来进行处理,使用端口号来加以区分。
端口号是一个短整型数据,长度为16位。具体分布:
- 系统端口:1~1023
- 注册端口:1024~49150
- 动态或私有端口:49151~65535(这是平常做实验可用的端口号范围)
TCP/UDP常见端口
当一个数据需要使用2个或以上字节来存储时,就会出现所谓字节序的概念。例如一个四字节数据,既可以将低有效位(78)放在低地址处,反之亦可放在高地址处。
通常,将最低有效位(即78)放在低地址的存储方式称为小端序,反之即大端序。在单机编程中,字节序是系统内部的存储细节,与程序无关。但在网络编程中,由于数据是在两台不同的机器中传输和表达,因此如果字节序不一致,则会出现牛头不对马嘴的现象。
解决这一困境的办法是,将网络中的数据,统一为某种固定的字节序,比如大端序。这样一来,凡是从主机往外发的数据,一律转换为大端序,凡是从网络接收的数据,也一律是大端序,网络字节序屏蔽了通信双方的具体细节,从而使得双方通信成为可能。
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
29
30
31
32
33
34
35
36
37
38
39
40
|
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#define HOST "127.0.0.1"
#define PORT 8080
int main() {
// 新建socket 文件
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
perror("socket");
exit(-1);
}
// 设置服务器地址
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET; // 设置为IPv4
server_addr.sin_port = htons(PORT); // 转换为大端序
server_addr.sin_addr.s_addr = inet_addr(HOST); // 设置为本地地址
// 连接服务器
if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
perror("connect fail");
exit(-1);
}
// 发送数据
char msg[1024];
while (1) {
printf("请输入要发送的消息:\n");
scanf("%s", msg);
send(sockfd, msg, sizeof(msg), 0);
}
}
|
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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
|
#include <netinet/in.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdlib.h>
#include "threadpool.h"
#define MIN_THREADS 10
#define MAX_THREADS 128
#define HOST "127.0.0.1"
#define PORT 8080
void *socket_routine(void *arg) {
int client_fd = *(int *)arg;
// 接收客户端数据
char buf[1024];
while (1) {
ssize_t recv_len = recv(client_fd, buf, sizeof(buf), 0);
if (recv_len < 0) {
perror("recv failed");
close(client_fd);
return NULL;
}
printf("Received message: %s\n", buf);
}
}
int main() {
// 创建socket 文件
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
perror("create socket failed");
exit(-1);
}
// 设置服务器地址
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
// 绑定socket 文件到服务器地址
if (bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
perror("bind addr failed");
exit(-1);
}
// 监听socket 文件
if (listen(sockfd, 10) < 0) {
perror("socket listen failed");
exit(-1);
}
// 接受客户端连接
struct sockaddr_in client_addr;
socklen_t client_addr_len = sizeof(client_addr);
// 创建线程池
ThreadPool_t *pool = threadpool_init(MIN_THREADS, MAX_THREADS);
while (1) {
int client_fd = accept(sockfd, (struct sockaddr *)&client_addr, &client_addr_len);
if (client_fd < 0) {
perror("accept error");
close(sockfd);
exit(-1);
}
threadpool_add_task(pool, socket_routine, &client_fd);
}
// 关闭socket 文件
close(sockfd);
return 0;
}
|