欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  网络运营

网络编程实战(TCP流式协议报文读取)

程序员文章站 2022-06-10 10:26:38
TCP是流式协议发送端:如调用了send函数发送了hello和world报文,实际的发送情况有很多种,可能两个报文在相同的分组,也可能在不同的分组。不应该觉得数据流和TCP分组一一对应,因为发送缓冲区的存在。接收端:●hello 和 world 的顺序肯定会保持,这个是TCP严格保证的。●如果有TCP分组丢失,后续分组到达会被缓存,直到丢失的分组到达。最终形成可以被读取的数据流。网络字节排序大端字节序和小端字节序:●大端:将数据的高字节放在起始位置●小端:将数据的低字节放在起始位置转换函数...

TCP是流式协议

发送端:如调用了send函数发送了hello和world报文,实际的发送情况有很多种,可能两个报文在相同的分组,也可能在不同的分组。不应该觉得数据流和TCP分组一一对应,因为发送缓冲区的存在。

接收端
●hello 和 world 的顺序肯定会保持,这个是TCP严格保证的。

●如果有TCP分组丢失,后续分组到达会被缓存,直到丢失的分组到达。最终形成可以被读取的数据流。

网络字节排序

大端字节序和小端字节序
●大端:将数据的高字节放在起始位置
●小端:将数据的低字节放在起始位置

转换函数
n 代表的就是 network,h 代表的是 host,s 表示的是 short,l 表示的是 long,分别表示 16 位和 32 位的整数。

uint16_t htons (uint16_t hostshort) uint16_t ntohs (uint16_t netshort) uint32_t htonl (uint32_t hostlong) uint32_t ntohl (uint32_t netlong) 

报文读取和解析

方法1:将报文长度通过报文告知接收端

网络编程实战(TCP流式协议报文读取)
发送端:将消息长度 + 消息类型 + 正文 发送给接收端

#include "../lib/common.h" int main(int argc, char **argv){ if(argc != 2){ error(1, 0, "usage wrong"); } int socket_fd = socket(AF_INET, SOCK_STREAM, 0); struct sockaddr_in server_addr; bzero(&server_addr, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_port = htons(SERV_PORT); inet_pton(AF_INET, argv[1], &server_addr.sin_addr); int connect_rt = connect(socket_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)); if(connect_rt < 0) error(1, errno, "connect failed "); //报文结构体 struct{ u_int32_t message_length; u_int32_t message_type; char buf[128]; }message; int n; //从输入读取数据 while(fgets(message.buf, sizeof(message.buf), stdin) != NULL){ n = strlen(message.buf); message.message_length = htonl(n); message.message_type = 1; if(send(socket_fd, (char *)&message, sizeof(message.message_length) + sizeof(message.message_type) + n, 0) < 0) error(1, errno, "send failure"); } exit(0); } 

接收端:

#include "../lib/common.h" static int count; static void sig_int(int signo){ printf("\nreceived %d datagrams\n", count); exit(0); } //从fd中读取length长度的数据到buffer size_t readn(int fd, void *buffer, size_t length){ size_t count; ssize_t nread; char *ptr = buffer; count = length; while(count > 0){ nread = read(fd, ptr, count); if(nread < 0){ if(errno == EINTR) continue; else return -1; } else if(nread == 0) break; count -= nread; ptr += nread; } return length - count; } //调用readn读取固定大小的数据,读取报文的三个部分,并将真正的内容放入buf size_t read_message(int fd, char *buffer, size_t length){ u_int32_t msg_length; u_int32_t msg_type; int rc; rc = readn(fd, (char *)&msg_length, sizeof(u_int32_t)); if(rc != sizeof(u_int32_t)) return rc < 0 ? -1 : 0; //注意要转变顺序 msg_length = ntohl(msg_length); rc = readn(fd, (char *)&msg_type, sizeof(u_int32_t)); if(rc != sizeof(u_int32_t)) return rc < 0 ? -1 : 0; //大于缓冲区的大小 if(msg_length > length) return -1; rc = readn(fd, buffer, msg_length); if (rc != msg_length) return rc < 0 ? -1 : 0; return rc; return rc; } int main(int argc, char **argv){ //申请listenfd int listenfd = socket(AF_INET, SOCK_STREAM, 0); //初始化server_addr struct sockaddr_in server_addr; bzero(&server_addr, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = htonl(INADDR_ANY); server_addr.sin_port = htons(SERV_PORT); //设置reuseaddr int on = 1; setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); //bind int rt = bind(listenfd, (struct sockaddr *)&server_addr, sizeof(server_addr)); if(rt < 0) error(1, errno, "bind failed "); rt = listen(listenfd, LISTENQ); if (rt < 0) error(1, errno, "listen failed "); signal(SIGPIPE, SIG_IGN); //accept两个返回值 //获取1.客户端的client_addr以及client_len 2.服务端的connfd int connfd; struct sockaddr_in client_addr; socklen_t client_len = sizeof(client_addr); if((connfd = accept(listenfd, (struct sockaddr*)&client_addr, &client_len)) < 0) error(1, errno, "bind failed "); char buf[128]; count = 0; while(1){ int n = read_message(connfd, buf, sizeof(buf)); if(n < 0){ error(1, errno, "error read message"); } else if(n == 0) error(1, 0, "client closed \n"); buf[n] = 0; printf("received %d bytes: %s\n", n, buf); count++; } exit(0); } 

方法2:特殊字符作为边界
HTTP:通过设置回车符、换行符作为HTTP报文协议的边界网络编程实战(TCP流式协议报文读取)
读取:下面的 read_line 函数就是在尝试读取一行数据,也就是读到回车符\r,或者读到回车换行符\r\n为止。这个函数每次尝试读取一个字节,第 9 行如果读到了回车符\r,接下来在 11 行的“观察”下看有没有换行符,如果有就在第 12 行读取这个换行符;如果没有读到回车符,就在第 16-17 行将字符放到缓冲区,并移动指针。

int read_line(int fd, char *buf, int size) { int i = 0; char c = '\0'; int n; //到结尾或者遇到\n结束循环 while ((i < size - 1) && (c != '\n')) { n = recv(fd, &c, 1, 0); if (n > 0) { //遇到\r if (c == '\r') { //MSG_PEEK读取但不移除,下一个读取的还是这个字符 n = recv(fd, &c, 1, MSG_PEEK); //如果是\n就读出\n if ((n > 0) && (c == '\n')) recv(fd, &c, 1, 0); //只有\r,即windows中,将c变为\n用来结束循环 else c = '\n'; } buf[i] = c; i++; } else c = '\n'; } //结尾填入\0 buf[i] = '\0'; return (i); } 

本文地址:https://blog.csdn.net/qq_36459662/article/details/107916640

相关标签: 网络编程实战