计算机网络:套接字

1.相关函数:

1.1 socket()

获取套接字socket( int domain , int type , int potacl);

参数解析:

domain

指定通信域,主要的类型:

  1. UNIX(AF_UNIX) 同一台主机的进程间通信。

  2. IPV4域(AF_INET)跨网络通信。

  3. IPV6域(AF_INET6)跨网络通信。

type:

  1. SOCK_STREAM (流式套接字)
  • 通信模式:提供面向连接的、可靠的、双向的、基于字节流的通信。
  • 核心特性:
  • 可靠:数据无差错、无丢失、无重复,且按发送顺序到达(TCP 协议的特性)。
  • 流式:数据没有明确的"消息"边界。发送方多次写入的数据,接收方可能一次读出,反之亦然。应用层需要自己处理消 息边界(如添加长度头或分隔符)。
  • 连接导向:通信前必须建立连接(三次握手),通信后需要断开连接(四次挥手)。
  • 底层协议:通常与 TCP 协议配对。
  • 典型应用:HTTP、HTTPS、FTP、SSH、数据库连接等所有需要可靠数据传输的场景。
  1. SOCK_DGRAM (数据报套接字)
  • 通信模式:提供无连接的、不可靠的、固定最大长度的、基于数据报的通信。
  • 核心特性:
  • 不可靠:不保证数据一定到达,不保证顺序,不保证不重复。
  • 有边界:每个 send 或 sendto 调用产生一个独立的数据报,接收方 recvfrom 会完整收到整个数据报。消息不会粘在一 起。
  • 无连接:无需建立和断开连接,直接发送给目标地址。
  • 底层协议:通常与 UDP 协议配对。
  • 典型应用:DNS 查询、音视频流、在线游戏、广播/多播、SNMP 等能容忍少量丢包或要求低延迟的场景。

protocal:
场景 推荐设置 说明
普通 TCP套接字 protocol = 0 系统自动选择 TCP
普通 UDP 套接字 protocol = 0 系统自动选择 UDP
原始套接字 必须显式指定 IPPROTO_ICMP、IPPROTO_RAW
使用非默认协议 指定具体协议常量 IPPROTO_SCTP
需要协议过滤 指定要过滤的协议 原始套接字按协议过滤
返回值:成功返回一个socket,失败返回-1。与文件描述符一样,通过不同的int类型的描述符打开某个文件或指定获取特定端口的数据。

复制代码
sockfd = socket(AF_INET, SOCK_DGRAM, 0);  // 使用获取udp通信的套接字

1.2 struct_sockaddr_in结构体

用于指定套接字对应的端口和允许接收来自特定ip的消息。

参数解析:
_SOCKADDR_COMMON (sin)


通过拼接的方式实现的变量 sa_family_t sin_family。
用于指定通信域,对应socket中的domain。
sin_port指定套接字需要对应的端口号。

复制代码
typedef uint16_t in_port_t;

struct in_addrsin_addr:指定32位的ip。

复制代码
typedef uint32_t in_addr_t;
struct in_addr
  {
    in_addr_t s_addr;
  };

struct sockaddr_in serveraddr;
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(port);
// serveraddr.sin_addr.s_addr=inet_addr(ip.cstr());     // 指定特定的ip访问
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY); // 接收所有的ip

注意:

1.由于主机和网络在数据表示上的差异,需要使用函数进行数据来回转化,htons(host to net shore)将shore类型(16个比特位)的数据从主机转换为网络数据。

  1. 我们输入ip的形式是"127.0.0.1"需要调用inet_addr函数转化为网络数据,INADDR_ANY定义为0x00000000,当ip为0.0.0.0时,表示可以接收所有访问服务端port的ip,服务器接收ip一般设为INADDR_ANY。

1.3 bind()

bind( int sockfd ,const struct sockaddr * addr ,socklen_t len ):服务器通过 bind() 告诉操作系统:"请在我的这个套接字上监听指定的 IP 和端口"。

实现初始化函数

复制代码
 void Init()
    {
        // 1.获取套接字
        sockfd = socket(AF_INET, SOCK_DGRAM, 0);

        // 2.指定直接接收消息的端口和ip地址到sockaddr_in
        struct sockaddr_in serveraddr;
        serveraddr.sin_family = AF_INET;
        serveraddr.sin_port = htons(port);
        // serveraddr.sin_addr.s_addr=inet_addr(ip.cstr());     // 指定特定的ip访问
        serveraddr.sin_addr.s_addr = htonl(INADDR_ANY); // 接收所有的ip

        // 3.绑定套接字和sockaddr_in
        int ret = bind(sockfd, (const sockaddr *)&serveraddr, (socklen_t)sizeof(serveraddr));
        if (ret == -1)
        {
            cout << "绑定失败:" << strerror(errno) << endl;
            exit(1);
        }
    }

1.4 recvfrom

复制代码
recvfrom (int __fd, void *__restrict __buf, size_t __n, int __flags,
	  __SOCKADDR_ARG __addr, socklen_t *__restrict __addr_len)

参数解析:

_fd:自己监听的套接字。

_buf:接收的消息放在buf中,输出型参数。

_n:buf最大可以存储数据的字节数。

_addr:和struct sockaddr_in 一样,在这里,存储的是访问者(客户端)的ip,port,domain,输出型参数。

_len:_addr的长度的地址。

复制代码
 // 接收消息
    void start()
    {

        while (1)
        {
            char buffer[1024];
            struct sockaddr_in cilentmessage;
            socklen_t len = sizeof(cilentmessage);
            ssize_t ret = recvfrom(sockfd, buffer, sizeof(buffer), 0, (struct sockaddr *)&cilentmessage,&len);
            if (ret > 0)
            {
                buffer[ret] = 0;
                int16_t port = htons(cilentmessage.sin_port);
                string ip = inet_ntoa(cilentmessage.sin_addr);
                string message = buffer;
                cout << "ip: " << ip << "  port: " << port << "  message: " << message << endl;
            }
        }
    }

2. 实现主函数

复制代码
void manual()
{
    printf("use manual: \n            filename:        port: \n");
}
int main(int argc,const char* argv[])
{
    if(argc!=2)
    {
        manual();
        exit(2);
    }
    string port=argv[1];
    unique_ptr<server> myserver(new server(stoi(port)));
    myserver->Init();
    myserver->start();
    return 0;
}

执行程序,查看网络情况


客户端正在执行,实现成功。

3. 用户端

复制代码
cilent.cpp:

#include<iostream>
#include<memory>
#include"cilent.h"
using namespace std;

void manual()
{
    printf("use manual: \n            filename:       ip:      port: \n");
}
int main(int argc,const char* argv[])
{
    if(argc!=3)
    {
        manual();
        exit(2);
    }
    string port=argv[2];
    string ip=argv[1];
    unique_ptr<cilent> mycilent(new cilent(stoi(port),ip));
    mycilent->Init();
    mycilent->start();
    return 0;
}




cilent.h:

class cilent
{
    int16_t port;
    string ip;
    int sockfd;

public:
    cilent(int16_t _port, string _ip) : port(_port), ip(_ip) {}
    ~cilent() {}
    void Init()
    {
        sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (sockfd == -1)
        {
            cout << "error: " << strerror(errno) << endl;
            exit(2);
        }
        // cilent不需要bind,OS会自己选择使用哪个端口去访问客户端
    }

    void start()
    {
        struct sockaddr_in servermess;
        memset(&servermess,0,sizeof(servermess));
        servermess.sin_port = htons(port);
        servermess.sin_family = AF_INET;
        servermess.sin_addr.s_addr = inet_addr(ip.c_str()); // 使用127.0.0.1测试
        char buffer[1024];
        while (1)
        {
            cout<<"Please Enter: ";
            fgets(buffer,sizeof(buffer),stdin);
            socklen_t len = sizeof(servermess);
            int ret = sendto(sockfd, buffer, strlen(buffer), 0, (struct sockaddr *)&servermess,len);
        }
    }
};

4. 测试交流

相关推荐
剑神一笑8 小时前
Linux pgrep 命令详解:按名称查找进程 PID 的高效方法
linux·运维·chrome
yyuuuzz8 小时前
独立站的技术基础与常见运维问题
大数据·运维·服务器·网络·数据库·aws
剑神一笑8 小时前
Linux killall 命令详解:按进程名批量终止进程的原理与实践
linux·运维·chrome
日取其半万世不竭12 小时前
iftop、nethogs 和 nload:Linux 服务器网络流量实时监控工具介绍
linux·运维·服务器
mounter62512 小时前
Linux 内核资源管理:控制组(cgroup)的演进与“策略组”新提案
linux·运维·服务器·cgroup·kernel
bksczm12 小时前
文件在磁盘中的存储方式
linux·运维·服务器
L16247612 小时前
OpenSSH 半自动升级方案(独立编译 + 手动迁移 + 重建 systemd 服务)
linux·服务器·ssh
半旧夜夏12 小时前
【保姆级】微服务组件环境搭建(Docker Compose版)
java·linux·spring cloud·微服务·云原生·容器
Wpa.wk13 小时前
win环境本地文件上传远程服务器(scp/远程连接工具)
运维·服务器
爱莉希雅&&&13 小时前
zabbix快速搭建和使用
android·linux·数据库·zabbix·监控