百万并发web服务器-简单的服务器

一个简单的服务器

Socket编程步骤

  • 创建Socket:在客户端和服务器端都需要创建一个socket。
  • 绑定(bind):服务器端需要将其socket绑定到一个端口和IP地址上,以便客户端能够知道如何连接。
  • 监听(listen):服务器端socket监听客户端的连接请求。
  • 接受连接(accept):服务器接受客户端的连接。
  • 数据传输(send/recv):建立连接后,双方可以进行数据的发送和接收。
  • 关闭Socket:通信完成后,需要关闭socket以释放资源。

使用c++来实现一个服务器

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

#define handle_error(msg)   \
    do                      \
    {                       \
        perror(msg);        \
        exit(EXIT_FAILURE); \
    } while (0)

int main()
{

    // 创建socket文件描述符
    int server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd < 0) {
        handle_error("socket");
    }
    std::cout << "create socket success " << server_fd << "\n";

    // 绑定socket到端口8080
    const char *ip = "192.168.1.2";
    struct sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(8080);
	
	// 成功返回1,<=0都是返回失败,并且有相应的errno
    if (inet_pton(AF_INET, ip, &server_addr.sin_addr.s_addr) <= 0) {
        handle_error("inet_pton");
    }
    std::cout << "create sockaddr success\n";
	// 成功返回0,否则返回-1
    if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
        handle_error("bind");
    }
    std::cout << "server bind ip&port success\n";
    // 成功返回0,否则返回-1
    if (listen(server_fd, 3) < 0) {
        handle_error("listen");
    }
	std::cout << "server start listen...\n";
    // 创建接收的socket地址
    sockaddr_in client_addr;
    socklen_t client_addr_length = sizeof(client_addr);
    // 接收客户端连接
    int client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_addr_length);
    if (client_fd < 0) {
        handle_error("accept");
    }
    std::cout << "accept success " << client_fd << std::endl;
    // 读取数据
    char recv_buf[1024];
    memset(recv_buf, 0, sizeof(recv_buf));
    int recvbytes = recv(client_fd, recv_buf, sizeof(recv_buf), 0);
    if (recvbytes == 0) {
        std::cout << "client is disconnect: " << client_fd << std::endl;
    }
    // 接收错误,继续
    if (recvbytes < 0) {
        std::cout << "recv error: " << errno << std::endl;
    }
    std::cout << "Message from client: " << recv_buf << std::endl;

    // 发送数据
    int sendbytes = send(client_fd, recv_buf, sizeof(recv_buf), 0);
    if (sendbytes == 0) {
    	std::cout << "send failed \n";
    }
    if (sendbytes < 0) {
		perror("send");
	}
    std::cout << "Hello message sent\n";
    close(client_fd);
    // 关闭socket
    close(server_fd);

    return 0;
}

接着实现一个客户端

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


#define handle_error(msg)  do { perror(msg); exit(EXIT_FAILURE); } while (0)

int main() {

    // 创建socket
    int client_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (client_fd < 0) {
        handle_error("socket");
    }

    struct sockaddr_in serv_addr;
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(8080);

    // 将IPv4地址从文本转换为二进制形式
    const char* serv_ip = "192.168.1.2";
    if (inet_pton(AF_INET, serv_ip, &serv_addr.sin_addr.s_addr) <= 0) {
        handle_error("inet_pton");
    }
    
	if (connect(client_fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
		handle_error("connect");
	}
    char send_buf[1024];
    std::cin >> send_buf;
    int sendbytes = send(client_fd, send_buf, sizeof(send_buf), 0);
    if (sendbytes == 0) {
    	std::cout << "send failed \n";
    }
    if (sendbytes < 0) {
		perror("send");
	}
    std::cout << "sned data: " << send_buf << std::endl;

    // 接收服务器数据
    char recv_buf[1024];
    memset(recv_buf, 0, sizeof(recv_buf));
    int recvbytes = recv(client_fd, recv_buf, sizeof(recv_buf), 0);
    if (recvbytes == 0) {
        std::cout << "server is disconnect: " << std::endl;
    }
    // 接收错误,继续
    if (recvbytes < 0) {
        perror("recv");
    }
    std::cout << "Message from server: " << recv_buf << std::endl;

    // 关闭socket
    close(client_fd);

    return 0;
}

对于第一次使用socket编程来说,有太多不知道的东西了,一点一点梳理整个流程,对于服务端来说

创建socket:

Socket(套接字)是计算机网络中的一个抽象概念,它提供了一个通信接口,允许网络中的不同计算机进程之间进行数据交换。socket翻译过来就是插座,服务端有一个插座,客户端有一个插座,两个插座连在一起就可以通信了,确实也很形象。

    // 创建socket文件描述符
    int server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd == -1) {
        handle_error("socket");
    }

其中

int socket(int domain, int type, int protocol);

  • domain:指定协议族,例如AF_INET表示IPv4协议。
  • type:指定套接字类型,例如SOCK_STREAM表示流式套接字(用于TCP),SOCK_DGRAM表示数据报套接字(用于UDP)。
  • protocol:通常设置为0,让系统自动选择与type对应的协议。

如果socket函数调用成功,它将返回一个整型的文件描述符,这个文件描述符用于后续的所有套接字操作。如果调用失败,它将返回-1,并且错误代码会被存储在全局变量errno中。

在大多数套接字API中,socket 函数返回 0 是不正常的,因为套接字通常是通过文件描述符来表示的,而文件描述符通常是从 3 开始的整数(0、1 和 2 分别被标准输入、标准输出和标准错误占用)。因此,如果你尝试创建一个套接字并且 socket 函数返回了 0,这通常意味着发生了错误。

这里暂时不考虑返回0的结果,如果返回-1,说明创建socket失败了,我们可以通过perror来打印出具体的失败原因。

socket绑定ip和port

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数说明:

  • sockfd:由 socket 函数返回的套接字文件描述符。
  • addr:指向 sockaddr 结构体的指针,该结构体包含了要绑定的地址信息(如IP地址和端口号)。
  • addrlen:addr 结构体的长度,以字节为单位。
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(8080); // 服务器将监听8080端口
if (inet_pton(AF_INET, ip, &server_addr.sin_addr.s_addr) <= 0) {
    handle_error("inet_pton");
}
if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
    perror("bind failed");
    close(server_fd);
    exit(EXIT_FAILURE);
} 

其中使用了一个ip转换的函数

int ret = inet_pton(AF_INET, ip, &server_addr.sin_addr.s_addr);
if (ret <= 0) {
    handle_error("inet_pton");
}

inet_pton 是一个计算机网络编程中使用的函数,用于将点分十进制表示的 IP 地址(例如 “192.168.1.1”)转换为网络字节序的二进制形式。inet_pton 的名字来源于 “integer to presentation” 或 “text to binary”,它接受一个点分十进制字符串和一个指向 sockaddr 结构的指针,然后填充该结构的 sin_addr 成员。

int inet_pton(int af, const char *src, void *dst);
参数说明:

  • af:地址族,可以是 AF_INET(用于 IPv4)或 AF_INET6(用于 IPv6)。
  • src:指向点分十进制字符串的指针。
  • dst:指向存储二进制地址的缓冲区的指针。

如果转换成功,inet_pton 函数返回 1。如果输入的字符串不是有效的点分十进制地址,函数返回 0。如果发生错误,函数返回 -1。

计算机中ip其实也是一个32位的整型数据,例如192.168.1.1转成二进制为11000000.10101000.00000001.00000001

  • 192 转换为二进制:11000000
  • 168 转换为二进制:10101000
  • 1 转换为二进制:00000001
  • 1 转换为二进制:00000001

这个二进制表示会被用于底层的数据传输和处理。

listen和accept

int listen(int sockfd, int backlog);
参数说明:

  • sockfd:由 socket 函数返回的套接字文件描述符,该套接字必须已经绑定到一个地址上(使用 bind 函数)。
  • backlog:指定队列中允许的最大未处理连接请求数。如果队列已满,新的连接请求可能会被拒绝。

如果 listen 调用成功,它将返回 0。如果调用失败,它将返回 -1,并且错误代码会被存储在全局变量 errno 中,可以通过 perror 函数来打印错误消息。

调用listen之后,socket就可以监听客户端的连接了,backlog可以设置接入客户端的个数。如果设置成3,意味值只能接收3个客户端,第4个客户端连接的时候就会失败。

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数说明:

  • sockfd:由 listen 函数返回的套接字文件描述符。
  • addr:指向 sockaddr 结构体的指针,该结构体将用于接收客户端的地址信息。
  • addrlen:指向一个 socklen_t 类型的指针,用于接收客户端地址的长度。

如果 accept 调用成功,它将返回一个新的套接字文件描述符,用于与客户端进行通信。这个新的套接字文件描述符是一个全双工的连接,可以用于后续的 send 和 recv 操作。同时,addr 和 addrlen 参数将包含客户端的地址信息。

如果 accept 调用失败,它将返回 -1,并且错误代码会被存储在全局变量 errno 中,可以通过 perror 函数来打印错误消息。常见的错误包括套接字错误(如 EBADF)或网络错误(如 ECONNREFUSED)等。

客户端与服务端通过tcp连接的时候会经过三次握手。

  1. 客户端首先发送一个SYN和seq给服务端,表示想要建立连接,这个时候客户端socke的状态会变成SYN_SEND
  2. 服务端收到SYN和seq后,会先查看自己的连接队列,也就listen那里设置的长度,如果队列未满,就返回SYN,ACK,已经ack和seq,表示自己ok,可以建立连接,并且将这个socket的状态改为SYN_RECV,如果队列满了,服务端就不管了,啥也不会返回,客户端就会重试,一直到失败。
  3. 客户端收到服务端的确认连接后,再次进行应答,客户端和服务端的状态就都变成了ESTABLISHED了。

在这里插入图片描述
对于服务端来讲就是

    // 接收客户端连接
    int client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_addr_length);
    if (client_fd < 0) {
        handle_error("accept");
    }

对于客户端来讲就是

	if (connect(client_fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
		handle_error("connect");
	}
  • 6
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值