Linux系统编程—网络编程

此文章为本人学习笔记,若有错误求指正,自学推荐书籍《Linux/UNIX系统编程手册》,需要该书籍pdf文档可无偿分享。

一、Linux网络编程概述

Linux网络编程是指在Linux操作系统上开发网络应用程序的过程。网络编程的核心是Socket编程,Socket是操作系统提供的用于网络通信的接口。

1. 网络通信模型

1.1 OSI 七层模型

网络通信通常基于OSI模型,该模型分为七层:物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。在实际编程中,我们主要关注传输层及以上的内容。

1.2 TCP/IP 五层参考模型

五层体系的协议结构是综合了 OSI 和 TCP/IP 优点的一种协议,包括应用层、传输层、网络层、数据链路层和物理层。其中应用层对应 OSI 的上三层,下四层和 OSI 相同。五层协议的体系结构只是为介绍网络原理而设计的,实际应用还是 TCP/IP 四层体系结构。

  1. 应用层 :为特定应用程序提供数据传输服务。
  2. 传输层 :为进程提供通用数据传输服务。
  3. 网络层 :为主机提供数据传输服务。而传输层协议是为主机中的进程提供数据传输服务。
  4. 数据链路层 :网络层针对的还是主机之间的数据传输服务,而主机之间可以有很多链路,链路层协议就是为同一链路的主机提供数据传输服务。
  5. 物理层 :负责比特流在传输介质上的传播。

2. TCP/IP协议

TCP/IP协议是互联网的核心协议,其中传输层协议TCP和UDP是网络编程中最常用的协议。

2.1 TCP

提供可靠的、面向连接的服务。常用于文件传输、邮件传输等对数据完整性要求高的场景。

TCP建立连接的三次握手:
TCP协议中,在发送数据的准备阶段,客户端与服务器之间的三次交互,以保证连接的可靠。

  1. Client首先向Server发送连接请求报文段,同步自己的seq(x),Client进入SYN_SENT状态。
  2. Server收到Client的连接请求报文段,返回给Client自己的seq(y)以及ack(x+1),Server进入SYN_REVD状态。
  3. Client收到Server的返回确认,再次向服务器发送确认报文段ack(y+1),这个报文段已经可以携带数据了。Client进入ESTABLISHED状态。
  4. Server再次收到Client的确认信息后,进入ESTABLISHED状态。

TCP断开连接的四次握手:

  1. Client向Server发送断开连接请求的报文段,seq=m(m为Client最后一次向Server发送报文段的最后一个字节序号加1),Client进入FIN-WAIT-1状态。
  2. Server收到断开报文段后,向Client发送确认报文段,seq=n(n为Server最后一次向Client发送报文段的最后一个字节序号加1),ack=m+1,Server进入CLOSE-WAIT状态。此时这个TCP连接处于半开半闭状态,Server发送数据的话,Client仍然可以接收到。
  3. Server向Client发送断开确认报文段,seq=u(u为半开半闭状态下Server最后一次向Client发送报文段的最后一个字节序号加1),ack=m+1,Server进入LAST-ACK状态。
  4. Client收到Server的断开确认报文段后,向Server发送确认断开报文,seq=m+1,ack=u+1,Client进入TIME-WAIT状态。
  5. Server收到Client的确认断开报文,进入CLOSED状态,断开了TCP连接。            
  6. Client在TIME-WAIT状态等待一段时间(时间为2*MSL((Maximum Segment Life)),确认Client向Server发送的最后一次断开确认到达(如果没有到达,Server会重发步骤(3)中的断开确认报文段给Client,告诉Client你的最后一次确认断开没有收到)。如果Client在TIME-WAIT过程中没有再次收到Server的报文段,就进入CLOSES状态。TCP连接至此断开

TCP:服务端和客户端的通信流程:

2.2 UDP

提供无连接的、不可靠的数据报服务。适用于对速度要求高,但对数据完整性要求不高的场景,如视频流、语音通信等。

二、Socket编程基础

Socket是网络编程的核心接口,用于建立和管理网络连接。

1.socket 编程流程

  • 服务器
  1. 创建套接字(socket)
  2. 将socket与IP地址和端口绑定(bind)
  3. 监听被绑定的端口(listen)
  4. 接收连接请求(accept)
  5. 从socket中读取客户端发送来的信息(read)
  6. 向socket中写入信息(write)
  7. 关闭socket(close)
  • 客户端
  1. 创建套接字(socket)
  2. 连接指定计算机的端口(connect)
  3. 向socket中写入信息(write)
  4. 从socket中读取服务端发送过来的消息(read)
  5. 关闭socket(close)

2. Socket相关函数

2.1 socket()函数 - 创建套接字

 函数概述

socket()函数用于创建一个新的套接字,并返回套接字的文件描述符。如果套接字创建失败,函数将返回-1。

函数原型

int socket(int domain, int type, int protocol);
  • domain: 指定协议族,如AF_INET(IPv4)或AF_INET6(IPv6)。
  • type: 指定套接字类型,如SOCK_STREAM(TCP)或SOCK_DGRAM(UDP)。
  • protocol: 通常为0,表示自动选择合适的协议。

示例代码

int sockfd = socket(AF_INET, SOCK_STREAM, 0);

if (sockfd == -1) {

    perror("socket creation failed");

    exit(EXIT_FAILURE);

}
2.2 bind()函数 - 绑定套接字

函数概述

bind()函数将套接字与特定的IP地址和端口绑定,使其能够接收发送到该地址和端口的数据。

函数原型

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
  • sockfd: 套接字的文件描述符。
  • addr: 指向sockaddr结构的指针,包含要绑定的地址和端口。
  • addrlen: addr结构的大小。

示例代码

struct sockaddr_in address;

address.sin_family = AF_INET;

address.sin_addr.s_addr = INADDR_ANY;

address.sin_port = htons(8080);



if (bind(sockfd, (struct sockaddr *)&address, sizeof(address)) < 0) {

    perror("bind failed");

    exit(EXIT_FAILURE);

}
2.3 listen()函数 - 监听被绑定的端口

函数概述

listen()函数将套接字设置为监听模式,准备接收来自客户端的连接请求。

函数原型

int listen(int sockfd, int backlog);
  • sockfd: 套接字的文件描述符。
  • backlog: 内核中连接队列的最大长度。

示例代码

if (listen(sockfd, 3) < 0) {

    perror("listen");

    exit(EXIT_FAILURE);

}
2.4 accept()函数 - 接收连接请求

函数概述

accept()函数从监听套接字的连接队列中取出第一个连接请求,生成一个新的套接字用于与客户端通信。

函数原型

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
  • sockfd: 监听套接字的文件描述符。
  • addr: 客户端的地址结构指针,用于存储客户端信息。
  • addrlen: addr结构的大小。

示例代码

int new_socket;

struct sockaddr_in client_address;

socklen_t addrlen = sizeof(client_address);

new_socket = accept(sockfd, (struct sockaddr *)&client_address, &addrlen);

if (new_socket < 0) {

    perror("accept");

    exit(EXIT_FAILURE);

}
2.5 connect()函数 - 发送连接请求

函数概述

connect()函数用于客户端与服务器建立连接,向指定的服务器地址和端口发送连接请求。

函数原型

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
  • sockfd: 套接字的文件描述符。
  • addr: 指向服务器sockaddr结构的指针。
  • addrlen: addr结构的大小。

5.3 示例代码

struct sockaddr_in serv_addr;

serv_addr.sin_family = AF_INET;

serv_addr.sin_port = htons(8080);



if (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) {

    perror("Invalid address/Address not supported");

    exit(EXIT_FAILURE);

}



if (connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {

    perror("Connection Failed");

    exit(EXIT_FAILURE);

}
2.6 send() 和 recv() 函数 - TCP发送、接收信息

函数概述

send():将数据发送到已连接的套接字。

recv():从已连接的套接字接收数据。

函数原型

ssize_t send(int sockfd, const void *buf, size_t len, int flags);

ssize_t recv(int sockfd, void *buf, size_t len, int flags);
  • sockfd: 套接字的文件描述符。
  • buf: 指向发送或接收数据的缓冲区。
  • len: 数据长度。
  • flags: 传输标志,通常为0。

6.3 示例代码

char *message = "Hello, Server";

send(sockfd, message, strlen(message), 0);



char buffer[1024] = {0};

recv(sockfd, buffer, sizeof(buffer), 0);

printf("Message from server: %s\n", buffer);
2.7 sendto() 和 recvfrom() 函数 - UDP发送、接收消息

函数概述

  • sendto():将数据发送到指定的地址(适用于UDP)。
  • recvfrom():从指定的地址接收数据(适用于UDP)。

函数原型

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
  • dest_addr: 目标地址。
  • src_addr: 源地址。

示例代码

struct sockaddr_in server_addr;

server_addr.sin_family = AF_INET;

server_addr.sin_port = htons(8080);

server_addr.sin_addr.s_addr = INADDR_ANY;



char *message = "Hello, UDP Server";

sendto(sockfd, message, strlen(message), 0, (struct sockaddr *)&server_addr, sizeof(server_addr));



char buffer[1024] = {0};

struct sockaddr_in client_addr;

socklen_t addr_len = sizeof(client_addr);

recvfrom(sockfd, buffer, sizeof(buffer), 0, (struct sockaddr *)&client_addr, &addr_len);

printf("Message from server: %s\n", buffer);

三. TCP Socket编程

以下是一个简单的TCP服务器和客户端的实现。

TCP服务器

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>

int main() {
    int server_fd, new_socket;
    struct sockaddr_in address;
    int addrlen = sizeof(address);
    char buffer[1024] = {0};
    
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
        perror("Socket failed");
        exit(EXIT_FAILURE);
    }

    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(8080);

    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
        perror("Bind failed");
        exit(EXIT_FAILURE);
    }
    if (listen(server_fd, 3) < 0) {
        perror("Listen failed");
        exit(EXIT_FAILURE);
    }
    if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {
        perror("Accept failed");
        exit(EXIT_FAILURE);
    }
    read(new_socket, buffer, 1024);
    printf("Message: %s\n", buffer);
    close(new_socket);
    close(server_fd);
    return 0;
}

TCP客户端

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>

int main() {
    int sock = 0;
    struct sockaddr_in serv_addr;
    char *hello = "Hello from client";
    char buffer[1024] = {0};
    
    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        printf("\n Socket creation error \n");
        return -1;
    }

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(8080);
    
    if (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) {
        printf("\nInvalid address/ Address not supported \n");
        return -1;
    }

    if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
        printf("\nConnection Failed \n");
        return -1;
    }
    send(sock, hello, strlen(hello), 0);
    printf("Hello message sent\n");
    read(sock, buffer, 1024);
    printf("Message: %s\n", buffer);
    close(sock);
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值