一、TCP、UDP间的关系

TCP 和 UDP -> 传输层的协议:

UDP:用户数据报协议,面向无连接,可以单播,多播,广播, 面向数据报,不可靠

TCP:传输控制协议,面向连接的,可靠的,基于字节流,仅支持单播传输

                                                UDP                                                 TCP

是否创建连接                         无连接                                           面向连接

是否可靠                                不可靠                                             可靠的

连接的对象个数       一对一、一对多、多对一、多对多            支持一对一

传输的方式                         面向数据报                                     面向字节流

首部开销                               8个字节                                      最少20个字节

适用场景                 实时应用(视频会议,直播)         可靠性高的应用(文件传输)

二、TCP通信的流程

1、服务器端(被动接收连接的角色) 

(1)创建一个用于监听的套接字

        - 监听:监听有客户端的连接

        - 套接字:这个套接字其实就是一个文件描述符

(2)将这个监听文件描述符和本地的IP和端口绑定(IP和端口就是服务器的地址信息)

        - 客户端连接服务器的时候使用的就是这个IP和端口

(3)设置监听,监听的fd开始工作

(4) 阻塞等待,当有客户端发起连接,解除阻塞,接受客户端的连接,会得到一个和客户端通信的套接字(fd)

(5)通信

        - 接收数据

        - 发送数据

(6)通信结束,断开连接 

2、客户端

(1)创建一个用于通信的套接字(fd)

(2)连接服务器,需要指定连接的服务器的 IP 和 端口

(3)连接成功了,客户端可以直接和服务器通信

        - 接收数据

        - 发送数据

(4) 通信结束,断开连接

3、TCP通信流程图

 三、套接字函数

#include <sys/types.h> 
#include <sys/socket.h> 
#include <arpa/inet.h> // 包含了这个头文件,上面两个就可以省略 
int socket(int domain, int type, int protocol); 
- 功能:创建一个套接字 
- 参数:
    - domain: 协议族
        AF_INET : ipv4 
        AF_INET6 : ipv6 
        AF_UNIX, AF_LOCAL : 本地套接字通信(进程间通信) 
    - type: 通信过程中使用的协议类型 
        SOCK_STREAM : 流式协议 
        SOCK_DGRAM : 报式协议 
    - protocol : 具体的一个协议。一般写0 
        - SOCK_STREAM : 流式协议默认使用 TCP 
        - SOCK_DGRAM : 报式协议默认使用 UDP 
- 返回值: 
    - 成功:返回文件描述符,操作的就是内核缓冲区。 
    - 失败:-1 

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); // socket命 名 
- 功能:绑定,将fd 和本地的IP + 端口进行绑定 
- 参数: 
    - sockfd : 通过socket函数得到的文件描述符 
    - addr : 需要绑定的socket地址,这个地址封装了ip和端口号的信息 - addrlen : 第二个参数结构体        
占的内存大小 

int listen(int sockfd, int backlog); // /proc/sys/net/core/somaxconn 
- 功能:监听这个socket上的连接 
- 参数:
    - sockfd : 通过socket()函数得到的文件描述符 
    - backlog : 未连接的和已经连接的和的最大值, 5 

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); 
- 功能:接收客户端连接,默认是一个阻塞的函数,阻塞等待客户端连接 
- 参数: 
    - sockfd : 用于监听的文件描述符 
    - addr : 传出参数,记录了连接成功后客户端的地址信息(ip,port) 
    - addrlen : 指定第二个参数的对应的内存大小 - 返回值:- 成功 :用于通信的文件描述符 - -1 : 失败 

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
- 功能: 客户端连接服务器 
- 参数: 
    - sockfd : 用于通信的文件描述符 
    - addr : 客户端要连接的服务器的地址信息 
    - addrlen : 第二个参数的内存大小 
- 返回值:
    成功 0, 
    失败 -1 

ssize_t write(int fd, const void *buf, size_t count); // 写数据 
ssize_t read(int fd, void *buf, size_t count); // 读数据

四、TCP通信案例

1、服务器端

// TCP 通信的服务器端

#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

int main() {

    // 1.创建socket(用于监听的套接字)
    int lfd = socket(AF_INET, SOCK_STREAM, 0);

    if(lfd == -1) {
        perror("socket");
        exit(-1);
    }

    // 2.绑定(IP地址和端口)
    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;
    // inet_pton(AF_INET, "192.168.193.128", saddr.sin_addr.s_addr);    //指定具体网卡
    saddr.sin_addr.s_addr = INADDR_ANY;  // 0.0.0.0 同时绑定多个网卡,使用哪个都可以
    saddr.sin_port = htons(9999);
    int ret = bind(lfd, (struct sockaddr *)&saddr, sizeof(saddr));

    if(ret == -1) {
        perror("bind");
        exit(-1);
    }

    // 3.监听
    ret = listen(lfd, 8);
    if(ret == -1) {
        perror("listen");
        exit(-1);
    }

    // 4.接收客户端连接
    struct sockaddr_in clientaddr;
    int len = sizeof(clientaddr);
    int cfd = accept(lfd, (struct sockaddr *)&clientaddr, &len);
    
    if(cfd == -1) {
        perror("accept");
        exit(-1);
    }

    // 输出客户端的信息
    char clientIP[16];
    inet_ntop(AF_INET, &clientaddr.sin_addr.s_addr, clientIP, sizeof(clientIP));
    unsigned short clientPort = ntohs(clientaddr.sin_port);
    printf("client ip is %s, port is %d\n", clientIP, clientPort);

    // 5.通信
    char recvBuf[1024] = {0};
    while(1) {
        
        // 获取客户端的数据
        int num = read(cfd, recvBuf, sizeof(recvBuf));
        if(num == -1) {
            perror("read");
            exit(-1);
        } else if(num > 0) {
            printf("recv client data : %s\n", recvBuf);
        } else if(num == 0) {
            // 表示客户端断开连接
            printf("clinet closed...");
            break;
        }

        char * data = "hello,i am server";
        // 给客户端发送数据
        write(cfd, data, strlen(data));
    }
   
    // 关闭文件描述符
    close(cfd);
    close(lfd);

    return 0;
}

2、客户端

// TCP通信的客户端

#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

int main() {

    // 1.创建套接字
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if(fd == -1) {
        perror("socket");
        exit(-1);
    }

    // 2.连接服务器端
    struct sockaddr_in serveraddr;
    serveraddr.sin_family = AF_INET;
    inet_pton(AF_INET, "192.168.193.128", &serveraddr.sin_addr.s_addr);
    serveraddr.sin_port = htons(9999);
    int ret = connect(fd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));

    if(ret == -1) {
        perror("connect");
        exit(-1);
    }

    
    // 3. 通信
    char recvBuf[1024] = {0};
    while(1) {

        char * data = "hello,i am client";
        // 给客户端发送数据
        write(fd, data , strlen(data));

        sleep(1);
        
        int len = read(fd, recvBuf, sizeof(recvBuf));
        if(len == -1) {
            perror("read");
            exit(-1);
        } else if(len > 0) {
            printf("recv server data : %s\n", recvBuf);
        } else if(len == 0) {
            // 表示服务器端断开连接
            printf("server closed...");
            break;
        }

    }

    // 关闭连接
    close(fd);

    return 0;
}

说明:在两个函数编译好后,在会话窗口1,先执行服务器端函数,他会阻塞等待;复制会话在新会话窗口执行客户端,开始通信; 

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐