【Linux篇】Socket编程TCP

📌 个人主页: 孙同学_

🔧 文章专栏: Liunx

💡 关注我,分享经验,助你少走弯路!

文章目录

    • Socket编程TCP
      • [TCP Socket 编写流程](#TCP Socket 编写流程)
      • [socket 创建套接字](#socket 创建套接字)
      • [bind 端口号](#bind 端口号)
      • [listen 监听](#listen 监听)
      • [accept 获取链接](#accept 获取链接)
      • [read 读数据](#read 读数据)
      • [write 发数据](#write 发数据)
      • [connect 客户端向服务器发起建立连接的请求](#connect 客户端向服务器发起建立连接的请求)
      • 简单演示

Socket编程TCP

TCP是一种有连接、面向字节流、可靠的传输层协议。在Linux环境下进行TCP Socket编程,其实就是编写一套双方如何通过网线打电话 的代码逻辑。

我们可以把**Socket(套接字)**想象成一个电话。Server(服务端)就像是10086的接线员,Client(客户端)就是打电话的用户。

TCP变成分为:

  • 服务端
  • 客户端

TCP Socket 编写流程

核心步骤:

角色 动作 函数名称 含义
Server 创建电话机 socket() 申请一个网络通信的文件描述符。
Server 固定电话号 bind() 把ip地址和端口号绑定到socket上 。
Server 开启铃声 listen() 进入监听状态,准备接听电话 。
Server 接听电话 accept() 阻塞等待,直到有客户端打进来,返回一个新的连接。
Clinet 拨号 connect() 主动连接指定ip和端口
Both 通话 read() / write() 双方收发数据(在Linux中,Socket被视为文件)
Both 挂断 close() 释放资源,断开连接。

服务器往往是进禁止拷贝的

我们可以直接定义一个Nocopy的类

cpp 复制代码
#pragma once
#include <iostream>
class nocopy
{
public:
    nocopy() {}
    nocopy(const nocopy &) = delete; //把nocopy的拷贝构造删掉
    const nocopy &operator=(const nocopy &) = delete;//把nocopy的拷贝赋值语句也删掉
    ~nocopy() {}
};
};

要形成TcpServer的拷贝,就得先把基类拷贝一下,而基类是禁止去拷贝和赋值的。


socket 创建套接字


第三个参数我们直接设置为0.

返回值:成功返回一个文件描述符,失败直接返回-1


bind 端口号


listen 监听

第一个参数:我们要监听的文件的文件描述符sockfd,

第二个参数:允许底层排队的最大链接数

返回值:成功0被返回,失败-1被设置

netstat -tnlp

t表示显示的协议都是tcp的

n表示显示成数字

l表示listen状态的 a表示所有的

p表示进程相关的信息
telnet 表示可以远程登录我们指定的tcp服务

例如:telnet baidu.com 80

结论:只要服务器处于listen状态,那么它就已经可以被连接了

为什么今天查到的是两条连接?


accept 获取链接

从内核中直接获取,而建立连接的过程和accept无关.

第二个参数和第三个参数是输出型参数,等同于recvfrom的后两个参数。

返回值:成功,这个系统调用会返回一个合法的整数,它是一个文件描述符。失败返回-1,错误码被设置。

这个文件描述符和sockfd有何区别??

好再来鱼庄就是我们自己写的服务器,在马路边上拉人,这个马路就相当于操作系统内部,张三相当于我们创建的_sockfd,这种_sockfd只负责在操作系统内部获取链接,我们把这种获取链接的套接字叫做listensockfd.当我们accept的时候,accept的动作就相当于拉客的动作。拉到客人提供的服务由accept的返回值来提供,这个服务通常是读写服务。

UDP是面向数据报,读取时要用特定的接口recvfrom,我们读取TCP的时候,用的接口是read


read 读数据

返回值:读成功了返回实际读到的字节数,读失败会有等于0的情况和小于0的情况

返回值:通常有三种情况

a. n>0:读取成功

b. n<0:读取失败

c. n==0:我在读取的时候,对端把连接关了,相当于服务器读到了文件的结尾 (管道pipe:一i个新打开的管道,如果读端一直在读,写端不写了,写端不写,读端就会一直阻塞等它写。如果它不光不写了,并且还把写端关闭了,读就会读到0,表明读到文件结尾)


write 发数据


connect 客户端向服务器发起建立连接的请求

返回值:成功0被返回。否则-1被返回


问题1: 进程如果退出了,曾经打开的文件会怎么办?

默认会被自动释放,文件描述符会被自动关闭。

问题2: 进程如果打开了一个文件,得到了一个fd,如果再创建子进程,这个子进程能拿到父进程的fd进行访问吗?


问题1: 如果进程打开了一个文件,得到了一个fd,这个fd线程能看见吗?

答案是能看到,因为创建线程的时候,只是创建了TCB,并没有创建对应的文件描述符表,文件描述符表属于被所有线程共享的。

问题2: 线程敢不敢关闭自己不需要的fd?为什么?

不敢,因为线程已经共享了,所以我们就不能像多进程那样关闭不需要的文件描述符。

简单演示

服务端(server.c)

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>

int main() {
    // 1. 创建 socket (IPv4, TCP协议)
    int serv_sock = socket(AF_INET, SOCK_STREAM, 0);

    // 2. 绑定 IP 和端口
    struct sockaddr_in serv_addr;
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 监听本机所有IP
    serv_addr.sin_port = htons(8888);              // 监听8888端口

    bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));

    // 3. 监听
    listen(serv_sock, 5);
    printf("服务器启动,正在等待连接...\n");

    // 4. 接受连接
    struct sockaddr_in clnt_addr;
    socklen_t clnt_addr_size = sizeof(clnt_addr);
    int clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size);
    printf("客户端已连接!\n");

    // 5. 读取数据并打印
    char buffer[1024];
    read(clnt_sock, buffer, sizeof(buffer)-1);
    printf("收到消息: %s\n", buffer);

    // 6. 关闭
    close(clnt_sock);
    close(serv_sock);
    return 0;
}

客户端(client.c)

c 复制代码
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>

int main() {
    // 1. 创建 socket
    int sock = socket(AF_INET, SOCK_STREAM, 0);

    // 2. 连接服务器 (连接本地 127.0.0.1)
    struct sockaddr_in serv_addr;
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    serv_addr.sin_port = htons(8888);

    connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));

    // 3. 发送数据
    char *msg = "Hello, Linux Network!";
    write(sock, msg, strlen(msg));
    printf("消息已发送!\n");

    // 4. 关闭
    close(sock);
    return 0;
}

👍 如果对你有帮助,欢迎:

  • 点赞 ⭐️
  • 收藏 📌
  • 关注 🔔
相关推荐
NamoAmitabha1282 小时前
libpng12-0 1.2.54-1ubuntu1.1 (amd64 binary) in ubuntu xenial
linux·ubuntu20.4
IPdodo_2 小时前
跨境 API 调用不稳定怎么办:出口、超时重试与链路监控的实践
网络·python·网络协议
Madison-No72 小时前
搭建项目测试环境
linux·运维·服务器·python
吴声子夜歌3 小时前
Shell编程实例——编写安全的shell脚本(二)
linux·运维·shell
xiaoye-duck5 小时前
《Linux 网络编程》深入理解 IP 协议(二):网段划分、私有 IP 与 NAT 地址转换
linux·网络·ip
zx_741484816 小时前
【Linux入门】Shell 函数、正则表达式与文本处理:cut 与 awk
linux·运维·正则表达式
Zenova EdgeOS6 小时前
Linux dmesg 工业边缘实战:内核日志过滤、持久化与故障定位
linux·运维·边缘计算·工业边缘·dmesg·内核日志
金士顿6 小时前
System.Text.Json Source Generator 深度解析:为什么 Native AOT 不喜欢反射
linux·asp.net core·arm64·net·native aot
做运维的阿瑞7 小时前
Python 标准库汇总:分类速览与常用模块清单
linux·运维·python
百万蹄蹄向前冲8 小时前
密码都对Node.js却连不上Linux数据库
linux·mysql