IP 地址的意义就是标识公网内唯一一台主机。
- 端口号是一个 2 字节,16 比特位的整数。
- 一台主机中,一个端口号只能被一个进程所占用。
传输层协议(TCP 和 UDP)的数据段中也有两个端口号, 分别叫做源端口号和目的端口号.,它们描述 “数据是那个进程发送的, 要发给另外那个进程”。
Socket 网络通信
socket 通信的本质就是跨网络的进程间通信
TCP(传输控制协议)
TCP是一种面向连接的、可靠的、基于字节流的传输层通信协议,具有以下特点:
- 传输层协议:TCP工作在OSI模型的传输层,负责在网络中的两个主机之间提供可靠的数据传输服务。
- 有连接:在数据传输开始之前,TCP需要建立一个连接,通过三次握手过程来确保双方准备好进行数据传输。
- 可靠传输:TCP通过确认和重传机制确保数据正确无误地从源传输到目的地。
- 面向字节流:TCP将数据视为字节流,不保留数据边界,这意味着发送的数据被看作是一连串的字节,TCP协议负责将这些字节顺序地、可靠地传输到目的地。
UDP(用户数据报协议)
UDP是一种无连接的、不可靠的、基于数据报的传输层通信协议,具有以下特点:
- 传输层协议:UDP同样工作在OSI模型的传输层,但它提供的是一种简单的消息传递服务。
- 无连接:UDP在数据传输前不需要建立连接,它直接发送数据,不保证数据的到达。
- 不可靠传输:UDP不保证数据的可靠传输,它不进行错误检测和修正,如果数据在传输过程中丢失或出错,UDP不会重传。
- 面向数据报:UDP将数据视为一系列独立的数据报,每个数据报都包含完整的信息,包括源端口号、目的端口号和数据负载。UDP发送和接收的数据单位是数据报。
数据报和字节流
- 数据报:是一种独立的、包含完整信息的数据单元。在UDP中,每个数据报都是一个独立的网络消息,包含源和目的端口信息,以及数据负载。数据报的传输是无序的,且不保证到达。
- 字节流:是一种连续的、无结构的字节序列。在TCP中,数据被看作是一连串的字节,不保留任何数据边界。TCP负责按顺序、可靠地将这些字节从发送方传输到接收方,确保数据的完整性和顺序。
网络字节序(常考)
我们知道,内存中的数据权值排列相对于内存地址的大小有大端和小端之分:
是以低地址的高位字节(大端)低位字节(小端)决定的
- 大端存储:高权值数字存到内存的低地址位置上,低权值存到高地址上。
- 小端存储:高权值数字存到内存的高地址位置上,低权值存到高地址上
如果发送端和接收端主机的存储字节序不同,则会造成发送的数据和识别出来的数据不一致的问题,如下图所示:
TCP/IP 协议规定,网络数据流应采用大端字节序,即低地址高权值:
如果发送主机是小端机, 就需要先进行数据转换;否则忽略,直接发送即可。
如果接受主机是小端机,则拿到数据后需要进行转换;否则忽略,直接读取即可。
Socket 编程
UDP 服务器代码(udp_server.cpp)
#include <iostream>
#include <cstring>
#include <arpa/inet.h>#define BUFSIZE 1024class UdpServer {
public:UdpServer(uint16_t port) : port_(port), sockfd_(-1) {if ((sockfd_ = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {std::cerr << "Socket creation error" << std::endl;exit(EXIT_FAILURE);}sockaddr_in server_addr;memset(&server_addr, 0, sizeof(server_addr));server_addr.sin_family = AF_INET;server_addr.sin_port = htons(port_);server_addr.sin_addr.s_addr = INADDR_ANY;if (bind(sockfd_, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {std::cerr << "Bind error" << std::endl;exit(EXIT_FAILURE);}}void serve() {char buffer[BUFSIZE];sockaddr_in client_addr;socklen_t client_len = sizeof(client_addr);while (true) {memset(buffer, 0, BUFSIZE);int bytes_received = recvfrom(sockfd_, buffer, BUFSIZE, 0,(struct sockaddr*)&client_addr, &client_len);if (bytes_received < 0) {std::cerr << "Recvfrom error" << std::endl;continue;}std::cout << "Received message: " << buffer << std::endl;std::string response = "Echo: " + std::string(buffer);sendto(sockfd_, response.c_str(), response.size(), 0,(struct sockaddr*)&client_addr, client_len);}}~UdpServer() {if (sockfd_ != -1) {close(sockfd_);}}private:uint16_t port_;int sockfd_;
};int main(int argc, char* argv[]) {if (argc != 2) {std::cerr << "Usage: " << argv[0] << " <port>" << std::endl;return 1;}uint16_t port = static_cast<uint16_t>(std::atoi(argv[1]));UdpServer server(port);server.serve();return 0;
}
UDP 客户端代码(udp_client.cpp)
#include <iostream>
#include <cstring>
#include <arpa/inet.h>#define BUFSIZE 1024class UdpClient {
public:UdpClient(const std::string& server_ip, uint16_t server_port): server_ip_(server_ip), server_port_(server_port), sockfd_(-1) {if ((sockfd_ = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {std::cerr << "Socket creation error" << std::endl;exit(EXIT_FAILURE);}}void send_receive() {sockaddr_in server_addr;memset(&server_addr, 0, sizeof(server_addr));server_addr.sin_family = AF_INET;server_addr.sin_port = htons(server_port_);server_addr.sin_addr.s_addr = inet_addr(server_ip_.c_str());std::string message = "Hello from UDP client";sendto(sockfd_, message.c_str(), message.size(), 0,(struct sockaddr*)&server_addr, sizeof(server_addr));char buffer[BUFSIZE];socklen_t server_len = sizeof(server_addr);int bytes_received = recvfrom(sockfd_, buffer, BUFSIZE, 0,(struct sockaddr*)&server_addr, &server_len);if (bytes_received < 0) {std::cerr << "Recvfrom error" << std::endl;return;}std::cout << "Received message: " << buffer << std::endl;}~UdpClient() {if (sockfd_ != -1) {close(sockfd_);}}private:std::string server_ip_;uint16_t server_port_;int sockfd_;
};int main(int argc, char* argv[]) {if (argc != 4) {std::cerr << "Usage: " << argv[0] << " <server_ip> <server_port>" << std::endl;return 1;}std::string server_ip = argv[1];uint16_t server_port = static_cast<uint16_t>(std::atoi(argv[2]));UdpClient client(server_ip, server_port);client.send_receive();return 0;
}
TCP 客户端服务器代码
TCP 服务器代码(tcp_server.cpp)
#include <iostream>
#include <cstring>
#include <arpa/inet.h>
#include <unistd.h>#define BUFSIZE 1024class TcpServer {
public:TcpServer(uint16_t port) : port_(port), listenfd_(-1) {if ((listenfd_ = socket(AF_INET, SOCK_STREAM, 0)) < 0) {std::cerr << "Socket creation error" << std::endl;exit(EXIT_FAILURE);}sockaddr_in server_addr;memset(&server_addr, 0, sizeof(server_addr));server_addr.sin_family = AF_INET;server_addr.sin_port = htons(port_);server_addr.sin_addr.s_addr = INADDR_ANY;if (bind(listenfd_, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {std::cerr << "Bind error" << std::endl;exit(EXIT_FAILURE);}if (listen(listenfd_, 5) < 0) {std::cerr << "Listen error" << std::endl;exit(EXIT_FAILURE);}}void serve() {sockaddr_in client_addr;socklen_t client_len = sizeof(client_addr);while (true) {int connfd = accept(listenfd_, (struct sockaddr*)&client_addr, &client_len);if (connfd < 0) {std::cerr << "Accept error" << std::endl;continue;}std::cout << "Connection established" << std::endl;char buffer[BUFSIZE];while (true) {memset(buffer, 0, BUFSIZE);int bytes_received = read(connfd, buffer, BUFSIZE);if (bytes_received < 0) {std::cerr << "Read error" << std::endl;break;}if (bytes_received == 0) {std::cout << "Client disconnected" << std::endl;break;}std::cout << "Received message: " << buffer << std::endl;write(connfd, buffer, bytes_received);}close(connfd);}}~TcpServer() {if (listenfd_ != -1) {close(listenfd_);}}private:uint16_t port_;int listenfd_;
};int main(int argc, char* argv[]) {if (argc != 2) {std::cerr << "Usage: " << argv[0] << " <port>" << std::endl;return 1;}uint16_t port = static_cast<uint16_t>(std::atoi(argv[1]));TcpServer server(port);server.serve();return 0;
}
TCP 客户端代码(tcp_client.cpp)
#include <iostream>
#include <cstring>
#include <arpa/inet.h>#define BUFSIZE 1024class TcpClient {
public:TcpClient(const std::string& server_ip, uint16_t server_port): server_ip_(server_ip), server_port_(server_port), sockfd_(-1) {if ((sockfd_ = socket(AF_INET, SOCK_STREAM, 0)) < 0) {std::cerr << "Socket creation error" << std::endl;exit(EXIT_FAILURE);}}void connect_and_communicate() {sockaddr_in server_addr;memset(&server_addr, 0, sizeof(server_addr));server_addr.sin_family = AF_INET;server_addr.sin_port = htons(server_port_);server_addr.sin_addr.s_addr = inet_addr(server_ip_.c_str());if (connect(sockfd_, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {std::cerr << "Connect error" << std::endl;exit(EXIT_FAILURE);}std::cout << "Connected to the server" << std::endl;char buffer[BUFSIZE];while (true) {std::cout << "Enter message: ";std::cin.getline(buffer, BUFSIZE);if (write(sockfd_, buffer, std::strlen(buffer)) < 0) {std::cerr << "Write error" << std::endl;break;}memset(buffer, 0, BUFSIZE);if (read(sockfd_, buffer, BUFSIZE) <
多线程TCP服务器代码(threaded_tcp_server.cpp)
#include <iostream>
#include <cstring>
#include <arpa/inet.h>
#include <unistd.h>
#include <thread>
#include <vector>#define BUFSIZE 1024// 处理每个客户端的函数
void handle_client(int client_fd, sockaddr_in client_addr) {char buffer[BUFSIZE];std::vector<char> reply(BUFSIZE);while (true) {memset(buffer, 0, BUFSIZE);int bytes_received = read(client_fd, buffer, BUFSIZE);if (bytes_received <= 0) {std::cout << "Client disconnected or read error" << std::endl;break;}std::cout << "Received message from " << inet_ntoa(client_addr.sin_addr) << ":" << ntohs(client_addr.sin_port) << ": " << buffer << std::endl;// Echo the message back to the clientstd::strcpy(reply.data(), buffer);write(client_fd, reply.data(), std::strlen(buffer));}close(client_fd);
}// 线程函数,接受新的客户端连接
void accept_clients(int listenfd) {sockaddr_in client_addr;socklen_t client_len = sizeof(client_addr);while (true) {int client_fd = accept(listenfd, (struct sockaddr*)&client_addr, &client_len);if (client_fd < 0) {std::cerr << "Accept error" << std::endl;continue;}std::cout << "New client connected: " << inet_ntoa(client_addr.sin_addr) << ":" << ntohs(client_addr.sin_port) << std::endl;// 创建新线程处理客户端std::thread client_thread(handle_client, client_fd, client_addr);client_thread.detach(); // 分离线程,让它独立运行}
}class ThreadedTcpServer {
public:ThreadedTcpServer(uint16_t port) : port_(port), listenfd_(-1) {if ((listenfd_ = socket(AF_INET, SOCK_STREAM, 0)) < 0) {std::cerr << "Socket creation error" << std::endl;exit(EXIT_FAILURE);}sockaddr_in server_addr;memset(&server_addr, 0, sizeof(server_addr));server_addr.sin_family = AF_INET;server_addr.sin_port = htons(port_);server_addr.sin_addr.s_addr = INADDR_ANY;if (bind(listenfd_, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {std::cerr << "Bind error" << std::endl;exit(EXIT_FAILURE);}if (listen(listenfd_, 5) < 0) {std::cerr << "Listen error" << std::endl;exit(EXIT_FAILURE);}}void serve() {accept_clients(listenfd_);}~ThreadedTcpServer() {if (listenfd_ != -1) {close(listenfd_);}}private:uint16_t port_;int listenfd_;
};int main(int argc, char* argv[]) {if (argc != 2) {std::cerr << "Usage: " << argv[0] << " <port>" << std::endl;return 1;}uint16_t port = static_cast<uint16_t>(std::atoi(argv[1]));ThreadedTcpServer server(port);server.serve();return 0;
}