【网络编程】初始化一个简单的TCP服务器

初始化套接字环境

WSADATA data;
WSAStartup(MAKEWORD(1, 1), &data);//todo: 返回值处理

这两行代码用于在Windows下初始化套接字环境。其中,WSADATA data是用来存储套接字环境信息的结构体,WSAStartup函数用于初始化套接字库,并将相关信息填充到 WSADATA结构体中。

检验(容错机制)

if (WSAStartup(MAKEWORD(1, 1), &data) != 0)
{
	printf("WSAStartup errorNum = %d\n", GetLastError());
	return;
}

创建套接字socket

创建一个Socket对象,通过调用系统函数创建一个套接字。套接字可以是流式套接字(SOCK_STREAM)用于TCP协议通信或者数据报套接字(SOCK_DGRAM)用于UDP协议通信。

SOCKET listenSocket = socket(PF_INET, SOCK_STREAM, 0);
//todo:校验

这行代码创建了一个套接字(socket)。PF_INET 和 SOCK_STREAM 参数分别指定了套接字的协议簇和套接字类型,该示例中使用的是TCP协议和可靠的数据传输。

协议族

PF_INET是一个宏,用于指定协议族,表示使用IPv4地址族。在IPv4地址族中,地址用32位整型表示,通常以点分十进制表示,例如 192.168.0.1。

协议族(Protocol Family)指的是一组协议的集合,其定义了一系列相关的协议和规范,用于支持网络中的通信。常见的协议族有:
PF_INET:表示使用IPv4地址族;
PF_INET6:表示使用IPv6地址族;
PF_PACKET:表示使用底层数据包通信。

在 C 语言和 C++ 的套接字编程中,协议族通常与套接字类型(Socket Type)和协议(Protocol)一起使用,创建一个指定类型和协议的套接字。

在示例中,使用 PF_INET协议族创建了一个 TCP 套接字,表示使用 IPv4 地址以及可靠的数据传输方式。

套接字类型

  • SOCK_STREAM[流套接字] ——TCP
    面向连接、可靠的数据传输 适合传输大量的数据,不支持广播、多播
  • SOCK_DGRAM[数据包套接字] ——UDP
    无连接 支持广播、多播

容错机制

if (INVALID_SOCKET == listenSocket)
{
	printf("listenSocket errorNum = %d\n", GetLastError());
	return -1;
}

bind

使用bind()函数将套接字绑定到服务器的IP地址和端口号上。指定服务器的IP地址和端口号是为了让客户端能够连接到服务器。

sockaddr_in serverAddress;
serverAddress.sin_family = AF_INET;
serverAddress.sin_addr.s_addr = INADDR_ANY;  // 使用本地IP地址
serverAddress.sin_port = htons(8080);  // 设置监听端口
bind(listenSocket, (struct sockaddr*)&serverAddress, sizeof(serverAddress));
//todo:校验

这段代码定义了服务器的地址结构体 serverAddress,并设置了服务器的IP地址(使用 INADDR_ANY 表示任意地址)和端口号(8080)。之后通过 bind 函数将套接字绑定到指定的地址。

sockaddr_in

sockaddr_in 是一个结构体(structure),用于表示 Internet 套接字地址。它在 netinet/in.h 头文件中定义,是一个底层的网络编程结构体,在实际编程中经常用到。

该结构体定义如下:

struct sockaddr_in {
    short int sin_family;          /* Address family   */
    unsigned short int sin_port;   /* Port number      */
    struct in_addr sin_addr;       /* Internet address */
    unsigned char sin_zero[8];     /* Same size as struct sockaddr */
};

其中,成员变量的含义如下:

  • sin_family:地址簇,指定 Internet 地址族。在使用 TCP/IP 协议时,该值通常设置为 AF_INET
  • sin_port:端口号,用于标识通信的应用程序。该值需要使用 htons 函数进行端口号字节序的转换,以适应网络字节序(Network Byte Order)。
  • sin_addr:IP 地址,用于指定 Internet 上的主机地址。
  • sin_zero:填充,保证 sin_addrsin_port 处于整数类型的边界上。

在使用 sockaddr_in 时,还需要进行强制类型转换为 sockaddr 类型,例如 bind 函数的参数、recvfrom 函数的第二个参数等。

示例中,使用 sockaddr_in 结构体设置服务器的地址信息,并通过 bind 函数绑定到创建的套接字上。

listen

使用listen()函数监听指定端口上的连接请求。此时服务器处于等待状态,等待客户端发起连接。

listen(listenSocket, 5);

这行代码将套接字设置为监听状态,第二个参数5为最大监听数,表示同时最多允许一个连接处于等待状态。

最大监听数目,相当于一个队列,比如最大监听5,则1 2 3 4 5 6 ,6就监听不到,等1出队,6才能入队

容错机制

if (listen(listenSocket, 5) == -1) {  // 允许同时连接的最大客户端数为5
    std::cerr << "Failed to listen on socket." << std::endl;
     return -1;
 }

accept

使用accept()函数从队列中取出一个客户端连接请求,并创建一个新的套接字用于与客户端进行通信。

// 接受客户端连接
struct sockaddr_in clientAddress;
socklen_t clientAddressLength = sizeof(clientAddress);
int clientSocket = accept(listenSocket, (struct sockaddr*)&clientAddress, &clientAddressLength);

容错机制

if (clientSocket == -1) {
    std::cerr << "Failed to accept client connection." << std::endl;
}

recv send

通过新的套接字与客户端进行通信,可以使用read()和write()等函数来读写数据。

 // 处理客户端请求
char buffer[1024];
int bytesRead = recv(clientSocket, buffer, sizeof(buffer), 0);
if (bytesRead == -1) 
     std::cerr << "Failed to receive data from client." << std::endl;
     
send(clientSocket, buffer, sizeof(buffer), 0);

可以在接收到数据后根据自己的需求进行处理,比如发送响应给客户端。此处send将接收到的buffer返回给客户端

关闭套接字

当通信完成后,关闭套接字,释放资源。

// 关闭客户端socket
  close(clientSocket);

 // 关闭监听socket
 close(listenSocket);

清理套接字环境

WSACleanup();

这行代码用于清理套接字环境,并释放相关资源。

初始化一个简单的TCP服务器

#include <iostream>
#include <cstring>
#include <sys/socket.h>
#include <netinet/in.h>

int main() {
//socket bind listen accept read write close
// 
//套接字初始化(在windows下需要进行套接字环境的初始化,linux不需要)
WSADATA data;
WSAStartup(MAKEWORD(1, 1),&data);//todo: 返回值处理

// 创建socket
int listenSocket = socket(AF_INET, SOCK_STREAM, 0);
if (listenSocket == -1) {
    std::cerr << "Failed to create socket." << std::endl;
    return -1;
}

// 设置服务器地址和端口
struct sockaddr_in serverAddress;
serverAddress.sin_family = AF_INET;
serverAddress.sin_addr.s_addr = INADDR_ANY;  // 使用本地IP地址
serverAddress.sin_port = htons(8080);  // 设置监听端口

// 绑定socket到指定地址和端口
if (bind(listenSocket, (struct sockaddr*)&serverAddress, sizeof(serverAddress)) == -1) {
    std::cerr << "Failed to bind socket." << std::endl;
    return -1;
}

// 开始监听连接
if (listen(listenSocket, 5) == -1) {  // 允许同时连接的最大客户端数为5
    std::cerr << "Failed to listen on socket." << std::endl;
    return -1;
}

std::cout << "Server started. Listening on port 8080..." << std::endl;

while (true) {
    // 接受客户端连接
    struct sockaddr_in clientAddress;
    socklen_t clientAddressLength = sizeof(clientAddress);
    int clientSocket = accept(listenSocket, (struct sockaddr*)&clientAddress, &clientAddressLength);
    if (clientSocket == -1) {
        std::cerr << "Failed to accept client connection." << std::endl;
        continue;
    }

    // 处理客户端请求
     char buffer[1024];
     int bytesRead = recv(clientSocket, buffer, sizeof(buffer), 0);
     if (bytesRead == -1) {
         std::cerr << "Failed to receive data from client." << std::endl;
         continue;
     }

     // 在这里根据buffer的内容进行相应的处理,比如发送响应给客户端

     // 关闭客户端socket
     close(clientSocket);
 }

    // 关闭监听socket
    close(listenSocket);

    return 0;
}

//清理网络
WSACleanup();

以上使用了基本的socket函数来创建、绑定和监听一个服务器socket。然后使用accept函数来接受客户端连接,并使用recv函数来接收客户端发送的数据。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值