C/C++网络编程

针对网络编程,就写个简单的demo,说到网络编程绕不开TCP三次握手四次挥手。

三次握手:(客户端服务端建立连接)
第一次握手 :客户端向服务器发送一个SYN(同步)标志位,表示请求建立连接。此时,客户端进入SYN_SENT状态
第二次握手 :服务器收到SYN请求后,向客户端回传一个SYN+ACK(确认)标志位,表示同意建立连接,并确认客户端的请求。服务器此时进入SYN_RECEIVED状态
第三次握手 :客户端收到服务器的SYN+ACK后,再次发送一个ACK报文,确认连接建立。此时,客户端和服务器都进入ESTABLISHED状态,连接正式建立。

四次挥手:(关闭连接)
第一次挥手:客户端发送一个FIN(终止)标志位,表示不再发送数据,但仍可以接收数据。此时,客户端进入FIN_WAIT_1状态
第二次挥手:服务器收到FIN后,发送一个ACK确认,表示同意关闭连接的一半(即不再接收客户端的数据),但仍然可以向客户端发送数据。服务器此时进入CLOSE_WAIT状态 ,客户端进入FIN_WAIT_2状态
第三次挥手:服务器发送FIN,表示不再向客户端发送数据,准备关闭连接。此时,服务器进入LAST_ACK状态
第四次挥手:客户端收到FIN后,发送ACK确认,并进入TIME_WAIT状态 ,等待一段时间(通常是2个MSL,最大报文生存时间),确保服务器收到ACK后再关闭连接。服务器收到ACK后,进入CLOSED状态,连接正式关闭。

但是对上述编程起来却没有这么复杂:

首先,对服务器端进行编程,TCP网络编程基本上按照下面思路进行:创建套接字socket,设置访问地址/端口等,绑定套接字,监听连接,等待接受客户端信号并进行确认,最后关闭连接。

1.创建套接字socket

bash 复制代码
SOCKET serverSocket = socket(AF_INET, SOCK_STREAM, 0);

对于socket函数 ,第一个是指定通信协议 ,对于IPv4地址,使用AF_INET 。对于IPv6地址,使用AF_INET6

第二个指定socket的类型,SOCK_STREAM 表示一个面向连接、可靠的字节流socket,通常是TCP。

第三个参数指定一个特定的协议来使用。设置为0通常表示让系统选择一个默认的协议。对于SOCK_STREAM类型 ,如果protocol设置为0,系统通常默认使用TCP (传输控制协议)。对于SOCK_DGRAM ,如果protocol设置为0,系统通常默认使用UDP

  1. 初始化地址结构体,设置访问地址/端口
bash 复制代码
struct sockaddr_in serverAddr;
serverAddr.sin_family = AF_INET;
serverAddr.sin_addr.s_addr =INADDR_ANY;
serverAddr.sin_port = htons(8080);

3.绑定套接字

bash 复制代码
bind(serverSocket, (struct sockaddr*)&serverAddr, sizeof(serverAddr)

上述函数作用:将服务器的socket与一个特定的地址和端口绑定在一起,使得服务器能够在指定的端口上监听和接受客户端的连接请求。

第一个参数:serverSocket 是之前通过调用 socket 函数创建的socket描述符。

第二个参数:(struct sockaddr)&serverAddr * 是将 serverAddr 的地址强制转换为 sockaddr 类型的指针。serverAddr 通常是一个 struct sockaddr_in 类型的结构体,它包含了服务器的地址信息。强制类型转换是为了与 bind 函数的参数要求匹配。

第三个参数:sizeof(serverAddr) 是获取 serverAddr 结构体的大小,这个大小将作为 addrlen 参数传递给 bind 函数。这个参数告诉 bind 函数 addr 指针指向的内存块的大小,以确保 bind 函数不会读取超出结构体实际大小的内存。

4.监听连接

bash 复制代码
listen(serverSocket, 1)

serverSocket 是已经通过 socket 创建并通过 bind 绑定到特定地址和端口的socket描述符。调用 listen(serverSocket, 1) 会使得这个socket开始监听连接请求,1 表示允许挂起的未完成连接队列的最大长度为1。

5.等待接受客户端信号并进行确认

bash 复制代码
recv(clientSocket, buffer, BUFFER_SIZE, 0);

clientSocket 是一个已经建立连接的socket文件描述符。
buffer 是一个指向内存的指针,用于存放接收到的数据。
BUFFER_SIZE 是定义的缓冲区大小,表示recv尝试从socket中接收的最大字节数。
0 作为flags参数,表示没有使用任何特殊标志,recv将按照默认行为接收数据。

6.关闭连接

bash 复制代码
closesocket(clientSocket);

上面是服务端的相关操作,客户端也大抵相同,不过,客户端有个发送函数值得一提send(clientSocket, buffer, strlen(buffer), 0);这个发送函数与接受recv函数相似。

最后,汇总上述代码:

服务器端:

bash 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winsock2.h>
#pragma comment(lib, "ws2_32.lib")

#define BUFFER_SIZE 1024

int main() {
    WSADATA wsaData;
    SOCKET serverSocket, clientSocket;
    struct sockaddr_in serverAddr, clientAddr;
    int clientAddrLen = sizeof(clientAddr);
    char buffer[BUFFER_SIZE];

    // 初始化 Winsock
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
        printf("WSAStartup failed!\n");
        return -1;
    }

    // 创建套接字
    serverSocket = socket(AF_INET, SOCK_STREAM, 0);
    if (serverSocket == INVALID_SOCKET) {
        printf("Socket creation failed!\n");
        WSACleanup();
        return -1;
    }

    // 设置服务器地址
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_addr.s_addr = INADDR_ANY;
    serverAddr.sin_port = htons(8080);

    // 绑定套接字
    if (bind(serverSocket, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) {
        printf("Bind failed!\n");
        closesocket(serverSocket);
        WSACleanup();
        return -1;
    }

    // 监听连接
    if (listen(serverSocket, 1) == SOCKET_ERROR) {
        printf("Listen failed!\n");
        closesocket(serverSocket);
        WSACleanup();
        return -1;
    }

    printf("Server is listening on port 8080...\n");

    // 接受客户端连接
    clientSocket = accept(serverSocket, (struct sockaddr*)&clientAddr, &clientAddrLen);
    if (clientSocket == INVALID_SOCKET) {
        printf("Accept failed!\n");
        closesocket(serverSocket);
        WSACleanup();
        return -1;
    }

    printf("Client connected.\n");

    while (1) {
        // 接收客户端消息
        int bytesReceived = recv(clientSocket, buffer, BUFFER_SIZE, 0);
        if (bytesReceived <= 0) {
            printf("Client disconnected.\n");
            break;
        }

        buffer[bytesReceived] = '\0';
        printf("Received: %s\n", buffer);

        // 发送确认消息
        const char* response = "Message received";
        send(clientSocket, response, strlen(response), 0);
    }

    // 关闭套接字
    closesocket(clientSocket);
    closesocket(serverSocket);
    WSACleanup();

    return 0;
}

客户端:

bash 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winsock2.h>
#pragma comment(lib, "ws2_32.lib")

#define BUFFER_SIZE 1024

int main() {
    WSADATA wsaData;
    SOCKET clientSocket;
    struct sockaddr_in serverAddr;
    char buffer[BUFFER_SIZE];

    // 初始化 Winsock
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
        printf("WSAStartup failed!\n");
        return -1;
    }

    // 创建套接字
    clientSocket = socket(AF_INET, SOCK_STREAM, 0);
    if (clientSocket == INVALID_SOCKET) {
        printf("Socket creation failed!\n");
        WSACleanup();
        return -1;
    }

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

    // 连接服务器
    if (connect(clientSocket, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) {
        printf("Connection failed!\n");
        closesocket(clientSocket);
        WSACleanup();
        return -1;
    }

    printf("Connected to server. Type your message (or 'exit' to quit):\n");

    while (1) {
        // 获取用户输入
        printf("> ");
        fgets(buffer, BUFFER_SIZE, stdin);
        buffer[strcspn(buffer, "\n")] = 0;  // 移除换行符

        if (strcmp(buffer, "exit") == 0) {
            break;
        }

        // 发送消息给服务器
        send(clientSocket, buffer, strlen(buffer), 0);

        // 接收服务器响应
        int bytesReceived = recv(clientSocket, buffer, BUFFER_SIZE, 0);
        if (bytesReceived > 0) {
            buffer[bytesReceived] = '\0';
            printf("Server response: %s\n", buffer);
        }
    }

    // 关闭套接字
    closesocket(clientSocket);
    WSACleanup();

    return 0;
}

效果如下:

相关推荐
Ronin3051 小时前
11.vector的介绍及模拟实现
开发语言·c++
✿ ༺ ོIT技术༻1 小时前
C++11:新特性&右值引用&移动语义
linux·数据结构·c++
字节高级特工1 小时前
【C++】深入剖析默认成员函数3:拷贝构造函数
c语言·c++
计算机学长大白2 小时前
C中设计不允许继承的类的实现方法是什么?
c语言·开发语言
唐诺7 小时前
几种广泛使用的 C++ 编译器
c++·编译器
XH华7 小时前
初识C语言之二维数组(下)
c语言·算法
冷眼看人间恩怨8 小时前
【Qt笔记】QDockWidget控件详解
c++·笔记·qt·qdockwidget
红龙创客8 小时前
某狐畅游24校招-C++开发岗笔试(单选题)
开发语言·c++
Lenyiin8 小时前
第146场双周赛:统计符合条件长度为3的子数组数目、统计异或值为给定值的路径数目、判断网格图能否被切割成块、唯一中间众数子序列 Ⅰ
c++·算法·leetcode·周赛·lenyiin
fantasy_arch8 小时前
CPU性能优化-磁盘空间和解析时间
网络·性能优化