前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >从零开始:Linux 网络基础到聊天室搭建

从零开始:Linux 网络基础到聊天室搭建

原创
作者头像
花花Binki
发布2024-09-05 22:35:49
2120
发布2024-09-05 22:35:49
举报
文章被收录于专栏:岚的工作随笔
封面
封面

浅谈Socket

在拨号上网的时代,上网被看作一个通过与“互联网”这位朋友打电话的行为。这种信息建的交互形成网络,再按照一定规则协议,形成了套接字(Socket)。

早期计算机技术发展过程中,很多术语来自于英文,译者在寻找合适的中文术语时,会结合字面意义和技术特性,偶尔有时会采用音译(ex.鲁棒性robustness)。如果将Socket去翻译软件翻译,得到的会是插座,代表的意思接近一个链接点或接口。根据几个字拆开来再与直译对比来看:

”有包围、套住含义,“”有链接的含义,“”,是一种计算机的单位。

这样组合起来,就表达了这个socket背后的含义,即使表面有点拗口。

现如今,更多接受的翻译方式是不翻译。

到这里可能还是一个模糊的概念,用一个从安装电话到打电话(沟通)的过程来解释这个概念。

一个“套接字”流程
一个“套接字”流程
代码语言:c
复制
// 该函数用于创建一个套接字
extern int __sys_socket(int family, int type, int protocol);
// 该函数用于将套接字绑定到一个地址上
extern int __sys_bind(int fd, struct sockaddr __user *umyaddr, int addrlen);
// 监听指定套接字的连接请求
extern int __sys_listen(int fd, int backlog);
// 该函数用于接受一个传入的连接请求。
extern int __sys_accept4(int fd, struct sockaddr __user *upeer_sockaddr, int __user *upeer_addrlen, int flags);

Linux 中的 Socket

如果说在 Java 中,万物皆对象,那么在Linux中可以说万物皆文件。

Socket 也是一种文件,所以 Linux 在网络传输的过程中可以使用文件I/O相关的函数。

代码语言:c
复制
// sys_close函数用于关闭一个已打开的文件描述符。
// 参数:  fd - 要关闭的文件描述符。文件,Socket,Pipe都可以传入
// 返回值: 成功时返回0,失败时返回-1,并设置errno。
int sys_close(int fd)

在Linux中创建一个Socket,通过下面的方法实现

代码语言:c
复制
// @param family 套接字地址族,如AF_INET表示IPv4
// @param type 套接字类型,如SOCK_STREAM表示TCP流式套接字
// @param protocol 使用的协议,通常为0,系统将自动选择合适的协议
// @return 成功时返回新创建的套接字文件描述符,失败时返回-1并设置errno
int __sys_socket(int family, int type, int protocol);
  • family

这里和font family类似,可以理解为“族”,你可以在linux/include/sockect.h找到支持的全部协议。

代码语言:c
复制
/* Supported address families. */
#define AF_UNSPEC	0
#define AF_UNIX		1	/* Unix domain sockets 		*/
#define AF_LOCAL	1	/* POSIX name for AF_UNIX	*/
#define AF_INET		2	/* Internet IP Protocol   常说的IPV4	*/
#define AF_AX25		3	/* Amateur Radio AX.25 		*/
#define AF_IPX		4	/* Novell IPX 			*/
#define AF_APPLETALK	5	/* AppleTalk DDP 		*/
#define AF_NETROM	6	/* Amateur Radio NET/ROM 	*/
#define AF_BRIDGE	7	/* Multiprotocol bridge 	*/
省略...
  • type

类型,相关定义在include/linux/net.h

代码语言:c
复制
enum sock_type {
	SOCK_STREAM	= 1,
	SOCK_DGRAM	= 2,
	SOCK_RAW	= 3,
	SOCK_RDM	= 4,
	SOCK_SEQPACKET	= 5,
	SOCK_DCCP	= 6,
	SOCK_PACKET	= 10,
};

第一种流式,比较常见。第二种源码的注释这样描述datagram (conn.less) socket

这二者的关系就像是TCP和UDP。再后面的就是根据不同场景和协议层的不同类型,有的面相传递传递顺序做优化,SOCK_RDM。有的则更多用于自定义,SOCK_RAW。

  • protocol

协议,和地址族支持一致。

Protocol families, same as address families.

TCP建立过程

知道了以上这些基础,则可以创建一个简单的TCP Socket

代码语言:c
复制
int tcp_socket = socket(AF_INET, SOCK_STREAM, 0)

但如果想要“打电话”,还需要接一根电话线,知道对方“号码”。

IP 与 Port

当启动一个Spring Boot后,你会熟练的打开127.0.0.1:8080来查看一下是否正常。如果偷懒不想输入端口号,会在application.yml里设置:

代码语言:yml
复制
server:
  port: 80

前面的一串数字就是IP,冒号后面的就是端口号。我们创建的socket也需要分配这样一个“组合”。

有很长一段时间里,不明真相的我仍认为IP + Port = Socket。

客户端与服务端

客户端(client)与服务端(server),能分得这么清晰的时候,通常是用浏览器访问一个网站,此时浏览器叫做客户端,被访问的网站就是服务端。

还有一个词容易混淆,他就是终端。终端来自于他属于最边缘的设备,客户端通常是终端上的软件。你的手机,电脑就是终端,上面运行的浏览器便是客户端。

如果是两个人在局域网聊天,那双方各自为client和server。所以另一台机器也需要去创建一个Socket且分配IP和Port。

代码语言:c
复制
# 绑定到一个IP地址和端口号
server_address = ('0.0.0.0', 8080)  # 监听所有可用的网络接口上的8080端口
socket.bind(server_address)

三次握手

握手
握手

三次握手,Three-Way Handshake

及时双方各自为client和server,那在一次请求时,也有发送和接收。发送和接收都有失败的概率,为了收到了你的收到,为了不“黑暗森林”,双方以这样一种形式确定了对接成功。

收发之前,记得先让Socket开始监听

代码语言:c
复制
listen(socket, 8888)

下面是一个模拟的过程

代码语言:txt
复制
1.客户端发送 SYN 包:
Client -> Server: SYN (ISN = x)

2. 服务器发送 SYN+ACK 包:
Server -> Client: SYN (ISN = y), ACK (x + 1)

3.客户端发送 ACK 包:
Client -> Server: ACK (y + 1)

在这个过程中,每个数据包都包含以下信息:

  • 源 IP 地址和目标 IP 地址
  • 源端口和目标端口
  • 序列号(Sequence Number)
  • 确认号(Acknowledgment Number)
  • 标志位(Flags),如 SYN、ACK 等。

聊天室

服务端

代码语言:c
复制
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define MAX_CLIENTS 10
#define BUFFER_SIZE 1024

int main() {
    int server_fd, new_socket;
    struct sockaddr_in address;
    int addrlen = sizeof(address);
    char buffer[BUFFER_SIZE] = {0};
    fd_set readfds;

    // 创建Socket
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }

    // 绑定地址
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(8080);

    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
        perror("bind failed");
        exit(EXIT_FAILURE);
    }

    // 监听连接
    if (listen(server_id, MAX_CLIENTS) < 0) {
        perror("listen");
        exit(EXITTargetException);
    }

    printf("Chat server started on port 8080\n");

    while (1) {
        // 清空文件描述符集合
        FD_ZERO(&readfds);

        // 添加服务器Socket到集合
        FD_SET(server_fd, &readfds);
        int max_sd = server_fd;

        // 添加客户端Socket到集合
        for (int i = 0; i < MAX_CLIENTS; i++) {
            int sd = client_sockets[i];
            if (sd > 0) {
                FD_SET(sd, &readfds);
            }
            if (sd > max_sd) {
                max_sd = sd;
            }
        }

        // 监听所有Socket
        if (select(max_sd + 1, &readfds, NULL, NULL, NULL) < 0) {
            perror("select error");
            exit(EXIT_FAILURE);
        }

        // 处理新的连接
        if (FD_ISSET(server_fd, &readfds)) {
            if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {
                perror("accept");
                exit(EXIT_FAILURE);
            }

            printf("New connection, socket fd is %d, ip is : %s, port : %d\n", new_socket, inet_ntoa(address.sin_addr), ntohs(address.sin_port));

            // 将新连接添加到客户端Socket数组
            for (int i = 0; i < MAX_CLIENTS; i++) {
                if (client_sockets[i] == 0) {
                    client_sides[i] = new_socket;
                    break;
                }
            }
        }

        // 处理客户端消息
        for (int i = 0; i < MAX_CLIENTS; i++) {
            int sd = client_sockets[i];
            if (FD_ISSET(sd, &readfds)) {
                if (read(sd, buffer, BUFFER_SIZE) == 0) {
                    // 客户端断开连接
                    getpeername(sd, (struct sockaddr*)&address, (socklen_t*)&addrlen);
                    printf("Host disconnected, ip %s, port %d\n", inet_ntoa(address.sin_addr), ntohs(address.sin_port));
                    close(sd);
                    client_sockets[i] = 0;
                } else {
                    // 广播消息给所有客户端
                    buffer[BUFFER_SIZE - 1] = '\0';
                    printf("Received message: %s\n", buffer);
                    for (int j = 0; j < MAX_CLIENTS; j++) {
                        int client_sd = client_sockets[j];
                        if (client_sd != 0 && client_sd != sd) {
                            send(client_sd, buffer, strlen(buffer), 0);
                        }
                    }
                }
            }
        }
    }

    return 0;
}

客户端

代码语言:c
复制
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define PORT 8080
#define BUFFER_SIZE 1024

int main() {
    int sock = 0;
    struct sockaddr_in serv_addr;
    char buffer[BUFFER_IDEA] = {0};
    char *message = "Hello from client";

    // 创建Socket
    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        printf("\n Socket creation error \n");
        return -1;
    }

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(PORT);

    // 将IP地址转换为二进制形式
    if (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) {
        printf("\nInvalid address/ Address not supported \n");
        return -1;
    }

    // 连接到服务器
    if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(servai_addr)) < 0) {
        printf("\nConnection Failed \n");
        return -1;
    }

    // 发送消息到服务器
    send(sock, message, strlen(message), 0);

    // 接收服务器消息
    int valread = read(sock, buffer, BUFFER_SIZE);
    printf("\nServer: %s\n", buffer);

    return 0;
}

one more thing

如何优化性能?

从面试最烦人的三次握手开始优化。倘若三次握手还是没成功,会不断尝试,但时间会依次递增,所以可以设置一个三次重试后直接失败返回。

自定义协议。比如使用跳过TCP层的SOCK_RAW类型。(注意风险)

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 浅谈Socket
    • Linux 中的 Socket
    • TCP建立过程
      • IP 与 Port
        • 客户端与服务端
          • 三次握手
          • 聊天室
            • 服务端
              • 客户端
              • one more thing
              相关产品与服务
              NAT 网关
              NAT 网关(NAT Gateway)提供 IP 地址转换服务,为腾讯云内资源提供高性能的 Internet 访问服务。通过 NAT 网关,在腾讯云上的资源可以更安全的访问 Internet,保护私有网络信息不直接暴露公网;您也可以通过 NAT 网关实现海量的公网访问,最大支持1000万以上的并发连接数;NAT 网关还支持 IP 级流量管控,可实时查看流量数据,帮助您快速定位异常流量,排查网络故障。
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档