TCP(TCP客户端、服务器如何通信)

打印 上一主题 下一主题

主题 871|帖子 871|积分 2613

一、TCP先容

TCP的特点:


  • 面向连接的协议:TCP是一种可靠的、面向连接的协议,在通信之前需要创建连接,以确保数据的可靠传输。这意味着在传输数据之前,发送方和吸收方之间需要创建一条可靠的连接通道。
  • 流式协议:TCP将数据看作是连续串的字节流,而不是独立的数据包。这意味着在传输过程中,数据可以被分割成多个数据包举行传输,并在吸收端举行重新组装。
  • 可靠传输:TCP通过使用校验和、序列号和确认应答等机制,确保数据的可靠传输。如果在传输过程中发生错误或丢失,TCP会举行重传,以确保数据的完整性和正确性。
  • 出错重传:当TCP吸收到错误的数据包时,它会要求发送方重新发送该数据包,以确保数据的正确性。
  • 确认应答:TCP使用确认应答机制来确保数据的可靠传输。当吸收方收到一个数据包时,它会向发送方发送一个确认应答,表现已经收到了该数据包。
  • 服务器被动连接,客户端自动连接:在TCP连接中,服务器通常处于被动状态,等待客户端的连接请求。而客户端则处于自动状态,负责发起连接请求。一旦连接创建成功,双方就可以举行数据传输。  
TCP与UDP的差异


TCP与UDP流程对比 


TCP编程流程 

服务器端流程:


  • 创建套接字(socket):使用socket()函数创建一个套接字,指定使用的协议族(如IPv4或IPv6)和socket范例(如流式socket)。
  • 绑定套接字(bind):使用bind()函数将套接字与服务器的网络信息(如IP地点和端标语)举行绑定。
  • 监听套接字(listen):使用listen()函数将套接字设置为监听状态,准备吸收客户端的连接请求。
  • 接受连接(accept):使用accept()函数阻塞等待客户端的连接请求,并返回一个新的套接字用于与该客户端举行通信。
  • 举行通信(recv/send):使用recv()和send()函数与客户端举行数据的吸收和发送。
  • 关闭套接字(close):通信完成后,使用close()函数关闭套接字,释放资源。
客户端流程:


  • 创建套接字(socket):与服务器端相同,使用socket()函数创建一个套接字。
  • 连接服务器(connect):使用connect()函数向服务器发起连接请求,指定服务器的IP地点和端标语。
  • 举行通信(send/recv):连接创建后,使用send()和recv()函数与服务器举行数据的发送和吸收。
  • 关闭套接字(close):通信完成后,使用close()函数关闭套接字,释放资源。
 二、TCP编程-socket

  1. #include <sys/types.h>
  2. #include <sys/socket.h>
  3. int socket(int domain, int type, int protocol);
  4. 功能:
  5.     创建一个套接字,返回一个文件描述符
  6. 参数:
  7.     domain:通信域,协议族
  8.       AF_UNIX 本地通信
  9.       AF_INET ipv4网络协议
  10.       AF_INET6 ipv6网络协议
  11.       AF_PACKET 底层接口
  12.     type:套接字的类型
  13.       SOCK_STREAM 流式套接字(tcp)
  14.       SOCK_DGRAM 数据报套接字(udp)
  15.       SOCK_RAW 原始套接字(用于链路层)
  16.     protocol:附加协议,如果不需要,则设置为0
  17. 返回值:
  18.     成功:文件描述符
  19.     失败:‐1
复制代码
 案例
  1. #include <stdio.h>
  2. #include <sys/types.h>
  3. #include <sys/socket.h>
  4. #include <stdlib.h>
  5. int main(int argc, char const *argv[])
  6. {
  7.     //通过socket函数创建一个TCP套接字
  8.     int sockfd;
  9.     if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
  10.     {
  11.         perror("fail to socket");
  12.         exit(1);
  13.     }
  14.     printf("sockfd = %d\n", sockfd);
  15.     return 0;
  16. }
复制代码
  这段代码演示了如何使用C语言创建一个TCP套接字。在代码中,首先通过socket()函数创建了一个套接字,指定了协议族为AF_INET(IPv4),套接字范例为SOCK_STREAM(TCP)。如果创建失败,则会输出错误信息并退出程序。如果创建成功,则会输出套接字的文件形貌符。最后,程序返回0表现正常退出。
   执行结果

 三、TCP客户端-connect、send、recv

3.1 connect函数

  1. #include <sys/types.h> /* See NOTES */
  2. #include <sys/socket.h>
  3. int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
  4. 功能:
  5.     给服务器发送客户端的连接请求
  6. 参数:
  7.     sockfd:文件描述符,socket函数的返回值
  8.     addr:要连接的服务器的网络信息结构体(需要自己设置)
  9.     addrlen:add的长度
  10. 返回值:
  11.     成功:0
  12.     失败:-1
复制代码
留意:

  • connect创建连接之后不会产生新的套接字
  • 连接成功后才可以开始传输TCP数据
  • 头文件:#include 
3.2 send函数

  1. #include <sys/types.h>
  2. #include <sys/socket.h>
  3. ssize_t send(int sockfd, const void *buf, size_t len, int flags);
  4. 功能:
  5.     发送数据
  6. 参数:
  7.     sockfd:文件描述符
  8. 客户端:
  9.     socket函数的返回值
  10. 服务器:
  11.     accept函数的返回值
  12.      buf:发送的数据
  13.      len:buf的长度
  14.      flags:标志位
  15.        0 阻塞
  16.        MSG_DONTWAIT 非阻塞
  17. 返回值:
  18.     成功:发送的字节数
  19.     失败:-1
复制代码
留意不能用TCP协议发送0长度的数据包
3.3 recv函数

  1. #include <sys/types.h>
  2. #include <sys/socket.h>
  3. ssize_t recv(int sockfd, void *buf, size_t len, int flags);
  4. 功能:
  5.     接收数据
  6. 参数:
  7.     sockfd:文件描述符
  8. 客户端:socket函数的返回值
  9. 服务器:accept函数的返回值
  10.     buf:保存接收到的数据
  11.     len:buf的长度
  12.     flags:标志位
  13.       0 阻塞
  14.       MSG_DONTWAIT 非阻塞
  15. 返回值:
  16.     成功:接收的字节数
  17.     失败:-1
  18. 如果发送端关闭文件描述符或者关闭进程,则recv函数会返回0
复制代码
3.4 客户端code

使用windows下的网络调试助手作为服务器

客户端的程序 
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <unistd.h>
  4. #include <sys/types.h>
  5. #include <sys/socket.h>
  6. #include <arpa/inet.h>
  7. #include <netinet/in.h>
  8. #include <string.h>
  9. #define N 128
  10. int main(int argc, char const *argv[])
  11. {
  12.     if(argc < 3)
  13.     {
  14.         fprintf(stderr, "Usage: %s [ip] [port]\n", argv[0]);
  15.         exit(1);
  16.     }
  17.     //第一步:创建套接字
  18.     int sockfd;
  19.     if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
  20.     {
  21.         perror("fail to socket");
  22.         exit(1);
  23.     }
  24.     //printf("sockfd = %d\n", sockfd);
  25.     //第二步:发送客户端连接请求
  26.     struct sockaddr_in serveraddr;
  27.     socklen_t addrlen = sizeof(serveraddr);
  28.     serveraddr.sin_family = AF_INET;
  29.     serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
  30.     serveraddr.sin_port = htons(atoi(argv[2]));
  31.     if(connect(sockfd, (struct sockaddr *)&serveraddr, addrlen) == -1)
  32.     {
  33.         perror("fail to connect");
  34.         exit(1);
  35.     }
  36.     //第三步:进行通信
  37.     //发送数据
  38.     char buf[N] = "";
  39.     fgets(buf, N, stdin);
  40.     buf[strlen(buf) - 1] = '\0';
  41.     if(send(sockfd, buf, N, 0) == -1)
  42.     {
  43.         perror("fail to send");
  44.         exit(1);
  45.     }
  46.     //接收数据
  47.     char text[N] = "";
  48.     if(recv(sockfd, text, N, 0) == -1)
  49.     {
  50.         perror("fail to recv");
  51.         exit(1);
  52.     }
  53.     printf("from server: %s\n", text);
  54.     //第四步:关闭套接字文件描述符
  55.     close(sockfd);
  56.     return 0;
  57. }
复制代码
  这段代码是一个简单的TCP客户端程序。它吸收两个命令行参数:服务器的IP地点和端标语。然后,程序会执行以下步调:
  

  • 创建一个TCP套接字。
  • 使用connect()函数向服务器发送连接请求。
  • 从尺度输入读取一行数据,并将其发送给服务器。
  • 吸收服务器返回的数据,并将其打印到尺度输出。
  • 关闭套接字文件形貌符。
   执行结果
 

四、TCP服务器-bind、listen、accept 

4.1 做为TCP服务器需要具备的条件

1、具备一个可以确知的地点
2、让操作系统知道是一个服务器,而不是客户端
3、等待连接的到来 对于面向连接的TCP协议来说,连接的创建才真正意味着数据通信的开始
4.2 bind函数

  1. #include <sys/types.h>
  2. #include <sys/socket.h>
  3. int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
  4. 功能:
  5.     将套接字与网络信息结构体绑定
  6. 参数:
  7.     sockfd:文件描述符,socket的返回值
  8.     addr:网络信息结构体
  9.       通用结构体(一般不用)
  10.       struct sockaddr
  11.       网络信息结构体 sockaddr_in
  12. #include <netinet/in.h>
  13. struct sockaddr_in
  14. addrlen:
  15.     addr的长度
  16. 返回值:
  17.     成功:0
  18.     失败:-1
复制代码
 简单写个例子
  1. #include <stdio.h>
  2. #include <sys/types.h>
  3. #include <sys/socket.h>
  4. #include <netinet/in.h>
  5. int main() {
  6.     // 创建一个流式套接字
  7.     int sockfd = socket(AF_INET, SOCK_STREAM, 0);
  8.     if (sockfd == -1) {
  9.         perror("socket");
  10.         return -1;
  11.     }
  12.     // 初始化网络信息结构体
  13.     struct sockaddr_in addr;
  14.     addr.sin_family = AF_INET;
  15.     addr.sin_port = htons(8080); // 绑定到8080端口
  16.     addr.sin_addr.s_addr = htonl(INADDR_ANY); // 绑定到任意IP地址
  17.     // 将套接字与网络信息结构体绑定
  18.     socklen_t addrlen = sizeof(addr);
  19.     if (bind(sockfd, (struct sockaddr *)&addr, addrlen) == -1) {
  20.         perror("bind");
  21.         return -1;
  22.     }
  23.     printf("Bind successful\n");
  24.     // 关闭套接字
  25.     close(sockfd);
  26.     return 0;
  27. }
复制代码
   在这个实例中,我们首先使用socket()函数
  1. #include <sys/types.h> /* See NOTES */
  2. #include <sys/socket.h>
  3. int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
  4. 功能:
  5.     阻塞等待客户端的连接请求
  6. 参数:
  7.     sockfd:文件描述符,socket函数的返回值
  8.     addr:接收到的客户端的信息结构体(自动填充,定义变量即可)
  9.     addrlen:addr的长度
  10. 返回值:
  11.     成功:新的文件描述符(只要有客户端连接,就会产生新的文件描述符,这个新的文件描述符专门与指定的客户端进行通信的)
  12.     失败:-1
复制代码
创建了一个流式套接字,然后初始化了一个sockaddr_in结构体,将套接字绑定到8080端口和任意IP地点。最后,我们使用bind()函数将套接字与网络信息结构体绑定,如果绑定成功,则会输出"Bind successful"。
   4.3 listen函数

  1. #include <sys/types.h> /* See NOTES */
  2. #include <sys/socket.h>
  3. int listen(int sockfd, int backlog);
  4. 功能:
  5.     将套接字设置为被动监听状态,这样做之后就可以接收到连接请求
  6. 参数:
  7.     sockfd:文件描述符,socket函数返回值
  8.     backlog:允许通信连接的主机个数,一般设置为5、10
  9. 返回值:
  10.     成功:0
  11.     失败:-1
复制代码
4.4 accept函数

  1. #include <sys/types.h> /* See NOTES */
  2. #include <sys/socket.h>
  3. int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
  4. 功能:
  5.     阻塞等待客户端的连接请求
  6. 参数:
  7.     sockfd:文件描述符,socket函数的返回值
  8.     addr:接收到的客户端的信息结构体(自动填充,定义变量即可)
  9.     addrlen:addr的长度
  10. 返回值:
  11.     成功:新的文件描述符(只要有客户端连接,就会产生新的文件描述符,这个新的文件描述符专门与指定的客户端进行通信的)
  12.     失败:-1
复制代码
 4.5 TCP服务器例子

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <unistd.h>
  4. #include <sys/types.h>
  5. #include <sys/socket.h>
  6. #include <arpa/inet.h>
  7. #include <netinet/in.h>
  8. #include <string.h>
  9. #define N 128
  10. int main(int argc, char const *argv[])
  11. {
  12.     if(argc < 3)
  13.     {
  14.         fprintf(stderr, "Usage: %s [ip] [port]\n", argv[0]);
  15.         exit(1);
  16.     }
  17.     //第一步:创建套接字
  18.     int sockfd;
  19.     if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
  20.     {
  21.         perror("fail to socket");
  22.         exit(1);
  23.     }
  24.     //第二步:将套接字与服务器网络信息结构体绑定
  25.     struct sockaddr_in serveraddr;
  26.     socklen_t addrlen = sizeof(serveraddr);
  27.     serveraddr.sin_family = AF_INET;
  28.     serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
  29.     serveraddr.sin_port = htons(atoi(argv[2]));
  30.     if(bind(sockfd, (struct sockaddr *)&serveraddr, addrlen) == -1)
  31.     {
  32.         perror("fail to bind");
  33.         exit(1);
  34.     }
  35.     //第三步:将套接字设置为被动监听状态
  36.     if(listen(sockfd, 10) == -1)
  37.     {
  38.         perror("fail to listen");
  39.         exit(1);
  40.     }
  41.     //第四步:阻塞等待客户端的链接请求
  42.     int acceptfd;
  43.     struct sockaddr_in clientaddr;
  44.     if((acceptfd = accept(sockfd, (struct sockaddr *)&clientaddr, &addrlen)) == -1)
  45.     {
  46.         perror("fail to accept");
  47.         exit(1);
  48.     }
  49.     //打印连接的客户端的信息
  50.     printf("ip:%s, port:%d\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
  51.     //第五步:进行通信
  52.     //tcp服务器与客户端通信时,需要使用accept函数的返回值
  53.     char buf[N] = "";
  54.     if(recv(acceptfd, buf, N, 0) == -1)
  55.     {
  56.         perror("fail to recv");
  57.     }
  58.     printf("from client: %s\n", buf);
  59.     strcat(buf, " *_*");
  60.     if(send(acceptfd, buf, N, 0) == -1)
  61.     {
  62.         perror("fail to send");
  63.         exit(1);
  64.     }
  65.     //关闭套接字文件描述符
  66.     close(acceptfd);
  67.     close(sockfd);
  68.     return 0;
  69. }
复制代码
 
   

  • 创建一个TCP套接字。
  • 将套接字与服务器的IP地点和端标语绑定。
  • 将套接字设置为被动监听状态,等待客户端的连接请求。
  • 阻塞等待客户端的连接请求,并接受连接。
  • 打印连接的客户端的IP地点和端标语。
  • 吸收客户端发送的数据,并将其打印出来。
  • 向客户端发送数据。
  • 关闭与客户端的连接,并关闭套接字。
   执行结果

4.6  close关闭套接字

   

  • 使用close函数即可关闭套接字 关闭一个代表已连接套接字将导致另一端吸收到一个0长度的数据包
  • 做服务器时 1>关闭监听套接字将导致服务器无法吸收新的连接,但不会影响已经创建的连接 2>关闭accept返回的已连接套接字将导致它所代表的连接被关闭,但不会影响服务器 的监听
  • 做客户端时 关闭连接就是关闭连接,不意味着其他
   

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

光之使者

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表