基于TCP传输层协议的Socket套接字编程基本流程

Socket套接字

1:Socket 是一个编程接口( 网络编程接口 ),是一种特殊的 文件描述符 write/read )。 Socket 并不仅限于 TCP/IP。
2:Socket 独立于具体协议的编程接口,这个接口位于 TCP/IP 四层模型的应用层与传输层 之间

Socket的类型

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

 

基于TCP套件字编程基本要求

1:任何网络应用都会有通信双方:
Send 发送端
recv 接收端
2:TCP 网络应用
Client 客户端( TCP
Server 服务端( TCP
3:任何的网络应用
传输层的协议( TCP/UDP + 端口 + IP 地址
4:网络地址
任何网络应用任意一方都需要有一个 网络地址 IP+ 端口

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

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

TCP网络应用的编程流程

TCP-Server服务端

建立一个套件字
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>
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被设置
*/
绑定一个网络地址
并不是任意的地址都可以(需要合法且能够正常访问)
把一个套接字和一个网络地址进行绑定。如果想让其他人来主动联系 / 连接,就需要绑定一个
地址,并且需要把这个地址告诉其他人。不进行绑定,并代表套接字没有地址,不进行绑定套
接字在进行通信时候,内核会动态为套接字指定一个地址
SYNOPSIS
#include <sys/types.h> /* See NOTES */
#include <sys/socket.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
*/
等待监听
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
*/
等待客户端的连接
等待客户端来发起连接和客户端建立 TCP 连接 三次握手
多次调用函数就可以与不同的客户端进行连接
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被设置。
*/
数据的传输
发送数据: write/send/sendto
接收数据: read/recv/recvfrom
#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,同时...
*/
关闭套接字
#include <sys/socket.h>
int shutdown(int sockfd, int how);
/*
作用:
关闭一个套接字
@sockfd:
需要关闭操作的套接字描述符
@how:
关闭方式:
SHUT_RD 关闭读
SHUT_WR 关闭写

TCP-Client客户端

建立一个套接字socket

有Server服务端一样

绑定地址

可选/可以绑定也可也不绑定

发起连接请求
主动的与 TCP - Server 建立连接。三次握手
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

举一个代码示例:

Server服务端
#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;
}
Client客户端
#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;
}

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值