关于c语言的tcp通讯详细讲解

目录

1、TCP概览

1.1 TCP基本特征

1.2 TCP通信流程基本原理

2、TCP编程的函数接口说明

3、TCP通讯测试代码

1、TCP概览

TCP全称 Transmition Control Protocol,即:传输控制协议。是面向连接的协议。通常,TCP 通信还会被冠以 可靠传输协议 的头衔。但请注意,这里的可靠并非指发出去的数据对方一定能收到(这是不可能的),而仅指TCP能使发送方可靠地知道对方是否收到了数据。

1.1 TCP基本特征

  • 有连接:通信双方需要事先连接成功,方可传输数据
  • 有确认:一方收到对端的任何数据,都会给另一方发回执确认
  • 保证数据有序、不重复、丢失会重发
  • 如果网络拥堵,会自动调节发送量
  • 采用帧异步的流式通信方式(即通信双方每次的收发数据量不必相等)

简单来讲,TCP 类似于打电话,说话前需要花一定的时间接通电话,等到对方接听了之后双方才能开始通信,通信的过程中每个数据的传送,接收方都会给发送方回执确认,断开的时候也会互相通知以便于释放各自相关的资源。可以看出来,TCP 相对于 UDP 而言资源开销更大,提供更丰富的功能,TCP适合用在如下情形:

  • 传输质量要求较高,不能丢失数据
  • 大数据量的通信,以至于通信前后的连接和断开的开销可以忽略不计
  • 用户登录、账户管理等相关的功能

1.2 TCP通信流程基本原理

TCP的通信流程跟打电话是几乎一样的,因此可以将通信的过程细分为主动发起连接者(客户端)和被动接受连接者(服务端)两方来分别讨论。

被动的服务端

  1. 建立TCP套接字sockfd,即通信端点
  2. 绑定套接字sockfd与网络地址,即IP+端口
  3. 设定套接字sockfd进入被动监听状态,即将套接字设定为监听套接字
  4. 静静等待远程客户端的连接请求
  5. 收到连接请求后,得到一个专用于收发数据的连接套接字connfd
  6. 使用连接套接字connfd与客户端通信

主动的客户端

  1. 建立TCP套接字sockfd,即通信端点
  2. 对服务端发起连接请求
  3. 若连接成功,则直接通过套接字sockfd与服务端通信

注意点:

  • 在服务端中,监听套接字和连接套接字是严格区分的,不可混用
  • 服务端所绑定的地址(IP+PORT)需要对外公开,否则客户端无法发起连接
  • 客户端在发起连接前一般无需绑定地址,此时系统会为此连接自动分配恰当的地址资源

2、TCP编程的函数接口说明

TCP通讯的步骤如下:

客户端(client)

1、建立套接字(socket)

//2、绑定主机的IP地址和端口号(bind)(可以省略)

3、填充服务器的结构体,向服务器发起连接请求(connect)

4、聊天 -- 发送数据(send/write)

5、断开连接(close)

服务器(server)

1、建立套接字(socket)

2、填充服务器的结构体,绑定主机的IP地址和端口号(bind)

3、设置监听(listen)

4、阻塞等待接收客户端的连接(accept)新的套接字文件描述符

5、创建结构体存放客户端IP和端口,聊天 -- 接收数据 (recv/read)

6、断开连接(close)

函数接口说明如下:

1、建立套接字(socket)
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain,int type,int protocol);
函数作用:建立套接字,返回套接字文件描述符
函数参数:domain:你要选择哪一种地址族
        PF_INET/AF_INET  IPV4网络协议  PF ---> Protocol Family
        PF_INET6/AF_INET6 IPV6网络协议    AF ---> Address Family
        type:你要选择哪一种协议
        SOCK_STREAM选择TCP -- 流式套接字   --->Stream Sockets
        SOCK_DGRAM选择UDP -- 数据报套接字  --->Datagram Sockets
        protocol:传0表示使用默认协议
返回值:成功:套接字文件描述符sockfd
        失败:-1

2、绑定主机的IP地址和端口号(bind)
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd,const struct sockaddr* addr,socklen_t addrlen);
函数作用:绑定主机的IP地址和端口号
参数:sockfd:套接字文件描述符
     addr:自己的IP地址和端口号
     addelen:地址的大小长度
返回:成功

3、发起连接(connect)
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd,const struct sockaddr * addr,socklen_t addrlen);
函数作用:发起连接
函数参数:sockfd:套接字文件描述符
        addr:对方的IP地址和端口号
        addrlen:地址的长度(sizeof(struct sockaddr_in)))
IPV4结构体
struct sockaddr_in
{
    short int sin_family;
    unsigned short int sin_port;
    struct in_addr sin_addr;
};
struct in_addr
{
  in_addr_t s_addr;/in_addr_t为32位的unsigned int,该无符号整数采用大端字节序/  
};
初始化 IP地址和端口号 --IPV4
struct sockaddr_in sereverAddr;
serverAddr.sin_family = PF_INET;
serverAddr.sin_port = htons(5000);
serverAddr.sin_addr.s_addr = inet_addr("主机IP地址");

4、聊天 -- 发送数据(send)
#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
返回值:成功:发送的字节数
        失败:-1

5、断开连接(close)

#include <arpa/inet.h>
uint16_t htons(uint16_t hostshort);//将主机端口号转成网络端口号
uint16_t ntohs(uint16_t netshort);//将网络端口号转成主机端口号
说明:h代表主机(host) n代表网络(network) s代表端口号(short)
返回值:成功:要转换的字节序
        失败:-1
从什么(h,n)端口号到(to)什么(n,h)端口号(s)

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
in_addr_t inet_addr(const char *cp);    //将主机IP转成网络IP
char* inet_ntoa(struct in_addr in);    //将网络IP转成主机IP
主机转网络addr(address) 网络转主机ntoa(network to address)

tcp的服务端需要设置端口复用

端口复用设置在服务器的代码里面

#include <sys/types.h>

#include <sys/socket.h>

int setsockopt(int s,int level,int optname,const void* optval,socklen_toptlen);

函数作用:设置端口号可以复用

参数:s:套接字文件描述符

level:代表欲设置的网络层,一般设成SOL_SOCKET以存取socket层

optname:SO_REUSEADDR端口号复用

optval:代表欲设置的值,比如给他一个1,表示使能 端口号复用

optlen则为optval的长度 //所以设置端口号可以复用,这两条语句放在绑定bind之前

int optval = 1;

setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&optval,sizeof(optval));

3、TCP通讯测试代码

//tcp_server.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define SERVER_PORT 60000          //-->1024 ~ 65535
#define SERVER_IP "192.168.5.184"  //-->虚拟机ip或主机ip

/*
    TCP的服务端的代码实现步骤
    1、建立套接字
    2、绑定(IP和端口号)
    3、监听(listen)
    4、阻塞连接(accept)
    5、接收数据(recv,read)
    6、关闭
*/

int main(int argc,char** argv)
{
    //手动输入端口号和IP地址
    // if(argc != 3)
    // {
    //     perror("./a.out ip port");
    //     return -1;
    // }

    int ret = 0;
    char buf[1024] = { 0 };

    //1、建立套接字
    //参数:              地址族 流式套接字 默认协议
    int socketfd = socket(AF_INET,SOCK_STREAM,0);
    if(socketfd == -1)
    {
        perror("socket fail");
        return -1;
    }

    //设置端口复用
    int optval = 1;
    //参数  套接字文件描述符    网络层   端口复用    设置值   设置值的大小
    setsockopt(socketfd, SOL_SOCKET, SO_REUSEADDR,&optval, sizeof(optval));

    
    //2、填充服务端结构体的端口和IP
    struct sockaddr_in server_addr;
    server_addr.sin_family = AF_INET;
    //server_addr.sin_port = htons(atoi(argv[2]));              //传参方式
    server_addr.sin_port = htons(SERVER_PORT);                  //宏定义方式
    //server_addr.sin_addr.s_addr = inet_addr(argv[1]);         //传参方式
    server_addr.sin_addr.s_addr = inet_addr(SERVER_IP);         //宏定义方式

    //3、绑定
    //参数  套接字文件描述符 IP和端口号(旧结构体指针强转取地址)新结构体的大小
    ret = bind(socketfd,(struct sockaddr*)&server_addr,sizeof(struct sockaddr_in));
    if(ret == -1)
    {
        perror("bind fail");
        return -1;
    }

    //4、监听
    //参数 套接字文件描述符 支持的客户端连接最大数量
    ret = listen(socketfd,20);
    if(ret == -1)
    {
        perror("bind fail");
        return -1;
    }

    //5、阻塞等待客户端连接
    //printf("绑定服务器IP:%s 端口号PORT:%hu\n",argv[1],atoi(argv[2]));
    printf("绑定服务器IP:%s 端口号PORT:%hu\n",SERVER_IP,SERVER_PORT);

    struct sockaddr_in client_addr;
    int len = sizeof(struct sockaddr_in);
    //参数           套接字文件描述符 IP和端口号(旧结构体指针强转取地址)长度取址
    //返回新的套接字文件描述符
    int newClientfd = accept(socketfd,(struct sockaddr*)&client_addr,&len);

    //6、拿到客户端的地址和端口号
    char* ip = inet_ntoa(client_addr.sin_addr);
    int port = ntohs(client_addr.sin_port);
    printf("客户端的IP:%s 端口号:%d\n",ip,port);

    //7、接收数据
    while(1)
    {
        //缓存区清零
        bzero(buf,sizeof(buf));

        //接收数据,计算返回值(buf真实数据大小)
        //参数  套接字文件描述符 缓存区 缓存区大小 默认属性
        //使用返回的新套接字文件描述符
        //ret = recv(newClientfd,buf,sizeof(buf),0);
        ret = read(newClientfd,buf,sizeof(buf));
        if(ret == 0)
        {
            printf("客户端掉线,服务器退出。。。\n");
            return -1;
        }

        printf("收到数据:%s 大小:%d\n",buf,ret);

        //做主动退出的判断条件
        if(!strcmp(buf,"exit"))
            break;

    }

    //8、关闭套接字
    close(socketfd);
    close(newClientfd);

    return 0;
}

//tcp_client.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define SERVER_PORT 60000          //-->1024 ~ 65535
#define SERVER_IP "192.168.5.184"  //-->服务器的IP

/*
    TCP的客户端的代码实现步骤
    1、建立套接字
    2、绑定(IP和端口号)(可有可无)
    3、发送连接请求(connect)
    4、发送数据(send,write)
    5、关闭
*/

int main(int argc,char** argv)
{
    //手动输入端口号和IP地址
    // if(argc != 3)
    // {
    //     perror("./a.out ip port");
    //     return -1;
    // }

    int ret = 0;
    char buf[1024] = { 0 };

    //1、建立套接字
    //参数:              地址族 流式套接字 默认协议
    int socketfd = socket(AF_INET,SOCK_STREAM,0);
    if(socketfd == -1)
    {
        perror("socket fail");
        return -1;
    }

    //2、填充服务端结构体的端口和IP
    struct sockaddr_in server_addr;
    server_addr.sin_family = AF_INET;
    //server_addr.sin_port = htons(atoi(argv[2]));              //传参方式
    server_addr.sin_port = htons(SERVER_PORT);        //宏定义方式
    //server_addr.sin_addr.s_addr = inet_addr(argv[1]);   //传参方式
    server_addr.sin_addr.s_addr = inet_addr(SERVER_IP);   //宏定义方式

    //3、连接服务器
    //参数   套接字文件描述符  IP和端口号(旧结构体指针强转取地址)新结构体的大小
    ret = connect(socketfd,(struct sockaddr *)&server_addr,sizeof(struct sockaddr_in));
    if(ret < 0)
    {
        perror("connect fail");
        return -1;
    }
    printf("连接服务器成功[%s][%d]\n",SERVER_IP,SERVER_PORT);
    //printf("连接服务器IP:%s 端口号PORT:%hu\n",argv[1],atoi(argv[2]));

    //4、与服务器之间发送数据
    while(1)
    {
        //缓存区清零    
        bzero(buf,sizeof(buf));

        //发送数据,计算返回值(buf真实数据大小)
        scanf("%s",buf);

        //参数  套接字文件描述符 缓存区 缓存区真实大小 默认属性
        //ret = send(socketfd,buf,strlen(buf),0);
        ret = write(socketfd,buf,strlen(buf));
        if(ret  == -1)
        {
            printf("服务端掉线,客户端发送数据失败。。。\n");
            return -1;
        }

        printf("发送成功 ret:%d\n",ret);

        //做主动退出的判断条件
        if(!strcmp(buf,"exit"))
            break;
    }

    //关闭套接字
    close(socketfd);

    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值