详解【网路编程】之Socket套接字编程


谢谢帅气美丽且优秀的你看完我的文章还要点赞、收藏加关注

没错,说的就是你,不用再怀疑!!!

希望我的文章内容能对你有帮助,一起努力吧!!!


1、Socket套接字

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

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

1.1Socket的类型

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

1.2基于的TCP套件字编程流程

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

  • Send 发送端
  • recv 接收端

TCP 网络应用

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

任何的网络应用:

  • 传输层的协议(UDP/TCP)+端口号+IP地址

网络地址:

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

1.2.1TCP网络应用执行的大致过程

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

1.3TCP网络应用的编程流程

1.3.1TCP-Server服务端

1)建立一个套件字:( socket )

2)绑定一个网络地址:( bind )

  • 并不是任意的地址都可以(需要合法且能够正常访问)
  • 把一个套接字和一个网络地址进行绑定。如果想让其他人来主动联系/连接,就需要绑定一个 地址,并且需要把这个地址告诉其他人。不进行绑定,并代表套接字没有地址,不进行绑定套 接字在进行通信时候,内核会动态为套接字指定一个地址。

3)等待监听:( listen)

让一个套接字进入一个 监听状态

4)等待客户端的连接:( accept )

  • 等待客户端来发起连接和客户端建立 TCP 连接
    • 三次握手
  • 函数成功返回表示和一个客户端完成连接
  • 多次调用函数就可以与不同的客户端进行连接

5)数据的传输: 读/写

发送数据: write/send/sendto

接收数据: read/recv/recvfrom

6)关闭套接字:( close/shutdown)

四次挥手

1.3.2TCP-Client客户端

  • 建立一个套接字: socket
  • 绑定地址:可选/可以绑定也可也不绑定
  • 发起连接请求: connect
    • 主动的与 TCP-Server 建立连接。三次握手
  • 数据的传输:读/写
    • 发送数据:write/send/sendto
    • 接受数据:read/recv/recvfrom
  • 关闭套接字
    • close

1.4网络通信协议

协议:就是通信双方约定好的通信规则。

下面举例一个基础的C/S过程:

客户端:

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>

typedef enum
{
    CMD,
    MSG,
    IMG
}Data_t;

struct package
{
	// 数据类型
	Data_t DataType;
	
	// 数据实际的大小
	int DataSize;
	
	// 数据本体
	unsigned char Datas[0]; // 柔性数组
};

/*
    通过main函数的参数传递IP和端口
*/
int main(int argc,const char *argv[])
{
    // 申请套接字
    int sock_fd = socket(AF_INET,SOCK_STREAM,0);
    if(sock_fd == -1)
    {
        perror("申请失败");
        return -1;
    }

    std::cout << "套接字申请成功" << std::endl;

    std::cout << "客户端发起连接..." << std::endl;
    // 等待客户端连接
    struct sockaddr_in server;
    server.sin_family = AF_INET;
    server.sin_addr.s_addr = inet_addr(argv[1]);
    server.sin_port = atoi(argv[2]);
    
    if(connect(sock_fd,(struct sockaddr*)&server,sizeof(server) )== -1)
    {
        perror("连接失败");
        close(sock_fd);
        return -1;
    }

    std::cout << "客户端连接成功" << std::endl;

    // 数据传输
    while(1)
    {
        std::string str;
        std::cout << "输入消息:";
        std::cin >> str;

        // 应用层封包过程
        struct package *data = (struct package*)new char[sizeof(struct package)+str.size()];
        data->DataType = MSG;
        data->DataSize = str.size();
        strcpy((char *)data->Datas,str.c_str());

        int ret = write(sock_fd,data,sizeof(struct package)+str.size());
        if(ret == -1||str == "exit")
            break;
    }

    // 关闭套接字
    close(sock_fd);
    return 0;
}

服务端:

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>

typedef enum
{
    CMD,
    MSG,
    IMG
}Data_t;


struct package
{
	// 数据类型
	Data_t DataType;
	
	// 数据实际的大小
	int DataSize;
	
	// 数据本体
	unsigned char Datas[0]; // 柔性数组
};

/*
    通过main函数的参数传递IP和端口
*/
int main(int argc,const char *argv[])
{
    // 申请套接字
    int sock_fd = socket(AF_INET,SOCK_STREAM,0);
    if(sock_fd == -1)
    {
        perror("申请失败");
        return -1;
    }

    std::cout << "套接字申请成功" << std::endl;

    // 绑定网络地址
    struct sockaddr_in local; // 绑定本机的网络地址
    local.sin_family = AF_INET;
    local.sin_addr.s_addr = inet_addr(argv[1]); // 将字符串的ip地址转化为网络字节序列
    local.sin_port = atoi(argv[2]);
    
    if(bind(sock_fd,(struct sockaddr*)&local,sizeof(local)) == -1)
    {
        perror("绑定失败");
        close(sock_fd);
        return -1;
    }

    std::cout << "套接字绑定成功" << std::endl;

    // 进入监听状态
    if(listen(sock_fd,10) == -1)
    {
        perror("监听失败");
        close(sock_fd);
        return -1;
    }

    std::cout << "套接字监听成功" << std::endl;

    std::cout << "等待客户端连接..." << std::endl;
    // 等待客户端连接
    struct sockaddr_in client;
    socklen_t client_size = sizeof(client);
    int newSock = accept(sock_fd,(struct sockaddr*)&client,&client_size);

    // 判断客户端是否连接成功
    if(newSock == -1)
    {
        perror("客户端连接失败");
        close(sock_fd);
        return -1;
    }

    std::cout << "客户端连接成功" << std::endl;

    // 数据传输
    while(1)
    {
        struct package *data = (struct package*)malloc(sizeof(struct package));

        // 获取第一次数据
        int ret = read(newSock,data,sizeof(struct package));
        if(ret == -1)
            break;

        std::cout << "本次数据包的大小:" << data->DataSize << std::endl;

        // 重新为结构体分配空间
        data = (struct package*)realloc(data,sizeof(struct package)+data->DataSize);

        // 获取第二次数据
        ret = read(newSock,data->Datas,data->DataSize);
        if(ret == -1 || std::string((char *)data->Datas) == "exit")
            break;

        std::cout << "来自服务端的消息<" << inet_ntoa(client.sin_addr) << ">" << data->Datas << std::endl;

        free(data);
    }

    // 关闭套接字
    close(sock_fd);
    return 0;
}

1.5UDP套接字编程

UDP 传输层的协议,面向无连接,数据报的传输层协议。

“ 无连接 ”:不可靠

  • 在网络环境较好的情况下, UDP 效率较高
  • 在网络环境较差的情况下, UDP 可能存在丢包的情况
  • 同时一些“ 实时应用 ” 采用 UDP
  • 在应用层加一些保证传输可靠的“ 控制协议 ”

1.5.1UDP Server(接收端) :

  • 创建一个套接字
  • 绑定网络地址
  • 数据通信
  • 关闭套接字

1.5.2UDP Client(发送端) :

  • 创建一个套接字
  • 关闭套接字

下面是有关UDP的C/S过程基础示例:

服务端:

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>


int main()
{
    int sock_id = socket(AF_INET,SOCK_DGRAM,0);

    if(sock_id == -1)
    {
        perror("套接字申请失败");
        return -1;
    }

    while(1)
    {
        char buf[100]={0};

        std::cout << "请输入发送的消息:";
        std::cin >> buf;

        // 指定目标地址
        struct sockaddr_in revcAddress;
        revcAddress.sin_family = AF_INET;
        revcAddress.sin_addr.s_addr = inet_addr("192.168.8.130");
        revcAddress.sin_port = 8899;
        if(sendto(sock_id,buf,strlen(buf),0,(struct sockaddr*)&revcAddress,sizeof(revcAddress)) == -1||std::string(buf) == "exit")
            break;
    }



    return 0;
}

客户端:

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>


int main()
{
    int sock_id = socket(AF_INET,SOCK_DGRAM,0);

    if(sock_id == -1)
    {
        perror("套接字申请失败");
        return -1;
    }

    // 进行地址绑定,绑定了你才可以接收消息
    struct sockaddr_in local;
    local.sin_family = AF_INET;
    local.sin_port   = 8899;
    local.sin_addr.s_addr = inet_addr("192.168.8.130");
    if(bind(sock_id,(struct sockaddr*)&local,sizeof(local)) == -1)
    {
        perror("套接字申请失败");
        close(sock_id);
        return -1;
    }


    while(1)
    {
        char buf[100]={0};


        // 指定目标地址
        struct sockaddr_in sendAddress;
        socklen_t size = sizeof(sendAddress);
        if(recvfrom(sock_id,buf,1000,0,(struct sockaddr*)&sendAddress,&size) == -1||std::string(buf) == "exit")
            break;

        std::cout << "来自于<"<< inet_ntoa(sendAddress.sin_addr) <<"> : " << buf << std::endl;
    }



    return 0;
}

2、设置套接字

每一个套接字都有一些不同行为和属性:

  • 如:每个套接字在内核中,都会有两个缓冲区
    • send buffer
    • recv buffer
  • 每个缓冲区的大小是多少?
  • 缓冲区可以设置其大小的。

2.1获取套接字选项

2.2设置套接字选项

2.3套接字选项表

  • 14
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值