4.9 字节序
/* 字节序:字节在内存中存储的顺序。 小端字节序:数据的高位字节存储在内存的高位地址,低位字节存储在内存的低位地址 大端字节序:数据的低位字节存储在内存的高位地址,高位字节存储在内存的低位地址 */ // 通过代码检测当前主机的字节序 #include <stdio.h> int main() { union { short value; // 2字节 char bytes[sizeof(short)]; // char[2] } test; test.value = 0x0102; if((test.bytes[0] == 1) && (test.bytes[1] == 2)) { printf("大端字节序\n"); } else if((test.bytes[0] == 2) && (test.bytes[1] == 1)) { printf("小端字节序\n"); } else { printf("未知\n"); } return 0; }
4.10 字节序转换函数
/* 网络通信时,需要将主机字节序转换成网络字节序(大端), 另外一段获取到数据以后根据情况将网络字节序转换成主机字节序。 // 转换端口 uint16_t htons(uint16_t hostshort); // 主机字节序 - 网络字节序 uint16_t ntohs(uint16_t netshort); // 主机字节序 - 网络字节序 // 转IP uint32_t htonl(uint32_t hostlong); // 主机字节序 - 网络字节序 uint32_t ntohl(uint32_t netlong); // 主机字节序 - 网络字节序 */ /* h -host 主机,主机字节序 to n -network s -short l -long */ #include <stdio.h> #include <arpa/inet.h> int main() { // htons 转换端口 printf("端口的主机字节序转网络字节序:\n"); unsigned short a = 0x0102; printf("主机字节序(小端) : %x\n", a); unsigned short b = htons(a); //转换成网络字节序(大端) printf("网络字节序(大端): %x\n", b); printf("=======================\n"); // ntohs 自己写 // htonl 转换IP printf("IP的主机字节序转网络字节序:\n"); char buf[4] = {192, 168, 1, 100}; int num = *(int *)buf; int sum = htonl(num); unsigned char *p = (char *)∑ printf("%d %d %d %d\n", *p, *(p+1), *(p+2), *(p+3)); printf("=======================\n"); // ntohl printf("IP的网络字节序转主机字节序:\n"); unsigned char buf1[4] = {1, 1, 168, 192}; int num1 = *(int *)buf1; int sum1 = ntohl(num1); unsigned char *p1 = (unsigned char *)&sum1; printf("%d %d %d %d\n", *p1, *(p1+1), *(p1+2), *(p1+3)); return 0; }
4.12 IP地址转换函数
/* #include <arpa/inet.h> // p:点分十进制的IP字符串,n:表示network,网络字节序的整数 int inet_pton(int af, const char *src, void *dst); af:地址族: AF_INET AF_INET6 src:需要转换的点分十进制的IP字符串 dst:转换后的结果保存在这个里面 // 将网络字节序的整数,转换成点分十进制的IP地址字符串 const char *inet_ntop(int af, const void *src, char *dst, socklen_t size); af:地址族: AF_INET AF_INET6 src: 要转换的ip的整数的地址 dst: 转换成IP地址字符串保存的地方 size:第三个参数的大小(数组的大小) 返回值:返回转换后的数据的地址(字符串),和 dst 是一样的 */ #include <stdio.h> #include <arpa/inet.h> int main() { // 创建一个ip字符串,点分十进制的IP地址字符串 char buf[] = "192.168.1.4"; unsigned int num = 0; // 将点分十进制的IP字符串转换成网络字节序的整数 inet_pton(AF_INET, buf, &num); unsigned char * p = (unsigned char *)# printf("%d %d %d %d\n", *p, *(p+1), *(p+2), *(p+3)); // 将网络字节序的IP整数转换成点分十进制的IP字符串 char ip[16] = ""; const char * str = inet_ntop(AF_INET, &num, ip, 16); printf("str : %s\n", str); printf("ip : %s\n", ip); printf("%d\n", ip == str); return 0; }
4.13 TCP通信流程
UDP:用户数据报协议,面向无连接,可以单播,多播,广播,面向数据报,不可靠 TCP:传输控制协议,面向连接,可靠的,基于字节流,仅支持单播传输,点对点传输 UDP TCP 是否创建连接 无连接 有连接 是否可靠 不可靠 可靠 连接的对象个数 一对一、一对多、多对一、多对多 支持一对一 传输的方式 面向数据报 面向字节流 首部开销 8个字节 最少20个字节 适用场景 实时应用(视频会议,直播) 可靠性高的应用(文件传输)
TCP通信流程:
//服务器端(被动接受连接的解决) 1. 创建一个用于监听的套接字 socket() - 监听:监听有客户端的连接 - 套接字:这个套接字其实就是一个文件描述符 2. 将这个监听文件描述符和本地的IP和端口号绑定(IP和端口就是服务器的地址信息) bind() - 客户端连接服务器的时候使用的就是这个IP和端口号 3. 设置监听,监听的fd开始工作 listen() 4. 阻塞等待,当有客户端发起连接,解除阻塞,接收客户端的连接,会得到一个核客户端通信的套接字(fd) accept(),阻塞函数 5. 通信 recv() send() - 接收数据 - 发送数据 6. 通信结束,断开连接 close()
//客户端 1. 创建一个用于通信的套接字(fd) 2. 连接服务器,需要指定连接的服务器的IP和端口 3. 连接成功了,客户端可以直接和服务器通信 - 接收数据 - 发送数据 4. 通信结束,断开连接
4.14 socket函数
server.c
// 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); //0表示默认tcp协议 返回文件描述符 if(lfd == -1) { perror("socket"); exit(-1); } // 2.绑定 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 宏就是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; }
client.c
// 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, "172.17.0.1", &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; }
4.18 TCP滑动窗口
滑动窗口是TCP中实现诸如ACP确认、流量控制、拥塞控制的承载结构
窗口可以理解成缓冲区的大小
滑动窗口的大小是动态变化的
通信的双方都有发送缓冲区和接收数据的缓冲区
服务器: 发送缓冲区(发送缓冲区的窗口) 接收缓冲区(接收缓冲区的窗口) 客户端: 发送缓冲区(发送缓冲区的窗口) 接收缓冲区(接收缓冲区的窗口)
4.19 四次挥手
发生在close()
阶段,用于断开连接
客户端和服务器都可以主动发起断开连接
4.20 TCP通信并发
要实现TCP通信服务器处理并发的任务,使用多线程或者多进程来解决 思路: 1. 一个父进程,一个子进程 2. 父进程负责等待并接受客户端的连接 3. 子进程:完成通信,接受一个客户端连接,就创建一个字进程用于通信
4.21 多进程实现通信
server.c
#include <stdio.h> #include <arpa/inet.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <signal.h> #include <wait.h> #include <errno.h> void recyleChild(int arg) { while(1) { int ret = waitpid(-1, NULL, WNOHANG); if(ret == -1) { // 所有的子进程都回收了 break; }else if(ret == 0) { // 还有子进程活着 break; } else if(ret > 0){ // 被回收了 printf("子进程 %d 被回收了\n", ret); } } } int main() { struct sigaction act; act.sa_flags = 0; sigemptyset(&act.sa_mask); act.sa_handler = recyleChild; // 注册信号捕捉 sigaction(SIGCHLD, &act, NULL); // 创建socket int lfd = socket(PF_INET, SOCK_STREAM, 0); if(lfd == -1){ perror("socket"); exit(-1); } struct sockaddr_in saddr; saddr.sin_family = AF_INET; saddr.sin_port = htons(9999); saddr.sin_addr.s_addr = INADDR_ANY; // 绑定 int ret = bind(lfd,(struct sockaddr *)&saddr, sizeof(saddr)); if(ret == -1) { perror("bind"); exit(-1); } // 监听 ret = listen(lfd, 128); if(ret == -1) { perror("listen"); exit(-1); } // 不断循环等待客户端连接 while(1) { struct sockaddr_in cliaddr; int len = sizeof(cliaddr); // 接受连接 int cfd = accept(lfd, (struct sockaddr*)&cliaddr, &len); if(cfd == -1) { if(errno == EINTR) { continue; } perror("accept"); exit(-1); } // 每一个连接进来,创建一个子进程跟客户端通信 pid_t pid = fork(); if(pid == 0) { // 子进程 // 获取客户端的信息 char cliIp[16]; inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, cliIp, sizeof(cliIp)); unsigned short cliPort = ntohs(cliaddr.sin_port); printf("client ip is : %s, prot is %d\n", cliIp, cliPort); // 接收客户端发来的数据 char recvBuf[1024]; while(1) { int len = read(cfd, &recvBuf, sizeof(recvBuf)); if(len == -1) { perror("read"); exit(-1); }else if(len > 0) { printf("recv client : %s\n", recvBuf); } else if(len == 0) { printf("client closed....\n"); break; } write(cfd, recvBuf, strlen(recvBuf) + 1); } close(cfd); exit(0); // 退出当前子进程 } } close(lfd); return 0; }
client.c
// 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, "172.17.0.1", &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]; int i = 0; while(1) { sprintf(recvBuf, "data : %d\n", i++); // 给服务器端发送数据 write(fd, recvBuf, strlen(recvBuf)+1); int len = read(fd, recvBuf, sizeof(recvBuf)); if(len == -1) { perror("read"); exit(-1); } else if(len > 0) { printf("recv server : %s\n", recvBuf); } else if(len == 0) { // 表示服务器端断开连接 printf("server closed..."); break; } sleep(1); } // 关闭连接 close(fd); return 0; }
4.22 多线程实现并发服务器端
#include <stdio.h> #include <arpa/inet.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <pthread.h> struct sockInfo { int fd; // 通信的文件描述符 struct sockaddr_in addr; pthread_t tid; // 线程号 }; struct sockInfo sockinfos[128]; void * working(void * arg) { // 子线程和客户端通信 cfd 客户端的信息 线程号 // 获取客户端的信息 struct sockInfo * pinfo = (struct sockInfo *)arg; char cliIp[16]; inet_ntop(AF_INET, &pinfo->addr.sin_addr.s_addr, cliIp, sizeof(cliIp)); unsigned short cliPort = ntohs(pinfo->addr.sin_port); printf("client ip is : %s, prot is %d\n", cliIp, cliPort); // 接收客户端发来的数据 char recvBuf[1024]; while(1) { int len = read(pinfo->fd, &recvBuf, sizeof(recvBuf)); if(len == -1) { perror("read"); exit(-1); }else if(len > 0) { printf("recv client : %s\n", recvBuf); } else if(len == 0) { printf("client closed....\n"); break; } write(pinfo->fd, recvBuf, strlen(recvBuf) + 1); } close(pinfo->fd); return NULL; } int main() { // 创建socket int lfd = socket(PF_INET, SOCK_STREAM, 0); if(lfd == -1){ perror("socket"); exit(-1); } struct sockaddr_in saddr; saddr.sin_family = AF_INET; saddr.sin_port = htons(9999); saddr.sin_addr.s_addr = INADDR_ANY; // 绑定 int ret = bind(lfd,(struct sockaddr *)&saddr, sizeof(saddr)); if(ret == -1) { perror("bind"); exit(-1); } // 监听 ret = listen(lfd, 128); if(ret == -1) { perror("listen"); exit(-1); } // 初始化数据 int max = sizeof(sockinfos) / sizeof(sockinfos[0]); for(int i = 0; i < max; i++) { bzero(&sockinfos[i], sizeof(sockinfos[i])); sockinfos[i].fd = -1; sockinfos[i].tid = -1; } // 循环等待客户端连接,一旦一个客户端连接进来,就创建一个子线程进行通信 while(1) { struct sockaddr_in cliaddr; int len = sizeof(cliaddr); // 接受连接 int cfd = accept(lfd, (struct sockaddr*)&cliaddr, &len); struct sockInfo * pinfo; for(int i = 0; i < max; i++) { // 从这个数组中找到一个可以用的sockInfo元素 if(sockinfos[i].fd == -1) { pinfo = &sockinfos[i]; break; } if(i == max - 1) { sleep(1); i--; } } pinfo->fd = cfd; memcpy(&pinfo->addr, &cliaddr, len); // 创建子线程 pthread_create(&pinfo->tid, NULL, working, pinfo); pthread_detach(pinfo->tid); } close(lfd); return 0; }
4.23 TCP状态转换
TIME_WAIT等待2MSL时间;确保通信的另外一方能够收到ACK
4.24 半关闭、端口复用
tcp_server.c
#include <stdio.h> #include <ctype.h> #include <arpa/inet.h> #include <unistd.h> #include <stdlib.h> #include <string.h> int main(int argc, char *argv[]) { // 创建socket int lfd = socket(PF_INET, SOCK_STREAM, 0); if(lfd == -1) { perror("socket"); return -1; } struct sockaddr_in saddr; saddr.sin_family = AF_INET; saddr.sin_addr.s_addr = INADDR_ANY; saddr.sin_port = htons(9999); //int optval = 1; //setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)); //允许重用本地端口 int optval = 1; //1表示可以复用 0表示不可以复用 setsockopt(lfd, SOL_SOCKET, SO_REUSEPORT, &optval, sizeof(optval)); //允许重用本地端口 // 绑定 int ret = bind(lfd, (struct sockaddr *)&saddr, sizeof(saddr)); if(ret == -1) { perror("bind"); return -1; } // 监听 ret = listen(lfd, 8); if(ret == -1) { perror("listen"); return -1; } // 接收客户端连接 struct sockaddr_in cliaddr; socklen_t len = sizeof(cliaddr); int cfd = accept(lfd, (struct sockaddr *)&cliaddr, &len); if(cfd == -1) { perror("accpet"); return -1; } // 获取客户端信息 char cliIp[16]; inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, cliIp, sizeof(cliIp)); unsigned short cliPort = ntohs(cliaddr.sin_port); // 输出客户端的信息 printf("client's ip is %s, and port is %d\n", cliIp, cliPort ); // 接收客户端发来的数据 char recvBuf[1024] = {0}; while(1) { int len = recv(cfd, recvBuf, sizeof(recvBuf), 0); if(len == -1) { perror("recv"); return -1; } else if(len == 0) { printf("客户端已经断开连接...\n"); break; } else if(len > 0) { printf("read buf = %s\n", recvBuf); } // 小写转大写 for(int i = 0; i < len; ++i) { recvBuf[i] = toupper(recvBuf[i]); } printf("after buf = %s\n", recvBuf); // 大写字符串发给客户端 ret = send(cfd, recvBuf, strlen(recvBuf) + 1, 0); if(ret == -1) { perror("send"); return -1; } } close(cfd); close(lfd); return 0; }
tcp_client.c
#include <stdio.h> #include <arpa/inet.h> #include <stdlib.h> #include <unistd.h> #include <string.h> int main() { // 创建socket int fd = socket(PF_INET, SOCK_STREAM, 0); if(fd == -1) { perror("socket"); return -1; } struct sockaddr_in seraddr; inet_pton(AF_INET, "127.0.0.1", &seraddr.sin_addr.s_addr); seraddr.sin_family = AF_INET; seraddr.sin_port = htons(9999); // 连接服务器 int ret = connect(fd, (struct sockaddr *)&seraddr, sizeof(seraddr)); if(ret == -1){ perror("connect"); return -1; } while(1) { char sendBuf[1024] = {0}; fgets(sendBuf, sizeof(sendBuf), stdin); write(fd, sendBuf, strlen(sendBuf) + 1); // 接收 int len = read(fd, sendBuf, sizeof(sendBuf)); if(len == -1) { perror("read"); return -1; } else if(len > 0) { printf("read buf = %s\n", sendBuf); } else { printf("服务器已经断开连接...\n"); break; } } close(fd); return 0; }
4.25 IO多路复用
4.27 select
client.c
#include <stdio.h> #include <arpa/inet.h> #include <stdlib.h> #include <unistd.h> #include <string.h> int main() { // 创建socket int fd = socket(PF_INET, SOCK_STREAM, 0); if(fd == -1) { perror("socket"); return -1; } struct sockaddr_in seraddr; inet_pton(AF_INET, "127.0.0.1", &seraddr.sin_addr.s_addr); seraddr.sin_family = AF_INET; seraddr.sin_port = htons(9999); // 连接服务器 int ret = connect(fd, (struct sockaddr *)&seraddr, sizeof(seraddr)); if(ret == -1){ perror("connect"); return -1; } int num = 0; while(1) { char sendBuf[1024] = {0}; sprintf(sendBuf, "send data %d", num++); write(fd, sendBuf, strlen(sendBuf) + 1); // 接收 int len = read(fd, sendBuf, sizeof(sendBuf)); if(len == -1) { perror("read"); return -1; }else if(len > 0) { printf("read buf = %s\n", sendBuf); } else { printf("服务器已经断开连接...\n"); break; } sleep(1); //usleep(1000); } close(fd); return 0; }
select.c
#include <stdio.h> #include <arpa/inet.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <sys/select.h> int main() { // 创建socket int lfd = socket(PF_INET, SOCK_STREAM, 0); struct sockaddr_in saddr; saddr.sin_port = htons(9999); saddr.sin_family = AF_INET; saddr.sin_addr.s_addr = INADDR_ANY; // 绑定 bind(lfd, (struct sockaddr *)&saddr, sizeof(saddr)); // 监听 listen(lfd, 8); // 创建一个fd_set的集合,存放的是需要检测的文件描述符 fd_set rdset, tmp; FD_ZERO(&rdset); FD_SET(lfd, &rdset); int maxfd = lfd; while(1) { tmp = rdset; // 调用select系统函数,让内核帮检测哪些文件描述符有数据 int ret = select(maxfd + 1, &tmp, NULL, NULL, NULL); if(ret == -1) { perror("select"); exit(-1); } else if(ret == 0) { continue; } else if(ret > 0) { // 说明检测到了有文件描述符的对应的缓冲区的数据发生了改变 if(FD_ISSET(lfd, &tmp)) { // 表示有新的客户端连接进来了 struct sockaddr_in cliaddr; int len = sizeof(cliaddr); int cfd = accept(lfd, (struct sockaddr *)&cliaddr, &len); // 将新的文件描述符加入到集合中 FD_SET(cfd, &rdset); // 更新最大的文件描述符 maxfd = maxfd > cfd ? maxfd : cfd; } for(int i = lfd + 1; i <= maxfd; i++) { if(FD_ISSET(i, &tmp)) { // 说明这个文件描述符对应的客户端发来了数据 char buf[1024] = {0}; int len = read(i, buf, sizeof(buf)); if(len == -1) { perror("read"); exit(-1); } else if(len == 0) { printf("client closed...\n"); close(i); FD_CLR(i, &rdset); } else if(len > 0) { printf("read buf = %s\n", buf); write(i, buf, strlen(buf) + 1); } } } } } close(lfd); return 0; }
4.28 poll
pool.c
#include <stdio.h> #include <arpa/inet.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <poll.h> int main() { // 创建socket int lfd = socket(PF_INET, SOCK_STREAM, 0); struct sockaddr_in saddr; saddr.sin_port = htons(9999); saddr.sin_family = AF_INET; saddr.sin_addr.s_addr = INADDR_ANY; // 绑定 bind(lfd, (struct sockaddr *)&saddr, sizeof(saddr)); // 监听 listen(lfd, 8); // 初始化检测的文件描述符数组 struct pollfd fds[1024]; for(int i = 0; i < 1024; i++) { fds[i].fd = -1; fds[i].events = POLLIN; } fds[0].fd = lfd; int nfds = 0; while(1) { // 调用poll系统函数,让内核帮检测哪些文件描述符有数据 int ret = poll(fds, nfds + 1, -1); if(ret == -1) { perror("poll"); exit(-1); } else if(ret == 0) { continue; } else if(ret > 0) { // 说明检测到了有文件描述符的对应的缓冲区的数据发生了改变 if(fds[0].revents & POLLIN) { // 表示有新的客户端连接进来了 struct sockaddr_in cliaddr; int len = sizeof(cliaddr); int cfd = accept(lfd, (struct sockaddr *)&cliaddr, &len); // 将新的文件描述符加入到集合中 for(int i = 1; i < 1024; i++) { if(fds[i].fd == -1) { fds[i].fd = cfd; fds[i].events = POLLIN; break; } } // 更新最大的文件描述符的索引 nfds = nfds > cfd ? nfds : cfd; } for(int i = 1; i <= nfds; i++) { if(fds[i].revents & POLLIN) { // 说明这个文件描述符对应的客户端发来了数据 char buf[1024] = {0}; int len = read(fds[i].fd, buf, sizeof(buf)); if(len == -1) { perror("read"); exit(-1); } else if(len == 0) { printf("client closed...\n"); close(fds[i].fd); fds[i].fd = -1; } else if(len > 0) { printf("read buf = %s\n", buf); write(fds[i].fd, buf, strlen(buf) + 1); } } } } } close(lfd); return 0; }
client.c
#include <stdio.h> #include <arpa/inet.h> #include <stdlib.h> #include <unistd.h> #include <string.h> int main() { // 创建socket int fd = socket(PF_INET, SOCK_STREAM, 0); if(fd == -1) { perror("socket"); return -1; } struct sockaddr_in seraddr; inet_pton(AF_INET, "127.0.0.1", &seraddr.sin_addr.s_addr); seraddr.sin_family = AF_INET; seraddr.sin_port = htons(9999); // 连接服务器 int ret = connect(fd, (struct sockaddr *)&seraddr, sizeof(seraddr)); if(ret == -1){ perror("connect"); return -1; } int num = 0; while(1) { char sendBuf[1024] = {0}; sprintf(sendBuf, "send data %d", num++); write(fd, sendBuf, strlen(sendBuf) + 1); // 接收 int len = read(fd, sendBuf, sizeof(sendBuf)); if(len == -1) { perror("read"); return -1; }else if(len > 0) { printf("read buf = %s\n", sendBuf); } else { printf("服务器已经断开连接...\n"); break; } // sleep(1); usleep(1000); } close(fd); return 0; }
4.30 epoll
client.c
#include <stdio.h> #include <arpa/inet.h> #include <stdlib.h> #include <unistd.h> #include <string.h> int main() { // 创建socket int fd = socket(PF_INET, SOCK_STREAM, 0); if(fd == -1) { perror("socket"); return -1; } struct sockaddr_in seraddr; inet_pton(AF_INET, "127.0.0.1", &seraddr.sin_addr.s_addr); seraddr.sin_family = AF_INET; seraddr.sin_port = htons(9999); // 连接服务器 int ret = connect(fd, (struct sockaddr *)&seraddr, sizeof(seraddr)); if(ret == -1){ perror("connect"); return -1; } int num = 0; while(1) { char sendBuf[1024] = {0}; sprintf(sendBuf, "send data %d", num++); write(fd, sendBuf, strlen(sendBuf) + 1); // 接收 int len = read(fd, sendBuf, sizeof(sendBuf)); if(len == -1) { perror("read"); return -1; }else if(len > 0) { printf("read buf = %s\n", sendBuf); } else { printf("服务器已经断开连接...\n"); break; } // sleep(1); usleep(1000); } close(fd); return 0; }
epoll.c
#include <stdio.h> #include <arpa/inet.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <sys/epoll.h> int main() { // 创建socket int lfd = socket(PF_INET, SOCK_STREAM, 0); struct sockaddr_in saddr; saddr.sin_port = htons(9999); saddr.sin_family = AF_INET; saddr.sin_addr.s_addr = INADDR_ANY; // 绑定 bind(lfd, (struct sockaddr *)&saddr, sizeof(saddr)); // 监听 listen(lfd, 8); // 调用epoll_create()创建一个epoll实例 int epfd = epoll_create(100); //参数随便给,必须>0 // 将监听的文件描述符相关的检测信息添加到epoll实例中 struct epoll_event epev; epev.events = EPOLLIN; epev.data.fd = lfd; epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &epev); //加入 struct epoll_event epevs[1024]; while(1) { int ret = epoll_wait(epfd, epevs, 1024, -1); if(ret == -1) { perror("epoll_wait"); exit(-1); } printf("ret = %d\n", ret); for(int i = 0; i < ret; i++) { int curfd = epevs[i].data.fd; if(curfd == lfd) { // 监听的文件描述符有数据达到,有客户端连接 struct sockaddr_in cliaddr; int len = sizeof(cliaddr); int cfd = accept(lfd, (struct sockaddr *)&cliaddr, &len); epev.events = EPOLLIN; epev.data.fd = cfd; epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &epev); } else { if(epevs[i].events & EPOLLOUT) { continue; } // 有数据到达,需要通信 char buf[1024] = {0}; int len = read(curfd, buf, sizeof(buf)); if(len == -1) { perror("read"); exit(-1); } else if(len == 0) { printf("client closed...\n"); epoll_ctl(epfd, EPOLL_CTL_DEL, curfd, NULL); close(curfd); } else if(len > 0) { printf("read buf = %s\n", buf); write(curfd, buf, strlen(buf) + 1); } } } } close(lfd); close(epfd); return 0; }
4.31 epoll的两种工作模式