C++实现socket编程(Windows、Linux)

C++实现socket编程(Windows、Linux)

来自AI助手的总结
C++ Socket编程通过TCP和UDP协议实现网络通信,适用于文件传输、网页浏览、在线游戏等场景。

基本概念

C++ Socket 编程是实现网络通信的一种方式,利用 Socket 接口,程序可以通过计算机网络进行数据的传输与接收。Socket 是一种用于实现通信的抽象概念,它可看作是连接网络中两个通信端点的工具。在网络编程中,Socket 被广泛用于客户端和服务器之间的数据交换。

主要协议

在 C++ Socket 编程中,最常用的传输层协议有两种:TCP(传输控制协议)和 UDP(用户数据报协议)。

  1. TCP(Transmission Control Protocol):

    • 连接导向: TCP 是面向连接的协议,在数据传输之前,客户端和服务器之间需要建立一个连接(即三次握手)。
    • 可靠性: TCP 提供可靠的数据传输,确保数据包按顺序到达且不丢失。
    • 流量控制: TCP 是面向字节流的,能够进行流量控制和拥塞控制。
  2. UDP(User Datagram Protocol):

    • 无连接: UDP 是无连接的协议,数据传输时不需要建立连接。
    • 较低的延迟: 由于没有建立连接的开销,UDP 比 TCP 的延迟更低,但不保证数据包的顺序和完整性。
    • 适用场景: UDP 适用于一些对实时性要求高但可以容忍丢包的应用,例如视频会议和在线游戏。

协议区别

特性 TCP UDP
连接方式 面向连接 无连接
可靠性 可靠,确保数据的顺序与完整性 不可靠,数据包可能丢失或乱序
传输方式 字节流 数据报(Datagram)
开销 较大,需进行三次握手和连接维护 较小,没有连接维护开销
适用场景 文件传输、网页浏览、电子邮件等需要可靠传输的场景 实时视频、语音通话、在线游戏等

适用场景

  • TCP 适用场景:

    • 文件传输: 在 FTP(文件传输协议)中,可靠性是关键,因此 TCP 是首选协议。
    • 网络浏览: HTTP 和 HTTPS 协议基于 TCP,当用户浏览网页时,信息的完整性和顺序非常重要。
    • 电子邮件: SMTP 和 IMAP 协议基于 TCP,它们确保电子邮件在发送和接收过程中的可靠性。
  • UDP 适用场景:

    • 视频流媒体: 在视频会议或在线直播中,实时性是首要考虑因素,使用 UDP 可以减少延迟。
    • 在线游戏: 许多实时在线游戏使用 UDP 进行通信,以提高游戏体验,尽量减少延迟。
    • DNS 查询: DNS(域名系统)的查询通常使用 UDP,因为它们是短小的数据包,要求迅速回应。

Windows:

TCP:

TCP 服务器的主要步骤包括:

  1. 加载套接字库,创建套接字(WSAStartup()/socket());
  2. 创建 Socket: 使用 socket() 函数创建一个用于网络通信的套接字。
  3. 绑定(Bind): 将服务器的地址和端口绑定到创建的套接字上。
  4. 监听(Listen): 开始监听客户端的连接请求。
  5. 接受连接(Accept): 接受来自客户端的连接请求,并返回一个新的套接字用于与该客户端进行通信。
  6. 处理请求: 读取客户端发送的数据,并发送响应。
  7. 关闭连接: 完成通信后,关闭套接字。

实现代码(MSVC – C++14 – Release – x64):

#include <stdio.h>  
#include <winsock2.h>  

#pragma comment(lib,"ws2_32.lib")  
#pragma warning(disable:4996)

int main(int argc, char* argv[])
{
    //初始化WSA  
    WORD sockVersion = MAKEWORD(2, 2);
    WSADATA wsaData;
    if (WSAStartup(sockVersion, &wsaData) != 0)
    {
        return 0;
    }

    //创建套接字  
    SOCKET slisten = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (slisten == INVALID_SOCKET)
    {
        printf("socket error !");
        return 0;
    }

    //绑定IP和端口  
    sockaddr_in sin;
    sin.sin_family = AF_INET;
    sin.sin_port = htons(23333);
    sin.sin_addr.S_un.S_addr = INADDR_ANY;
    if (bind(slisten, (LPSOCKADDR)&sin, sizeof(sin)) == SOCKET_ERROR)
    {
        printf("bind error !");
    }

    //开始监听  
    if (listen(slisten, 5) == SOCKET_ERROR)
    {
        printf("listen error !");
        return 0;
    }

    //循环接收数据  
    SOCKET sClient;
    sockaddr_in remoteAddr;
    int nAddrlen = sizeof(remoteAddr);
    char revData[255];
    while (true)
    {
        printf("等待连接...\n");
        sClient = accept(slisten, (SOCKADDR*)&remoteAddr, &nAddrlen);
        if (sClient == INVALID_SOCKET)
        {
            printf("accept error !");
            continue;
        }
        printf("接受到一个连接:%s \r\n", inet_ntoa(remoteAddr.sin_addr));

        //接收数据  
        int ret = recv(sClient, revData, 255, 0);
        if (ret > 0)
        {
            revData[ret] = '\0';
            printf("收到消息:%s\n", revData);
        }

        //发送数据  
        char sendData[255];
        strcpy_s(sendData, 255, revData);
        send(sClient, sendData, strlen(sendData), 0);
        closesocket(sClient);
    }

    closesocket(slisten);
    WSACleanup();
    return 0;
}

TCP 客户端的主要步骤包括:

  1. 加载套接字库,创建套接字(WSAStartup()/socket());
  2. 创建 Socket: 使用 socket() 函数创建一个套接字。
  3. 设置服务器地址: 设置要连接的服务器的地址和端口号。
  4. 连接(Connect): 使用 connect() 函数连接到服务器。
  5. 发送请求: 向服务器发送数据。
  6. 接收响应: 从服务器接收数据。
  7. 关闭连接: 完成通信后,关闭套接字。

实现代码(MSVC – C++14 – Release – x64):

#include<winsock2.h>
#include<stdio.h>
#include<iostream>
#include<cstring>
using namespace std;
#pragma comment(lib, "ws2_32.lib")
#pragma warning(disable: 4996)

int main()
{
	WORD sockVersion = MAKEWORD(2, 2);
	WSADATA data;
	if (WSAStartup(sockVersion, &data) != 0)
	{
		return 0;
	}
	while (true) {
		SOCKET sclient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
		if (sclient == INVALID_SOCKET)
		{
			printf("invalid socket!");
			return 0;
		}

		sockaddr_in serAddr;
		serAddr.sin_family = AF_INET;
		serAddr.sin_port = htons(目标端口号,int);
		serAddr.sin_addr.S_un.S_addr = inet_addr(你的服务器IP,字符串形式);
		if (connect(sclient, (sockaddr*)&serAddr, sizeof(serAddr)) == SOCKET_ERROR)
		{  //连接失败 
			printf("connect error !");
			closesocket(sclient);
			return 0;
		}

		string data;
		cin >> data;
		const char* sendData;
		sendData = data.c_str();   //string转const char* 
		//char * sendData = "你好,TCP服务端,我是客户端\n";
		send(sclient, sendData, strlen(sendData), 0);
		//send()用来将数据由指定的socket传给对方主机
		//int send(int s, const void * msg, int len, unsigned int flags)
		//s为已建立好连接的socket,msg指向数据内容,len则为数据长度,参数flags一般设0
		//成功则返回实际传送出去的字符数,失败返回-1,错误原因存于error 

		char recData[255];
		int ret = recv(sclient, recData, 255, 0);
		if (ret > 0) {
			recData[ret] = '\0';
			printf("收到回复:%s\n", recData);
		}
		closesocket(sclient);
	}


	WSACleanup();
	return 0;

}

UDP

UDP服务器流程:
  1. 加载套接字库,创建套接字(WSAStartup()/socket());
  2. socket():与客户端一样,服务器首先创建一个UDP套接字,通过调用socket()函数。
  3. bind():将套接字与指定的IP地址和端口绑定。服务器必须绑定到一个特定的端口上,这样才能接收来自客户端的数据。bind()是服务器端特有的操作,客户端通常不需要显式调用bind()
  4. recvfrom():服务器使用recvfrom()接收客户端发送的数据报,并进入阻塞状态,直到接收到数据为止。
  5. 处理请求:收到数据后,服务器可以处理这个请求。例如,解析数据、执行相关操作.
  6. sendto():处理完成后,服务器通过sendto()向客户端发送响应数据。
  7. 继续等待:服务器可以继续调用recvfrom()来接收下一个数据请求。

实现代码(MSVC – C++14 – Release – x64):

#include <stdio.h> 
#include <winsock2.h> 
 
#pragma comment(lib,"ws2_32.lib")  
 
int main(int argc, char* argv[]) 
{ 
   WSADATA wsaData; 
   WORD sockVersion = MAKEWORD(2,2); 
   if(WSAStartup(sockVersion, &wsaData) != 0) 
   { 
       return 0; 
   } 
 
   SOCKET serSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);  
   if(serSocket == INVALID_SOCKET) 
   { 
       printf("socket error !"); 
       return 0; 
   } 
 
   sockaddr_in serAddr; 
   serAddr.sin_family = AF_INET; 
   serAddr.sin_port = htons(8888); 
   serAddr.sin_addr.S_un.S_addr = INADDR_ANY; 
   if(bind(serSocket, (sockaddr *)&serAddr, sizeof(serAddr)) ==SOCKET_ERROR) 
   { 
       printf("bind error !"); 
       closesocket(serSocket); 
       return 0; 
   } 
     
   sockaddr_in remoteAddr; 
   int nAddrLen = sizeof(remoteAddr);  
   while (true) 
   { 
       char recvData[255];   
       int ret = recvfrom(serSocket, recvData, 255, 0, (sockaddr*)&remoteAddr, &nAddrLen); 
       if (ret > 0) 
       { 
           recvData[ret] = 0x00; 
           printf("接受到一个连接:%s \r\n",inet_ntoa(remoteAddr.sin_addr)); 
           printf(recvData);            
       } 
 
       const char * sendData = "一个来自服务端的UDP数据包\n"; 
       sendto(serSocket, sendData,strlen(sendData), 0, (sockaddr *)&remoteAddr, nAddrLen);     
 
   } 
   closesocket(serSocket);  
   WSACleanup(); 
   return 0; 
}

UDP客户端流程

  1. 加载套接字库,创建套接字(WSAStartup()/socket());
  2. socket():创建一个UDP套接字(Socket)。这是启动UDP通信的第一步,客户端通过调用socket()函数生成一个用于通信的套接字。
  3. sendto():向服务器发送数据。客户端使用sendto()函数来将数据报发送到指定的服务器IP地址和端口。这是一个无连接的操作,不需要事先建立连接。
  4. 等待响应:客户端调用recvfrom()函数,进入阻塞状态,等待从服务器返回的数据。recvfrom()会接收来自服务器的数据报,函数会在接收到数据后解除阻塞。
  5. recvfrom():接收到服务器返回的数据后,继续处理该数据。
  6. close():通信完成后,关闭客户端套接字,释放系统资源

实现代码:

#include <stdio.h> 
#include <winsock2.h> 
 
#pragma comment(lib,"ws2_32.lib")  
 
int main(int argc, char* argv[]) 
{ 
   WORD socketVersion = MAKEWORD(2,2); 
   WSADATA wsaData;  
   if(WSAStartup(socketVersion, &wsaData) != 0) 
   { 
       return 0; 
   } 
   SOCKET sclient = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); 
     
   sockaddr_in sin; 
   sin.sin_family = AF_INET; 
   sin.sin_port = htons(8888); 
   sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); 
   int len = sizeof(sin); 
     
   const char * sendData = "来自客户端的数据包.\n"; 
   sendto(sclient, sendData, strlen(sendData), 0, (sockaddr *)&sin,len); 
 
   char recvData[255];      
   int ret = recvfrom(sclient, recvData, 255, 0, (sockaddr *)&sin,&len); 
   if(ret > 0) 
   { 
       recvData[ret] = 0x00; 
       printf(recvData); 
   } 
 
   closesocket(sclient); 
   WSACleanup(); 
   return 0; 
}

Linux:

TCP:

TCP 服务器的主要步骤包括:

  1. 创建 Socket: 使用 socket() 函数创建一个用于网络通信的套接字。
  2. 绑定(Bind): 将服务器的地址和端口绑定到创建的套接字上。
  3. 监听(Listen): 开始监听客户端的连接请求。
  4. 接受连接(Accept): 接受来自客户端的连接请求,并返回一个新的套接字用于与该客户端进行通信。
  5. 处理请求: 读取客户端发送的数据,并发送响应。
  6. 关闭连接: 完成通信后,关闭套接字。

实现代码:

#include <iostream>        // 引入输入输出库
#include <sys/socket.h>    // 引入 Socket 库
#include <netinet/in.h>    // 引入 Internet 地址族
#include <unistd.h>        // 引入 UNIX 标准库
#include <cstring>         // 引入字符串操作库,用于 memset()

int main() {
    int server_fd, new_socket;                 // 声明服务器 Socket 和新的客户端 Socket
    struct sockaddr_in address;                 // 声明用于存储地址信息的结构体
    int opt = 1;                                // 设置选项
    int addrlen = sizeof(address);              // 地址结构体的大小
    const int PORT = 8080;                      // 服务器监听的端口号

    // 创建 Socket
    server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd == 0) {                       // 检查 Socket 创建是否成功
        std::cerr << "Socket creation failed!" << std::endl; // 输出错误信息
        return 1;                                // 退出程序
    }

    // 设置 Socket 选项,允许重用地址
    setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    // 设置服务器地址信息
    address.sin_family = AF_INET;                // 使用 IPv4 地址族
    address.sin_addr.s_addr = INADDR_ANY;       // 允许接收来自任何 IP 地址的连接
    address.sin_port = htons(PORT);              // 设置监听的端口号

    // 将 Socket 绑定到指定地址和端口
    if (bind(server_fd, (struct sockaddr*)&address, sizeof(address)) < 0) {
        std::cerr << "Bind failed!" << std::endl; // 输出绑定失败的信息
        return 1;                                // 退出程序
    }

    // 开始监听传入的连接请求,队列长度为 3
    if (listen(server_fd, 3) < 0) {
        std::cerr << "Listen failed!" << std::endl; // 输出监听失败的信息
        return 1;                                // 退出程序
    }

    std::cout << "Server is listening on port " << PORT << "..." << std::endl; // 输出服务器监听状态

    // 无限循环,接受客户端连接
    while (true) {
        // 接受客户端的连接请求
        new_socket = accept(server_fd, (struct sockaddr*)&address, (socklen_t*)&addrlen);
        if (new_socket < 0) {                   // 检查连接是否成功
            std::cerr << "Accept failed!" << std::endl; // 输出接受连接失败的信息
            continue;                            // 继续下一次循环
        }

        // 接收客户端发送的消息
        char buffer[1024] = {0};                 // 创建接收缓冲区
        ssize_t bytes_read = recv(new_socket, buffer, sizeof(buffer), 0); // 接收数据
        if (bytes_read > 0) {                    // 检查是否成功接收数据
            std::cout << "Client: " << buffer << std::endl; // 输出客户端发送的消息

            // 发送响应消息给客户端
            const char* message = "Hello from server"; // 响应消息
            send(new_socket, message, strlen(message), 0); // 发送数据
        }

        // 关闭与客户端的连接
        close(new_socket);                        // 关闭客户端 Socket
    }

    // 关闭服务器 Socket
    close(server_fd);                            // 关闭服务器 Socket
    return 0;                                    // 正常退出程序
}

TCP 客户端的主要步骤包括:

  1. 创建 Socket: 使用 socket() 函数创建一个套接字。
  2. 设置服务器地址: 设置要连接的服务器的地址和端口号。
  3. 连接(Connect): 使用 connect() 函数连接到服务器。
  4. 发送请求: 向服务器发送数据。
  5. 接收响应: 从服务器接收数据。
  6. 关闭连接: 完成通信后,关闭套接字。

实现代码:

#include <iostream>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>

int main() {
    int sock;
    struct sockaddr_in server;

    // 创建 Socket
    sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0) {
        std::cerr << "Socket creation failed!" << std::endl;
        return 1;
    }

    // 设置服务器地址
    server.sin_family = AF_INET;
    server.sin_port = htons(8080);
    server.sin_addr.s_addr = inet_addr("127.0.0.1");

    // 连接到服务器
    if (connect(sock, (struct sockaddr*)&server, sizeof(server)) < 0) {
        std::cerr << "Connection failed!" << std::endl;
        return 1;
    }

    // 发送消息
    const char* message = "Hello from client";
    send(sock, message, strlen(message), 0);

    // 接收响应
    char buffer[1024] = {0};
    recv(sock, buffer, sizeof(buffer), 0);
    std::cout << "Server: " << buffer << std::endl;

    // 关闭 Socket
    close(sock);
    return 0;
}

UDP:

UDP服务器流程

  1. socket():与客户端一样,服务器首先创建一个UDP套接字,通过调用socket()函数。
  2. bind():将套接字与指定的IP地址和端口绑定。服务器必须绑定到一个特定的端口上,这样才能接收来自客户端的数据。bind()是服务器端特有的操作,客户端通常不需要显式调用bind()
  3. recvfrom():服务器使用recvfrom()接收客户端发送的数据报,并进入阻塞状态,直到接收到数据为止。
  4. 处理请求:收到数据后,服务器可以处理这个请求。例如,解析数据、执行相关操作。
  5. sendto():处理完成后,服务器通过sendto()向客户端发送响应数据。
  6. 继续等待:服务器可以继续调用recvfrom()来接收下一个数据请求。

实现代码(Ubuntu 22 C/C++):

#include <iostream>
#include <cstring>   // for memset
#include <sys/socket.h>  // for socket functions
#include <arpa/inet.h>   // for sockaddr_in and inet_ntoa
#include <unistd.h>      // for close

#define PORT 8081
#define BUFFER_SIZE 1024

int main() {
    int sockfd;
    char buffer[BUFFER_SIZE];
    struct sockaddr_in server_addr, client_addr;
    socklen_t addr_len = sizeof(client_addr);

    // 创建UDP Socket
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0) {
        perror("Socket creation failed");
        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;  // 绑定到本地所有IP地址
    server_addr.sin_port = htons(PORT);  // 指定端口号

    // 绑定Socket到地址
    if (bind(sockfd, (const struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
        perror("Bind failed");
        close(sockfd);
        exit(EXIT_FAILURE);
    }

    std::cout << "UDP server is listening on port " << PORT << std::endl;

    while (true) {
        // 接收消息
        int n = recvfrom(sockfd, buffer, BUFFER_SIZE, 0, (struct sockaddr *)&client_addr, &addr_len);
        buffer[n] = '\0';  // 将接收到的数据转换为字符串格式
        std::cout << "Client: " << buffer << std::endl;

        // 响应消息
        const char *response = "Message received";
        sendto(sockfd, response, strlen(response), 0, (const struct sockaddr *)&client_addr, addr_len);
    }

    close(sockfd);
    return 0;
}

UDP客户端流程

  1. socket():创建一个UDP套接字(Socket)。这是启动UDP通信的第一步,客户端通过调用socket()函数生成一个用于通信的套接字。
  2. sendto():向服务器发送数据。客户端使用sendto()函数来将数据报发送到指定的服务器IP地址和端口。这是一个无连接的操作,不需要事先建立连接。
  3. 等待响应:客户端调用recvfrom()函数,进入阻塞状态,等待从服务器返回的数据。recvfrom()会接收来自服务器的数据报,函数会在接收到数据后解除阻塞。
  4. recvfrom():接收到服务器返回的数据后,继续处理该数据。
  5. close():通信完成后,关闭客户端套接字,释放系统资源。

实现代码:

#include <iostream>
#include <string>
#include <cstring>     // 使用strerror
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>    // 使用close()

#define PORT 8081                      // 定义端口号
#define BUFFER_SIZE 1024               // 定义缓冲区大小
#define IP "110.41.83.50"              // 定义服务器IP地址

int main() {
    int sockfd;
    char buffer[BUFFER_SIZE];
    struct sockaddr_in server_addr;

    // 创建UDP Socket
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0) {
        std::cerr << "Socket creation failed: " << strerror(errno) << std::endl;
        return 1;
    }

    // 配置服务器地址
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(PORT);
    
    // 使用inet_pton将IP地址从字符串转换为网络字节顺序
    if (inet_pton(AF_INET, IP, &server_addr.sin_addr) <= 0) {
        std::cerr << "Invalid address/ Address not supported" << std::endl;
        close(sockfd);
        return 1;
    }

    while (true) {
        // 发送消息到服务器
        std::string message;
        std::cout << "Enter message: ";
        std::getline(std::cin, message);

        // 发送消息
        int send_result = sendto(sockfd, message.c_str(), message.size(), 0, (struct sockaddr*)&server_addr, sizeof(server_addr));
        if (send_result < 0) {
            std::cerr << "sendto failed: " << strerror(errno) << std::endl;
            break;
        }

        // 接收服务器的响应
        socklen_t addr_len = sizeof(server_addr);  // 地址长度参数
        int n = recvfrom(sockfd, buffer, BUFFER_SIZE, 0, (struct sockaddr*)&server_addr, &addr_len);
        if (n < 0) {
            std::cerr << "recvfrom failed: " << strerror(errno) << std::endl;
            break;
        }

        buffer[n] = '\0';  // 添加字符串结束符
        std::cout << "Server: " << buffer << std::endl;  // 输出服务器响应
    }

    // 关闭Socket
    close(sockfd);

    return 0;
}
温馨提示:本文最后更新于2025-05-06 19:29:41,某些文章具有时效性,若有错误或已失效,请在下方留言或联系 站长
© 版权声明
THE END
喜欢就支持一下吧
点赞14 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容