Linux之Socket编程Tcp_linux tcp socket client
一、Tcp网络编程
1.1、EchoServer
接口介绍:
// 开始监听 socket (TCP, 服务器)
int listen(int socket, int backlog);
- 参数:
- socket:已绑定 (bind()) 的TCP套接字描述符(必须为 SOCK_STREAM 类型)
- backlog:等待连接队列的最大长度,即允许挂起(未 accept())的连接请求数量,不可以为0,也不可以太大。
- 返回值:
成功:返回
0
。失败:返回 -1,并设置 errno。
// 接收请求 (TCP, 服务器)
int accept(int socket, struct sockaddr* address, socklen_t* address_len);
// 建立连接 (TCP, 客户端)
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
- 参数:
- socket:客户端套接字描述符(由 socket() 创建)
- addr:指向目标服务器地址结构体(如 struct sockaddr_in)
- addrlen:地址结构体的长度(如 sizeof(struct sockaddr_in))
- 返回值:
- 成功(TCP):返回 0,表示连接已建立。
- 成功(UDP):仅绑定目标地址,无实际连接,后续 sendto() 可省略地址参数。
- 失败:返回 -1,并设置 errno。
示例代码网址:Linux_blog: Linux博客示例代码 - Gitee.com
注意:
- 由于客户端不需要固定的端口号,因此不必调用 bind(),客户端的端口号由内核自动分配。
- 客户端不是不允许调用 bind(), 只是没有必要显示的调用 bind()固定一个端口号。否则如果在同一台机器上启动多个客户端, 就会出现端口号被占用导致不能正确建立连接。
- 服务器也不是必须调用 bind(), 但如果服务器不调用 bind(), 内核会自动给服务器分配监听端口, 每次启动服务器时端口号都不一样, 客户端要连接服务器就会遇到麻烦。
1.2、telnet小工具
telnet 是一个经典的 网络协议 和 命令行工具,用于通过 TCP/IP 网络 与远程主机进行交互式通信。它基于 Telnet 协议(默认端口 23),但也可以用于测试其他 TCP 服务(如 HTTP、SMTP 等)。
基本用法:telnet [端口]
示例:
- telnet example.com # 连接 example.com 的 23 端口(Telnet 服务)
- telnet 192.168.1.1 23 # 连接 192.168.1.1 的 23 端口
Telnet 不仅可以用于 Telnet 协议,还可以测试 HTTP、SMTP、SSH、Redis 等 TCP 服务:
- telnet google.com 80 # 测试 HTTP(80 端口)
- telnet smtp.gmail.com 25 # 测试 SMTP(25 端口)
- telnet localhost 6379 # 测试 Redis(6379 端口)
手动发送 HTTP 请求:(按两次回车发送请求,查看服务器返回的 HTTP 响应)
telnet example.com 80
GET / HTTP/1.1
Host: example.com
常用操作:
- Ctrl + ] 进入 Telnet 命令模式
- quit 或 q 退出 Telnet
- open 重新连接另一台主机
- close 关闭当前连接
- status 查看当前连接状态
二、验证 TCP-windows 作为 client 访问 Linux
示例代码:
TcpClient.cc:(Windows端)
#include #include #include #pragma warning(disable : 4996)#pragma comment(lib, \"ws2_32.lib\")std::string serverip = \"\"; // 填写你的云服务器ipuint16_t serverport = 8888; // 填写你的云服务开放的端口号int main(){ WSADATA wsaData; int result = WSAStartup(MAKEWORD(2, 2), &wsaData); if (result != 0) { std::cerr << \"WSAStartup failed: \" << result << std::endl; return 1; } SOCKET clientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (clientSocket == INVALID_SOCKET) { std::cerr << \"socket failed\" << std::endl; WSACleanup(); return 1; } sockaddr_in serverAddr; serverAddr.sin_family = AF_INET; serverAddr.sin_port = htons(serverport); // 替换为服务器端口 serverAddr.sin_addr.s_addr = inet_addr(serverip.c_str()); // 替换为服务器IP地址 result = connect(clientSocket, (SOCKADDR *)&serverAddr, sizeof(serverAddr)); if (result == SOCKET_ERROR) { std::cerr << \"connect failed\" << std::endl; closesocket(clientSocket); WSACleanup(); return 1; } while (true) { std::string message; std::cout < 0) { buffer[bytesReceived] = \'\\0\'; // 确保字符串以 null 结尾 std::cout << \"Received from server: \" << buffer << std::endl; } else { std::cerr << \"recv failed\" << std::endl; } } closesocket(clientSocket); WSACleanup(); return 0;}
TcpServer.cc:(Linux端)
#include #include #include #include #include #include #include #include #include #include #include #include const static int default_backlog = 6;enum{ Usage_Err = 1, Socket_Err, Bind_Err, Listen_Err};#define CONV(addr_ptr) ((struct sockaddr *)addr_ptr)class TcpServer{public: TcpServer(uint16_t port) : _port(port), _isrunning(false) { } // 都是固定套路 void Init() { // 1. 创建socket, file fd, 本质是文件 _listensock = socket(AF_INET, SOCK_STREAM, 0); if (_listensock < 0) { exit(0); } int opt = 1; setsockopt(_listensock, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt)); // 2. 填充本地网络信息并bind struct sockaddr_in local; memset(&local, 0, sizeof(local)); local.sin_family = AF_INET; local.sin_port = htons(_port); local.sin_addr.s_addr = htonl(INADDR_ANY); // 2.1 bind if (bind(_listensock, CONV(&local), sizeof(local)) != 0) { exit(Bind_Err); } // 3. 设置socket为监听状态,tcp特有的 if (listen(_listensock, default_backlog) != 0) { exit(Listen_Err); } } void ProcessConnection(int sockfd, struct sockaddr_in &peer) { uint16_t clientport = ntohs(peer.sin_port); std::string clientip = inet_ntoa(peer.sin_addr); std::string prefix = clientip + \":\" + std::to_string(clientport); std::cout << \"get a new connection, info is : \" << prefix < 0) { inbuffer[s] = 0; std::cout << prefix << \"# \" << inbuffer << std::endl; std::string echo = inbuffer; echo += \"[tcp server echo message]\"; write(sockfd, echo.c_str(), echo.size()); } else { std::cout << prefix << \" client quit\" << std::endl; break; } } } void Start() { _isrunning = true; while (_isrunning) { // 4. 获取连接 struct sockaddr_in peer; socklen_t len = sizeof(peer); int sockfd = accept(_listensock, CONV(&peer), &len); if (sockfd < 0) { continue; } ProcessConnection(sockfd, peer); } } ~TcpServer() { }private: uint16_t _port; int _listensock; // TODO bool _isrunning;};using namespace std;void Usage(std::string proc){ std::cout << \"Usage : \\n\\t\" << proc << \" local_port\\n\" << std::endl;}// ./tcp_server 8888int main(int argc, char *argv[]){ if (argc != 2) { Usage(argv[0]); return Usage_Err; } uint16_t port = stoi(argv[1]); std::unique_ptr tsvr = make_unique(port); tsvr->Init(); tsvr->Start(); return 0;}
C++:
WinSock2.h 是 Windows Sockets API(应用程序接口)的头文件,用于在Windows 平台上进行网络编程。它包含了 Windows Sockets 2(Winsock2)所需 的数据类型、函数声明和结构定义,使得开发者能够创建和使用套接字 (sockets)进行网络通信。
在编写使用 Winsock2 的程序时,需要在源文件中包含 WinSock2.h 头文件。这 样,编译器就能够识别并理解 Winsock2 中定义的数据类型和函数,从而能够正确地编译和链接网络相关的代码。
此外,与 WinSock2.h 头文件相对应的是 ws2_32.lib 库文件。在链接阶段,需要 将这个库文件链接到程序中,以确保运行时能够找到并调用 Winsock2 API 中实现的函数。
在 WinSock2.h 中定义了一些重要的数据类型和函数,如:
- WSADATA:保存初始化 Winsock 库时返回的信息。
- SOCKET:表示一个套接字描述符,用于在网络中唯一标识一个套接字。
- sockaddr_in:IPv4 地址结构体,用于存储 IP 地址和端口号等信息。
- socket():创建一个新的套接字。
- bind():将套接字与本地地址绑定。
- listen():将套接字设置为监听模式,等待客户端的连接请求。
- accept():接受客户端的连接请求,并返回一个新的套接字描述符,用于与客户端进行通信。
C++:
WSAStartup 函数是 Windows Sockets API 的初始化函数,它用于初始化Winsock 库。该函数在应用程序或 DLL 调用任何 Windows 套接字函数之前必须首先执行,它扮演着初始化的角色。
以下是 WSAStartup 函数的一些关键点:
它接受两个参数:wVersionRequested 和 lpWSAData。wVersionRequested 用于指定所请求的 Winsock 版本,通常使用 MAKEWORD(major, minor)宏,其中major 和 minor 分别表示请求的主版本号和次版本号。lpWSAData 是一个指向WSADATA 结构的指针,用于接收初始化信息。
如果函数调用成功,它会返回 0;否则,返回错误代码。
WSAStartup 函数的主要作用是向操作系统说明我们将使用哪个版本的 Winsock库,从而使得该库文件能与当前的操作系统协同工作。成功调用该函数后,Winsock 库的状态会被初始化,应用程序就可以使用 Winsock 提供的一系列套接字 服务,如地址家族识别、地址转换、名字查询和连接控制等。这些服务使得应用程 序可以与底层的网络协议栈进行交互,实现网络通信。
在调用 WSAStartup 函数后,如果应用程序完成了对请求的 Socket 库的使用,应 调用 WSACleanup 函数来解除与 Socket 库的绑定并释放所占用的系统资源。
三、connect 的断线重连
示例代码:
#include #include #include #include #include #include #include #include #include using namespace std;void Usage(const std::string &process){ std::cout << \"Usage: \" << process << \" server_ip server_port\" << std::endl;}enum class Status // C++11 强类型枚举{ NEW, // 新建状态,就是单纯的连接 CONNECTING, // 正在连接,仅仅方便查询conn状态 CONNECTED, // 连接或者重连成功 DISCONNECTED, // 重连失败 CLOSED // 连接失败,经历重连,无法连接};class ClientConnection{public: ClientConnection(uint16_t serverport, const std::string &serverip) : _sockfd(-1), _serverport(serverport), _serverip(serverip), _retry_interval(1), _max_retries(5), _status(Status::NEW) { } void Connect() { // 1. 创建socket _sockfd = socket(AF_INET, SOCK_STREAM, 0); if (_sockfd < 0) { cerr << \"socket error\" <4字节IP 2. 网络序列 int n = connect(_sockfd, (struct sockaddr *)&server, sizeof(server)); // 自动进行bind哦! if (n < 0) { Disconnect(); // 恢复_sockfd的默认值,是连接没有成功,不代表sockfd创建没有成功 _status = Status::DISCONNECTED; // 没有连接成功 return; } _status = Status::CONNECTED; // 连接成功 } int SocketFd() { return _sockfd; } void Reconnect() { _status = Status::CONNECTING; // 正在重连 int count = 0; while (count < _max_retries) { Connect(); // 重连 if (_status == Status::CONNECTED) { return; } sleep(_retry_interval); count++; std::cout << \"重连次数: \" << count << \", 最大上限: \" << _max_retries << std::endl; } _status = Status::CLOSED; // 重连失败,可以关闭了 } void Disconnect() { if (_sockfd != -1) { close(_sockfd); _status = Status::CLOSED; _sockfd = -1; } } Status GetStatus() { return _status; } void Process() { // 简单的IO即可 while (true) { string inbuffer; cout < 0) { char buffer[1024]; ssize_t m = read(_sockfd, buffer, sizeof(buffer) - 1); if (m > 0) { buffer[m] = 0; cout < \" << buffer << endl; } else if (m == 0) // 这里证明server端掉线了 { _status = Status::DISCONNECTED; break; } else { std::cout << \"read m : \" << m << \"errno: \" << errno << \"errno string: \" << strerror(errno) << std::endl; _status = Status::CLOSED; break; } } else { std::cout << \"write n : \" << n << \"errno: \" << errno << \"errno string: \" << strerror(errno) << std::endl; _status = Status::CLOSED; break; } } } ~ClientConnection() { Disconnect(); }private: int _sockfd; uint16_t _serverport; // server port 端口号 std::string _serverip; // server ip地址 int _retry_interval; // 重试时间间隔 int _max_retries; // 重试次数 Status _status; // 连接状态};class TcpClient{public: TcpClient(uint16_t serverport, const std::string &serverip) : _conn(serverport, serverip) { } void Execute() { while (true) { switch (_conn.GetStatus()) { case Status::NEW: _conn.Connect(); break; case Status::CONNECTED: std::cout << \"连接成功, 开始进行通信.\" << std::endl; _conn.Process(); break; case Status::DISCONNECTED: std::cout << \"连接失败或者对方掉线,开始重连.\" << std::endl; _conn.Reconnect(); break; case Status::CLOSED: _conn.Disconnect(); std::cout << \"重连失败, 退出.\" << std::endl; return; // 退出 default: break; } } } ~TcpClient() { }private: ClientConnection _conn; // 简单组合起来即可};// class Tcp// ./tcp_client serverip serverportint main(int argc, char *argv[]){ if (argc != 3) { Usage(argv[0]); return 1; } std::string serverip = argv[1]; uint16_t serverport = stoi(argv[2]); TcpClient client(serverport, serverip); client.Execute(); return 0;}