• TCP 通信流程详解(附有案例代码)


    一、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通信流程图

     三、套接字函数

    1. #include
    2. #include
    3. #include // 包含了这个头文件,上面两个就可以省略
    4. int socket(int domain, int type, int protocol);
    5. - 功能:创建一个套接字
    6. - 参数:
    7. - domain: 协议族
    8. AF_INET : ipv4
    9. AF_INET6 : ipv6
    10. AF_UNIX, AF_LOCAL : 本地套接字通信(进程间通信)
    11. - type: 通信过程中使用的协议类型
    12. SOCK_STREAM : 流式协议
    13. SOCK_DGRAM : 报式协议
    14. - protocol : 具体的一个协议。一般写0
    15. - SOCK_STREAM : 流式协议默认使用 TCP
    16. - SOCK_DGRAM : 报式协议默认使用 UDP
    17. - 返回值:
    18. - 成功:返回文件描述符,操作的就是内核缓冲区。
    19. - 失败:-1
    20. int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); // socket命 名
    21. - 功能:绑定,将fd 和本地的IP + 端口进行绑定
    22. - 参数:
    23. - sockfd : 通过socket函数得到的文件描述符
    24. - addr : 需要绑定的socket地址,这个地址封装了ip和端口号的信息 - addrlen : 第二个参数结构体
    25. 占的内存大小
    26. int listen(int sockfd, int backlog); // /proc/sys/net/core/somaxconn
    27. - 功能:监听这个socket上的连接
    28. - 参数:
    29. - sockfd : 通过socket()函数得到的文件描述符
    30. - backlog : 未连接的和已经连接的和的最大值, 5
    31. int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
    32. - 功能:接收客户端连接,默认是一个阻塞的函数,阻塞等待客户端连接
    33. - 参数:
    34. - sockfd : 用于监听的文件描述符
    35. - addr : 传出参数,记录了连接成功后客户端的地址信息(ip,port)
    36. - addrlen : 指定第二个参数的对应的内存大小 - 返回值:- 成功 :用于通信的文件描述符 - -1 : 失败
    37. int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    38. - 功能: 客户端连接服务器
    39. - 参数:
    40. - sockfd : 用于通信的文件描述符
    41. - addr : 客户端要连接的服务器的地址信息
    42. - addrlen : 第二个参数的内存大小
    43. - 返回值:
    44. 成功 0
    45. 失败 -1
    46. ssize_t write(int fd, const void *buf, size_t count); // 写数据
    47. ssize_t read(int fd, void *buf, size_t count); // 读数据

    四、TCP通信案例

    1、服务器端

    1. // TCP 通信的服务器端
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. int main() {
    8. // 1.创建socket(用于监听的套接字)
    9. int lfd = socket(AF_INET, SOCK_STREAM, 0);
    10. if(lfd == -1) {
    11. perror("socket");
    12. exit(-1);
    13. }
    14. // 2.绑定(IP地址和端口)
    15. struct sockaddr_in saddr;
    16. saddr.sin_family = AF_INET;
    17. // inet_pton(AF_INET, "192.168.193.128", saddr.sin_addr.s_addr); //指定具体网卡
    18. saddr.sin_addr.s_addr = INADDR_ANY; // 0.0.0.0 同时绑定多个网卡,使用哪个都可以
    19. saddr.sin_port = htons(9999);
    20. int ret = bind(lfd, (struct sockaddr *)&saddr, sizeof(saddr));
    21. if(ret == -1) {
    22. perror("bind");
    23. exit(-1);
    24. }
    25. // 3.监听
    26. ret = listen(lfd, 8);
    27. if(ret == -1) {
    28. perror("listen");
    29. exit(-1);
    30. }
    31. // 4.接收客户端连接
    32. struct sockaddr_in clientaddr;
    33. int len = sizeof(clientaddr);
    34. int cfd = accept(lfd, (struct sockaddr *)&clientaddr, &len);
    35. if(cfd == -1) {
    36. perror("accept");
    37. exit(-1);
    38. }
    39. // 输出客户端的信息
    40. char clientIP[16];
    41. inet_ntop(AF_INET, &clientaddr.sin_addr.s_addr, clientIP, sizeof(clientIP));
    42. unsigned short clientPort = ntohs(clientaddr.sin_port);
    43. printf("client ip is %s, port is %d\n", clientIP, clientPort);
    44. // 5.通信
    45. char recvBuf[1024] = {0};
    46. while(1) {
    47. // 获取客户端的数据
    48. int num = read(cfd, recvBuf, sizeof(recvBuf));
    49. if(num == -1) {
    50. perror("read");
    51. exit(-1);
    52. } else if(num > 0) {
    53. printf("recv client data : %s\n", recvBuf);
    54. } else if(num == 0) {
    55. // 表示客户端断开连接
    56. printf("clinet closed...");
    57. break;
    58. }
    59. char * data = "hello,i am server";
    60. // 给客户端发送数据
    61. write(cfd, data, strlen(data));
    62. }
    63. // 关闭文件描述符
    64. close(cfd);
    65. close(lfd);
    66. return 0;
    67. }

    2、客户端

    1. // TCP通信的客户端
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. int main() {
    8. // 1.创建套接字
    9. int fd = socket(AF_INET, SOCK_STREAM, 0);
    10. if(fd == -1) {
    11. perror("socket");
    12. exit(-1);
    13. }
    14. // 2.连接服务器端
    15. struct sockaddr_in serveraddr;
    16. serveraddr.sin_family = AF_INET;
    17. inet_pton(AF_INET, "192.168.193.128", &serveraddr.sin_addr.s_addr);
    18. serveraddr.sin_port = htons(9999);
    19. int ret = connect(fd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));
    20. if(ret == -1) {
    21. perror("connect");
    22. exit(-1);
    23. }
    24. // 3. 通信
    25. char recvBuf[1024] = {0};
    26. while(1) {
    27. char * data = "hello,i am client";
    28. // 给客户端发送数据
    29. write(fd, data , strlen(data));
    30. sleep(1);
    31. int len = read(fd, recvBuf, sizeof(recvBuf));
    32. if(len == -1) {
    33. perror("read");
    34. exit(-1);
    35. } else if(len > 0) {
    36. printf("recv server data : %s\n", recvBuf);
    37. } else if(len == 0) {
    38. // 表示服务器端断开连接
    39. printf("server closed...");
    40. break;
    41. }
    42. }
    43. // 关闭连接
    44. close(fd);
    45. return 0;
    46. }

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

  • 相关阅读:
    vsCode 格式化配置
    Java学习笔记_No.1
    K8s: 在Pod中使用亲和性调度节点
    HTML期末学生大作业-使用HTML+CSS技术仿传智博客网站
    WebDAV之葫芦儿·派盘+FV管理器
    基于JavaWeb的大学社团管理系统的设计与实现
    Android 日期筛选器
    Pytest 的高级用法之插件开发
    【Unity每日一记】资源加载相关和检测相关
    Web版RSS阅读器yarr
  • 原文地址:https://blog.csdn.net/weixin_47156401/article/details/125884879