多线程处理
优点
在一个程序中,很多操作是非常耗时的,使用多线程,可以将耗时的任务在后台执行,同时执行其他操作。
可以提高程序的效率。
缺点:
太多线程,耗费系统资源,需要开辟每次和CPU调度;
太多线程,影响性能,多任务间切换
先read 函数的返回值 非常重要
> 0 实际读到的字节数
= 0 已经读到结尾(对端已经关闭)
-1 应进一步判断errno的值:重要!!! -1一定要进行判断,否则接收端一直在接收
errno = EAGAIN or EWOULDBLOCK 设置了非阻塞方式读,没有数据到达。
errno = EINTR 慢速系统调用被 中断。
errno = “其他情况” 异常。
多进程模型
socket()
bind()
listen()
//catch sigchild signal 回收子进程
while(1)
{
cfd=accept()
pid=fork()
if(pid==0)//child process
{
close(lfd)
read()/write()
}
else if(pid > 0)//parent process
{
close(cfd)
}
else
{
//error
}
}
fork之后的关闭套接字为什么是两个
socket描述符的引用计数,在执行完socket() accept()后, 引用计数分别为 1 、1。
fork()后 子进程复制了父进程的socket描述符,引用数增加,引用计数分别为2,2。
fork()返回值0代表子进程中,关闭子进程中的lfd(close(lfd));父进程中关闭子进程中的cfd。
保证了子进程处理与客户的连接。
shutdown close区别
close只有在对应socket的引用计数为0时,才会真正发送FIN报文来关闭这个连接,shutdown没有这个限制,直接发送FIN报文
close同时终止了读和写两个方向的数据传输。TCP是双工的,有时候值接收数据,不发送数据,使用shutdown可以指定关闭读端或者写端。
demo
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<stdio.h>
#include<sys/un.h>
#include<unistd.h>
#include<stdlib.h>
#include<signal.h>
#include<assert.h>
#include<string.h>
#include<ctype.h>
#include<errno.h>
#include<sys/wait.h>
#include<errno.h>
#define SERVER_PORT 6666
#define MAXLINE 1024
void catch_child(int signum)
{
while(waitpid(0,NULL,WNOHANG) > 0);
return;
}
static void sys_error(const char *str)
{
perror(str);
exit(1);
}
int main(int argc,char *argv[])
{
int lfd = 0,cfd = 0;
int ret;
char buffer[BUFSIZ],client_ip[56];
pid_t pid;
char str[1024];
struct sockaddr_in serv_addr,clit_addr;
socklen_t clit_addr_len,client_ip_len;
//signal
struct sigaction act;
act.sa_handler = catch_child;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
ret = sigaction(SIGCHLD,&act,NULL);
if(ret != 0)
{
sys_error("sigaction error");
}
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(SERVER_PORT);
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
lfd = socket(AF_INET,SOCK_STREAM,0);
if(lfd == -1)
sys_error("socket error");
int opt = 1;
setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
bind(lfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr));
listen(lfd,128);
socklen_t client_addrlength = sizeof(clit_addr);
client_addrlength = sizeof(clit_addr);
while(1){
cfd = accept(lfd,(struct sockaddr *)&clit_addr,&client_addrlength);
if(cfd < 0)
{
printf("error is:%d\n",errno);
}
pid = fork();
if (pid == 0) {//child
close(lfd);
while (1) {
ret = read(cfd, buffer, MAXLINE);
if (ret == 0) {
printf("the other side has been closed.\n");
break;
}
else if(ret == -1)
{
//printf("str=%s\n", strerror(errno));
if (errno == EINTR)
continue;
else
return -1;
}
write(cfd, buffer, ret);
}
close(cfd);
return 0;
} else if (pid > 0) {//parent
close(cfd);
}
else
{
perror("error exit\n");
exit(-1);
}
}
return 0;
}
多线程
对多进程的服务器的改进,多进程需要消耗较大的资源。数量级1:10000,共享 全局变量信息,带来同步问题。
多线程模型
socket()
bind()
listen()
while(1)
{
cfd = accept(lfd,);
pthread_create(&pid,NULL,fun,(void* )cfd);
prhread_detach(tid);//子线程分离
}
//child pthead
void *fun()
{
read()/write()
pthread_exit();
}
改进方案:线程池,协程
demo
#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <ctype.h>
#include <unistd.h>
#include <fcntl.h>
#include<errno.h>
#define MAXLINE 1024
#define SERV_PORT 6666
struct s_info {
struct sockaddr_in cliaddr;
int connfd;
};
void *work_pthread(void *arg)
{
int n,i;
struct s_info *ts = (struct s_info*)arg;
char buf[MAXLINE];
char str[INET_ADDRSTRLEN];
while (1) {
n = read(ts->connfd, buf, MAXLINE);
if (n == 0) {
printf("the client %d closed...\n", ts->connfd);
break;
}
else if(ret == -1)
{
//printf("str=%s\n", strerror(errno));
if (errno == EINTR)
continue;
else
return -1;
}
write(ts->connfd, buf, n); //回写
}
close(ts->connfd);
return (void *)0;
}
int main(void)
{
struct sockaddr_in servaddr, cliaddr;
socklen_t cliaddr_len;
int listenfd, connfd;
pthread_t tid;
struct s_info ts[256];
int i = 0;
listenfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
int opt = 1;
setsockopt(listenfd ,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
listen(listenfd, 128);
printf("Accepting client connect ...\n");
while (1) {
cliaddr_len = sizeof(cliaddr);
connfid = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
if(connfid < 0)
{
printf("error is %d\n",errno);
}
ts[i].cliaddr = cliaddr;
ts[i].connfd = connfd;
pthread_create(&tid, NULL, work_pthread, (void*)&ts[i]);
pthread_detach(tid);
i++;
}
return 0;
}
端口复用,在断开连接后,端口号可以再次被使用,否则要等到40多秒
int opt = 1;
setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
协程
那什么是协程呢?协程 Coroutines 是一种比线程更加轻量级的微线程。
线程与线程的调度开销对比
线程调度需要保存一个用户线程的状态,恢复另一个线程状态到寄存器,涉及用户态和内核态的转换。
协程拥有自己的寄存器上下文和栈,调度时将寄存器上下文和栈保存带其他地方,在切换回来时,恢复先前保存的寄存器上下文。直接操作用户空间栈,没有内核切换的开销。
优点
内存占用要小,且创建开销要小
减少上下文切换的开销