编程与数学 03-002 计算机网络 16_网络编程基础
摘要:本文是计算机网络课程中关于网络编程基础的学习笔记。网络编程涵盖客户端与服务器模型、套接字编程、基于TCP和UDP的网络编程。客户端与服务器模型是常见网络应用架构,客户端请求服务,服务器提供服务。套接字是网络编程基础,用于实现通信,分为流式、数据报和原始套接字。基于TCP的编程提供可靠连接服务,基于UDP的编程提供无连接服务,适用于实时应用。通过学习这些内容,可深入理解网络编程概念和方法,为网络应用开发打下基础。
关键词:网络编程、客户端与服务器、套接字、TCP、UDP、实时应用
人工智能助手:Kimi
一、网络编程的基本概念
(一)客户端与服务器模型
-
定义
- 客户端与服务器模型是一种常见的网络应用架构,其中客户端是请求服务的一方,服务器是提供服务的一方。客户端通过网络向服务器发送请求,服务器处理请求后返回响应。
-
特点
- 客户端:客户端是用户使用的应用程序,如Web浏览器、邮件客户端等。客户端的主要功能是向服务器发送请求,并接收服务器的响应。
- 服务器:服务器是提供服务的计算机,如Web服务器、邮件服务器等。服务器的主要功能是接收客户端的请求,并处理请求后返回响应。
- 通信方式:客户端与服务器之间的通信通常通过套接字(Socket)进行,套接字是网络编程中的基本概念,用于实现网络通信。
(二)套接字(Socket)编程的基本原理
-
定义
- 套接字(Socket)是网络编程中的基本概念,用于实现网络通信。套接字提供了一种抽象的接口,使得应用程序可以通过套接字进行网络通信。
-
类型
- 流式套接字(SOCK_STREAM):流式套接字用于TCP协议,提供可靠的、面向连接的通信服务。
- 数据报套接字(SOCK_DGRAM):数据报套接字用于UDP协议,提供不可靠的、无连接的通信服务。
- 原始套接字(SOCK_RAW):原始套接字用于直接访问网络层协议,如IP协议。原始套接字通常用于网络协议的开发和调试。
-
工作过程
- 创建套接字 :通过调用
socket()
函数创建套接字。 - 绑定地址 :通过调用
bind()
函数将套接字绑定到一个地址和端口。 - 监听连接 :对于TCP套接字,通过调用
listen()
函数监听连接请求。 - 接受连接 :对于TCP套接字,通过调用
accept()
函数接受连接请求。 - 发送和接收数据 :通过调用
send()
和recv()
函数发送和接收数据。 - 关闭套接字 :通过调用
close()
函数关闭套接字。
- 创建套接字 :通过调用
二、基于TCP的网络编程
(一)TCP套接字的创建与使用
-
创建TCP套接字
-
通过调用
socket()
函数创建TCP套接字:cint sockfd = socket(AF_INET, SOCK_STREAM, 0);
其中,
AF_INET
表示使用IPv4地址族,SOCK_STREAM
表示使用TCP协议。
-
-
绑定地址
-
通过调用
bind()
函数将套接字绑定到一个地址和端口:cstruct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons(port); addr.sin_addr.s_addr = INADDR_ANY; bind(sockfd, (struct sockaddr *)&addr, sizeof(addr));
其中,
port
是服务器的端口号,INADDR_ANY
表示绑定到所有可用的网络接口。
-
-
监听连接
-
通过调用
listen()
函数监听连接请求:clisten(sockfd, backlog);
其中,
backlog
是未完成连接队列的最大长度。
-
-
接受连接
-
通过调用
accept()
函数接受连接请求:cint clientfd = accept(sockfd, (struct sockaddr *)&client_addr, &client_len);
其中,
clientfd
是接受连接后返回的客户端套接字,client_addr
是客户端的地址信息,client_len
是客户端地址信息的长度。
-
-
发送和接收数据
-
通过调用
send()
和recv()
函数发送和接收数据:csend(clientfd, data, size, 0); recv(clientfd, buffer, size, 0);
其中,
data
是要发送的数据,size
是数据的大小,buffer
是接收数据的缓冲区。
-
-
关闭套接字
-
通过调用
close()
函数关闭套接字:cclose(sockfd); close(clientfd);
-
(二)示例程序(如简单的聊天程序)
-
服务器端代码
c#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> int main() { int sockfd, clientfd; struct sockaddr_in server_addr, client_addr; socklen_t client_len; char buffer[1024]; // 创建TCP套接字 sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) { perror("socket"); exit(1); } // 绑定地址 server_addr.sin_family = AF_INET; server_addr.sin_port = htons(8080); server_addr.sin_addr.s_addr = INADDR_ANY; if (bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) { perror("bind"); close(sockfd); exit(1); } // 监听连接 if (listen(sockfd, 5) < 0) { perror("listen"); close(sockfd); exit(1); } printf("Server is listening on port 8080...\n"); // 接受连接 client_len = sizeof(client_addr); clientfd = accept(sockfd, (struct sockaddr *)&client_addr, &client_len); if (clientfd < 0) { perror("accept"); close(sockfd); exit(1); } printf("Client connected: %s:%d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port)); // 通信循环 while (1) { // 接收客户端数据 int n = recv(clientfd, buffer, sizeof(buffer), 0); if (n < 0) { perror("recv"); break; } else if (n == 0) { printf("Client disconnected\n"); break; } buffer[n] = '\0'; printf("Received from client: %s\n", buffer); // 发送数据到客户端 send(clientfd, buffer, strlen(buffer), 0); } // 关闭套接字 close(clientfd); close(sockfd); return 0; }
-
客户端代码
c#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> int main() { int sockfd; struct sockaddr_in server_addr; char buffer[1024]; // 创建TCP套接字 sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) { perror("socket"); exit(1); } // 设置服务器地址 server_addr.sin_family = AF_INET; server_addr.sin_port = htons(8080); inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr); // 连接到服务器 if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) { perror("connect"); close(sockfd); exit(1); } printf("Connected to server at 127.0.0.1:8080\n"); // 通信循环 while (1) { // 从用户输入数据 printf("Enter message: "); fgets(buffer, sizeof(buffer), stdin); buffer[strcspn(buffer, "\n")] = '\0'; // 发送数据到服务器 send(sockfd, buffer, strlen(buffer), 0); // 接收服务器响应 int n = recv(sockfd, buffer, sizeof(buffer), 0); if (n < 0) { perror("recv"); break; } else if (n == 0) { printf("Server disconnected\n"); break; } buffer[n] = '\0'; printf("Received from server: %s\n", buffer); } // 关闭套接字 close(sockfd); return 0; }
三、基于UDP的网络编程
(一)UDP套接字的创建与使用
-
创建UDP套接字
-
通过调用
socket()
函数创建UDP套接字:cint sockfd = socket(AF_INET, SOCK_DGRAM, 0);
其中,
AF_INET
表示使用IPv4地址族,SOCK_DGRAM
表示使用UDP协议。
-
-
绑定地址
-
通过调用
bind()
函数将套接字绑定到一个地址和端口:cstruct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons(port); addr.sin_addr.s_addr = INADDR_ANY; bind(sockfd, (struct sockaddr *)&addr, sizeof(addr));
其中,
port
是服务器的端口号,INADDR_ANY
表示绑定到所有可用的网络接口。
-
-
发送和接收数据
-
通过调用
sendto()
和recvfrom()
函数发送和接收数据:csendto(sockfd, data, size, 0, (struct sockaddr *)&server_addr, sizeof(server_addr)); recvfrom(sockfd, buffer, size, 0, (struct sockaddr *)&client_addr, &client_len);
其中,
data
是要发送的数据,size
是数据的大小,buffer
是接收数据的缓冲区,server_addr
是服务器的地址信息,client_addr
是客户端的地址信息,client_len
是客户端地址信息的长度。
-
-
关闭套接字
-
通过调用
close()
函数关闭套接字:cclose(sockfd);
-
(二)UDP编程的特点与应用场景
-
特点
- 无连接:UDP协议是无连接的,发送方在发送数据前不需要建立连接,接收方在接收数据前也不需要建立连接。这使得UDP协议的开销较小,适合对实时性要求较高的应用。
- 不可靠:UDP协议不提供可靠传输机制,不保证数据的正确传输。如果数据在传输过程中丢失或出错,UDP协议不会进行重传。
- 简单:UDP协议的实现相对简单,协议开销较小,适合对实时性要求较高的应用,如视频会议、音频广播等。
- 支持多播:UDP协议支持多播通信,可以向多个目标地址同时发送数据。
-
应用场景
- 实时应用:UDP协议适合对实时性要求较高的应用,如视频会议、音频广播等。这些应用对数据的实时性要求较高,允许一定程度的数据丢失。
- 简单应用:UDP协议适合实现简单的网络应用,如DNS查询、SNMP等。这些应用对协议的开销要求较低,不需要复杂的可靠传输机制。
- 多播应用:UDP协议支持多播通信,适合向多个目标地址同时发送数据的应用,如多播视频会议、多播音频广播等。
四、总结
网络编程是计算机网络中的重要组成部分,涉及客户端与服务器模型、套接字编程、基于TCP的网络编程和基于UDP的网络编程等多个方面。客户端与服务器模型是一种常见的网络应用架构,客户端通过网络向服务器发送请求,服务器处理请求后返回响应。套接字是网络编程中的基本概念,用于实现网络通信。基于TCP的网络编程通过创建TCP套接字,实现可靠的、面向连接的通信服务;基于UDP的网络编程通过创建UDP套接字,实现不可靠的、无连接的通信服务。
通过学习网络编程的基础知识,我们可以更好地理解网络编程的基本概念和实现方法,为后续的网络应用开发打下坚实的基础。