匠心精神 - 良心品质腾讯认可的专业机构-IT人的高薪实战学院

咨询电话:4000806560

从零开始学习 Linux 网络编程:Socket 编程的实践教程

从零开始学习 Linux 网络编程:Socket 编程的实践教程

前言

随着云计算和大数据的快速发展,Linux 系统日益成为开发人员和 IT 运维人员的主要选择。而作为一名开发或运维人员,熟悉 Linux 系统的网络编程是必不可少的技能之一。本文将从零开始教您如何学习 Linux 网络编程中的 Socket 编程,并介绍一些实践教程。

一、什么是 Socket 编程

Socket 是 Linux 中用于网络编程的一种机制。它可以将计算机上的程序与远程程序联系起来,实现网络通信。Socket 编程分为两个部分:服务器和客户端。服务器是一个监听端口并等待客户端连接请求的程序;而客户端则是通过 Socket 连接到服务器并发送请求的程序。

二、Socket 编程的基本步骤

1. 创建 Socket

在 Linux 中,创建 Socket 需要使用 socket() 函数。这个函数将返回一个新的 Socket 描述符,用于后续的 Socket 操作。其函数原型为:

```
int socket(int domain, int type, int protocol);
```

其中,domain 参数指定了 Socket 的域,可以是以下任何一个值:

- AF_INET:表示 IPv4 网络协议
- AF_INET6:表示 IPv6 网络协议
- AF_UNIX:表示本地进程之间通信

type 参数指定了 Socket 的类型,可以是以下任何一个值:

- SOCK_STREAM:表示面向连接的 TCP Socket
- SOCK_DGRAM:表示无连接的 UDP Socket

protocol 参数指定了所使用的协议,通常情况下可以设置为 0,由系统自动选择默认协议。

2. 绑定 Socket

在创建 Socket 之后,需要将其绑定到一个特定的 IP 地址和端口。这可以通过 bind() 函数来完成。其函数原型为:

```
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
```

其中,sockfd 参数是 Socket 描述符,addr 参数是一个 sockaddr 结构体指针,表示要绑定的 IP 地址和端口,addrlen 参数是 sockaddr 结构体的长度。

3. 监听 Socket

绑定 Socket 之后,需要开始监听连接请求。这可以通过 listen() 函数来完成。其函数原型为:

```
int listen(int sockfd, int backlog);
```

其中,sockfd 参数是 Socket 描述符,backlog 参数指定了最大等待连接数。

4. 接受连接

一旦有客户端连接请求进来,服务器需要通过 accept() 函数来接受连接。其函数原型为:

```
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
```

其中,sockfd 参数是 Socket 描述符,addr 参数是一个 sockaddr 结构体指针,用于保存客户端的 IP 地址和端口,addrlen 参数是 sockaddr 结构体的长度。

5. 发送和接收数据

服务器和客户端之间可以通过 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 参数是 Socket 描述符,buf 参数是数据的指针,len 参数是数据的长度,flags 参数用于指定发送或接收数据的选项。

6. 关闭 Socket

当服务器或客户端不再需要使用 Socket 时,需要通过 close() 函数来关闭 Socket。其函数原型为:

```
int close(int sockfd);
```

其中,sockfd 参数是 Socket 描述符。

三、Socket 编程实践教程

下面将介绍几个 Socket 编程的实践教程,帮助读者更好的理解 Socket 编程的基本原理。

1. 基本的 TCP 服务器和客户端

下面是一个简单的 TCP 服务器和客户端实现,它们可以相互通信并发送数据。

服务器:

```
#include 
#include 
#include 
#include 
#include 
#include 

#define PORT 8080
#define BUFFER_SIZE 1024

int main() {
    int server_fd, new_socket, valread;
    struct sockaddr_in address;
    int opt = 1;
    int addrlen = sizeof(address);
    char buffer[BUFFER_SIZE] = {0};
    char *msg = "Hello from server";

    // Creating socket file descriptor
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }

    // Set socket options
    if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {
        perror("setsockopt error");
        exit(EXIT_FAILURE);
    }

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

    // Bind socket to given port
    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
        perror("bind failed");
        exit(EXIT_FAILURE);
    }

    // Start listening for connections
    if (listen(server_fd, 3) < 0) {
        perror("listen failed");
        exit(EXIT_FAILURE);
    }

    // Wait for incoming connections
    if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {
        perror("accept failed");
        exit(EXIT_FAILURE);
    }

    // Read data from client and send response
    valread = read(new_socket, buffer, BUFFER_SIZE);
    printf("Received message from client: %s\n", buffer);
    send(new_socket, msg, strlen(msg), 0);
    printf("Response sent\n");

    close(new_socket);
    close(server_fd);

    return 0;
}
```

客户端:

```
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define PORT 8080
#define BUFFER_SIZE 1024

int main() {
    struct sockaddr_in address;
    int sock = 0, valread;
    struct sockaddr_in serv_addr;
    char buffer[BUFFER_SIZE] = {0};
    char *msg = "Hello from client";

    // Create socket file descriptor
    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }

    memset(&serv_addr, '0', sizeof(serv_addr));

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(PORT);

    // Convert IPv4 and IPv6 addresses from text to binary form
    if (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) {
        perror("inet_pton failed");
        exit(EXIT_FAILURE);
    }

    // Connect to server
    if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
        perror("connect failed");
        exit(EXIT_FAILURE);
    }

    // Send message to server
    send(sock, msg, strlen(msg), 0);
    printf("Message sent\n");

    // Wait for response from server
    valread = read(sock, buffer, BUFFER_SIZE);
    printf("Received response from server: %s\n", buffer);

    close(sock);

    return 0;
}
```

2. 多连接 TCP 服务器

下面是一个可以处理多个客户端连接的 TCP 服务器实现。该服务器可以同时处理多个客户端请求,并向每个客户端发送消息。

```
#include 
#include 
#include 
#include 
#include 
#include 

#define PORT 8080
#define MAX_CLIENTS 5
#define BUFFER_SIZE 1024

int main() {
    int server_fd, new_socket, valread, client_sockets[MAX_CLIENTS], client_count = 0;
    struct sockaddr_in address;
    int opt = 1;
    int addrlen = sizeof(address);
    char buffer[BUFFER_SIZE] = {0};
    char *msg = "Hello from server";

    // Creating socket file descriptor
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }

    // Set socket options
    if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {
        perror("setsockopt error");
        exit(EXIT_FAILURE);
    }

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

    // Bind socket to given port
    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
        perror("bind failed");
        exit(EXIT_FAILURE);
    }

    // Start listening for connections
    if (listen(server_fd, MAX_CLIENTS) < 0) {
        perror("listen failed");
        exit(EXIT_FAILURE);
    }

    // Wait for incoming connections
    while (1) {
        if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {
            perror("accept failed");
            continue;
        }

        printf("New client connected\n");

        // Add client socket to list
        client_sockets[client_count++] = new_socket;

        // Read data from client and send response
        valread = read(new_socket, buffer, BUFFER_SIZE);
        printf("Received message from client: %s\n", buffer);
        send(new_socket, msg, strlen(msg), 0);
        printf("Response sent\n");
    }

    // Close all client sockets
    for (int i = 0; i < client_count; i++) {
        close(client_sockets[i]);
    }

    // Close server socket
    close(server_fd);

    return 0;
}
```

3. 无连接的 UDP 通信

下面是一个简单的无连接的 UDP 通信程序,它使用 sendto() 和 recvfrom() 函数进行数据发送和接收。

服务端:

```
#include 
#include 
#include 
#include 
#include 
#include 

#define PORT 8080
#define BUFFER_SIZE 1024

int main() {
    int sockfd;
    struct sockaddr_in servaddr, clientaddr;
    char buffer[BUFFER_SIZE];
    char *msg = "Hello from server";
    int len, n;

    // Creating socket file descriptor
    if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }

    memset(&servaddr, 0, sizeof(servaddr));
    memset(&clientaddr, 0, sizeof(clientaddr));

    // Filling server information
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = INADDR_ANY;
    servaddr.sin_port = htons(PORT);

    // Bind the socket with the server address
    if (bind(sockfd, (const struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) {
        perror("bind failed");
        exit(EXIT_FAILURE);
    }

    while (1) {
        len = sizeof(clientaddr);

        // Wait for data from client
        n = recvfrom(sockfd, (char *)buffer, BUFFER_SIZE, MSG_WAITALL, (struct sockaddr *)&clientaddr, &len);
        buffer[n] = '\0';
        printf("Received message from client: %s\n", buffer);

        // Send response to client
        sendto(sockfd, (const char *)msg, strlen(msg), MSG_CONFIRM, (const struct sockaddr *)&clientaddr, len);
        printf("Response sent\n");
    }

    close(sockfd);

    return 0;
}
```

客户端:

```
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define PORT 8080
#define BUFFER_SIZE 1024

int main() {
    int sockfd;
    char buffer[BUFFER_SIZE];
    char *msg = "Hello from client";
    struct sockaddr_in servaddr;

    // Creating socket file descriptor
    if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }

    memset(&servaddr, 0, sizeof(servaddr));

    // Filling server information
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(PORT);
    servaddr.sin_addr.s_addr = INADDR_ANY;

    int n, len;

    sendto(sockfd, (const char *)msg, strlen(msg), MSG_CONFIRM, (const struct sockaddr *)&servaddr, sizeof(servaddr));
    printf("Message sent\n");

    n = recvfrom(sockfd, (char *)buffer, BUFFER_SIZE, MSG_WAITALL, (struct sockaddr *)&servaddr, &len);
    buffer[n] = '\0';
    printf("Received response from server: %s\n", buffer);

    close(sockfd);

    return 0;
}
```

四、总结

本文介绍了 Linux 系统中 Socket 编程的基本原理和步骤,并提供了几个实践教程,旨在帮助读者更好地了解和掌握 Socket 编程的技能。Socket 编程是 Linux 中网络编程的核心技术之一,学习好 Socket 编程将有助于开发人员和 IT 运维人员更好地掌握 Linux 系统。