Socket编程之TCP套件字

基于的TCP套件字编程流程

1. Socket套接字

Socket是一个编程接口(网络编程接口 ),是一种特殊的文件描述符(write/read)。Socket并不 仅限于TCP/IP

Socket独立于具体协议的编程接口,这个接口位于TCP/IP四层模型的应用层与传输层之间

介绍一下网络通信的三种主要模式

  1. 单工 A B两个端 A只负责发送 B只负责接收
  2. 半双工 A、B都可以发送或者接受 但是同一时间点之只能发送或者接收
  3. 全双工 A、B同一时间点既可以发也可以收

不使用多线程和并发的话socket编程 只能支持到半双工

1.2 Socket的类型

  • 流式套接字:(SOCK_STREAM
    • 面向字节流,针对于传输层协议为TCP协议的网络应用
  • 数据报套接字:(SOCK_DGRAM
    • 面向数据报,针对于传输层协议为UDP协议的网络应用
  • 原始套接字:(SOCK_RAW)
    • s直接跳过传输层

2.基于的TCP套件字编程流程

任何网络应用都会有通信双方:

  • Send 发送端
  • recv 接收端

TCP网络应用(C/S模型)(长连接)

  • Client 客户端(TCP
  • Server 服务端(TCP

任何的网络应用:

传输层的协议(TCP/UDP)+ 端口 + IP地址
网络地址:

任何网络应用任意一方都需要有一个网络地址 (IP+端口0)

2.1 TCP网络应用执行的过程

  • 建立连接
    • 三次握手
  • 发送/接收数据
    • 发送数据:write/send/sendto
    • 接收数据:read/recv/recvfrom
  • 关闭连接
    • 四次挥手

2.2 TCP网络应用的编程流程

2.2.1 TCP-Server服务端
2.2.1.1 建立一个套件字:(socket
c 复制代码
SOCKET(2)                  Linux Programmer's Manual                 SOCKET(2)
 NAME
       socket - create an endpoint for communication
 SYNOPSIS
       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>
       // windows的socket在  winsock2.h
       int socket(int domain, int type, int protocol);
        /*
            @描述:
                申请一个指定类型和指定协议的套接字
            @domain:
                指定域/协议簇。socket接口不仅不局限于TCP/IP,它可以用于Bluetooth、本地通
信...
                每一种下面都有自己的许多协议,我们把IPV4下面的所有协议都归纳到了一个域:
                    AF_INET  IPV4
                    AF_INET6 IPV6
                    AF_UNIX AF_LOCAL 本地通信
                    AF_BULETOOTH 蓝牙
                    ...
                    
            @type:
                指定要创建的套件字的类型:
                    SOCK_STREAM 流式套接字
                    SOCK_DGRAM  数据报套接字
                    SOCK_RAW    原始套接字
                    ...
                    TCP采用流式套接字,UDP采用数据报套接字
            @protocol
                协议,指定具体的应用层协议,可以指定为0:表示采用不知名的私有的应用层
            @return:
                成功返回一个套接字描述符
                失败返回-1,同时errno被设置
        */
2.2.1.2 绑定一个网络地址:(bind)
  • 并不是任意的地址都可以(需要合法且能够正常访问)
  • 把一个套接字和一个网络地址进行绑定。如果想让其他人来主动联系/连接,就需要绑定一个地

​ 址,并且需要把这个地址告诉其他人。不进行绑定,并代表套接字没有地址,不进行绑定套接字在

​ 进行通信时候,内核会动态为套接字指定一个地址。

c 复制代码
 BIND(2)                    Linux Programmer's Manual                   
BIND(2)
 NAME
       bind - bind a name to a socket
 SYNOPSIS
       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>
     	#include <arpa/inet.h>
       int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
       /*
            @描述:
                用于给一个指定的套接字绑定网络地址
            @sockfd:
                需要绑定地址的套接字
            @addr:
                一个结构体类型,表示网络地址
                    socket接口不仅可以用于以太网(IPV4),也可以用于IPV6,同时也可以
用于Bluetooth,....
                    不同的协议簇,它的地址是不一样的。
                    socket编程接口,用一个通用的 " 网络地址接口 "
                        struct sockaddr
                        {
                            sa_family_t sin_family; // 指定协议簇
                            char sa_data[14];
                        };
                     协议地址结构:
                         struct sockaddr_in
                         {
                              sa_family_t sin_family; // 指定协议簇
                              u_int16_t sin_port;     // 端口号
                              struct in_addr sin_addr;// IP地址
                              char sin_zero[8];       // 填充8字节,为了和其他协
议簇地址结构体大小一样
                          };
                            如:
                        struct sockaddr_in sock_info;
                        sock_info.sin_family    = AF_INET; // 指定为IPV4
                        sock_info.sin_port      = htons(6666); //指定为6666端
口
                        sock_info.sin_addr.s_addr = 
inet_addr("192.168.31.1"); // 绑定ip地址
                        // inet_aton("192.168.31.1",&sock_info.sin_addr);
                        bind(sock,(struct sockaddr 
*)&sock_info,sizeof(sock_info));
              @addrlen
                    表示网络地址结构体的大小
              @return:
                    成功返回0,失败返回-1
       
       */

**struct sockaddr_inj解释一下 **这是他的头文件include <arpa/inet.h>

  • sockaddr_in 绑定网络地址

    • sock -》socket套接字

    • addr -》address 地址

    • in -》internet 网络

2.2.1.3 等待监听:(listen)
  • 让一个套接字进入一个监听状态
c++ 复制代码
 LISTEN(2)                  Linux Programmer's Manual                 
LISTEN(2)
 NAME
       listen - listen for connections on a socket
       SYNOPSIS
       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>
       int listen(int sockfd, int backlog);
       /*
            @描述
                设置指定的套接字进入监听模式
            @sockfd:
                需要进入监听模式的套接字
            @backlog:
                可以处理的最大请求数目,可以理解为发起请求的客户端的队伍可以有多长
            @return:
                成功返回0,失败返回-1
       */
2.2.1.4 等待客户端的连接:(accept)
  • 等待客户端来发起连接和客户端建立TCP连接
    • 三次握手
  • 函数成功返回表示和一个客户端完成连接
  • 多次调用函数就可以与不同的客户端进行连接
c 复制代码
 ACCEPT(2)                  Linux Programmer's Manual                 
ACCEPT(2)
 NAME
       accept, accept4 - accept a connection on a socket
 SYNOPSIS
       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>
       int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
        /*
            @描述:
                等待客户端连接套接字,等待客户端发起连接请求
            @sockfd:
                等待客户端连接的那个套接字
            @addr:
                网络地址结构体,用于存储连接成功的客户端信息的。
            @addrlen:
                网络地址结构体的长度指针,用来保存客户端地址结构体的长度的。
                在调用的时候addrlen指向的空间保存的是addr的结构体的最大长度。
                如果函数成功返回,addrlen指向的空间保存的是client客户端地址的结构体长
度。
            @return:
                成功返回与该客户端的连接套接字的描述符(后续服务端和客户端的数据通信,通
过该套件字通信 )
                失败返回-1,同时errno被设置。
2.2.1.5 数据的传输:读/写
  • 发送数据:write/send/sendto
  • 接收数据:read/recv/recvfrom
c++ 复制代码
#include <sys/types.h>
 #include <sys/socket.h>
 ssize_t send(int sockfd, const void *buf, size_t len, int flags);
 /*
    作用:
        往指定套接字中写入数据
    @sockfd:
        需要写入数据的套接字描述符
    @buf:
        需要写入的数据空间的指针
    @len:
        数据的长度
    @flags:
        一般给0," 带外数据 "
    @return:
        成功返回实际发送的字节数,失败返回-1,同时...
 */
 #include <sys/types.h>
 #include <sys/socket.h>
 ssize_t recv(int sockfd, void *buf, size_t len, int flags);
 /*
    作用:
        从指定套接字中获取数据
    @sockfd:
        需要读取数据的套接字描述符
    @buf:
        读取到的数据所要保存的空间的指针
    @len:
        需要获取的数据的长度
    @flags:
        一般给0," 带外数据 "
    @return:
        成功返回实际获取的字节数,失败返回-1,同时...
 */
2.2.1.6 关闭套接字:(close/shutdown)
  • 四次挥手
c++ 复制代码
 #include <sys/socket.h>
 int shutdown(int sockfd, int how);
 /*
    作用:
        关闭一个套接字
    @sockfd:
        需要关闭操作的套接字描述符
    @how:
        关闭方式:
 			SHUT_RD   关闭读
            SHUT_WR   关闭写
            SHUT_RDWR 关闭读写 -->close(sockfd);
 */
2.2.2 TCP-Client客户端
  • 建立一个套接字:socket

  • 绑定地址:可选

    • 可以绑定也可也不绑定(不推荐绑定,让系统分配)
  • 发起连接请求:connect

c 复制代码
 CONNECT(2)                 Linux Programmer's Manual                
CONNECT(2)
 NAME
       connect - initiate a connection on a socket
 SYNOPSIS
       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>
       int connect(int sockfd, const struct sockaddr *addr,socklen_t 
addrlen);
        /*
            @描述:
                用指定的套接字,对指定网络地址发起连接请求
            @sockfd:
                发起连接请求的套接字
                同时这个套接字是与服务端进行数据通信的套接字
            @addr:  
                需要连接到的网络地址,目标地址
            @addrlen:
                目标地址结构体的大小
            @return:
                成功返回0,失败返回-1
        */

数据的传输:读/写

  • 发送数据:write/send/sendto
  • 接收数据:read/recv/recvfrom

关闭套接字close

示例

客户端

c++ 复制代码
#include <iostream>
#include <sys/types.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <pthread.h>
#include <unistd.h>
#include <vector>
#include <cstring>
using namespace std;
vector<int> clients;
// 子线程 接收客户信息
void *myClient(void *arg)
{
    int newClient = *(int *)arg;
    // 等待客户端传过来数据
    char buffer[1024] = {0};
    while (1)
    {
        if (recv(newClient, buffer, 1024, 0) < 0)
        {
            bool shouldExit = false;
            cout << "接收客户端信息失败" << endl;
            close(newClient);
               break;
           
        }
        if (strcmp(buffer, "exit") == 0)
        {
            cout << "结束通信" << endl;
            close(newClient);
           break;
        }
        // 转发给其他客户端
        for (int i = 0; i < clients.size(); i++)
        {
            if (clients[i] == newClient)
                continue;
            if (send(clients[i], buffer, 1024, 0) < 0)
            {

                cout << "转发给其他客户端失败" << endl;
            }
        }
        cout << "客户端信息:" << buffer << endl;
    }
    

    close(newClient);
    return nullptr;
}

int main()
{
    // 1. 申请一个套机字 socket
    int socket_id = socket(AF_INET, SOCK_STREAM, 0);

    if (socket_id == -1)
    {
        perror("套接字创建失败");
    }
    cout << "创建套接字成功" << endl;
    // 2.绑定一个网络地址:(bind)
    //   struct sockaddr_in
    /*
    sockaddr_in   绑定网络地址
    sock -》`socket`套接字
    addr -》`address` 地址
    in   -》`internet  `网络
    */
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;                         // 地址结构 指定为IPV4
    addr.sin_port = htons(5555);                       // 端口号
    addr.sin_addr.s_addr = inet_addr("192.168.5.128"); // IP地址
    //  C 语言允许将非 const 类型的指针隐式地转换为 const 类型的指针会隐式转换吗
    if ((bind(socket_id, (const struct sockaddr *)&addr, sizeof(addr)) == -1))
    {
        perror("绑定失败");
        return -1;
    }
    cout << "绑定成功" << endl;
    // 3. 等待监听:(listen)
    if (listen(socket_id, 5) == -1)
    {
        perror("监听失败");
        return -1;
    }
    cout << "监听成功" << endl;

    cout << "服务请求开启成功" << endl;

    // 4.  等待客户端的连接:(accept)
    while (1)// 使用标志变量控制循环
    {
        struct sockaddr_in newClentInfor;
        socklen_t addrlen = sizeof(sockaddr_in);
        int newClent = accept(socket_id, (struct sockaddr *)&newClentInfor, &addrlen);
       
        if (newClent == -1)
            continue;
        cout << "新客户端连接成功[" << inet_ntoa(newClentInfor.sin_addr) << ":" << htons(newClentInfor.sin_port) << "]" << endl;

        // 增加一个客户端
        clients.push_back(newClent);
        // 创建一个线程
        pthread_t tid;
 
        pthread_create(&tid, nullptr, myClient, (void *)&newClent);
        pthread_detach(tid); // 线程分离,避免阻塞主线程
       
    }
    
    // 关闭套接字  socket本质是文件标识符 close可以关闭
    close(socket_id);

    return 0;
}

服务端

c++ 复制代码
#include <winsock2.h>
#include <windows.h>
#include <iostream>
#include <pthread.h>
using namespace std;

// 接收信息的线程
void *RecvThread(void *arg)
{
    // 套接字类型转换
    SOCKET socket_client = *(SOCKET *)arg;
    while(1)
    {
        char szMsg[256] = {0};
        int nlen =  recv(socket_client, szMsg, sizeof(szMsg), 0);
        // 清空输入缓冲区
        fflush(stdout);
        if(nlen > 0)
        {
            // 清空输入缓冲区
             fflush(stdout);
            cout << "接收到的消息:" << szMsg << endl;
        }
        else if(nlen ==  0)
        {
            cout << "服务器关闭" << endl;
            break;
        }

      
    }
      return nullptr;
}
int main()
{
    // 1. 指定网络库版本
    WSADATA waData;

    // 2. 初始化网络库  MAKEWORD(2, 2) 表示请求 Winsock 2.2 版本
    if (WSAStartup(MAKEWORD(2, 2), &waData) != 0)
    {
        perror("初始化网络库失败");
        return -1;
    }

    // 申请一个套接字
    SOCKET socket_client = socket(AF_INET, SOCK_STREAM, 0);
    if (socket_client == INVALID_SOCKET)
    {
        perror("套接字申请失败");
        return -1;
    }

    // 配置服务器地址
    SOCKADDR_IN addrSrv;
    addrSrv.sin_family = AF_INET;   // 指定为IPV4
    addrSrv.sin_port = htons(5555); // 端口号
    addrSrv.sin_addr.s_addr = inet_addr("192.168.5.128");

    // 连接服务器
    if (connect(socket_client, (sockaddr*)&addrSrv, sizeof(addrSrv)) == SOCKET_ERROR)
    {
        cerr << "连接失败,错误码:" << WSAGetLastError() << endl;
        closesocket(socket_client);
        WSACleanup();
        return -1;
    }
    // 接收信息线程
    pthread_t tid;
    pthread_create(&tid, nullptr, RecvThread, (void*)&socket_client);


    // 数据通信
    while (1)
    {
        cout << "请输入:" << endl;
        char szMsg[256] = {0};
        cin >> szMsg;
        if (send(socket_client, szMsg, strlen(szMsg), 0) == -1)
        {
            perror("发送失败");
            return -1;
        }
        if (strcmp(szMsg, "exit") == 0)
        {
             cout << "通信结束";
            // 关闭套接字
            closesocket(socket_client);
            // 释放网络库资源
            WSACleanup();
            exit(0);
           
        }
    }
    // 关闭套接字
    closesocket(socket_client);
    // 释放网络库资源
    WSACleanup();
    return 0;
}
  • socket_id :可以理解为服务器的"门岗",它负责监听指定的端口,等待客户端的连接请求。当有客户端请求连接时,socket_id 会接收到这个请求,并通过 accept() 函数创建一个新的套接字来处理这个客户端。
  • newClent :这是通过 accept() 函数创建的新套接字,它专门用于与连接的客户端进行通信。可以理解为服务器用来和特定客户端"对话"的通道。

socket_id 就像是一个门岗,负责接待来访的客户(监听连接请求);而 newClent 就像是服务器派去和客户具体沟通的服务人员,负责处理具体的信息交流。

不过要注意,newClent 并不是服务器本身,而是服务器为每个客户端创建的一个独立的套接字,用于和客户端进行数据传输。

相关推荐
0zxm1 分钟前
202403-02-相似度计算 csp认证
开发语言·数据结构·c++
iummature1 分钟前
wireshark分析国标rtp ps流
网络·测试工具·wireshark
这儿有一堆花5 分钟前
Wireshark 使用教程:让抓包不再神秘
网络·测试工具·wireshark
藥瓿亭29 分钟前
IPtables部署和使用
linux·运维·服务器·网络·iptables·防火墙·firewall
lona_cn1 小时前
VMProtect HWID 解析(C++)
c++
搬码临时工1 小时前
内网怎么映射外网ip? 内网的地址快速映射给外网访问用方法
运维·服务器·网络·物联网·网络协议·tcp/ip·智能路由器
superior tigre1 小时前
CNN卷积网络:让计算机拥有“火眼金睛“(superior哥AI系列第4期)
网络·人工智能·cnn
Chenyu_3101 小时前
05.MySQL表的约束
android·开发语言·网络·数据库·网络协议·mysql·php
zhanglb121 小时前
liunx - 麒麟系统v10 SP1 执行脚本文件
linux·docker
Rocky4011 小时前
开发的几种格式,TCP的十个重要机制
运维·服务器·网络