C socket网络通信

在C语言Socket通信中,客户端和服务器端的流程如下:

服务器端流程

  1. 创建服务器套接字(socket):使用socket()函数创建一个套接字,指定地址族(例如,AF_INET表示IPv4)和套接字类型(例如,SOCK_STREAM表示TCP套接字)。
  2. 绑定套接字到指定地址和端口(bind):使用bind()函数将套接字绑定到服务器的IP地址和监听端口上。
  3. 监听连接请求(listen):使用listen()函数监听指定端口上的连接请求。可以指定一个等待队列的最大长度。
  4. 接受连接请求(accept):使用accept()函数接受来自客户端的连接请求。它会阻塞程序,直到有客户端连接请求到达。返回一个新的套接字,用于与客户端进行通信。
  5. 与客户端通信:使用新的套接字进行与客户端的通信。可以使用read()write()函数从客户端接收数据和发送数据。
  6. 关闭套接字(close):通信结束后,使用close()函数关闭套接字。

客户端流程

  1. 创建客户端套接字(socket):使用socket()函数创建一个套接字,指定地址族和套接字类型。
  2. 设置服务器地址和端口:使用struct sockaddr_in结构设置服务器的IP地址和端口号。
  3. 连接服务器(connect):使用connect()函数连接到服务器。传入服务器的地址和端口号。
  4. 与服务器通信:使用已连接的套接字进行与服务器的通信。可以使用read()write()函数从服务器接收数据和发送数据。
  5. 关闭套接字(close):通信结束后,使用close()函数关闭套接字。

这些是基本的流程,你可以根据具体需求在通信过程中进行数据的读取和写入。记得在错误处理方面进行适当的处理,例如检查函数返回值是否为负数来判断是否出现错误,并采取相应的措施。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
/* server.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>

#define PORT 8888
#define BUFFER_SIZE 1024

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

// 创建套接字
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(PORT);

// 绑定套接字到指定端口
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);
}

// 从客户端接收数据
valread = read(new_socket, buffer, BUFFER_SIZE);
printf("Received message from client: %s\n", buffer);

// 向客户端发送响应
send(new_socket, hello, strlen(hello), 0);
printf("Hello message sent to client\n");

// 关闭套接字
close(new_socket);
close(server_fd);

return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
/* client.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define PORT 8888
#define BUFFER_SIZE 1024

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

// 创建套接字
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("socket creation failed");
exit(EXIT_FAILURE);
}

// 设置服务器信息
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(PORT);

// 将IPv4地址从点分十进制转换为二进制
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(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
perror("connection failed");
exit(EXIT_FAILURE);
}

// 向服务器发送数据
if (send(sock, hello, strlen(hello), 0) < 0) {
perror("send failed");
exit(EXIT_FAILURE);
}
printf("Hello message sent\n");

// 从服务器接收响应
if ((valread = read(sock, buffer, BUFFER_SIZE)) < 0) {
perror("read failed");
exit(EXIT_FAILURE);
}
printf("Received message from server: %s\n", buffer);

// 关闭套接字
close(sock);

return 0;
}

当前给出的客户端和服务器端示例是基于TCP的简单通信模型。

服务器端功能

  1. 创建一个服务器套接字,绑定到指定的IP地址和端口上。
  2. 监听来自客户端的连接请求。
  3. 当有连接请求到达时,接受连接并创建一个新的套接字,用于与客户端进行通信。
  4. 从客户端接收数据,并打印接收到的消息。
  5. 向客户端发送响应消息。
  6. 关闭与客户端的连接和套接字。
  7. 关闭服务器套接字。

客户端功能

  1. 创建一个客户端套接字。
  2. 设置服务器的IP地址和端口号。
  3. 连接到服务器。
  4. 向服务器发送消息。
  5. 从服务器接收响应消息,并打印接收到的消息。
  6. 关闭客户端套接字。

这两个示例展示了一个简单的客户端与服务器之间的通信过程。服务器监听客户端的连接请求,接受连接并与客户端进行通信。客户端与服务器建立连接后,发送消息给服务器并等待服务器的响应。

这只是一个基本的示例,你可以根据需要扩展它们来实现更复杂的功能,例如处理并发连接、实现多线程服务器、处理错误情况等。

struct sockaddr_in结构体

struct sockaddr_in是用于IPv4网络编程的C语言结构体,位于<netinet/in.h>头文件中。它用于存储IP地址和端口号信息。

下面是struct sockaddr_in结构体的定义:

1
2
3
4
5
6
struct sockaddr_in {
short int sin_family; // 地址家族,一般为AF_INET
unsigned short int sin_port; // 16位端口号
struct in_addr sin_addr; // 32位IP地址
unsigned char sin_zero[8]; // 填充,通常为0
};

该结构体的各个成员的含义如下:

  • sin_family:地址家族,一般为AF_INET,表示使用IPv4地址。
  • sin_port:16位端口号,用于标识应用程序中的网络服务。通过网络传输数据时,需要知道发送和接收数据的端口号。
  • sin_addrstruct in_addr类型的结构体成员,用于存储32位的IPv4地址。struct in_addr结构体定义在<netinet/in.h>头文件中,它的成员in_addr_t用于表示IPv4地址。
  • sin_zero:长度为8字节的填充字段,用于补齐结构体的大小。通常将其设置为0。

通过使用struct sockaddr_in结构体,可以方便地存储和操作IPv4地址和端口号信息。在进行网络编程时,常用的函数如socket()bind()connect()等,通常需要使用struct sockaddr_in结构体来表示网络地址信息。

setsockopt()函数

setsockopt() 函数用于设置套接字选项,可以用来配置套接字的各种属性和行为。它位于 <sys/socket.h> 头文件中,并具有以下原型:

1
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);

下面是对setsockopt()函数中各个参数的详细解释:

  • sockfd:套接字描述符,表示要设置选项的套接字。

  • level
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    :选项所属的协议层或套接字类型。常见的取值有:

    - `SOL_SOCKET`:通用套接字选项,适用于大多数套接字。
    - `IPPROTO_TCP`:TCP协议相关选项。
    - `IPPROTO_IP`:IP协议相关选项。
    - 其他协议层或套接字类型的特定选项。

    - ```
    optname
    :选项的名称,表示要设置的具体选项。取值可以是协议层或套接字类型定义的常量,如: - `SO_REUSEADDR`:允许地址重用。 - `SO_KEEPALIVE`:启用保活机制。 - `TCP_NODELAY`:禁用 Nagle 算法。 - 其他协议层或套接字类型定义的选项名称。
  • optval:指向存储选项值的缓冲区的指针。

  • optlen:指定 optval 缓冲区的大小。

setsockopt() 函数允许通过修改选项值来改变套接字的行为或属性。不同的选项具有不同的作用和取值范围,具体取值和含义请参考相关的文档和协议规范。

使用 setsockopt() 函数时,需要注意以下几点:

  • 返回值:函数返回0表示设置选项成功,返回-1表示失败,错误信息可以通过 errno 变量获取。
  • 套接字选项的设置和获取通常是在套接字创建后、绑定前或连接前进行。
  • 操作系统和协议的支持:不同的操作系统和网络协议可能对选项的支持和行为有所不同,请查阅相关文档以确保在特定环境中正确使用选项。

在网络编程中,使用 setsockopt() 函数可以根据需要配置套接字的属性,以满足特定的通信需求。

网络命名空间

网络命名空间(Network Namespace)是 Linux 内核提供的一种虚拟化技术,用于将网络资源隔离开来,使得每个网络命名空间具有独立的网络堆栈、接口、路由表和防火墙规则。

在 Linux 中,每个进程默认都会被放置在一个称为「默认网络命名空间」的命名空间中。这意味着它们共享同一个网络堆栈和网络配置。而通过创建额外的网络命名空间,可以实现对网络资源的隔离和划分。

名称空间的名称是用来标识和区分不同的网络命名空间的字符串,它在创建命名空间时被指定。可以使用工具如 ip netns 或编程接口如 setns() 来管理和操作网络命名空间。网络命名空间的名称通常与需要隔离的特定网络环境或应用场景相关,以方便识别和管理多个不同的网络命名空间。