网络编程day2——基本TCP服务器与客户端搭建流程

avatar
作者
猴君
阅读量:0

1.服务器

socket -> bind -> listen -> accept -> recv -> close

  此篇意在用服务器类比于邮政系统,来进行服务器搭建流程函数的理解,若有需求,务必简单浏览上一篇对于此类比的介绍。

网络编程day1——基本概念理解

1.1socket——建立套接字(获取设立邮局法律授权,取得营业凭证)

#include sys/socket.h

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

功能:站在内核的角度打开网络功能,并且可以产生可以使用网络通信的东西

返回值:成功返回套接字sockfd,相当于法律上邮局的授权;失败返回-1;

参数列表:

domain:协议簇 

//相当于所申请的邮局需要指定邮局服务的地理范围

        IPV4:AF_INET

        IPV6:AF_INET6

type:服务器的类型 :

//相当于明确邮局提供的服务种类,是要慢一点但是有追踪保障的(UDP),还是火速但是有丢失风险的(TCP)。

        TCP: SOCK_STREAM

        UDP: SOCK_DGRAM

protocol:额外协议

        不需要额外协议写0

        通过这个类比,我们可以更清晰地理解 socket() 函数的作用:它是创建网络通信端点(套接字)的第一步,为服务器在网络上提供服务打下基础。这个过程需要明确通信的细节,如服务范围、服务种类和具体协议,以确保后续通信的顺利进行。

1.2 bind——绑定套接字(取得授权后,施工建立邮局)

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

功能:

        给套接字绑定好IP和端口等信息

        //相当于选择好邮局具体的地址,与开设具体的服务窗口。以便于未来有顾客(客户端)有需求时可以找到这个邮局及其具体的窗口。

参数列表:

        sockfd:创建好的套接字//施工时拿出你的授权

        addr:一个指向sockaddr结构的指针,该结构包含了套接字需要绑定的地址信息。

        addrlen:结构体大小

其中addr常用新版结构体:

struct sockaddr_in {
    sa_family_t sin_family;   // 地址族,通常是 AF_INET
    in_port_t   sin_port;     // 网络字节序的端口号
    struct in_addr sin_addr;  // 网络字节序的IP地址
    char        sin_zero[8];  // 填充,保证sockaddr结构的大小一致
};

  • sin_family: 地址族,对于 IPv4 地址,这个字段通常设置为 AF_INET
  • sin_port: 端口号,以网络字节序表示,即大端序。通常使用 htons() 函数进行转换。
  • sin_addr: 结构体,包含一个 IPv4 地址,也是以网络字节序表示。可以使用 inet_addr() 或 inet_pton() 函数进行设置。
    • sin_addr.s_addr: IPv4 地址的无符号整数表示,以网络字节序存储。
  • sin_zero: 一个填充数组,用于确保 sockaddr_in 结构体的大小与更通用的 sockaddr 结构体一致。在大多数现代系统中,这个填充是不必要的,因为 sockaddr_in 结构体的大小已经被调整为与 sockaddr 一致。

1.3 listen——监听套接字(监听邮件收发服务请求)

int listen(int sockfd, int backlog);

功能:

        监听套接字是否有连接,确立监听队列的大小

返回值:

        成功返回0,失败返回-1

参数列表:

        sockfd:已经绑定好信息的套接字

        backlog:队列大小 2*backlog+1

1.4accept——接收套接字(接收并处理用户请求)

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

  • sockfd:这是监听套接字的文件描述符。服务器使用这个文件描述符来等待客户端的连接请求。

  • addr:这是一个可选参数,指向 sockaddr 结构的指针,该结构用于接收连接客户端的地址信息。如果不需要客户端地址,可以设置为 NULL

  • addrlen:这是一个指向 socklen_t 变量的指针,该变量在调用 accept 之前应该被初始化为 addr 所指向结构的长度。accept 函数可能会修改这个长度,以反映实际接收到的地址长度。如果不需要客户端地址,这个参数也可以设为 NULL

1.5recv——处理请求

(这个和邮局点不一样,这里相当于邮局会对顾客的邮件进行处理,也许是读取,也许是完成你的需求,相当于升级的更加多功能的邮局吧)

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

  • sockfd: 已连接的套接字的文件描述符。
  • buf: 指向一个缓冲区的指针,用于存储接收到的数据。
  • len: 缓冲区的长度,即可以接收的数据的最大字节数。
  • flags: 用于指定接收操作的特殊选项。常用的标志有:
    • 0: 正常接收数据。
    • MSG_PEEK: 窥视接收队列中的数据,但不从队列中移除。
    • MSG_WAITALL: 等待直到接收到 len 个字节的数据。
  • 成功时,recv 函数返回接收到的字节数,它可以小于 len 指定的缓冲区大小。
  • 如果对方关闭了连接,并且没有更多数据可接收,返回0。
  • 出错时,返回 -1,并设置全局变量 errno 以指示错误类型。

1.6close——关闭(相当于一次需求的处理完成)

int close(int fd);

  • fd: 要关闭的文件描述符(或套接字描述符)。

调用 close 函数时,如果出现错误,它会返回 -1。常见的错误原因包括:

  • EBADF:提供的文件描述符 fd 无效或未打开。
  • EINTR:关闭操作被中断,通常由于接收到信号。

2.客户端

socket -> bind(可选) -> connect -> send/recv -> close

这里和服务器的搭建相比基本相似,可参考服务器来理解。

3.完整示例

3.1服务器代码

        下面是一个使用TCP协议的简单C语言服务器端程序的示例,它遵循了 socket -> bind -> listen -> accept -> send/recv -> close 的流程:

#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h>  #define PORT 8080  // 服务器监听的端口号 #define BACKLOG 5  // 允许的最大等待连接数  int main() {     int server_fd, client_fd;  // 服务器和客户端的套接字文件描述符     struct sockaddr_in server_addr, client_addr;  // 服务器和客户端的地址结构     socklen_t client_len = sizeof(client_addr);  // 客户端地址结构的大小     char buffer[1024] = {0};  // 数据接收缓冲区     int ret;      // 创建套接字     server_fd = socket(AF_INET, SOCK_STREAM, 0);     if (server_fd < 0) {         perror("Cannot create socket");         exit(EXIT_FAILURE);     }      // 设置服务器地址参数     memset(&server_addr, 0, sizeof(server_addr));     server_addr.sin_family = AF_INET;  // 使用IPv4地址     server_addr.sin_addr.s_addr = INADDR_ANY;  // 绑定到所有可用接口     server_addr.sin_port = htons(PORT);  // 端口号      // 将套接字绑定到服务器地址     if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {         perror("Bind failed");         exit(EXIT_FAILURE);     }      // 开始监听传入连接     if (listen(server_fd, BACKLOG) < 0) {         perror("Listen failed");         exit(EXIT_FAILURE);     }      // 无限循环,接受连接     while (1) {         printf("Waiting for incoming connections...\n");          // 接受一个连接(会阻塞,直到一个客户端连接到服务器)         client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_len);         if (client_fd < 0) {             perror("Accept failed");             exit(EXIT_FAILURE);         }          // 打印客户端的IP地址和端口号         printf("Connection accepted from %s:%d\n",                inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));          // 通信示例:从客户端接收数据,然后发送数据         while ((ret = recv(client_fd, buffer, sizeof(buffer) - 1, 0)) > 0) {             // 发送收到的数据             send(client_fd, buffer, ret, 0);         }          if (ret < 0) {             perror("Recv failed");         }          // 关闭客户端套接字         close(client_fd);     }      // 关闭服务器套接字     close(server_fd);      return 0; }

3.2客户端代码

#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h>  #define PORT 8080  // 服务器监听的端口号 #define SERVER "127.0.0.1"  // 服务器的IP地址  int main() {     int sockfd;  // 套接字文件描述符     struct sockaddr_in serv_addr;  // 服务器的地址结构     char buffer[1024] = {0};  // 数据接收缓冲区      // 创建套接字     sockfd = socket(AF_INET, SOCK_STREAM, 0);     if (sockfd < 0) {         perror("Cannot create socket");         exit(EXIT_FAILURE);     }      // 设置服务器地址参数     memset(&serv_addr, 0, sizeof(serv_addr));     serv_addr.sin_family = AF_INET;  // 使用IPv4地址     serv_addr.sin_port = htons(PORT);  // 端口号     serv_addr.sin_addr.s_addr = inet_addr(SERVER);  // 服务器IP地址      // 连接到服务器     if (connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {         perror("Connect failed");         close(sockfd);         exit(EXIT_FAILURE);     }      printf("Connected to the server.\n");      // 发送消息到服务器     const char *message = "Hello, Server!";     int message_len = strlen(message) + 1;     int ret = send(sockfd, message, message_len, 0);     if (ret < 0) {         perror("Send failed");         close(sockfd);         exit(EXIT_FAILURE);     }      printf("Message sent.\n");      // 接收服务器的回显响应     ret = recv(sockfd, buffer, sizeof(buffer) - 1, 0);     if (ret < 0) {         perror("Recv failed");         close(sockfd);         exit(EXIT_FAILURE);     } else if (ret == 0) {         printf("The server closed the connection.\n");     } else {         // 添加字符串结束符,并打印接收到的数据         buffer[ret] = '\0';         printf("Received: %s\n", buffer);     }      // 关闭套接字     close(sockfd);      return 0; }

4.后续

        进行基本UDP服务器与客户端的搭建流程示例。

广告一刻

为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!