windows与linux环境进行网络编程,使用的库是不一样的,
下面主要说一下windows环境下的网络编程;
网络编程从大的方面说就是对信息的发送到接收,中间传输为物理线路的作用。网络编程最主要的工作就是在发送端把信息通过规定好的协议进行组装包,在接收端按照规定好的协议把包进行解析,从而提取出对应的信息,达到通信的目的。中间最主要的就是数据包的组装,数据包的过滤,数据包的捕获,数据包的分析。
网络编程的本质是让不同计算机上的进程通过网络交换数据。其核心依赖两个关键体系:
1. 协议栈:TCP/IP模型
计算机网络通过分层协议实现通信,实际应用中以TCP/IP四层模型为核心:
- 链路层:处理硬件设备(如网卡)的物理数据传输(如以太网帧)。
- 网络层:负责跨网络的数据包路由(核心协议:IP协议,定义数据包格式和地址)。
- 传输层:提供端到端的通信服务(核心协议:TCP、UDP)。
- 应用层:定义具体业务规则(如HTTP、FTP,由开发者实现)。
Socket接口主要工作在传输层和网络层,屏蔽了底层硬件和路由细节,让开发者可直接通过“端口+IP”定位目标进程。
2. 核心协议:TCP与UDP
传输层的两个核心协议决定了通信方式的差异:
- TCP(传输控制协议):
- 面向连接:通信前需通过“三次握手”建立连接,结束后“四次挥手”释放连接。
- 可靠传输:通过确认机制、重传机制、流量控制保证数据不丢失、不重复、按序到达。
- 字节流:数据以连续字节流形式传输(无边界)。
- 适用场景:文件传输、网页访问等需可靠数据的场景。
- UDP(用户数据报协议):
- 无连接:直接发送数据,无需建立连接。
- 不可靠传输:不保证数据到达,可能丢失、乱序。
- 数据报:数据以独立“数据包”形式传输(有边界)。
- 适用场景:视频通话、实时游戏等对延迟敏感的场景。
3. Socket(套接字)
Socket是操作系统提供的网络通信抽象,本质是一个“文件描述符”(类似文件句柄),通过它可读写网络数据。
- 每个Socket绑定一个IP地址+端口号,唯一标识网络中的一个进程(IP定位计算机,端口定位进程)。
- 分类:根据传输层协议,分为流式套接字(SOCK_STREAM,基于TCP) 和数据报套接字(SOCK_DGRAM,基于UDP)。
二、核心预备知识
在编写代码前,需掌握两个关键技术点:地址结构和字节序转换。
1. 网络地址结构
Socket通过结构体描述网络地址,最常用的是IPv4地址结构sockaddr_in
(定义在<netinet/in.h>
):
struct sockaddr_in {sa_family_t sin_family; // 地址族:必须为AF_INET(IPv4)in_port_t sin_port; // 端口号(网络字节序)struct in_addr sin_addr; // IP地址(网络字节序)unsigned char sin_zero[8]; // 填充字段,必须为0(与sockaddr兼容)
};struct in_addr {in_addr_t s_addr; // 32位IPv4地址(网络字节序)
};
通用地址结构sockaddr
(长度固定,用于函数参数统一):
struct sockaddr {sa_family_t sa_family; // 地址族char sa_data[14]; // 地址数据(含端口+IP,长度可变)
};
使用时需将sockaddr_in*
强制转换为sockaddr*
传给函数(如bind
、connect
)。
2. 字节序转换
网络中数据传输必须使用网络字节序(大端字节序),而主机可能是大端或小端(取决于CPU),因此需通过函数转换:
htons()
:主机字节序 → 网络字节序(16位,用于端口号)。htonl()
:主机字节序 → 网络字节序(32位,用于IP地址)。ntohs()
:网络字节序 → 主机字节序(16位)。ntohl()
:网络字节序 → 主机字节序(32位)。
示例:将端口号8080
转换为网络字节序:
uint16_t port = htons(8080); // 关键:端口必须转换,否则可能解析错误
3. IP地址转换
需将“点分十进制字符串”(如"192.168.1.1"
)与32位整数(网络字节序)互转:
- 推荐使用兼容IPv4/IPv6的函数:
inet_pton()
:字符串 → 网络字节序整数(presentation → network)。inet_ntop()
:网络字节序整数 → 字符串(network → presentation)。
示例:
struct sockaddr_in addr;
// 字符串IP → 网络字节序
inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr);
// 网络字节序 → 字符串
char ip_str[INET_ADDRSTRLEN]; // INET_ADDRSTRLEN:IPv4字符串最大长度(16)
inet_ntop(AF_INET, &addr.sin_addr, ip_str, INET_ADDRSTRLEN);
三、TCP编程(面向连接)
TCP通信需先建立连接(三次握手),再传输数据,最后释放连接(四次挥手)。
1. TCP服务器编程步骤
步骤 |
函数 |
作用 |
---|---|---|
1. 创建socket |
int socket(int domain, int type, int protocol); |
创建套接字描述符(文件句柄) |
2. 绑定地址 |
int bind(int sockfd, const struct sockaddr* addr, socklen_t addrlen); |
将socket与IP+端口绑定 |
3. 监听连接 |
int listen(int sockfd, int backlog); |
转为被动监听状态,等待客户端连接 |
4. 接受连接 |
int accept(int sockfd, struct sockaddr* addr, socklen_t* addrlen); |
阻塞等待并接受客户端连接,返回新socket |
5. 收发数据 |
ssize_t recv(int sockfd, void* buf, size_t len, int flags); / ssize_t send(int sockfd, const void* buf, size_t len, int flags); |
与客户端交换数据 |
6. 关闭连接 |
int close(int fd); |
释放资源 |
函数参数说明:
socket()
:domain=AF_INET
(IPv4),type=SOCK_STREAM
(TCP),protocol=0
(默认协议)。bind()
:addrlen
为sockaddr_in
的长度(sizeof(struct sockaddr_in)
)。listen()
:backlog
为等待队列最大长度(超过则拒绝新连接)。accept()
:addr
用于存储客户端地址,addrlen
需传入地址长度的指针(入参为缓冲区大小,出参为实际长度)。recv()
/send()
:flags=0
为默认模式(阻塞);recv
返回接收字节数(0表示对方关闭,-1表示错误);send
返回发送字节数(-1表示错误)。
2. TCP客户端编程步骤
步骤 |
函数 |
作用 |
---|---|---|
1. 创建socket |
socket() |
同服务器 |
2. 连接服务器 |
int connect(int sockfd, const struct sockaddr* addr, socklen_t addrlen); |
与服务器建立TCP连接 |
3. 收发数据 |
recv()/send() |
同服务器 |
4. 关闭连接 |
close() |
同服务器 |
3. TCP实战:回显服务器与客户端 (linux)
服务器代码(tcp_server.c):
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>#define PORT 8080
#define BUF_SIZE 1024int main() {// 1. 创建TCP socketint listen_fd = socket(AF_INET, SOCK_STREAM, 0);if (listen_fd == -1) {perror("socket failed");exit(EXIT_FAILURE);}// 2. 绑定地址(IP+端口)struct sockaddr_in serv_addr;memset(&serv_addr, 0, sizeof(serv_addr));serv_addr.sin_family = AF_INET;serv_addr.sin_addr.s_addr = INADDR_ANY; // 绑定所有本地IPserv_addr.sin_port = htons(PORT); // 端口转换为网络字节序if (bind(listen_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1) {perror("bind failed");close(listen_fd);exit(EXIT_FAILURE);}// 3. 监听连接(等待队列长度为5)if (listen(listen_fd, 5) == -1) {perror("listen failed");close(listen_fd);exit(EXIT_FAILURE);}printf("Server listening on port %d...\n", PORT);// 4. 接受客户端连接(循环处理单客户端,实际需并发)struct sockaddr_in client_addr;socklen_t client_len = sizeof(client_addr);int conn_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &client_len);if (conn_fd == -1) {perror("accept failed");close(listen_fd);exit(EXIT_FAILURE);}// 打印客户端信息char client_ip[INET_ADDRSTRLEN];inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, INET_ADDRSTRLEN);printf("Accepted connection from %s:%d\n", client_ip, ntohs(client_addr.sin_port));// 5. 回显数据(接收后原样返回)char buf[BUF_SIZE];ssize_t n;while ((n = recv(conn_fd, buf