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

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

  • 点赞 ⭐️
  • 收藏 📌
  • 关注 🔔
相关推荐
侯侯Hou2 小时前
Linux系统安装OpenClaw
linux
rgb2gray2 小时前
论文详解 | TWScan:基于收紧窗口的增强扫描统计,实现不规则形状空间热点精准检测
网络·人工智能·python·pandas·交通安全·出租车
zzb15802 小时前
RAG from Scratch-优化-routing
java·前端·网络·人工智能·后端·python·mybatis
流水迢迢lst2 小时前
靶场练习day14--任意文件读取
网络·安全
charlie1145141912 小时前
2026年IMX6ULL正点原子Alpha开发板学习方案——U-Boot完全移植概览:从官方源码到你的自制板,这条路有多远
linux·学习·嵌入式·uboot·嵌入式linux·工程实践·编程指南
虾..3 小时前
Linux 基于TCP实现服务端客户端通信(线程池)
java·网络协议·tcp/ip
z2014z3 小时前
Deflate 算法详解
网络·算法
高梦轩3 小时前
LNMP 环境部署笔记
linux·笔记
HAPPY酷3 小时前
Linux `shutdown` 命令速查:安全关机与重启
linux·chrome·安全