服务器模型
循环服务器
每次只能处理一个客户端,当前客户端退出后,才能处理下一个客户端。
(循环处理客户端,因此不能做耗时动作。)
练习: 实现 TCP 全双工
利用进程实现
利用线程实现
并发服务器
同一时刻可以响应多个客户端的请求。
多进程实现并发(不建议)
// ser_pro.c
// cli_pro.c
多线程实现并发
每来一个客户端连接,开一个子线程来专门处理客户端的数据,实现简单,资源占用少。
// ser_thr.c#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netinet/ip.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include <pthread.h>void *handler(void *arg){int fd = *(int *)arg;char buf[256] = {};while (1){int receriver = recv(fd, buf, sizeof(buf), 0);if (receriver < 0){perror("Failed to receive");return NULL;} else if (receriver == 0){printf("Client exited. \n");break;} else {printf("%s\n", buf);}}close(fd);pthread_exit(NULL);
}int main(int argc, char const *argv[])
{if (argc != 2){printf("Please input %s <port>. \n", argv[0]);return -1;}int sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0){perror("Failed to create a socket");return -1;}printf("sockfd: %d\n", sockfd);struct sockaddr_in saddr;saddr.sin_family = AF_INET;saddr.sin_port = htons(atoi(argv[1]));saddr.sin_addr.s_addr = inet_addr("0.0.0.0");if (bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0){perror("Failed to bind");return -1;}if (listen(sockfd, 6) < 0){perror("Failed to listen");return -1;}struct sockaddr_in caddr;socklen_t length = sizeof(caddr);char buf[256] = {};while (1){int accfd = accept(sockfd, (struct sockaddr *)&caddr, &length);if (accfd < 0){perror("Failed to accept");return -1;}printf("Client IPv4: %s\t\tport: %d\n", inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port));pthread_t tid;pthread_create(&tid, NULL, handler, &accfd);pthread_detach(tid);}close(sockfd); return 0;
}
// cli_thr.c#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <pthread.h>void *mythread_recv(void *arg)
{int fd = *((int *)arg);char buf[256] = {};while (1){int receriver = recv(fd, buf, sizeof(buf), 0);if (receriver < 0){perror("recv is err:");return NULL;} else {printf("%s\n", buf);}}pthread_exit(NULL);
}int main(int argc, const char *argv[])
{if (argc != 3){printf("Please input %s <ip> <port>. \n", argv[0]);return -1;}int sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0){perror("Failed to create a socket");return -1;}struct sockaddr_in caddr;caddr.sin_family = AF_INET;caddr.sin_port = htons(atoi(argv[2]));caddr.sin_addr.s_addr = inet_addr(argv[1]);if (connect(sockfd, (struct sockaddr *)&caddr, sizeof(caddr)) < 0){perror("Failed to connect");return -1;}pthread_t tid;pthread_create(&tid, NULL, mythread_recv, &sockfd);pthread_detach(tid);char buf[128] = "";while (1){fgets(buf, sizeof(buf), stdin);if (buf[strlen(buf)-1] == '\n')buf[strlen(buf)-1] = '\0';if (!strcmp(buf, "quit")){printf("Client exited. \n");break;}send(sockfd, buf, sizeof(buf), 0);}close(sockfd);return 0;
}
实现效果如下:
// server.c#include "mymacro.h"linklist_t ph;void *server_send(void *arg){linklist_t p;char buf[256] = {};while (1){fgets(buf, sizeof(buf), stdin);if (buf[strlen(buf)-1] == '\n')buf[strlen(buf)-1] = '\0';p = (linklist_t)arg;while (p->next){p = p->next;send(p->fd, buf, sizeof(buf), 0);}}// pthread_exit(NULL); // 服务器端不需要退出此线程
}void *server_recv(void *arg){int fd = *(int *)arg;char buf[256] = {};while (1){int receriver = recv(fd, buf, sizeof(buf), 0);if (receriver < 0){perror("Failed to receive");return NULL;} else if (receriver == 0){DeleteFromLinkedList(ph, fd);printf("Client[%d] exited. \n", fd);break;} else {printf("Client[%d]: %s\n", fd, buf);}}close(fd);pthread_exit(NULL);
}int main(int argc, char const *argv[])
{if (argc != 2){printf("Please input %s <port>. \n", argv[0]);return -1;}int sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0){perror("Failed to create a socket");return -1;}// printf("sockfd: %d\n", sockfd);ADDRIN saddr, caddr;saddr.sin_family = AF_INET;saddr.sin_port = htons(atoi(argv[1]));saddr.sin_addr.s_addr = inet_addr("0.0.0.0");socklen_t length = sizeof(caddr);if (bind(sockfd, (ADDR *)&saddr, sizeof(saddr)) < 0){perror("Failed to bind");return -1;}if (listen(sockfd, 6) < 0){perror("Failed to listen");return -1;}ph = CreateLinkedList();head = ph;char buf[256] = {};pthread_t tid_recv, tid_send;pthread_create(&tid_send, NULL, server_send, head);pthread_detach(tid_send);while (1){int accfd = accept(sockfd, (ADDR *)&caddr, &length);if (accfd < 0){perror("Failed to accept");return -1;}// printf("Client IPv4: %s\t\tport: %d\n", // inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port));InsertIntoLinkedList(head, accfd);ShowLinkedList(head);pthread_create(&tid_recv, NULL, server_recv, &accfd);pthread_detach(tid_recv);}close(sockfd); return 0;
}
```c
// client.c#include "mymacro.h"void *client_recv(void *arg)
{int fd = *((int *)arg);char buf[256] = {};while (1){int receriver = recv(fd, buf, sizeof(buf), 0);if (receriver < 0){perror("recv is err:");return NULL;} else {printf("Server: %s\n", buf);}}pthread_exit(NULL);
}int main(int argc, const char *argv[])
{if (argc != 3){printf("Please input %s <ip> <port>. \n", argv[0]);return -1;}int sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0){perror("Failed to create a socket");return -1;}struct sockaddr_in caddr;caddr.sin_family = AF_INET;caddr.sin_port = htons(atoi(argv[2]));caddr.sin_addr.s_addr = inet_addr(argv[1]);if (connect(sockfd, (struct sockaddr *)&caddr, sizeof(caddr)) < 0){perror("Failed to connect");return -1;}pthread_t tid;pthread_create(&tid, NULL, client_recv, &sockfd);pthread_detach(tid);char buf[256] = {};while (1){fgets(buf, sizeof(buf), stdin);if (buf[strlen(buf)-1] == '\n')buf[strlen(buf)-1] = '\0';if (!strcmp(buf, "quit")){printf("Client exited. \n");break;}send(sockfd, buf, sizeof(buf), 0);}close(sockfd);return 0;
}
// mymacro.h#ifndef __MYMACRO_H_
#define __MYMACRO_H_#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netinet/ip.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include <pthread.h>
#include <fcntl.h>
#include <sys/stat.h>#define N 32typedef struct sockaddr_in ADDRIN;
typedef struct sockaddr ADDR;typedef struct linkedlist{int fd;struct linkedlist *next;
} linknode_t, *linklist_t;linklist_t head;/* *************** 以下函数可写到一个新的.c文件中 **************** */linklist_t CreateLinkedList(){linklist_t p = (linklist_t)malloc(sizeof(linknode_t));if (!p){perror("Failed to create a linked list");return NULL;}p->next = NULL;return p;
}int InsertIntoLinkedList(linklist_t p, int data){linklist_t pnw = (linklist_t)malloc(sizeof(linknode_t));if (!pnw){perror("Failed to create a new node");return -1;}pnw->fd = data;while (p->next)p = p->next;p->next = pnw;pnw->next = NULL;return 0;
}int DeleteFromLinkedList(linklist_t p, int data){while (p->next){if (p->next->fd == data){linklist_t pdel = p->next;p->next = pdel->next;free(pdel);pdel = NULL;}else // 必须加,否则段错误p = p->next;}return 0;
}void ShowLinkedList(linklist_t p){p = p->next;while (p){printf("%d ", p->fd);p = p->next;}putchar(10);
}#endif
实现效果如下:
IO 多路复用
借助 select, poll, epoll 机制,将新连接的客户端描述符添加到描述符表中,只需要一个线程即可处理所有的客户端连接,在嵌入式开发中应用广泛,但代码复杂度较高。
( 点此跳转到 IO 多路复用 )
总结
网络超时检测
使用原因:
避免进程在没有数据时无休止阻塞,当到达设定的时间, 进程从原操作返回,继续执行下面的代码。
通过函数参数设置超时
select 超时检测
// 无超时fd_set readfds, tempfds; // 1. 先构造一张有关文件描述符的表FD_ZERO(&readfds); // 2. 清空表FD_SET(0, &readfds); // 3. 将关心的文件描述符添加到表中FD_SET(fd, &readfds);int maxfd = fd;char buf[256] = {};while (1){tempfds = readfds; // 4. 备份表select(maxfd+1, &tempfds, NULL, NULL, NULL); // 5. 调用 select 函数if (FD_ISSET(0, &tempfds)){ // 6. 产生事件,进行相应处理fgets(buf, sizeof(buf), stdin);if (buf[strlen(buf)-1] == '\n')buf[strlen(buf)-1] = '\0';printf("Keyboard: %s\n", buf);}if (FD_ISSET(fd, &tempfds)){int len = read(fd, buf, sizeof(buf));buf[len] = '\0';printf("Mouse: %s\n", buf);}}
// 添加超时fd_set readfds, tempfds; // 1. 先构造一张有关文件描述符的表FD_ZERO(&readfds); // 2. 清空表FD_SET(0, &readfds); // 3. 将关心的文件描述符添加到表中FD_SET(fd, &readfds);int maxfd = fd;char buf[256] = {};while (1){tempfds = readfds; // 4. 备份表struct timeval tiv = {3, 0}; // 3s + 0 msint ret = select(maxfd+1, &tempfds, NULL, NULL, &tiv); // 5. 调用 select 函数if (ret < 0){perror("Failed to select");return -1;} else if (ret == 0){printf("Timeout! \n");continue;} else {}if (FD_ISSET(0, &tempfds)){ // 6. 产生事件,进行相应处理fgets(buf, sizeof(buf), stdin);if (buf[strlen(buf)-1] == '\n')buf[strlen(buf)-1] = '\0';printf("Keyboard: %s\n", buf);}if (FD_ISSET(fd, &tempfds)){int len = read(fd, buf, sizeof(buf));buf[len] = '\0';printf("Mouse: %s\n", buf);}}
poll 超时检测
// 无超时struct pollfd fds[64];fds[0].fd = 0;fds[0].events = POLLIN;fds[1].fd = sockfd;fds[1].events = POLLIN; int last = 1;char buf[256] = {};int accfd, receiver;struct sockaddr_in caddr;int length = sizeof(caddr);while (1){int getpoll = poll(fds, last+1, -1);if (getpoll < 0){perror("Failed to poll");return -1;}for (int i = 0; i < last+1; i++){if (fds[i].revents == POLLIN){if (fds[i].fd == 0){/* 相应处理 */}if (fds[i].fd == sockfd){/* 相应处理 */}}}}
// 添加超时struct pollfd fds[64];fds[0].fd = 0;fds[0].events = POLLIN;fds[1].fd = sockfd;fds[1].events = POLLIN; int last = 1;char buf[256] = {};int accfd, receiver;struct sockaddr_in caddr;int length = sizeof(caddr);while (1){int getpoll = poll(fds, last+1, 3000); // 设置 3000ms 超时检测if (getpoll < 0){perror("Failed to poll");return -1;} else if (getpoll == 0){printf("Timeout! \n");continue;} else {}for (int i = 0; i < last+1; i++){if (fds[i].revents == POLLIN){if (fds[i].fd == 0){/* 相应处理 */}if (fds[i].fd == sockfd){/* 相应处理 */}}}}
epoll 超时检测
// 无超时struct epoll_event ev, eves[16];ev.data.fd = 0;ev.events = EPOLLIN | EPOLLET;epoll_ctl(epfd, EPOLL_CTL_ADD, 0, &ev);ev.data.fd = sockfd;ev.events = EPOLLIN | EPOLLET;epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev);char buf[256] = {};while (1){ int num = epoll_wait(epfd, eves, 16, -1);if (num < 0){perror("Failed to epwait");return -1;}for (int i = 0; i < num; i++){if (eves[i].data.fd == 0){/* 相应处理 */}if (eves[i].data.fd == sockfd){/* 相应处理 */}}}
// 添加超时struct epoll_event ev, eves[16];ev.data.fd = 0;ev.events = EPOLLIN | EPOLLET;epoll_ctl(epfd, EPOLL_CTL_ADD, 0, &ev);ev.data.fd = sockfd;ev.events = EPOLLIN | EPOLLET;epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev);char buf[256] = {};while (1){ int num = epoll_wait(epfd, eves, 16, 3000); // 设置 3000ms 超时检测if (num < 0){perror("Failed to epwait");return -1;} else if (num == 0){printf("Timeout! \n");continue;} else {}for (int i = 0; i < num; i++){if (eves[i].data.fd == 0){/* 相应处理 */}if (eves[i].data.fd == sockfd){/* 相应处理 */}}}
setsockopt() 设置套接字属性
#include<sys.socket.h>
#include<sys/types.h>
#include<sys/time.h>
int setsockopt(int sockfd, int level, int optname, void *optval, socklen_t optlen);功能:获得/设置套接字属性
参数:sockfd: 套接字描述符level: 协议层optname: 选项名optval: 选项值optlen: 选项值大小指针
返回值: 成功: 0 失败: -1
端口与地址复用
// 端口与地址复用 server.cint sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0){perror("Failed to create a socket");return -1;}printf("sockfd: %d\n", sockfd);struct sockaddr_in saddr;saddr.sin_family = AF_INET;saddr.sin_port = htons(atoi(argv[1]));saddr.sin_addr.s_addr = inet_addr("0.0.0.0");int op = 1; // 非 0 即可setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &op, sizeof(op));if (bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0){perror("Failed to bind");return -1;}
超时检测,打断接下来的阻塞
while (1){struct timeval tiv = {2, 500}; // 设置 2.5s 超时检测setsockopt(acceptfd, SOL_SOCKET, SO_RCVTIMEO, &tiv, sizeof(tiv));int recvbyte = recv(acceptfd, buf, sizeof(buf), 0); if (recvbyte < 0){perror("Failed to receive");// return -1; // 注释掉,每2.5s接收不到客户端消息,打印一次上一行代码} else if (recvbyte == 0){printf("Client exited\n");break;} else {if (!strcmp(buf, "quit")){flag = 1;break;}printf("%s\n", buf);}}
alarm() 定时器 + sigaction() 修改信号的行为
alarm() 定时器
若 alarm(n); 则 n 秒后,会有 SGIALRM信号 产生,从而终止程序。
// 程序退出alarm(2);while (1){}
// 2s后,程序退出,打印 “闹钟” 或 "Alarm clock"
但将 alarm(n); 置于死循环中,则不会终止程序。
while (1){alarm(2);
}
// 死循环
// 程序退出char buf[64] = {};
while (1){alarm(2);fgets(buf, sizeof(buf), stdin); // 有阻塞 // 不输入任何内容
}
// 2s后,程序退出,打印 “闹钟” 或 "Alarm clock"
sigaction 修改信号的行为
#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);功能:对接收到的指定信号处理
参数: 1. signum 信号 2. act:设置新行为 oldact:设置旧行为
返回值: 成功: 0失败: -1结构体如下: struct sigaction { void (*sa_handler)(int); // 函数指针其他的结构体成员如mark(信号集),flag(对信号的标记)都不常用};==================== 需要定义一个函数 ====================void handler(int sig){printf("timeout .....\n");}
超时检测
#include <stdio.h>
#include <signal.h>
#include <unistd.h>void handler(int sig){printf("Timeout! \n");
}int main(int argc, char const *argv[])
{struct sigaction act;sigaction(SIGALRM, NULL, &act); // 获取 SIGALRM信号 原来的属性act.sa_handler = handler; // 修改属性sigaction(SIGALRM, &act, NULL); // 写回属性char buf[64] = {};while (1){alarm(2); // 2s后,产生一个 SIGALRM信号if (fgets(buf, sizeof(buf), stdin) == NULL){perror("Failed to get buffer");continue;}printf("Nothing. \n");}return 0;
}
运行结果如下: