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状态,连接正式关闭。


C/C++网络编程


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

1.创建套接字socket

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

2. 初始化地址结构体,设置访问地址/端口

struct sockaddr_in serverAddr;
serverAddr.sin_family = AF_INET;
serverAddr.sin_addr.s_addr =INADDR_ANY;
serverAddr.sin_port = htons(8080);

3.绑定套接字

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.监听连接

listen(serverSocket, 1)

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

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

recv(clientSocket, buffer, BUFFER_SIZE, 0);

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

6.关闭连接

closesocket(clientSocket);

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

最后,汇总上述代码:

服务器端代码:

#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;
}

客户端代码:

#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;
}

效果如下:
在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值