网站怎么做会让神马搜索到,旺店通app手机企业版下载,怎么做私人彩票网站,河南省建设网站TCP网络通讯
TCP编程流程 接口介绍 socket()方法是用来创建一个套接字#xff0c;有了套接字就可以通过网络进行数据的收发。创建套接字时要指定使用的服务类型#xff0c;使用 TCP 协议选择流式服务#xff08;SOCK_STREAM#xff09;。 **bind()方法是用来指定套接字使…TCP网络通讯
TCP编程流程 接口介绍 socket()方法是用来创建一个套接字有了套接字就可以通过网络进行数据的收发。创建套接字时要指定使用的服务类型使用 TCP 协议选择流式服务SOCK_STREAM。 **bind()方法是用来指定套接字使用的 IP 地址和端口。**IP 地址就是自己主机的地址测试程序时可以使用回环地址“127.0.0.1”。端口是一个 16 位的整形值一般 0-1024 为知名端口如 HTTP 使用的 80 号端口。这类端口一般用户不能随便使用。其次1024-4096 为保留端口用户一般也不使用。4096 以上为临时端口用户可以使用。在Linux 上1024 以内的端口号只有 root 用户可以使用。 **listen()方法是用来创建监听队列。**监听队列有两种一个是存放未完成三次握手的连接一种是存放已完成三次握手的连接。listen()第二个参数就是指定已完成三次握手队列的长度。 accept()处理存放在 listen 创建的已完成三次握手的队列中的连接。每处理一个连接则accept()返回该连接对应的套接字描述符。如果该队列为空则 accept 阻塞。 connect()方法一般由客户端程序执行需要指定连接的服务器端的 IP 地址和端口。该方法执行后会进行三次握手 建立连接。 send()方法用来向 TCP 连接的对端发送数据。send()执行成功只能说明将数据成功写入到发送端的发送缓冲区中并不能说明数据已经发送到了对端。send()的返回值为实际写入到发送缓冲区中的数据长度。 recv()方法用来接收 TCP 连接的对端发送来的数据。recv()从本端的接收缓冲区中读取数据如果接收缓冲区中没有数据则 recv()方法会阻塞返回值是实际读到的字节数如果recv()返回值为 0 说明对方已经关闭了 TCP 连接。 close()方法用来关闭 TCP 连接。此时会进行四次挥手。
客户端代码
#include stdio.h
#include stdlib.h
#include unistd.h
#include string.h
#include assert.h
#include sys/socket.h
#include sys/types.h
#include arpa/inet.h
#include netinet/in.h
int main()
{int sockfd socket(AF_INET, SOCK_STREAM, 0);assert(sockfd ! -1);struct sockaddr_in saddr;memset(saddr, 0, sizeof(saddr));saddr.sin_family AF_INET;saddr.sin_port htons(6000);saddr.sin_addr.s_addr inet_addr(127.0.0.1);int res connect(sockfd, (struct sockaddr *)saddr, sizeof(saddr));if (-1 res){ exit(1);}while (1){char buff[128] {0};printf(input:\n);fgets(buff, 128, stdin);if (strncmp(buff, end, 3) 0){break;}send(sockfd, buff, strlen(buff), 0);memset(buff, 0, 128);recv(sockfd, buff, 127, 0);printf(buff%s\n, buff);}close(sockfd);exit(0);
}
服务端代码
#include stdio.h
#include stdlib.h
#include unistd.h
#include string.h
#include assert.h
#include sys/socket.h
#include sys/types.h
#include arpa/inet.h
#include netinet/in.hint main()
{int sockfd socket(AF_INET, SOCK_STREAM, 0);if(-1 sockfd){exit(1);}struct sockaddr_in saddr;memset(saddr, 0, sizeof(saddr));saddr.sin_family AF_INET;saddr.sin_port htons(6000); // htons 将主机字节序转换为网络字节saddr.sin_addr.s_addr inet_addr(127.0.0.1); // 回环地址int res bind(sockfd, (struct sockaddr *)saddr, sizeof(saddr));if (-1 res){exit(1);}res listen(sockfd, 5);if (-1 res){exit(1);}struct sockaddr_in caddr;socklen_t len sizeof(caddr);int n 0;int c -1;while (1) // 服务器循环接收客户端连接{char data[128] {0};if (n 0){c accept(sockfd, (struct sockaddr *)caddr, len); // 阻塞if (c -1){printf(accept error );continue;;}}n recv(c, data, 127, 0); // 阻塞if (n 0) //连接关闭{close(c);printf(client close\n);continue;}else if (n 0) //出错{printf(recv error);continue;}printf(n %d, buff %s\n, n, data);send(c, OK, 2, 0);}close(sockfd); exit(0);
}运行结果 引入多线程处理并发
服务器端代码
#include stdio.h
#include stdlib.h
#include unistd.h
#include string.h
#include assert.h
#include sys/socket.h
#include netinet/in.h
#include arpa/inet.h
#include pthread.hvoid *run(void *arg)
{int c (int)arg;while (1){char buff[128] {0};if (recv(c, buff, 127, 0) 0){break;}printf(recv(%d)%s, c, buff);send(c, ok, 2, 0);}printf(one client over(%d)\n, c);close(c);
}int main()
{int sockfd socket(AF_INET, SOCK_STREAM, 0);if (-1 sockfd){exit(1);}struct sockaddr_in saddr, caddr;memset(saddr, 0, sizeof(saddr));saddr.sin_family AF_INET;saddr.sin_port htons(6000);saddr.sin_addr.s_addr inet_addr(127.0.0.1);int res bind(sockfd, (struct sockaddr *)saddr, sizeof(saddr));if (-1 res){exit(1);}listen(sockfd, 10);while (1){int len sizeof(caddr);int c accept(sockfd, (struct sockaddr *)caddr, len);if (c 0){continue;}printf(accept c %d\n, c);pthread_t id;pthread_create(id, NULL, run, (void *)c);}close(sockfd);exit(0);
}
运行结果
引入fork处理并发
服务器端代码
#include stdio.h
#include stdlib.h
#include unistd.h
#include string.h
#include assert.h
#include sys/socket.h
#include sys/types.h
#include netinet/in.h
#include arpa/inet.h
#include signal.hvoid DealClientLink(int c, struct sockaddr_in caddr)
{while (1){char buff[128] {0};int n recv(c, buff, 127, 0);if (n 0){break;}printf(%s:%d %s, inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port), buff);send(c, OK, 2, 0);}printf(One Client Close\n);close(c);
}void Signal_Fun(int sign)
{wait(NULL);
}
int main()
{signal(SIGCHLD, Signal_Fun); // 用wait()处理僵死进程int sockfd socket(AF_INET, SOCK_STREAM, 0);if (-1 sockfd){printf(create sockfd error\n);exit(1);}struct sockaddr_in saddr;saddr.sin_family AF_INET;saddr.sin_port htons(6000);saddr.sin_addr.s_addr inet_addr(127.0.0.1);int res bind(sockfd, (struct sockaddr *)saddr, sizeof(saddr));assert(-1 ! res);listen(sockfd, 10);while (1){struct sockaddr_in caddr;int len sizeof(caddr);int c accept(sockfd, (struct sockaddr *)caddr, len);assert(-1 ! c);printf(%s:%d Link Success\n, inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port));pid_t pid fork();if (-1 pid){exit(1);}if (0 pid){DealClientLink(c,caddr);exit(0); //必须结束子进程否则会有多个进程调 accept}else{close(c); //父子进程都需要关闭 c}}close(sockfd);exit(0);
}运行结果
TCP连接状态转变图 三次握手
流程图 使用netstat工具查看状态变化参考图3-8
四次挥手 流程图 使用netstat命令查看状态参考图3-8
TIME_WAIT的作用
在图3-8中当客户端连接在收到服务器的结束报文段之后并没有直接进人CLOSED 状态而是转移到 TIME_WAIT 状态。在这个状态客户端连接要等待段长为2MSL(Maximum Segment Life报文段最大生存时间)的时间才能完全关闭MSL是 TCP 报文段在网络中的最大生存时间标准文档 RFC 1122 的建议值是2 min
TIME WAIT 状态存在的原因有两点: 可靠地终止TCP 连接 当服务器发给客户端的ACK中途丢失客户端收不到ACK,会重新发送FIN如果此时服务器已经关闭无法接收来自客户端的FIN,便会陷入一种“藕断丝连”状态一方关闭一方未关闭。这显然是不合适的因为TCP 连接是全双工的双方完成数据交换之后通信双方都必须断开连接以释放系统资源。 保证让迟来的TCP 报文段有足够的时间被识别并丢弃 在 Linux 系统上一个TCP 端口不能被同时打开多次(两次及以上)。当一个TCP 连接处于 TIME_WAIT 状态时我们将无法立即使用该连接占用着的端口来建立一个新连接。反过来如果不存在 TIME WAIT 态则应用序能够立即建立一个和刚关闭的连接相似的连接(这里说的相似是指它们具有相同的 IP 地址和端口号)。这个新的、和原来相似的连接被称为原来的连接的化身 (incarmation)。新的化身可能接收到属于原来的连接的、携带应用程序数据的 TCP 报文段(迟到的报文段)这显然是不应该发生的。这就是 TIMEWAIT 状态存在的第二个原因。
TCP协议特点
流式服务
TCP 字节流的特点发送端执行的写操作次数和接收端执行的读操作次数之间没有任何数量关系应用程序对数据的发送和接收是没有边界限制的。如下图 TCP连接的可靠性
IPV4报文格式: TCP报文格式 应答机制 超时重传 TCP 传输是可靠的。首先TCP 协议采用发送应答机制即发送端发送的每个 TCP 报文段都必须得到接收方的应答才认为这个 TCP 报文段传输成功。其次TCP 协议采用超时重传机制发送端在发送出1个 TCP 报文段之后启动定时器如果在定时时间内未收到应答它将重发该报文段。最后因为 TCP 报文段最终是以 IP数据报发送的而 数据报到达接收端可能乱序、重复所以 TCP 协议还会对接收到的 TCP 报文段重排、整理再交付给应用层。
粘包问题
在流式服务中如上图3-9所示尽管报文已经按顺序整理好并接受但是无法分割成正确的信息就形成了所谓的粘包问题为了解决此问题我们可以每次发送时进行标记分割以便于接收方进行分析和拆分如下图