TCP 状态转换
红客户端 绿服务器端 黑色异常
2MSL(Maximum Segment Lifetime) 主动断开连接的一方, 最后进入一个 TIME_WAIT状态, 这个状态会持续: 2msl 为了确保服务器端可以收到ACK,确保正确关闭 msl: 官方建议: 2分钟, 实际是30s 当 TCP 连接主动关闭方接收到被动关闭方发送的 FIN 和最终的 ACK 后,连接的主动关闭方必须处于TIME_WAIT 状态并持续 2MSL 时间。
这样就能够让 TCP 连接的主动关闭方在它发送的 ACK 丢失的情况下重新发送最终的 ACK。因为如果最后一个ack丢失的话,被动关闭方会一直等待,等不到就会再发送FIN,直到收到ACK;
主动关闭方重新发送的最终 ACK 并不是因为被动关闭方重传了 ACK(它们并不消耗序列号,被动关闭方也不会重传),而是因为被动关闭方重传了它的 FIN。事实上,被动关闭方总是重传 FIN 直到它收到一个最终的 ACK。
4.24 半关闭 端口复用 半关闭(能接收 不能发) 当 TCP 链接中 A 向 B 发送 FIN 请求关闭,另一端 B 回应 ACK 之后(A 端进入 FIN_WAIT_2状态),并没有立即发送 FIN 给 A,A 方处于半连接状态(半开关),此时 A 可以接收 B 发送的数据,但是 A 已经不能再向 B 发送数据 。
从程序的角度,可以使用 API shutdown (SHUT_WR) 来控制实现半连接状态:
#include <sys/socket.h> int shutdown (int sockfd, int how) ;sockfd: 需要关闭的socket的描述符 how: 允许为shutdown操作选择以下几种方式: SHUT_RD(0 ): 关闭sockfd上的读功能,此选项将不允许sockfd进行读操作。该套接字不再接收数据,任何当前在套接字接受缓冲区的数据将被无声的丢弃掉。 SHUT_WR(1 ): 关闭sockfd的写功能,此选项将不允许sockfd进行写操作。进程不能在对此套接字发出写操作。 SHUT_RDWR(2 ):关闭sockfd的读写功能。相当于调用shutdown两次 相当于close:首先是以SHUT_RD,然后以SHUT_WR。
使用 close 中止一个连接,但它只是减少描述符的引用计数,并不直接关闭连接,只有当描述符的引用计数为 0 时才关闭连接。shutdown 不考虑描述符的引用计数,直接关闭描述符。也可选择中止一个方向的连接,只中止读或只中止写。(有子进程则加一) 注意:
如果有多个进程共享一个套接字,close 每被调用一次,计数减 1 ,直到计数为 0 时,也就是所用进程都调用了 close,套接字将被释放。
在多进程中如果一个进程调用了 shutdown(sfd, SHUT_RDWR) 后,其它的进程将无法进行通信。但如果一个进程 close(sfd) 将不会影响到其它进程。
端口复用 端口复用的用途是:防止服务器重启时 之前绑定的端口还没释放。程序突然退出而系统没有释放端口。
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[]) { 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_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 () { 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 ; }
运行服务器端(9999)、运行客户端(36514)之后,第二个答案里的第三行是专门用来通信的socket
如果断开服务器,客户端被动关闭处于close wait状态 ,服务器端主动关闭,处于fw2
如果快速断开被动关闭的客户端 服务器处于time-wait 等待2msl后释放端口
端口复用最常用的用途是: 1 防止服务器重启时之前绑定的端口还未释放 2 程序突然退出而系统没有释放 端口
#include <sys/types.h> #include <sys/socket.h> int setsockopt (int sockfd, int level, int optname, const void *optval, socklen_toptlen) ;参数: - sockfd : 要操作的文件描述符 - level : 级别 - SOL_SOCKET (端口复用的级别) - optname : 选项的名称 - SO_REUSEADDR - SO_REUSEPORT - optval : 端口复用的值(整型) - 1 : 可以复用 - 0 : 不可以复用 - optlen : optval参数的大小 端口复用,设置的时机是在服务器绑定端口之前。 setsockopt(); bind();
4.25 IO多路复用 I/O 多路复用使得程序能同时监听多个文件描述符,能够提高程序的性能, Linux 下实现 I/O 多路复用的 系统调用主要有 select、poll 和 epoll。
文件和内存!!!的IO。
4.26 select
主旨思想:
首先要构造一个关于文件描述符的列表,将要监听的文件描述符添加到该列表中。
调用一个系统函数,监听该列表中的文件描述符,直到这些描述符中的一个或者多个进行I/O操作时,该函数才返回。
这个函数是阻塞
函数对文件描述符的检测的操作是由内核完成的
在返回时,它会告诉进程有多少(哪些)描述符要进行I/O操作。
#include <sys/time.h> #include <sys/types.h> #include <unistd.h> #include <sys/select.h> int select (int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout) ;- 参数: - nfds : 委托内核检测的最大文件描述符的值 + 1 - readfds : 要检测的文件描述符的读的集合,委托内核检测哪些文件描述符的读的属性 - 一般检测读操作 - 对应的是对方发送过来的数据,因为读是被动的接收数据,检测的就是读缓冲区 - 是一个传入传出参数 - writefds : 要检测的文件描述符的写的集合,委托内核检测哪些文件描述符的写的属性 - 委托内核检测写缓冲区是不是还可以写数据(不满的就可以写) - exceptfds : 检测发生异常的文件描述符的集合(一般不用) - timeout : 设置的超时时间 struct timeval { long tv_sec; long tv_usec; }; - NULL : 永久阻塞,直到检测到了文件描述符有变化 - tv_sec = 0 tv_usec = 0 , 不阻塞 - tv_sec > 0 tv_usec > 0 , 阻塞对应的时间 - 返回值 : - -1 : 失败 - >0 (n) : 检测的集合中有n个文件描述符发生了变化 void FD_CLR(int fd, fd_set *set );int FD_ISSET (int fd, fd_set *set ) ;void FD_SET (int fd, fd_set *set ) ;void FD_ZERO (fd_set *set ) ;
100 101没有发送,就置零
#include <stdio.h> #include <arpa/inet.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <sys/select.h> int main () { 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 rdset, tmp; FD_ZERO(&rdset); FD_SET(lfd, &rdset); int maxfd = lfd; while (1 ) { tmp = rdset; 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 ; }
#include <stdio.h> #include <arpa/inet.h> #include <stdlib.h> #include <unistd.h> #include <string.h> int main () { 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 ; } usleep(1000 ); } close(fd); return 0 ; }
结果是多客户端可以连接到服务器。
poll 解决select 的第三个和第四个缺点。
4.28 poll #include <poll.h> struct pollfd { int fd; short events; short revents; }; struct pollfd myfd ; myfd.fd = 5 ; myfd.events = POLLIN | POLLOUT; int poll (struct pollfd *fds, nfds_t nfds, int timeout) ;- 参数: - fds : 是一个struct pollfd 结构体数组,这是一个需要检测的文件描述符的集合 - nfds : 这个是第一个参数数组中最后一个有效元素的下标 + 1 - timeout : 阻塞时长 0 : 不阻塞 -1 : 阻塞,当检测到需要检测的文件描述符有变化,解除阻塞 >0 : 阻塞的时长 - 返回值: -1 : 失败 >0 (n) : 成功,n表示检测到集合中有n个文件描述符发生变化
#include <stdio.h> #include <arpa/inet.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <poll.h> int main () { 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 ) { 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 ; }
poll 解决select 的第三个和第四个缺点。仍然还是需要遍历。
4.29 epoll 没有用户态到内核态的切换,之前是线性数据结构,现在是红黑树数据结构。
epoll是如何实现的?首先使用epoll_create
在内核区创建实例,是一个eventpoll的结构体类型,返回文件描述符。
这个数据结构rbr红黑树结构、rdlist链表 。如果有数据了就从rbr放入rdlist中。以前拷贝回去的都是所有的,而这里拷贝回去的是就绪的。
#include <sys/epoll.h> int epoll_create (int size) ; - 参数: size : 目前没有意义了。随便写一个数,必须大于0 - 返回值: -1 : 失败 > 0 : 文件描述符,操作epoll实例的 typedef union epoll_data { void *ptr; int fd; uint32_t u32; uint64_t u64; } epoll_data_t ; struct epoll_event { uint32_t events; epoll_data_t data; }; 常见的Epoll检测事件events: - EPOLLIN - EPOLLOUT - EPOLLERR int epoll_ctl (int epfd, int op, int fd, struct epoll_event *event) ; - 参数: - epfd : epoll实例对应的文件描述符 - op : 要进行什么操作 EPOLL_CTL_ADD: 添加 EPOLL_CTL_MOD: 修改 EPOLL_CTL_DEL: 删除 - fd : 要检测的文件描述符 - event : 检测文件描述符什么事情 int epoll_wait (int epfd, struct epoll_event *events, int maxevents, int timeout) ; - 参数: - epfd : epoll实例对应的文件描述符 - events : 传出参数,保存了发送了变化的文件描述符的信息 - maxevents : 第二个参数结构体数组的大小 - timeout : 阻塞时间 - 0 : 不阻塞 - -1 : 阻塞,直到检测到fd数据发生变化,解除阻塞 - > 0 : 阻塞的时长(毫秒) - 返回值: - 成功,返回发送变化的文件描述符的个数 > 0 - 失败 -1
#include <stdio.h> #include <arpa/inet.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <sys/epoll.h> int main () { 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 ); int epfd = epoll_create(100 ); 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 的两种工作模式
struct epoll_event { uint32_t events; epoll_data_t data; }; 常见的Epoll检测事件: - EPOLLIN - EPOLLOUT - EPOLLERR - EPOLLET
#include <stdio.h> #include <arpa/inet.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <sys/epoll.h> int main () { 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 ); int epfd = epoll_create(100 ); 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[5 ] = {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 ; }
#include <stdio.h> #include <arpa/inet.h> #include <stdlib.h> #include <unistd.h> #include <string.h> int main () { 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 }; 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 ; }
#include <stdio.h> #include <arpa/inet.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <sys/epoll.h> #include <fcntl.h> #include <errno.h> int main () { 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 ); int epfd = epoll_create(100 ); 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); int flag = fcntl(cfd, F_GETFL); flag | O_NONBLOCK; fcntl(cfd, F_SETFL, flag); epev.events = EPOLLIN | EPOLLET; epev.data.fd = cfd; epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &epev); } else { if (epevs[i].events & EPOLLOUT) { continue ; } char buf[5 ]; int len = 0 ; while ( (len = read(curfd, buf, sizeof (buf))) > 0 ) { write(STDOUT_FILENO, buf, len); write(curfd, buf, len); } if (len == 0 ) { printf ("client closed...." ); }else if (len == -1 ) { if (errno == EAGAIN) { printf ("data over....." ); }else { perror("read" ); exit (-1 ); } } } } } close(lfd); close(epfd); return 0 ; }
4.32 UDP通信
而tcp
#include <sys/types.h> #include <sys/socket.h> ssize_t sendto (int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen) ; - 参数: - sockfd : 通信的fd - buf : 要发送的数据 - len : 发送数据的长度 - flags : 0 - dest_addr : 通信的另外一端的地址信息 - addrlen : 地址的内存大小 ssize_t recvfrom (int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen) ; - 参数: - sockfd : 通信的fd - buf : 接收数据的数组 - len : 数组的大小 - flags : 0 - src_addr : 用来保存另外一端的地址信息,不需要可以指定为NULL - addrlen : 地址的内存大小
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <arpa/inet.h> int main () { int fd = socket(PF_INET, SOCK_DGRAM, 0 ); if (fd == -1 ) { perror("socket" ); exit (-1 ); } struct sockaddr_in addr ; addr.sin_family = AF_INET; addr.sin_port = htons(9999 ); addr.sin_addr.s_addr = INADDR_ANY; int ret = bind(fd, (struct sockaddr *)&addr, sizeof (addr)); if (ret == -1 ) { perror("bind" ); exit (-1 ); } while (1 ) { char recvbuf[128 ]; char ipbuf[16 ]; struct sockaddr_in cliaddr ; int len = sizeof (cliaddr); int num = recvfrom(fd, recvbuf, sizeof (recvbuf), 0 , (struct sockaddr *)&cliaddr, &len); printf ("client IP : %s, Port : %d\n" , inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, ipbuf, sizeof (ipbuf)), ntohs(cliaddr.sin_port)); printf ("client say : %s\n" , recvbuf); sendto(fd, recvbuf, strlen (recvbuf) + 1 , 0 , (struct sockaddr *)&cliaddr, sizeof (cliaddr)); } close(fd); return 0 ; }
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <arpa/inet.h> int main () { int fd = socket(PF_INET, SOCK_DGRAM, 0 ); if (fd == -1 ) { perror("socket" ); exit (-1 ); } struct sockaddr_in saddr ; saddr.sin_family = AF_INET; saddr.sin_port = htons(9999 ); inet_pton(AF_INET, "127.0.0.1" , &saddr.sin_addr.s_addr); int num = 0 ; while (1 ) { char sendBuf[128 ]; sprintf (sendBuf, "hello , i am client %d \n" , num++); sendto(fd, sendBuf, strlen (sendBuf) + 1 , 0 , (struct sockaddr *)&saddr, sizeof (saddr)); int num = recvfrom(fd, sendBuf, sizeof (sendBuf), 0 , NULL , NULL ); printf ("server say : %s\n" , sendBuf); sleep(1 ); } close(fd); return 0 ; }
可以实现多个客户端。
4.33 UDP广播 向子网中多台计算机发送消息,并且子网中所有的计算机都可以接收到发送方发送的消息,每个广播消息都包含一个特殊的IP地址,这个IP中子网内主机标志部分的二进制全部为1(255)。 a.只能在局域网中使用。 b.客户端需要绑定服务器广播使用的端口,才可以接收到广播消息。
int setsockopt (int sockfd, int level, int optname,const void *optval, socklen_t optlen) ; - sockfd : 文件描述符 - level : SOL_SOCKET - optname : SO_BROADCAST - optval : int 类型的值,为1 表示允许广播 - optlen : optval的大小
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <arpa/inet.h> int main () { int fd = socket(PF_INET, SOCK_DGRAM, 0 ); if (fd == -1 ) { perror("socket" ); exit (-1 ); } int op = 1 ; setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &op, sizeof (op)); struct sockaddr_in cliaddr ; cliaddr.sin_family = AF_INET; cliaddr.sin_port = htons(9999 ); inet_pton(AF_INET, "192.168.193.255" , &cliaddr.sin_addr.s_addr); int num = 0 ; while (1 ) { char sendBuf[128 ]; sprintf (sendBuf, "hello, client....%d\n" , num++); sendto(fd, sendBuf, strlen (sendBuf) + 1 , 0 , (struct sockaddr *)&cliaddr, sizeof (cliaddr)); printf ("广播的数据:%s\n" , sendBuf); sleep(1 ); } close(fd); return 0 ; }
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <arpa/inet.h> int main () { int fd = socket(PF_INET, SOCK_DGRAM, 0 ); if (fd == -1 ) { perror("socket" ); exit (-1 ); } struct in_addr in ; struct sockaddr_in addr ; addr.sin_family = AF_INET; addr.sin_port = htons(9999 ); addr.sin_addr.s_addr = INADDR_ANY; int ret = bind(fd, (struct sockaddr *)&addr, sizeof (addr)); if (ret == -1 ) { perror("bind" ); exit (-1 ); } while (1 ) { char buf[128 ]; int num = recvfrom(fd, buf, sizeof (buf), 0 , NULL , NULL ); printf ("server say : %s\n" , buf); } close(fd); return 0 ; }
4.33 组播(多播) 单播地址标识单个 IP 接口,广播地址标识某个子网的所有 IP 接口,多播地址标识一组 IP 接口。单播和广播是寻址方案的两个极端(要么单个要么全部),多播则意在两者之间提供一种折中方案。多播数据报只应该由对它感兴趣的接口接收,也就是说由运行相应多播会话应用系统的主机上的接口接收。另外,广播一般局限于局域网内使用,而多播则既可以用于局域网,也可以跨广域网使用。 a.组播既可以用于局域网,也可以用于广域网 b.客户端需要加入多播组,才能接收到多播的数据
int setsockopt (int sockfd, int level, int optname,const void *optval, socklen_t optlen) ; - level : IPPROTO_IP - optname : IP_MULTICAST_IF - optval : struct in_addr // 客户端加入到多播组: - level : IPPROTO_IP - optname : IP_ADD_MEMBERSHIP - optval : struct ip_mreq struct ip_mreq { struct in_addr imr_multiaddr ; struct in_addr imr_interface ; }; typedef uint32_t in_addr_t ;struct in_addr { in_addr_t s_addr; };
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <arpa/inet.h> int main () { int fd = socket(PF_INET, SOCK_DGRAM, 0 ); if (fd == -1 ) { perror("socket" ); exit (-1 ); } struct in_addr imr_multiaddr ; inet_pton(AF_INET, "239.0.0.10" , &imr_multiaddr.s_addr); setsockopt(fd, IPPROTO_IP, IP_MULTICAST_IF, &imr_multiaddr, sizeof (imr_multiaddr)); struct sockaddr_in cliaddr ; cliaddr.sin_family = AF_INET; cliaddr.sin_port = htons(9999 ); inet_pton(AF_INET, "239.0.0.10" , &cliaddr.sin_addr.s_addr); int num = 0 ; while (1 ) { char sendBuf[128 ]; sprintf (sendBuf, "hello, client....%d\n" , num++); sendto(fd, sendBuf, strlen (sendBuf) + 1 , 0 , (struct sockaddr *)&cliaddr, sizeof (cliaddr)); printf ("组播的数据:%s\n" , sendBuf); sleep(1 ); } close(fd); return 0 ; }
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <arpa/inet.h> int main () { int fd = socket(PF_INET, SOCK_DGRAM, 0 ); if (fd == -1 ) { perror("socket" ); exit (-1 ); } struct in_addr in ; struct sockaddr_in addr ; addr.sin_family = AF_INET; addr.sin_port = htons(9999 ); addr.sin_addr.s_addr = INADDR_ANY; int ret = bind(fd, (struct sockaddr *)&addr, sizeof (addr)); if (ret == -1 ) { perror("bind" ); exit (-1 ); } struct ip_mreq op ; inet_pton(AF_INET, "239.0.0.10" , &op.imr_multiaddr.s_addr); op.imr_interface.s_addr = INADDR_ANY; setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &op, sizeof (op)); while (1 ) { char buf[128 ]; int num = recvfrom(fd, buf, sizeof (buf), 0 , NULL , NULL ); printf ("server say : %s\n" , buf); } close(fd); return 0 ; }
4.35 本地套接字通信
本地套接字的作用:本地的进程间通信 有关系的进程间的通信,父子进程 没有关系的进程间的通信 本地套接字实现流程和网络套接字类似,一般采用TCP的通信流程。
#define UNIX_PATH_MAX 108 struct sockaddr_un { sa_family_t sun_family; char sun_path[UNIX_PATH_MAX]; }; 1. 创建监听的套接字 int lfd = socket(AF_UNIX/AF_LOCAL, SOCK_STREAM, 0 ); 2. 监听的套接字绑定本地的套接字文件 -> server端 struct sockaddr_un addr ; bind(lfd, addr, len); 3. 监听 listen(lfd, 100 ); 4. 等待并接受连接请求 struct sockaddr_un cliaddr ; int cfd = accept(lfd, &cliaddr, len); 5. 通信 接收数据:read/recv 发送数据:write/send 6. 关闭连接 close(); 1. 创建通信的套接字 int fd = socket(AF_UNIX/AF_LOCAL, SOCK_STREAM, 0 ); 2. 监听的套接字绑定本地的IP 端口 struct sockaddr_un addr ; bind(lfd, addr, len); 3. 连接服务器 struct sockaddr_un serveraddr ; connect(fd, &serveraddr, sizeof (serveraddr)); 4. 通信 接收数据:read/recv 发送数据:write/send 5. 关闭连接 close();
#include <stdio.h> #include <string.h> #include <unistd.h> #include <stdlib.h> #include <arpa/inet.h> #include <sys/un.h> int main () { unlink("server.sock" ); int lfd = socket(AF_LOCAL, SOCK_STREAM, 0 ); if (lfd == -1 ) { perror("socket" ); exit (-1 ); } struct sockaddr_un addr ; addr.sun_family = AF_LOCAL; strcpy (addr.sun_path, "server.sock" ); int ret = bind(lfd, (struct sockaddr *)&addr, sizeof (addr)); if (ret == -1 ) { perror("bind" ); exit (-1 ); } ret = listen(lfd, 100 ); if (ret == -1 ) { perror("listen" ); exit (-1 ); } struct sockaddr_un cliaddr ; int len = sizeof (cliaddr); int cfd = accept(lfd, (struct sockaddr *)&cliaddr, &len); if (cfd == -1 ) { perror("accept" ); exit (-1 ); } printf ("client socket filename: %s\n" , cliaddr.sun_path); while (1 ) { char buf[128 ]; int len = recv(cfd, buf, sizeof (buf), 0 ); if (len == -1 ) { perror("recv" ); exit (-1 ); } else if (len == 0 ) { printf ("client closed....\n" ); break ; } else if (len > 0 ) { printf ("client say : %s\n" , buf); send(cfd, buf, len, 0 ); } } close(cfd); close(lfd); return 0 ; }
#include <stdio.h> #include <string.h> #include <unistd.h> #include <stdlib.h> #include <arpa/inet.h> #include <sys/un.h> int main () { unlink("client.sock" ); int cfd = socket(AF_LOCAL, SOCK_STREAM, 0 ); if (cfd == -1 ) { perror("socket" ); exit (-1 ); } struct sockaddr_un addr ; addr.sun_family = AF_LOCAL; strcpy (addr.sun_path, "client.sock" ); int ret = bind(cfd, (struct sockaddr *)&addr, sizeof (addr)); if (ret == -1 ) { perror("bind" ); exit (-1 ); } struct sockaddr_un seraddr ; seraddr.sun_family = AF_LOCAL; strcpy (seraddr.sun_path, "server.sock" ); ret = connect(cfd, (struct sockaddr *)&seraddr, sizeof (seraddr)); if (ret == -1 ) { perror("connect" ); exit (-1 ); } int num = 0 ; while (1 ) { char buf[128 ]; sprintf (buf, "hello, i am client %d\n" , num++); send(cfd, buf, strlen (buf) + 1 , 0 ); printf ("client say : %s\n" , buf); int len = recv(cfd, buf, sizeof (buf), 0 ); if (len == -1 ) { perror("recv" ); exit (-1 ); } else if (len == 0 ) { printf ("server closed....\n" ); break ; } else if (len > 0 ) { printf ("server say : %s\n" , buf); } sleep(1 ); } close(cfd); return 0 ; }