服务器模型
循环服务器
每次只能处理一个客户端,当前客户端退出后,才能处理下一个客户端。
(循环处理客户端,因此不能做耗时动作。)
练习: 实现 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 32
typedef 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 ms
int 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.c
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");
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;
}
运行结果如下: