06 网络1

06 网络1

socket

体系函数过程

socket过程

三次握手

image

四次挥手

image

socket api

socket

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

    • 参数
      • domain: 通信域,例如AF_INET表示IPv4
      • type: 套接字类型,例如SOCK_STREAM表示TCP
      • protocol: 协议,通常设为0,让系统选择默认协议
    • 返回值:成功时返回套接字描述符,失败时返回-1

bind

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

    • 参数
      • sockfd: 套接字描述符
      • addr: 指向sockaddr结构的指针,包含服务器的地址信息
      • addrlen: sockaddr结构的大小
    • 返回值:成功时返回0,失败时返回-1

listen

  • int listen(int sockfd, int backlog)

    • 参数
      • sockfd: 套接字描述符
      • backlog: 等待队列中的最大连接数
    • 返回值:成功时返回0,失败时返回-1

accept

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

    • 参数
      • sockfd: 套接字描述符
      • addr: 指向sockaddr结构的指针,用于存储客户端的地址信息
      • addrlen: 指向sockaddr结构大小的指针
    • 返回值:成功时返回一个新的套接字描述符,用于与客户端通信,失败时返回-1

read

  • ssize_t read(int fd, void *buf, size_t count)

    • 参数
      • fd: 套接字描述符
      • buf: 指向缓冲区的指针,用于存储读取的数据
      • count: 要读取的最大字节数
    • 返回值:成功时返回实际读取的字节数,失败时返回-1

write

  • ssize_t write(int fd, const void *buf, size_t count)

    • 参数
      • fd: 套接字描述符
      • buf: 指向要发送的数据的指针
      • count: 要发送的字节数
    • 返回值:成功时返回实际发送的字节数,失败时返回-1

close

  • int close(int fd)

    • 参数
      • fd: 套接字描述符
    • 返回值:成功时返回0,失败时返回-1

在提供的代码片段中,使用了以下socket API,这些API在之前的回答中没有列出:

inet_pton

  • int inet_pton(int af, const char *src, void *dst)

    • 参数
      • af: 地址族,例如AF_INET表示IPv4
      • src: 要转换的地址字符串
      • dst: 指向存储转换结果的缓冲区的指针
    • 返回值:成功时返回1,如果输入无效返回0,失败时返回-1

connect

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

    • 参数
      • sockfd: 套接字描述符
      • addr: 指向sockaddr结构的指针,包含服务器的地址信息
      • addrlen: sockaddr结构的大小
    • 返回值:成功时返回0,失败时返回-1

send

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

    • 参数
      • sockfd: 套接字描述符
      • buf: 指向要发送的数据的指针
      • len: 要发送的字节数
      • flags: 套接字选项标志
    • 返回值:成功时返回实际发送的字节数,失败时返回-1

recv

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

    • 参数
      • sockfd: 套接字描述符
      • buf: 指向缓冲区的指针,用于存储接收的数据
      • len: 缓冲区的大小
      • flags: 套接字选项标志
    • 返回值:成功时返回实际接收的字节数,连接关闭时返回0,失败时返回-1

socket example - echo server

服务器代码

#include <iostream>
#include <vector>
#include <thread>
#include <mutex>
#include <atomic>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <cstring>
#include <cerrno>
#include <cstdlib>

#define PORT 12345
#define MAX_CLIENTS 5
#define BUFFER_SIZE 1024

std::atomic<bool> running(true);

// 线程安全的打印
std::mutex cout_mutex;

void thread_safe_print(const std::string &msg) {
std::lock_guard<std::mutex> lock(cout_mutex);
std::cout << msg << std::endl;
}

// 处理客户端连接的函数
void handle_client(int client_socket) {
char buffer[BUFFER_SIZE];
ssize_t bytes_read;

while (running && (bytes_read = read(client_socket, buffer, BUFFER_SIZE)) > 0) {
buffer[bytes_read] = '\0'; // 确保字符串以空字符结尾
thread_safe_print("Received: " + std::string(buffer));

// 发送响应
const char *response = buffer;
if (write(client_socket, response, strlen(response)) < 0) {
thread_safe_print("Failed to write to socket: " + std::to_string(errno));
break;
}
}

thread_safe_print("Closing client socket");
close(client_socket);
}

int main() {
int server_fd, client_fd;
struct sockaddr_in server_addr, client_addr;
socklen_t client_len = sizeof(client_addr);
std::vector<std::thread> threads;

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

// 初始化服务器地址结构
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
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, MAX_CLIENTS) < 0) {
perror("listen failed");
exit(EXIT_FAILURE);
}

thread_safe_print("Server is listening on port " + std::to_string(PORT));

while (running) {
// 接受客户端连接
if ((client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_len)) < 0) {
perror("accept failed");
continue;
}

threads.push_back(std::thread(handle_client, client_fd));
}

running = false;
// 等待所有线程完成
for (auto &th : threads) {
if (th.joinable()) {
th.join();
}
}

// 关闭服务器套接字
close(server_fd);
return 0;
}

客户端代码

下面是一个简单的C++客户端socket使用案例,该客户端连接到服务器,发送一条消息,并接收服务器的响应。

#include <iostream>
#include <string>
#include <cstring> // for memset
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h> // for inet_pton
#include <unistd.h> // for close

#define SERVER_IP "127.0.0.1" // 服务器IP地址
#define PORT 12345 // 服务器端口
#define BUFFER_SIZE 1024 // 缓冲区大小

int main() {
int sock_fd; // 套接字文件描述符
struct sockaddr_in server_addr;
char buffer[BUFFER_SIZE];
std::string messageToSend; // 要发送的消息

// 创建套接字
if ((sock_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
std::cerr << "Failed to create socket." << std::endl;
return 1;
}

// 设置服务器地址结构
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);

// 将IP地址转换为网络字节序
if (inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr) <= 0) {
std::cerr << "Invalid address or Address family not supported." << std::endl;
close(sock_fd);
return 1;
}

// 连接到服务器
if (connect(sock_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
std::cerr << "Failed to connect to the server." << std::endl;
close(sock_fd);
return 1;
}

std::cout << "Connected to the server." << std::endl;

std::cout << "Input your message:";
std::cin >> messageToSend;
// 发送消息到服务器
if (send(sock_fd, messageToSend.c_str(), messageToSend.size(), 0) < 0) {
std::cerr << "Send failed." << std::endl;
close(sock_fd);
return 1;
}

std::cout << "Message sent to the server." << std::endl;

// 接收服务器的响应
int bytesReceived = recv(sock_fd, buffer, BUFFER_SIZE, 0);
if (bytesReceived < 0) {
std::cerr << "Receive failed." << std::endl;
} else if (bytesReceived == 0) {
std::cout << "The server closed the connection." << std::endl;
} else {
buffer[bytesReceived] = '\0'; // 确保字符串以空字符结尾
std::cout << "Server response: " << buffer << std::endl;
}

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

return 0;
}

练习

  • socket写一个echo server以及一个client

  • file server:1024字节 - 分包粘包问题

    • 客户端发送文件名
    • 服务器查找文件是否存在
      • 如果存在,把文件里的内容读出来,发给客户端
      • 如果不存在,发送“文件不存在”
    • 客户端接收信息
      • 如果接收到文件,就存到本地
      • 如果接收不到文件,接收到“文件不存在”,则在输出打印出来不存在,让用户重新输入文件名
  • 在linux系统上使用CMakeLists.txt

apt install cmake g++ gdb -y

cmake -S . -B build && cmake --build build

运行重定向

./server >> log.txt
./client