unix环境高级编程学习——网络编程

[复制链接]
查看580 | 回复5 | 2023-9-15 14:31:13 | 显示全部楼层 |阅读模式

1. UNIX网络编程概述

网络编程是编写能通过网络进行通信的程序的过程。网络程序在UNIX系统上得到了广泛的应用,包括TCP/IP协议族的各种服务,如HTTP、FTP、SMTP等。UNIX网络编程通常涉及套接字(sockets)编程,这是一种用于在网络上进行通信的基本抽象概念。套接字提供了一种通用的接口,使得程序可以使用不同的协议族进行通信。


2. 套接字编程基础

在UNIX系统中,套接字是一种抽象的概念,它提供了应用程序和网络协议族之间的接口。通过使用套接字,应用程序可以连接到远程服务器,发送和接收数据。套接字编程涉及到以下几个基本步骤:


2.1 创建套接字

使用socket()函数来创建一个套接字。该函数接受两个参数:协议族和套接字类型。例如,要创建一个TCP/IP的流式套接字,可以使用如下代码:

  1. int sockfd = socket(AF_INET, SOCK_STREAM, 0);
复制代码
2.2 绑定地址

使用bind()函数将地址(IP地址和端口号)绑定到套接字上。例如,要绑定一个IP地址为127.0.0.1,端口号为8080的套接字,可以使用如下代码:

  1. struct sockaddr_in servaddr;  
  2. bzero(&servaddr, sizeof(servaddr));  
  3. servaddr.sin_family = AF_INET;  
  4. servaddr.sin_addr.s_addr = htonl(INADDR_ANY);  
  5. servaddr.sin_port = htons(8080);  
  6. bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
复制代码
2.3 监听连接

使用listen()函数使得套接字变为被动模式,等待客户端的连接。例如,可以使用如下代码:

  1. listen(sockfd, BACKLOG);
复制代码
2.4 接受连接

使用accept()函数接受客户端的连接。例如,可以使用如下代码:

  1. struct sockaddr_in clientaddr;  
  2. socklen_t clientaddrlen = sizeof(clientaddr);  
  3. int newsockfd = accept(sockfd, (struct sockaddr *)&clientaddr, &clientaddrlen);
复制代码
2.5 发送和接收数据

使用send()和recv()函数来发送和接收数据。例如,可以使用如下代码:

  1. char sendbuf[MAXLINE] = "Hello, world!";  
  2. send(newsockfd, sendbuf, strlen(sendbuf), 0);  
  3. char recvbuf[MAXLINE];  
  4. recv(newsockfd, recvbuf, sizeof(recvbuf), 0);
复制代码
2.6 关闭连接

使用close()函数来关闭连接。例如,可以使用如下代码:

  1. close(newsockfd);  
  2. close(sockfd);
复制代码
3. 高级套接字编程技术
3.1 非阻塞式I/O操作

默认情况下,套接字I/O操作是阻塞的,即当操作不能立即完成时,调用会阻塞程序的执行。例如,当程序调用读取操作时,如果数据尚未准备好,那么读取操作将会阻塞,直到数据准备好为止。在这种情况下,程序将无法执行其他任务,直到读取操作完成。
非阻塞式I/O允许程序设置套接字为非阻塞模式,这样当操作不能立即完成时,调用会立即返回。这样可以提高程序的并发性能,因为程序可以在等待I/O操作完成时执行其他任务。

可以使用fcntl()函数来设置非阻塞模式:

  1. int flags = fcntl(sockfd, F_GETFL, 0);      
  2. fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);
复制代码

这段代码将会获取当前套接字的文件状态标志(通过调用F_GETFL命令),然后将其与O_NONBLOCK标志进行按位或操作(通过调用F_SETFL命令),以设置套接字为非阻塞模式。在此模式下,当套接字上的I/O操作不能立即完成时,操作将返回一个错误代码,而程序可以继续执行其他任务。

3.2 多线程和多进程服务器程序

多线程和多进程服务器程序可以提高服务器的并发处理能力。在多线程服务器程序中,每个连接都由一个线程来处理。在多进程服务器程序中,每个连接都由一个进程来处理。可以使用fork()函数来创建子进程,使用pthread_create()函数来创建线程。
对于多线程服务器程序,每个线程都会独立地处理一个连接。这种方式的优点是,由于线程共享相同的内存空间,线程间的通信和数据共享相对简单。然而,多线程编程也需要注意线程同步和互斥问题,以防止数据竞争等问题。例如,在C++中,可以使用互斥锁(mutex)和条件变量(condition variable)等机制来实现线程同步和互斥。

对于多进程服务器程序,每个进程都会独立地处理一个连接。由于进程拥有独立的内存空间,进程间的通信和数据共享相对复杂。常用的进程间通信方式包括管道(pipe)、消息队列(message queue)、共享内存(shared memory)等。例如,在Linux系统中,可以使用pipe()函数来创建一个管道,使得父进程和子进程可以通过管道进行通信。

无论是多线程还是多进程服务器程序,都需要考虑并发访问的安全性和效率问题。例如,在多线程服务器程序中,需要使用线程同步机制来确保多个线程不会同时访问共享资源;在多进程服务器程序中,需要使用进程间通信机制来确保多个进程可以协同工作。同时,还需要注意避免死锁和饥饿等问题,以确保服务器的稳定性和性能。



4.高级网络编程技术
4.1 TCP协议的高级特性

TCP协议有许多高级特性,如滑动窗口、确认应答、重传、流量控制、拥塞控制等。这些特性可以提供可靠的数据传输服务,同时也保证了网络的高效利用。
比如滑动窗口,就是一种流量控制技术,用于控制发送方和接收方之间的数据流量。它允许发送方在接收到确认应答之前发送多个数据包,从而提高网络的吞吐量。确认应答是一种可靠性机制,用于确保接收方正确接收数据包。当接收方收到数据包时,它会发送一个确认应答给发送方,告诉发送方数据包已经正确接收。重传是一种可靠性机制,用于在数据包丢失或损坏时重新发送数据包。当接收方没有收到数据包或者收到的数据包损坏时,它会发送一个重传请求给发送方,请求重新发送数据包。流量控制是一种机制,用于防止发送方发送过多的数据给接收方,从而导致接收方无法处理。通过流量控制,发送方可以根据接收方的处理能力来控制数据包的发送速率。拥塞控制是一种机制,用于避免过多的数据包同时在网络中传输,从而导致网络拥塞。当网络出现拥塞时,拥塞控制机制会降低发送方的发送速率,以减少网络中的数据包数量。

这些高级特性使得TCP协议能够在不可靠的IP网络上提供可靠的数据传输服务。例如,当网络中出现拥塞时,拥塞控制机制会降低发送方的发送速率,以避免网络拥塞。这样,TCP协议可以在网络拥塞时保持数据传输的可靠性。另外,通过滑动窗口、确认应答和重传等机制,TCP协议可以在数据包丢失或损坏时重新发送数据包,从而确保数据的完整性。这些特性使得TCP协议成为互联网上最常用的传输协议之一。


4.2 UDP协议的高级特性

UDP协议虽然不像TCP协议那样有众多高级特性,但它提供了简单、快速的数据传输服务,适用于不需要可靠传输的场景。
UDP(User Datagram Protocol,用户数据报协议)是一种无连接的协议,它不像TCP那样需要建立连接和进行握手,因此可以减少一些传输延迟。UDP不提供可靠传输服务,也就是说,它不保证数据包的顺序、重复或丢失问题。这使得UDP适用于那些需要快速传输和对丢失数据包可以容忍的场景,如视频流、语音通话和游戏等。

此外,UDP协议还有一些其他的高级特性,例如:


  •     广播和多播:UDP支持广播和多播,这意味着一台计算机可以同时向一个子网或一组计算机发送数据包。这种特性使得UDP适用于一些需要高效发送消息给多个接收者的应用,如网络直播和多人在线游戏。
  •     校验和:虽然UDP不提供可靠传输,但它包含一个校验和字段,用于检测数据包在传输过程中的完整性。如果数据包在传输过程中被损坏,接收方可以通过校验和来检测并丢弃损坏的数据包。
  •     端口号:与TCP一样,UDP也使用端口号来标识发送和接收应用程序。这使得一台计算机可以同时运行多个基于UDP的应用程序。
  •     数据包大小:UDP对数据包的大小没有限制,这使得它适用于那些需要发送大量数据的应用,如视频流和游戏。

总的来说,虽然UDP没有TCP那么多的高级特性,但它的简单性和快速性使得它在某些场景下更为适用。理解这些高级特性可以帮助开发人员更好地利用UDP协议进行网络编程。


4.3 数据报文分割和重组

在某些情况下,需要将数据报文分割成更小的数据片段进行传输,然后在接收端进行重组。这时可以使用TCP/IP协议中的分片和重组机制。
例如,当发送一份大文件时,我们可以将文件分割成多个小的数据包,每个数据包都包含一部分文件内容。然后,这些数据包可以通过TCP/IP协议进行传输,接收端在收到所有数据包后,可以将它们按照顺序重新组合成原始文件。

另外,在一些实时通信场景中,如视频通话或在线游戏,也需要将数据报文分割和重组。因为这些应用需要快速传输大量数据,而且需要保证数据的实时性,所以通常会将数据分割成较小的片段进行传输,以确保数据的实时性和稳定性。

总之,数据报文分割和重组是网络编程中常见的操作,可以提高传输效率和保证数据的实时性。



4.4 数据加密和压缩

为了保证数据的机密性和可用性,需要对数据进行加密和压缩。常用的加密算法有AES、DES等,常用的压缩算法有Zlib、LZMA等。
例如,当我们发送一份重要文件时,我们可以使用AES算法对文件进行加密,以确保文件内容不会被窃取或篡改。同时,我们可以使用Zlib算法对文件进行压缩,以减小文件大小并加快传输速度。
         另外,在一些特殊的应用场景中,如军事通信或金融交易,数据的机密性和完整性更加重要。这时,我们可以使用更高级的加密算法,如RSA或ECC,以确保数据的安全性。
         总之,数据加密和压缩是网络编程中必要的操作,可以保护数据的机密性和完整性,并提高传输效率和性能。


4.5 网络测量和控制

网络测量和控制是网络编程的重要部分。可以使用ping、traceroute等工具进行网络测量,使用SNMP、Netstat等工具进行网络控制。
例如,我们可以使用ping命令来测试网络的连通性,通过发送ICMP协议的数据包,检测目标主机是否可达,并获取网络延迟等信息。traceroute命令可以用来追踪数据包在网络中的传输路径,帮助我们了解数据包经过哪些路由器节点,从而分析网络的拓扑结构和性能瓶颈。

另外,SNMP(简单网络管理协议)是一种用于网络管理的协议,它可以让网络管理员远程监控和管理网络设备,如路由器、交换机等。Netstat命令可以用来查看网络连接状态、监听端口等信息,帮助我们了解网络的使用情况和性能表现。

总之,网络测量和控制是网络编程中不可或缺的部分,它可以帮助我们了解网络的性能和状态,从而优化网络配置和提高网络效率。



5.网络编程实践
通过实践可以更好地理解和掌握网络编程。以下是一些网络编程实践的建议:
5.1 编写一个简单的Echo服务器程序
以下是一个使用TCP协议的Echo服务器程序的示例代码:
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <string.h>  
  4. #include <sys/socket.h>  
  5. #include <arpa/inet.h>  
  6. #include <unistd.h>  
  7.   
  8. #define PORT 8080  
  9.   
  10. int main(int argc, char const *argv[]) {  
  11.     int server_fd, new_socket;  
  12.     struct sockaddr_in address;  
  13.     int addrlen = sizeof(address);  
  14.     char buffer[1024] = {0};  
  15.     char *hello = "Hello from server";  
  16.       
  17.     // Creating socket file descriptor  
  18.     if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {  
  19.         perror("socket failed");  
  20.         exit(EXIT_FAILURE);  
  21.     }  
  22.       
  23.     address.sin_family = AF_INET;  
  24.     address.sin_addr.s_addr = INADDR_ANY;  
  25.     address.sin_port = htons(PORT);  
  26.       
  27.     // Forcefully attaching socket to the port 8080  
  28.     if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {  
  29.         perror("bind failed");  
  30.         exit(EXIT_FAILURE);  
  31.     }  
  32.     if (listen(server_fd, 3) < 0) {  
  33.         perror("listen");  
  34.         exit(EXIT_FAILURE);  
  35.     }  
  36.     if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {  
  37.         perror("accept");  
  38.         exit(EXIT_FAILURE);  
  39.     }  
  40.     int valread = read(new_socket, buffer, 1024);  
  41.     printf("%s\n",buffer);  
  42.     send(new_socket, hello, strlen(hello), 0 );  
  43.     printf("Hello message sent\n");  
  44.     return 0;  
  45. }
复制代码
5.2 编写一个简单的Web服务器程序
以下是一个使用TCP协议的Web服务器程序的示例代码:
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <string.h>  
  4. #include <unistd.h>  
  5. #include <sys/socket.h>  
  6. #include <arpa/inet.h>  
  7. #include <netinet/in.h>  
  8.   
  9. #define PORT 8080 // 服务器监听的端口号  
  10. #define BUFFER_SIZE 1024 // 接收缓冲区大小  
  11.   
  12. int main() {  
  13.     int server_fd, new_socket;  
  14.     struct sockaddr_in address;  
  15.     int addrlen = sizeof(address);  
  16.     char buffer[BUFFER_SIZE] = {0};  
  17.     char *response = "HTTP/1.1 200 OK\nContent-Type: text/html\n\n<html><body><h1>Hello, World!</h1></body></html>";  
  18.       
  19.     // 创建套接字文件描述符  
  20.     if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {  
  21.         perror("socket failed");  
  22.         exit(EXIT_FAILURE);  
  23.     }  
  24.       
  25.     address.sin_family = AF_INET;  
  26.     address.sin_addr.s_addr = INADDR_ANY;  
  27.     address.sin_port = htons(PORT);  
  28.       
  29.     // 将套接字绑定到指定的端口号  
  30.     if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {  
  31.         perror("bind failed");  
  32.         exit(EXIT_FAILURE);  
  33.     }  
  34.     if (listen(server_fd, 3) < 0) {  
  35.         perror("listen");  
  36.         exit(EXIT_FAILURE);  
  37.     }  
  38.     if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {  
  39.         perror("accept");  
  40.         exit(EXIT_FAILURE);  
  41.     }  
  42.       
  43.     // 接收客户端请求并发送响应  
  44.     memset(buffer, 0, BUFFER_SIZE);  
  45.     if ((recv(new_socket, buffer, BUFFER_SIZE, 0)) < 0) {  
  46.         perror("recv failed");  
  47.         exit(EXIT_FAILURE);  
  48.     }  
  49.     printf("Received: %s\n", buffer);  
  50.     send(new_socket, response, strlen(response), 0);  
  51.     printf("Sent: %s\n", response);  
  52.       
  53.     // 关闭套接字和套接字文件描述符  
  54.     close(new_socket);  
  55.     close(server_fd);  
  56.     return 0;  
  57. }
复制代码
5.3 编写一个简单的FTP服务器程序
FTP服务器程序可以让客户端上传和下载文件,并保证文件的完整性。以下是使用TCP协议的FTP服务器程序的示例代码:

  1. #include <stdio.h>  // 引入标准输入输出库  
  2. #include <stdlib.h> // 引入标准库  
  3. #include <string.h> // 引入字符串处理库  
  4. #include <sys/socket.h> // 引入套接字库  
  5. #include <arpa/inet.h> // 引入网络编程库  
  6. #include <unistd.h> // 引入Unix系统编程库  
  7.   
  8. #define PORT 21 // 定义FTP服务器端口号  
  9.   
  10. int main(int argc, char const *argv[]) {  
  11.     int server_fd, new_socket; // 定义服务器套接字和客户端套接字  
  12.     struct sockaddr_in address; // 定义地址结构体  
  13.     int addrlen = sizeof(address); // 定义地址结构体长度  
  14.     char buffer[1024] = {0}; // 定义接收缓冲区  
  15.     char *response = "220 Welcome to FTP Server"; // 定义FTP服务器欢迎响应  
  16.       
  17.     // 创建套接字文件描述符  
  18.     if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {  
  19.         perror("socket failed"); // 套接字创建失败处理  
  20.         exit(EXIT_FAILURE); // 退出程序  
  21.     }  
  22.       
  23.     address.sin_family = AF_INET; // 设置地址族为IPv4  
  24.     address.sin_addr.s_addr = INADDR_ANY; // 设置IP地址为任意地址  
  25.     address.sin_port = htons(PORT); // 设置端口号为定义的FTP服务器端口号  
  26.       
  27.     // 将套接字绑定到指定的端口号  
  28.     if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {  
  29.         perror("bind failed"); // 绑定失败处理  
  30.         exit(EXIT_FAILURE); // 退出程序  
  31.     }  
  32.     if (listen(server_fd, 3) < 0) { // 开始监听客户端连接请求  
  33.         perror("listen"); // 监听失败处理  
  34.         exit(EXIT_FAILURE); // 退出程序  
  35.     }  
  36.     if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) { // 接受客户端连接请求  
  37.         perror("accept"); // 接受连接失败处理  
  38.         exit(EXIT_FAILURE); // 退出程序  
  39.     }  
  40.     // FTP命令列表  
  41.     char *command[] = { "USER", "PASS", "CWD", "LIST", "GET", "PUT", NULL };  
  42.     int j = 0;  
  43.     while(1) { // 循环处理客户端请求  
  44.         memset(buffer, 0, sizeof(buffer)); // 清空接收缓冲区  
  45.         if((recv(new_socket, buffer, 1024, 0)) < 0) { // 接收客户端发送的数据  
  46.             perror("recv failed"); // 接收数据失败处理  
  47.             exit(EXIT_FAILURE); // 退出程序  
  48.         }  
  49.         for(int i = 0; command[i]; i++) { // 遍历FTP命令列表  
  50.             if(strncmp(buffer, command[i], strlen(command[i])) == 0) { // 判断接收到的数据是否与FTP命令匹配  
  51.                 if(buffer[strlen(command[i])] == ' ') { // 判断命令后是否有空格,表示命令带有参数  
  52.                     send(new_socket, response, strlen(response), 0); // 向客户端发送命令执行结果(正面结果)  
  53.                 } else { // 命令没有参数的情况  
  54.                     send(new_socket, "500 Command not implemented", strlen("500 Command not implemented"), 0); // 向客户端发送命令执行结果(负面结果)  
  55.                 } // 结束判断命令是否带有参数的if-else条件语句  
  56.             } // 结束判断接收到的数据是否与FTP命令匹配的if条件语句  
  57.         } // 结束遍历FTP命令列表的for循环  
  58.         memset(buffer, 0, sizeof(buffer)); // 清空接收缓冲区,准备接收下一个命令或数据  
  59.     } // 结束处理客户端请求的while循环  
  60.     // 关闭套接字和套接字文件描述符  
  61.     close(new_socket);  
  62.     close(server_fd);  
  63.     return 0; // 程序正常结束,返回0  
  64. } // 结束main函数  
  65. // FTP服务器程序结束
复制代码
6. 网络编程参考资料
以下是一些网络编程的参考资料:
  •     《Unix网络编程》卷1和卷2,作者:W.Richard Stevens;
  •     《TCP/IP详解》卷1、卷2和卷3,作者:W.Richard Stevens;
  •     《计算机网络》第5版,作者:William Stallings;
  •     《网络是怎样连接的》,作者:村尾修一;
  •     《深入理解计算机系统》,作者:Randal E. Bryant 和 David R. O'Hallaron;


回复

使用道具 举报

CHENQIGUANG1998 | 2023-9-15 14:32:14 | 显示全部楼层
感觉还是用不太习惯这个排版
回复 支持 反对

使用道具 举报

ai_mcu | 2023-9-15 14:33:00 | 显示全部楼层
这知识点看得头皮发麻
明天总会更好
回复 支持 反对

使用道具 举报

496199544 | 2023-9-15 17:16:38 | 显示全部楼层
眼花缭乱,慢慢学习
回复 支持 反对

使用道具 举报

san | 2024-1-11 14:39:45 | 显示全部楼层
学习
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则