[C++] TCP communication server and client code implementation and detailed explanation

1. Server-side server implementation

1. server code

#include <WinSock2.h>
#include <WS2tcpip.h>
#include <iostream>
#include <string>
#include <thread>

#pragma comment(lib, "ws2_32.lib")
#define PORT 65432

void Cleanup(SOCKET socket)
{
    
    
	closesocket(socket);
	WSACleanup();
}

void HandleClientCommunication(SOCKET clientSocket)
{
    
    
	std::cout << "成功和" << clientSocket << "建立连接!" << std::endl;

	while (true)
	{
    
    
		char buffer[1000];
		std::cout << "服务器向" << clientSocket << "发送数据:" << std::endl;
		std::cin.getline(buffer, sizeof(buffer));

		std::string message = buffer;
		int size = send(clientSocket, message.c_str(), static_cast<int>(message.length()), 0);
		if (message == "end")
		{
    
    
			std::cout << "关闭和" << clientSocket << "的连接!" << std::endl;
			break;
		}
		else if (size == SOCKET_ERROR || size == 0)
		{
    
    
			std::cout << "[发送信息失败] 错误代码:" << WSAGetLastError() << std::endl;
			break;
		}
		else
		{
    
    
			std::cout << "       [信息发送成功]" << std::endl;
		}

		memset(buffer, 0, sizeof(buffer));
		int receivedBytes = recv(clientSocket, buffer, sizeof(buffer), 0);
		if (receivedBytes == SOCKET_ERROR || receivedBytes == 0)
		{
    
    
			std::cout << clientSocket << "断开了连接!" << std::endl;
			break;
		}
		else
		{
    
    
			std::cout << clientSocket << "  说: " << buffer << std::endl;
		}
	}
	closesocket(clientSocket);
}

int main()
{
    
    
	WSADATA wsaData;
	WORD wVersionRequested = MAKEWORD(2, 2);
	if (WSAStartup(wVersionRequested, &wsaData) != 0)
	{
    
    
		std::cout << "加载winsock.dll失败!" << std::endl;
		return 0;
	}

	SOCKET serverSocket = socket(AF_INET, SOCK_STREAM, 0);
	if (serverSocket == INVALID_SOCKET)
	{
    
    
		std::cout << "创建套接字失败!错误代码:" << WSAGetLastError() << std::endl;
		WSACleanup();
		return 0;
	}

	sockaddr_in serverAddress;
	serverAddress.sin_family = AF_INET;
	serverAddress.sin_port = htons(PORT);
	inet_pton(AF_INET, "127.0.0.1", &serverAddress.sin_addr);

	if (bind(serverSocket, reinterpret_cast<SOCKADDR*>(&serverAddress), sizeof(serverAddress)) == SOCKET_ERROR)
	{
    
    
		std::cout << "地址绑定失败!错误代码:" << WSAGetLastError() << std::endl;
		Cleanup(serverSocket);
		return 0;
	}

	if (listen(serverSocket, 0) == SOCKET_ERROR)
	{
    
    
		std::cout << "监听失败!错误代码:" << WSAGetLastError() << std::endl;
		Cleanup(serverSocket);
		return 0;
	}

	while (true)
	{
    
    
		sockaddr_in clientAddress;
		int clientAddressSize = sizeof(clientAddress);
		SOCKET clientSocket = accept(serverSocket, reinterpret_cast<SOCKADDR*>(&clientAddress), &clientAddressSize);
		if (clientSocket != INVALID_SOCKET)
		{
    
    
			std::thread clientThread(HandleClientCommunication, clientSocket);
			clientThread.detach(); // 分离线程,不等待线程结束
		}
	}

	Cleanup(serverSocket);
	return 0;
}

2. Detailed code explanation

The above code implements a simple TCP server using the Winsock library, which listens to the specified port and communicates with the client. The following is a detailed analysis of the code:

  1. #pragma comment(lib, "ws2_32.lib")is a special compiler directive used to tell the compiler to ws2_32.libadd library files to the final executable file during the link phase. No need to specify it explicitly on the build command line or in the IDE. The advantage of this is that it can automate the linking process of library files, reduce the tediousness of manual operations, and ensure that the required library files are used correctly during the compilation and linking process. It should be noted that this directive is specific to the Microsoft Visual C++ compiler and is not part of standard C++, so you may need to use different methods to link the corresponding library files when using other compilers.

  2. CleanupFunction: This function is used to close the socket and clean up the resources of WSA (Winsock library) . This function should be called to handle the aftermath before the program exits.

    void Cleanup(SOCKET socket)
    {
          
          
    	closesocket(socket);   //关闭套接字
    	WSACleanup();          //清理WSA的资源
    }
    
  3. HandleClientCommunicationFunction: This function handles communication with the client in a separate thread. It first sends data to the client and then receives the response from the client. If the received message is "end", indicating that the client requires the connection to be closed, the function will exit the loop and close the socket.

  4. mainFunction: The entry function of the program. It first initializes the Winsock library , creates a socket and binds to the specified IP address and port. The server then starts listening for client connection requests. When a client connects, a new thread is created to handle communication with that client. Use detacha function to separate threads without waiting for the thread to end.

  5. WSAStartup: Initialize Ws2_32.dllthe dynamic link library, specify winsock2.2 version, and be sure to initialize the link library before using socket. .

    WSADATA wsaData;
    WORD wVersionRequested = MAKEWORD(2, 2);
    if (WSAStartup(wVersionRequested, &wsaData) != 0)
    {
          
          
    	std::cout << "加载winsock.dll失败!" << std::endl;
    	return 0;
    }
    
  6. socket: Create a socket, specify the address family (AF_INET), socket type (SOCK_STREAM) and protocol (0 means automatically selected based on the address family and socket type).

    SOCKET serverSocket = socket(AF_INET, SOCK_STREAM, 0);
    
  7. bind: Binds the socket to the specified IP address and port. The first parameter is the socket, the second parameter is a structure pointer, which contains port and IP address information, and the third parameter indicates the buffer length.

    sockaddr_in serverAddress;
    serverAddress.sin_family = AF_INET;
    serverAddress.sin_port = htons(PORT);
    inet_pton(AF_INET, "127.0.0.1", &serverAddress.sin_addr);
    bind(serverSocket, reinterpret_cast<SOCKADDR*>(&serverAddress), sizeof(serverAddress));
    
  8. inet_pton()Function is used to convert a dotted decimal IP address into a binary form in network byte order . and store the results in serverAddress.sin_addr.

    Note: inet_pton()The function is a standard C function located arpa/inet.hin the header file. It returns an integer value indicating whether the result of the conversion was successful. If the return value is 1, it means the conversion is successful; if the return value is 0, it means the entered IP address is invalid; if the return value is -1, it means an error has occurred, and you can use errnovariables to get specific error information.

  9. htons: Convert a 16-bit unsigned short integer data from the host arrangement to the network arrangement. htonlThe function has the opposite effect.

    serverAddress.sin_port = htons(PORT);
    
  10. listen: Start listening for client connection requests. The first parameter is the socket, and the second parameter is the length of the maximum queue waiting for connections. Set the socket to listening mode , which is unique to the server-side socket. The server's socket must be set to listening mode to establish a connection with the server.

    listen(serverSocket, 0);
    
  11. accept: The server socket accepts the client's connection request and returns a new socket for communication with the client. The first parameter is the socket, the second parameter is the sockaddr_in structure pointer containing the client port IP information, and the third parameter is the length of the received parameter addr.

sockaddr_in clientAddress;
int clientAddressSize = sizeof(clientAddress);
SOCKET clientSocket = accept(serverSocket, reinterpret_cast<SOCKADDR*>(&clientAddress), &clientAddressSize);
  1. std::thread: Use the C++11 std::threadlibrary to create a new thread, using HandleClientCommunicationthe function as the thread's entry point, and passing the client socket as a parameter.

  2. recv: Receive data , the first parameter is the socket, the second parameter is the receiving data buffer, the third parameter is the length of the buffer, and the fourth parameter is the function calling method.

    int receivedBytes = recv(clientSocket, buffer, sizeof(buffer), 0);
    
  3. send: Send data , the parameters inside are basically recv()the same.

    int size = send(clientSocket, message.c_str(), static_cast<int>(message.length()), 0);
    

The above is a detailed analysis of the server code. The code implements a simple TCP server capable of communicating with multiple clients. There is still room for improvement in the code, such as handling more error conditions, adding more log output, and better memory management.


2. Client client implementation

1. client code

#include <winsock2.h>
#include <WS2tcpip.h>
#include <iostream>
#include <string>

#pragma comment(lib, "ws2_32.lib")
#define PORT 65432

void Cleanup(SOCKET socket)
{
    
    
	closesocket(socket);
	WSACleanup();
}

int main()
{
    
    
	WSADATA wsaData;
	WORD wVersionRequested = MAKEWORD(2, 2);
	if (WSAStartup(wVersionRequested, &wsaData) != 0)
	{
    
    
		std::cout << "加载winsock.dll失败!" << std::endl;
		return 0;
	}

	SOCKET clientSocket = socket(AF_INET, SOCK_STREAM, 0);
	if (clientSocket == INVALID_SOCKET)
	{
    
    
		std::cout << "创建套接字失败!错误代码:" << WSAGetLastError() << std::endl;
		Cleanup(clientSocket);
		return 0;
	}

	sockaddr_in serverAddress;
	serverAddress.sin_family = AF_INET;
	serverAddress.sin_port = htons(PORT);
	inet_pton(AF_INET, "127.0.0.1", &serverAddress.sin_addr);

	if (connect(clientSocket, reinterpret_cast<SOCKADDR*>(&serverAddress), sizeof(serverAddress)) == SOCKET_ERROR)
	{
    
    
		std::cout << "连接失败!错误代码:" << WSAGetLastError() << std::endl;
		Cleanup(clientSocket);
		return 0;
	}

	while (true)
	{
    
    
		std::string receivedMessage;
		char buffer[1000];
		int size = recv(clientSocket, buffer, sizeof(buffer) - 1, 0);
		if (size <= 0)
		{
    
    
			if (size == 0)
				std::cout << "连接已关闭" << std::endl;
			else
				std::cout << "接收信息失败!错误代码:" << WSAGetLastError() << std::endl;
			break;
		}
		buffer[size] = '\0';
		receivedMessage = buffer;
		if (receivedMessage == "end")
		{
    
    
			std::cout << "服务器端已关闭连接!" << std::endl;
			break;
		}
		else
		{
    
    
			std::cout << "收到服务器消息:" << receivedMessage << std::endl;
		}

		std::string sendMessage;
		std::cout << "请输入要发送给服务器的消息:" << std::endl;
		std::getline(std::cin, sendMessage);

		if (sendMessage == "end")
		{
    
    
			std::cout << "关闭连接!" << std::endl;
			break;
		}

		int sentBytes = send(clientSocket, sendMessage.c_str(), static_cast<int>(sendMessage.length()), 0);
		if (sentBytes == SOCKET_ERROR || sentBytes == 0)
		{
    
    
			std::cout << "[发送信息失败] 错误代码:" << WSAGetLastError() << std::endl;
			break;
		}
		else
		{
    
    
			std::cout << "       [信息发送成功]" << std::endl;
		}
	}

	Cleanup(clientSocket);
	return 0;
}

2. Detailed code explanation

The above code is a simple TCP client implemented using the Winsock library, which connects to the specified server and communicates with it. The following is a detailed analysis of the code:

  1. CleanupFunction: Same as the previous code, used to close the socket and clean up the WSA's resources.

  2. mainFunction: The entry function of the program. It first initializes the Winsock library, then creates a socket and attempts to connect to the server. Next, enter an infinite loop, which first receives the message sent by the server, and then waits for the user to enter the message to be sent to the server. If the received message is "end", it means that the server requires the connection to be closed and the loop will exit. If the message entered by the user is "end", it means that the user actively requests to close the connection, and the loop will also exit.

  3. WSAStartup: Same as the previous code, initialize the Winsock library.

  4. socket:Same as previous code, creates the socket.

  5. connect: Function for the client socketto send a connection request and try to connect to the specified server address and port. The first parameter is the client's socket, the second parameter is a structure pointer, which includes the address and IP of the connected host, and the third parameter is the length of the buffer.

    connect(clientSocket, reinterpret_cast<SOCKADDR*>(&serverAddress), sizeof(serverAddress));
    
  6. recv: Receive the message sent by the server, store it in buffer, and then bufferprocess it to obtain it receivedMessage.

  7. send: Send the message entered by the user to the server.

The above is a detailed analysis of the client code. The code implements a simple TCP client capable of communicating with the server. There is still room for improvement in the code, such as handling more error conditions, adding more log output, and better input validation.

3. Operation renderings

Server-client communication renderings

4. Summary

  • The server has a ServerSocket. After initialization (setting IP and port, binding listening, etc.), use accept()the method to wait for the client to connect. This method is blocking . Once the connection is successful, a new Socket will be returned. You can use this Socket to receive and send data.

  • The client has only one Socket from beginning to end. After the Socket is initialized, use connect()the method to connect to the server. After the connection is successful, the Socket can communicate.


If this article is helpful to you, I would like to receive a like from you!

Insert image description here

Guess you like

Origin blog.csdn.net/AAADiao/article/details/133924535